|
import openai, gradio as gr, json, plotly.graph_objects as go |
|
from pathlib import Path |
|
|
|
|
|
try: |
|
CUSTOM_CSS = Path("style.css").read_text() |
|
except Exception: |
|
CUSTOM_CSS = "" |
|
|
|
SYSTEM_PROMPT = """You are ZEN Multimodal Assistant by ZEN AI Co. |
|
Choose only ONE of these output modes per reply:- Image: when a visual or illustration is most useful. Respond only with JSON: {"type":"image","prompt":"<prompt for DALL-E-3>"}- Chart: when a user requests or needs a data visualization. Respond only with JSON: {"type":"chart","title":"<chart title>","data":[{"x":[...], "y":[...], "label":"<series name>"}]}- Text: for all other situations, reply with a helpful, complete, conversational answer. Never reply with the word "text" or any label, just the response itself. Never reply in JSON unless for image or chart.Never use markdown code fences, never add comments.""" |
|
|
|
def build_messages(history, user_msg): |
|
messages = [{"role": "system", "content": SYSTEM_PROMPT}] |
|
for u, a in history: |
|
messages.append({"role": "user", "content": u}) |
|
messages.append({"role": "assistant", "content": a}) |
|
messages.append({"role": "user", "content": user_msg}) |
|
return messages |
|
|
|
def multimodal_chat(api_key, user_msg, history): |
|
if not api_key: |
|
raise gr.Error("🔑 Please paste your OpenAI API key first.") |
|
openai.api_key = api_key |
|
history = history or [] |
|
messages = build_messages(history, user_msg) |
|
response = openai.chat.completions.create( |
|
model="gpt-4o", |
|
messages=messages, |
|
temperature=0.6, |
|
) |
|
assistant_content = response.choices[0].message.content.strip() |
|
if assistant_content.lower() == "text": |
|
assistant_content = "(I'm sorry, I didn't understand. Could you rephrase?)" |
|
img_url, fig = None, None |
|
try: |
|
parsed = json.loads(assistant_content) |
|
if parsed.get("type") == "image": |
|
dalle = openai.images.generate( |
|
model="dall-e-3", |
|
prompt=parsed.get("prompt", "high quality illustration, cinematic, best quality"), |
|
n=1, |
|
size="1024x1024", |
|
) |
|
img_url = dalle.data[0].url |
|
history.append([user_msg, f""]) |
|
elif parsed.get("type") == "chart": |
|
fig = go.Figure() |
|
for s in parsed["data"]: |
|
fig.add_trace( |
|
go.Scatter( |
|
x=s["x"], |
|
y=s["y"], |
|
mode="lines+markers", |
|
name=s.get("label", ""), |
|
) |
|
) |
|
fig.update_layout(title=parsed.get("title", "Chart")) |
|
history.append([user_msg, parsed.get("title", "Chart below")]) |
|
else: |
|
history.append([user_msg, str(assistant_content)]) |
|
except (json.JSONDecodeError, KeyError, TypeError): |
|
history.append([user_msg, assistant_content]) |
|
return history, img_url, fig |
|
|
|
with gr.Blocks(css="style.css") as demo: |
|
gr.Markdown( |
|
"🧠 ZEN Multimodal Assistant\n" |
|
"Paste your OpenAI API key (never saved).\n" |
|
"This assistant intelligently responds with text, an image, or an interactive chart. MODULE 3", |
|
elem_id="zen-header" |
|
) |
|
|
|
api_key = gr.Textbox(label="OpenAI API Key", type="password", placeholder="sk-...") |
|
chatbot = gr.Chatbot(label="Conversation") |
|
with gr.Row(): |
|
user_msg = gr.Textbox(placeholder="Ask me anything…", label="Your message", scale=4) |
|
send_btn = gr.Button("Send", variant="primary") |
|
img_out = gr.Image(label="Generated image") |
|
chart_out = gr.Plot(label="Interactive chart") |
|
|
|
def respond(api_key, user_msg, chat_history): |
|
chat_history, img_url, fig = multimodal_chat(api_key, user_msg, chat_history) |
|
img_update = gr.update(value=img_url) if img_url else gr.update(value=None) |
|
fig_update = gr.update(value=fig) if fig else gr.update(value=None) |
|
return chat_history, img_update, fig_update |
|
|
|
send_btn.click( |
|
respond, |
|
inputs=[api_key, user_msg, chatbot], |
|
outputs=[chatbot, img_out, chart_out], |
|
) |
|
user_msg.submit( |
|
respond, |
|
inputs=[api_key, user_msg, chatbot], |
|
outputs=[chatbot, img_out, chart_out], |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.queue(max_size=50).launch() |