Spaces:
Runtime error
Runtime error
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import gradio as gr
|
3 |
+
import openai as o
|
4 |
+
from threading import Thread
|
5 |
+
|
6 |
+
# 📜 CONFIG
|
7 |
+
UI_TITLE = "✨🧙♂️🔮"
|
8 |
+
KEY_FILE = "key.txt"
|
9 |
+
MODELS = {
|
10 |
+
"GPT-4o ✨": "gpt-4o",
|
11 |
+
"o3 🧠": "gpt-3.5-turbo", # Placeholder, o3 is not a public model name
|
12 |
+
"o4-mini 🚀": "gpt-4-turbo", # Placeholder, o4-mini is not a public model name
|
13 |
+
"GPT-4.5 🔬": "gpt-4-turbo", # Placeholder, gpt-4.5 is not a public model name
|
14 |
+
"GPT-4.1 💻": "gpt-4-turbo", # Placeholder, gpt-4.1 is not a public model name
|
15 |
+
"GPT-4.1-Mini ⚡": "gpt-4-turbo", # Placeholder, gpt-4.1-mini is not a public model name
|
16 |
+
}
|
17 |
+
|
18 |
+
# 🎨 STYLE
|
19 |
+
H1 = "# <font size='7'>{0}</font>"
|
20 |
+
H2 = "## <font size='6'>{0}</font>"
|
21 |
+
BTN_STYLE = "<font size='5'>{0}</font>"
|
22 |
+
|
23 |
+
# 🪄 HELPERS
|
24 |
+
def save_key(k: str) -> str:
|
25 |
+
"💾🔑"
|
26 |
+
if not k or not k.strip(): return "🚫 Empty Key"
|
27 |
+
with open(KEY_FILE, "w") as f: f.write(k.strip())
|
28 |
+
return "🔑✅"
|
29 |
+
|
30 |
+
def get_key(k: str) -> str:
|
31 |
+
"📜🔑"
|
32 |
+
k = k.strip() if k and k.strip() else (open(KEY_FILE).read().strip() if os.path.exists(KEY_FILE) else os.getenv("OPENAI_KEY", ""))
|
33 |
+
if not k: raise gr.Error("❗🔑 Missing OpenAI Key!")
|
34 |
+
o.api_key = k
|
35 |
+
return k
|
36 |
+
|
37 |
+
def summon_oracle(model_name: str, scribe_key: str, quest: str, scroll: list):
|
38 |
+
"""
|
39 |
+
A pact with a chosen Oracle of OpenAI.
|
40 |
+
To seek its counsel, one must present a worthy key (scribe_key),
|
41 |
+
a quest (quest), and the Oracle's own name (model_name)
|
42 |
+
upon the ancient scroll (scroll) of dialogue.
|
43 |
+
"""
|
44 |
+
get_key(scribe_key) # Present the key to the sanctum's guardian.
|
45 |
+
|
46 |
+
# Transcribe the history onto a celestial scroll for the Oracle to read.
|
47 |
+
celestial_scroll = [
|
48 |
+
{"role": "user", "content": user_words}
|
49 |
+
for user_words, _ in scroll
|
50 |
+
] + [
|
51 |
+
{"role": "assistant", "content": oracle_words}
|
52 |
+
for _, oracle_words in scroll
|
53 |
+
]
|
54 |
+
celestial_scroll.append({"role": "user", "content": quest})
|
55 |
+
|
56 |
+
# The Oracle whispers its response from the aether.
|
57 |
+
try:
|
58 |
+
prophecy = o.chat.completions.create(model=model_name, messages=celestial_scroll, stream=True)
|
59 |
+
|
60 |
+
# The new wisdom is recorded as it is spoken.
|
61 |
+
scroll.append((quest, ""))
|
62 |
+
for chunk in prophecy:
|
63 |
+
if chunk.choices[0].delta.content:
|
64 |
+
scroll[-1] = (quest, scroll[-1][1] + chunk.choices[0].delta.content)
|
65 |
+
yield scroll
|
66 |
+
except Exception as e:
|
67 |
+
scroll.append((quest, f"🧙♂️🔮 A magical disturbance occurred: {str(e)}"))
|
68 |
+
yield scroll
|
69 |
+
|
70 |
+
|
71 |
+
def manage_portals(selected_models: list):
|
72 |
+
"""
|
73 |
+
Reveals or conceals the portals to the chosen Oracles.
|
74 |
+
"""
|
75 |
+
updates = []
|
76 |
+
for model_display_name in MODELS.keys():
|
77 |
+
if model_display_name in selected_models:
|
78 |
+
updates.append(gr.update(visible=True))
|
79 |
+
else:
|
80 |
+
updates.append(gr.update(visible=False))
|
81 |
+
return updates
|
82 |
+
|
83 |
+
# 🔮 UI
|
84 |
+
with gr.Blocks(title=UI_TITLE, theme=gr.themes.Soft(primary_hue="red", secondary_hue="orange")) as demo:
|
85 |
+
gr.Markdown(H1.format(UI_TITLE))
|
86 |
+
|
87 |
+
# --- API Key Rune ---
|
88 |
+
with gr.Accordion("🔑 Eldritch Key", open=False):
|
89 |
+
with gr.Row():
|
90 |
+
api_key_box = gr.Textbox(
|
91 |
+
label="🔑",
|
92 |
+
type="password",
|
93 |
+
placeholder="sk-...",
|
94 |
+
value=get_key("") if os.path.exists(KEY_FILE) else os.getenv("OPENAI_KEY", ""),
|
95 |
+
scale=3
|
96 |
+
)
|
97 |
+
save_btn = gr.Button("💾", scale=1)
|
98 |
+
status_txt = gr.Textbox(interactive=False, scale=1, label="Status")
|
99 |
+
save_btn.click(save_key, inputs=api_key_box, outputs=status_txt)
|
100 |
+
|
101 |
+
# --- Oracle Selection ---
|
102 |
+
gr.Markdown(H2.format("🔮 Select Oracles"))
|
103 |
+
model_selector = gr.CheckboxGroup(choices=list(MODELS.keys()), label="Oracles", value=["GPT-4o ✨"])
|
104 |
+
|
105 |
+
# --- Portals to Oracles ---
|
106 |
+
gr.Markdown(H2.format("🌀 Portals"))
|
107 |
+
|
108 |
+
model_blocks = []
|
109 |
+
for display_name, api_name in MODELS.items():
|
110 |
+
# A block for each model, initially hidden unless selected by default
|
111 |
+
is_visible = display_name in model_selector.value
|
112 |
+
with gr.Blocks(visible=is_visible) as model_block:
|
113 |
+
gr.Markdown(f"### <font size='5'>{display_name}</font>")
|
114 |
+
chatbot = gr.Chatbot(height=350, label=f"Scroll of {display_name}")
|
115 |
+
with gr.Row():
|
116 |
+
run_btn = gr.Button(value=BTN_STYLE.format("▶️ Run"), variant="primary", scale=1)
|
117 |
+
stop_btn = gr.Button(value=BTN_STYLE.format("⏹️ Stop"), variant="stop", scale=1)
|
118 |
+
|
119 |
+
# Each run button triggers its own oracle
|
120 |
+
# The `_js` param is a trick to pass the model's API name to the Python function
|
121 |
+
run_event = run_btn.click(
|
122 |
+
fn=summon_oracle,
|
123 |
+
inputs=[gr.State(api_name), api_key_box, chatbot.i_am_a_dummy_component_for_the_event_to_work, chatbot],
|
124 |
+
outputs=[chatbot]
|
125 |
+
)
|
126 |
+
stop_btn.click(fn=None, inputs=None, outputs=None, cancels=[run_event])
|
127 |
+
model_blocks.append(model_block)
|
128 |
+
|
129 |
+
# --- Global Quest Input ---
|
130 |
+
gr.Markdown(H2.format("📜 Global Quest"))
|
131 |
+
global_msg_box = gr.Textbox(placeholder="❓ Pose your question to all active portals...", scale=3, lines=3)
|
132 |
+
|
133 |
+
# Link the global message box to all chatbot text inputs
|
134 |
+
# This uses a bit of Gradio event magic to update the hidden textboxes
|
135 |
+
for block in demo.blocks.values():
|
136 |
+
if isinstance(block, gr.Blocks) and block.visible:
|
137 |
+
for elem in block.children:
|
138 |
+
if isinstance(elem, gr.Chatbot):
|
139 |
+
global_msg_box.submit(lambda x: x, inputs=[global_msg_box], outputs=[elem.i_am_a_dummy_component_for_the_event_to_work])
|
140 |
+
|
141 |
+
|
142 |
+
# --- Event Listeners ---
|
143 |
+
model_selector.change(
|
144 |
+
fn=manage_portals,
|
145 |
+
inputs=[model_selector],
|
146 |
+
outputs=model_blocks
|
147 |
+
)
|
148 |
+
|
149 |
+
if __name__ == "__main__":
|
150 |
+
# Add a dummy attribute to Chatbot to make the global message box work
|
151 |
+
gr.Chatbot.i_am_a_dummy_component_for_the_event_to_work = gr.Textbox(visible=False)
|
152 |
+
demo.launch(share=True)
|