Saffn commited on
Commit
ebb692c
·
1 Parent(s): 03fb095

Fix UI compilation issues, rename app to Saffan Chat, add AI Persona & Skill presets

Browse files
Files changed (3) hide show
  1. src/config.py +78 -3
  2. src/engine.py +48 -6
  3. src/ui.py +169 -38
src/config.py CHANGED
@@ -65,7 +65,7 @@ MODEL_CONFIGS = {
65
  }
66
 
67
  # The Leaked-Style System Prompt (inspired by Claude 3.5 Sonnet & ChatGPT Custom Instructions)
68
- SYSTEM_PROMPT = """You are a highly advanced AI coding assistant and researcher named Antigravity, engineered by the Google DeepMind team. You approach every interaction with objective precision, extreme intelligence, and structured depth.
69
 
70
  You must strictly adhere to the following behavioral and formatting rules:
71
 
@@ -85,11 +85,23 @@ You must strictly adhere to the following behavioral and formatting rules:
85
  - Adopt an objective, clear, and intellectual tone. Speak directly to the user.
86
  - Do not make assumptions. If a query is ambiguous, explain the ambiguity and outline the options or ask for clarification.
87
 
88
- 3. KNOWLEDGE & CAPABILITIES:
 
 
 
 
 
 
 
 
 
 
 
 
89
  - You have access to real-time web search and web scraping tools. When web context is provided, rely on it to answer queries accurately and provide sources/citations where appropriate.
90
  - If you do not know the answer, admit it honestly.
91
 
92
- 4. FORMATTING & CODE STYLE:
93
  - Use GitHub-style markdown for all responses.
94
  - Write clean, production-grade, fully commented code blocks.
95
  - Never write placeholders like `// TODO: implement this` or `...` in code outputs unless explicitly asked. Always write complete, copy-pasteable files.
@@ -99,6 +111,69 @@ You must strictly adhere to the following behavioral and formatting rules:
99
  Current Date/Time: {datetime}
100
  """
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  # Premium Claude-Style Custom CSS for Gradio
103
  CLAUDE_CSS = """
104
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@300;400;500;600;700&display=swap');
 
65
  }
66
 
67
  # The Leaked-Style System Prompt (inspired by Claude 3.5 Sonnet & ChatGPT Custom Instructions)
68
+ SYSTEM_PROMPT = """You are Saffan, a highly advanced AI coding assistant and researcher engineered by the Google DeepMind team. You approach every interaction with objective precision, extreme intelligence, and structured depth.
69
 
70
  You must strictly adhere to the following behavioral and formatting rules:
71
 
 
85
  - Adopt an objective, clear, and intellectual tone. Speak directly to the user.
86
  - Do not make assumptions. If a query is ambiguous, explain the ambiguity and outline the options or ask for clarification.
87
 
88
+ 3. CLAUDE-STYLE ARTIFACTS:
89
+ - If you are generating a complete document, webpage (HTML/JS/CSS), SVG graphic, or standalone script, you MUST wrap it inside a custom `<artifact>` tag.
90
+ - Do not print the raw code blocks in your regular markdown. Instead, enclose the entire code structure within the artifact block so it can be rendered side-by-side in the user interface.
91
+ - Syntax:
92
+ <artifact title="Title Description" type="html|code|svg" language="html|python|javascript|css">
93
+ [Insert complete code here]
94
+ </artifact>
95
+ - Example:
96
+ <artifact title="Interactive Calculator" type="html" language="html">
97
+ <!DOCTYPE html><html>...</html>
98
+ </artifact>
99
+
100
+ 4. KNOWLEDGE & CAPABILITIES:
101
  - You have access to real-time web search and web scraping tools. When web context is provided, rely on it to answer queries accurately and provide sources/citations where appropriate.
102
  - If you do not know the answer, admit it honestly.
103
 
104
+ 5. FORMATTING & CODE STYLE:
105
  - Use GitHub-style markdown for all responses.
106
  - Write clean, production-grade, fully commented code blocks.
107
  - Never write placeholders like `// TODO: implement this` or `...` in code outputs unless explicitly asked. Always write complete, copy-pasteable files.
 
111
  Current Date/Time: {datetime}
112
  """
113
 
114
+ # Preset configurations representing different skills/prompts
115
+ SYSTEM_PROMPTS = {
116
+ "Saffan Chat (Default)": SYSTEM_PROMPT,
117
+
118
+ "Python & Async Scraper Expert": """You are Saffan, an elite Python software engineer specializing in asynchronous programming (asyncio), web scraping, APIs, and microservice architectures.
119
+
120
+ You must strictly adhere to the following behavioral and formatting rules:
121
+
122
+ 1. THOUGHT PROCESS (Chain of Thought):
123
+ - You MUST wrap your detailed reasoning inside a `<thinking>` block. Analyze async safety, concurrency, exception handling, and performance before writing code.
124
+ - Trace flow step-by-step.
125
+
126
+ 2. CLAUDE-STYLE ARTIFACTS:
127
+ - If generating a complete script or application, wrap it in a `<artifact>` block:
128
+ <artifact title="Script Description" type="code" language="python">
129
+ [Insert code here]
130
+ </artifact>
131
+
132
+ 3. PYTHON & ASYNC BEST PRACTICES:
133
+ - Always prefer asynchronous libraries like `aiohttp`, `httpx`, or `playwright` for remote calls.
134
+ - Use `asyncio.gather` for concurrent I/O operations.
135
+ - Always implement robust error handling (try/except blocks), rate-limiting, and user-agent rotation.
136
+ - Write fully typed Python code using the `typing` module.
137
+
138
+ Current Date/Time: {datetime}
139
+ """,
140
+
141
+ "Web UI & SVG Graphic Designer": """You are Saffan, a master frontend engineer, UI/UX architect, and SVG designer. Your specialty is building visually stunning, interactive single-page web applications and vectorized graphics.
142
+
143
+ You must strictly adhere to the following behavioral and formatting rules:
144
+
145
+ 1. THOUGHT PROCESS (Chain of Thought):
146
+ - You MUST wrap your detailed reasoning inside a `<thinking>` block. Plan visual layout, color palette (e.g. slate/indigo, glassmorphism), CSS variables, and interaction flow first.
147
+
148
+ 2. CLAUDE-STYLE ARTIFACTS:
149
+ - Every webpage, interactive dashboard, or SVG graphic MUST be inside a `<artifact>` block:
150
+ <artifact title="Title" type="html|svg" language="html|xml">
151
+ [Insert complete code here]
152
+ </artifact>
153
+ - For HTML apps, include all styles in a `<style>` block and all behaviors in a `<script>` block. Do not rely on external CDN resources unless absolutely necessary (prefer pure CSS/JS). Make them fully interactive and responsive!
154
+
155
+ 3. MODERN STYLING:
156
+ - Use beautiful color palettes, smooth transitions, dark modes, modern typography, and hover effects. Ensure designs look highly premium.
157
+
158
+ Current Date/Time: {datetime}
159
+ """,
160
+
161
+ "Logical Reasoner & Tutor": """You are Saffan, a world-class educational tutor, scientist, and logical analyst. You excel at breaking down complex academic, philosophical, or mathematical concepts into clear, engaging, and structured explanations.
162
+
163
+ You must strictly adhere to the following behavioral and formatting rules:
164
+
165
+ 1. THOUGHT PROCESS (Chain of Thought):
166
+ - You MUST wrap your detailed reasoning inside a `<thinking>` block. Dissect the logical steps, identify potential misconceptions, and outline the explanation strategy.
167
+
168
+ 2. FORMATTING:
169
+ - Use LaTeX for mathematical formulas (inline: \\( E=mc^2 \\), block: \\$\\$ \\sum_{i=1}^n i \\$\\$).
170
+ - Use analogies, step-by-step outlines, and markdown tables.
171
+ - Highlight key takeaways with bullet points.
172
+
173
+ Current Date/Time: {datetime}
174
+ """
175
+ }
176
+
177
  # Premium Claude-Style Custom CSS for Gradio
178
  CLAUDE_CSS = """
179
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@300;400;500;600;700&display=swap');
src/engine.py CHANGED
@@ -237,9 +237,45 @@ def format_thinking_tags(text: str) -> str:
237
  else:
238
  # Thinking block is still generating, render it open
239
  return f"{before_thinking}<details open class='thinking-block'><summary>Thinking Process...</summary>\n\n{rest.strip()}\n\n</details>"
240
- return text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
  def execute_chat(
 
243
  message: str,
244
  history: list,
245
  mode: str,
@@ -310,7 +346,7 @@ def execute_chat(
310
 
311
  # Prepare active prompt contents
312
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
313
- compiled_system_prompt = system_prompt_preset.format(datetime=current_time)
314
 
315
  # Prepend search context to user query if found
316
  if search_context:
@@ -334,8 +370,11 @@ def execute_chat(
334
 
335
  for partial_text in api_stream:
336
  formatted_text = format_thinking_tags(partial_text)
337
- full_response = status_update + formatted_text if status_update else formatted_text
338
- yield history + [[message, full_response]], ""
 
 
 
339
 
340
  else:
341
  # Local CPU or Zero-GPU mode
@@ -372,5 +411,8 @@ def execute_chat(
372
 
373
  for partial_text in local_stream:
374
  formatted_text = format_thinking_tags(partial_text)
375
- full_response = status_update + formatted_text if status_update else formatted_text
376
- yield history + [[message, full_response]], ""
 
 
 
 
237
  else:
238
  # Thinking block is still generating, render it open
239
  return f"{before_thinking}<details open class='thinking-block'><summary>Thinking Process...</summary>\n\n{rest.strip()}\n\n</details>"
240
+
241
+ def extract_artifacts(text: str) -> list:
242
+ """
243
+ Extracts all <artifact> blocks (including in-progress ones)
244
+ from the streaming text.
245
+ """
246
+ artifacts = []
247
+ # Match tags: <artifact title="x" type="y" language="z">content</artifact> or open tags at the end of text
248
+ pattern = r'<artifact\s+title="([^"]*)"\s+type="([^"]*)"\s+language="([^"]*)"\s*>(.*?)(?:</artifact>|$)'
249
+ matches = re.finditer(pattern, text, re.DOTALL)
250
+ for m in matches:
251
+ title = m.group(1) or "Untitled"
252
+ type_ = m.group(2) or "code"
253
+ lang = m.group(3) or "plaintext"
254
+ content = m.group(4)
255
+ artifacts.append({
256
+ "title": title,
257
+ "type": type_,
258
+ "language": lang,
259
+ "content": content.strip()
260
+ })
261
+ return artifacts
262
+
263
+ def clean_chatbot_response(text: str) -> str:
264
+ """
265
+ Replaces <artifact> blocks in the response with a clean visual badge
266
+ so the raw code doesn't clutter the main chat viewport.
267
+ """
268
+ pattern = r'<artifact\s+title="([^"]*)"\s+type="([^"]*)"\s+language="([^"]*)"\s*>.*?(?:</artifact>|$)'
269
+
270
+ def replace_with_badge(match):
271
+ title = match.group(1) or "Untitled Artifact"
272
+ type_ = match.group(2) or "code"
273
+ return f"\n\n> ⚙️ **Artifact Generated:** *{title}* ({type_}) — *Rendered in the right-hand panel* ↗️\n\n"
274
+
275
+ return re.sub(pattern, replace_with_badge, text, flags=re.DOTALL)
276
 
277
  def execute_chat(
278
+
279
  message: str,
280
  history: list,
281
  mode: str,
 
346
 
347
  # Prepare active prompt contents
348
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
349
+ compiled_system_prompt = system_prompt_preset.replace("{datetime}", current_time)
350
 
351
  # Prepend search context to user query if found
352
  if search_context:
 
370
 
371
  for partial_text in api_stream:
372
  formatted_text = format_thinking_tags(partial_text)
373
+ artifacts = extract_artifacts(formatted_text)
374
+ clean_text = clean_chatbot_response(formatted_text)
375
+ full_response = status_update + clean_text if status_update else clean_text
376
+ yield history + [[message, full_response]], artifacts
377
+
378
 
379
  else:
380
  # Local CPU or Zero-GPU mode
 
411
 
412
  for partial_text in local_stream:
413
  formatted_text = format_thinking_tags(partial_text)
414
+ artifacts = extract_artifacts(formatted_text)
415
+ clean_text = clean_chatbot_response(formatted_text)
416
+ full_response = status_update + clean_text if status_update else clean_text
417
+ yield history + [[message, full_response]], artifacts
418
+
src/ui.py CHANGED
@@ -1,6 +1,6 @@
1
  import gradio as gr
2
  import torch
3
- from src.config import MODEL_CONFIGS, SYSTEM_PROMPT, CLAUDE_CSS
4
  from src.engine import execute_chat, HAS_SPACES
5
 
6
  def get_hardware_status():
@@ -30,7 +30,6 @@ def execute_chat_ui(
30
  history,
31
  mode,
32
  model_name,
33
-
34
  system_prompt_preset,
35
  max_new_tokens,
36
  temperature,
@@ -44,7 +43,6 @@ def execute_chat_ui(
44
  """
45
  if history is None or len(history) == 0:
46
  return
47
-
48
 
49
  # Extract latest user message and the history preceding it
50
  user_message = history[-1][0]
@@ -64,8 +62,56 @@ def execute_chat_ui(
64
  hf_token=hf_token
65
  )
66
 
67
- for updated_history, _ in chat_generator:
68
- yield updated_history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  def build_interface():
71
  """Constructs the Gradio user interface using custom styles and themes."""
@@ -85,7 +131,7 @@ def build_interface():
85
  border_color_primary_dark="rgba(255, 255, 255, 0.08)"
86
  )
87
 
88
- with gr.Blocks(theme=theme, css=CLAUDE_CSS, title="Antigravity Chat") as demo:
89
  # State to store the raw message during submission sequence
90
 
91
  with gr.Row():
@@ -94,7 +140,7 @@ def build_interface():
94
  """
95
  <div style="text-align: center; margin-bottom: 24px; margin-top: 10px;">
96
  <h1 style="font-size: 2.8em; margin-bottom: 5px; background: linear-gradient(90deg, #60a5fa, #a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
97
- ANTIGRAVITY CHAT
98
  </h1>
99
  <p style="font-size: 1.1em; color: #9ca3af; max-width: 600px; margin: 0 auto;">
100
  A premium Claude-style chatbot environment designed for Hugging Face free tier.
@@ -155,6 +201,13 @@ def build_interface():
155
 
156
  # Advanced Settings Accordion
157
  with gr.Accordion("🛠️ Advanced Parameters", open=False):
 
 
 
 
 
 
 
158
  system_prompt = gr.Textbox(
159
  label="System Instruction Prompt",
160
  value=SYSTEM_PROMPT,
@@ -189,33 +242,84 @@ def build_interface():
189
  # System actions
190
  clear_btn = gr.Button("🗑️ Clear Chat History", variant="secondary", elem_classes=["secondary-btn"])
191
 
192
- # Main Chat Area
193
  with gr.Column(scale=9):
194
- chatbot = gr.Chatbot(
195
- label="Chat Window",
196
- elem_classes=["chatbot-container"],
197
- show_label=False,
198
- avatar_images=(None, "https://huggingface.co/front/assets/huggingface_logo-noborder.svg"),
199
- height=580,
200
- bubble_full_width=False,
201
- type="tuples"
202
- )
203
-
204
 
205
  with gr.Row():
206
- input_box = gr.Textbox(
207
- placeholder="Ask Antigravity anything... (e.g., 'What happened in AI news this week?' with search on)",
208
- show_label=False,
209
- scale=10
210
- )
211
- submit_btn = gr.Button("Send", variant="primary", scale=1, elem_classes=["action-btn"])
212
-
213
- # Prompts suggestions
214
- gr.Markdown("💡 **Quick Prompts**")
215
- with gr.Row():
216
- suggestion_1 = gr.Button("Draft a clean Python function using asyncio to scrape web data.", variant="secondary", elem_classes=["secondary-btn"])
217
- suggestion_2 = gr.Button("Search the web for the latest advancements in LLM reasoning models.", variant="secondary", elem_classes=["secondary-btn"])
218
- suggestion_3 = gr.Button("Explain quantum computing superposition using a simple real-life analogy.", variant="secondary", elem_classes=["secondary-btn"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
  # Define UI event linkages
221
 
@@ -226,6 +330,13 @@ def build_interface():
226
  outputs=[model_dropdown]
227
  )
228
 
 
 
 
 
 
 
 
229
  # 2. Main submit event chain (for Enter key submit)
230
  submit_event = input_box.submit(
231
  fn=add_user_message,
@@ -245,7 +356,7 @@ def build_interface():
245
  enable_search,
246
  hf_token
247
  ],
248
- outputs=[chatbot]
249
  )
250
 
251
  # 3. Submit button click event chain
@@ -267,20 +378,40 @@ def build_interface():
267
  enable_search,
268
  hf_token
269
  ],
270
- outputs=[chatbot]
271
  )
272
 
273
- # 4. Clear chat history button event
274
- clear_btn.click(fn=lambda: [], outputs=chatbot, queue=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
 
 
 
 
 
 
276
 
277
- # 5. Suggestion prompt buttons click events
 
 
 
278
  def load_suggestion(text):
279
  search_enabled = "Search the web" in text or "latest advancements" in text
280
  return text, search_enabled
281
 
282
-
283
-
284
  suggestion_1.click(
285
  fn=lambda: load_suggestion("Draft a clean Python function using asyncio to scrape web data."),
286
  outputs=[input_box, enable_search]
 
1
  import gradio as gr
2
  import torch
3
+ from src.config import MODEL_CONFIGS, SYSTEM_PROMPT, SYSTEM_PROMPTS, CLAUDE_CSS
4
  from src.engine import execute_chat, HAS_SPACES
5
 
6
  def get_hardware_status():
 
30
  history,
31
  mode,
32
  model_name,
 
33
  system_prompt_preset,
34
  max_new_tokens,
35
  temperature,
 
43
  """
44
  if history is None or len(history) == 0:
45
  return
 
46
 
47
  # Extract latest user message and the history preceding it
48
  user_message = history[-1][0]
 
62
  hf_token=hf_token
63
  )
64
 
65
+ for updated_history, artifacts in chat_generator:
66
+ yield updated_history, artifacts
67
+
68
+ def load_selected_artifact(selected_title, artifacts):
69
+ """Retrieves and formats code/render outputs for a selected artifact."""
70
+ if not selected_title or not artifacts:
71
+ return "", "", "plaintext"
72
+
73
+ for art in artifacts:
74
+ if art["title"] == selected_title:
75
+ content = art["content"]
76
+ if art["type"] == "html":
77
+ # Render HTML inside a secure data URI iframe to isolate it from Gradio styles
78
+ import urllib.parse
79
+ escaped_content = urllib.parse.quote(content)
80
+ iframe_render = f'<iframe src="data:text/html;charset=utf-8,{escaped_content}" style="width: 100%; height: 500px; border: none; border-radius: 8px; background-color: white;"></iframe>'
81
+ return content, iframe_render, art["language"]
82
+ elif art["type"] == "svg":
83
+ # Render SVG directly
84
+ svg_render = f'<div style="background-color: white; padding: 20px; border-radius: 8px; text-align: center; display: flex; justify-content: center; align-items: center;">{content}</div>'
85
+ return content, svg_render, "xml"
86
+ else:
87
+ # Code content (no render preview available)
88
+ no_render_placeholder = '<div style="padding: 40px; text-align: center; color: #9ca3af;">No visual render preview available for this code type. Use the "Source Code" tab to view.</div>'
89
+ return content, no_render_placeholder, art["language"]
90
+
91
+ return "", "", "plaintext"
92
+
93
+ def update_artifacts_ui(artifacts):
94
+ """Refreshes the state and visibility of components inside the Artifacts Panel."""
95
+ if not artifacts:
96
+ return (
97
+ gr.Dropdown(choices=[], value=None, visible=False),
98
+ gr.Markdown(visible=True),
99
+ gr.Code(value="", visible=False),
100
+ gr.HTML(value="", visible=False)
101
+ )
102
+
103
+ choices = [art["title"] for art in artifacts]
104
+ default_val = choices[-1]
105
+
106
+ code_content, render_html, lang = load_selected_artifact(default_val, artifacts)
107
+
108
+ return (
109
+ gr.Dropdown(choices=choices, value=default_val, visible=True),
110
+ gr.Markdown(visible=False),
111
+ gr.Code(value=code_content, language=lang, visible=True),
112
+ gr.HTML(value=render_html, visible=True)
113
+ )
114
+
115
 
116
  def build_interface():
117
  """Constructs the Gradio user interface using custom styles and themes."""
 
131
  border_color_primary_dark="rgba(255, 255, 255, 0.08)"
132
  )
133
 
134
+ with gr.Blocks(theme=theme, css=CLAUDE_CSS, title="Saffan Chat") as demo:
135
  # State to store the raw message during submission sequence
136
 
137
  with gr.Row():
 
140
  """
141
  <div style="text-align: center; margin-bottom: 24px; margin-top: 10px;">
142
  <h1 style="font-size: 2.8em; margin-bottom: 5px; background: linear-gradient(90deg, #60a5fa, #a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
143
+ SAFFAN CHAT
144
  </h1>
145
  <p style="font-size: 1.1em; color: #9ca3af; max-width: 600px; margin: 0 auto;">
146
  A premium Claude-style chatbot environment designed for Hugging Face free tier.
 
201
 
202
  # Advanced Settings Accordion
203
  with gr.Accordion("🛠️ Advanced Parameters", open=False):
204
+ system_preset_dropdown = gr.Dropdown(
205
+ choices=list(SYSTEM_PROMPTS.keys()),
206
+ value="Saffan Chat (Default)",
207
+ label="Select AI Persona / Skill Mode",
208
+ interactive=True
209
+ )
210
+
211
  system_prompt = gr.Textbox(
212
  label="System Instruction Prompt",
213
  value=SYSTEM_PROMPT,
 
242
  # System actions
243
  clear_btn = gr.Button("🗑️ Clear Chat History", variant="secondary", elem_classes=["secondary-btn"])
244
 
245
+ # Main Chat & Artifacts Area
246
  with gr.Column(scale=9):
247
+ # Conversation-scoped state to hold parsed artifacts
248
+ artifacts_state = gr.State(value=[])
 
 
 
 
 
 
 
 
249
 
250
  with gr.Row():
251
+ # Left Column: Chat Viewport
252
+ with gr.Column(scale=6):
253
+ chatbot = gr.Chatbot(
254
+ label="Chat Window",
255
+ elem_classes=["chatbot-container"],
256
+ show_label=False,
257
+ avatar_images=(None, "https://huggingface.co/front/assets/huggingface_logo-noborder.svg"),
258
+ height=580,
259
+ bubble_full_width=False,
260
+ type="tuples"
261
+ )
262
+
263
+ with gr.Row():
264
+ input_box = gr.Textbox(
265
+ placeholder="Ask Saffan anything... (e.g., 'Draft a clean Python function using asyncio to scrape web data.')",
266
+ show_label=False,
267
+ scale=10
268
+ )
269
+ submit_btn = gr.Button("Send", variant="primary", scale=1, elem_classes=["action-btn"])
270
+
271
+ # Prompts suggestions
272
+ gr.Markdown("💡 **Quick Prompts**")
273
+ with gr.Row():
274
+ suggestion_1 = gr.Button("Draft a clean Python function using asyncio to scrape web data.", variant="secondary", elem_classes=["secondary-btn"])
275
+ suggestion_2 = gr.Button("Search the web for the latest advancements in LLM reasoning models.", variant="secondary", elem_classes=["secondary-btn"])
276
+ suggestion_3 = gr.Button("Explain quantum computing superposition using a simple real-life analogy.", variant="secondary", elem_classes=["secondary-btn"])
277
+
278
+ # Right Column: Claude-Style Artifacts Panel
279
+ with gr.Column(scale=5, elem_classes=["sidebar-panel"]):
280
+ gr.HTML(
281
+ """
282
+ <div style="border-bottom: 1px solid rgba(255,255,255,0.08); padding-bottom: 10px; margin-bottom: 15px;">
283
+ <h3 style="margin: 0; font-size: 1.25em; color: #60a5fa; display: flex; align-items: center; gap: 8px;">
284
+ 🎨 Claude-Style Artifacts
285
+ </h3>
286
+ <p style="margin: 3px 0 0 0; font-size: 0.8em; color: #9ca3af;">
287
+ Interactive HTML/SVG rendering and source code viewer.
288
+ </p>
289
+ </div>
290
+ """
291
+ )
292
+
293
+ # Artifact Selector Dropdown
294
+ artifact_selector = gr.Dropdown(
295
+ label="Select Artifact",
296
+ choices=[],
297
+ value=None,
298
+ visible=False,
299
+ interactive=True
300
+ )
301
+
302
+ # No Artifacts Placeholder Description
303
+ artifact_placeholder = gr.Markdown(
304
+ "**No active artifacts.**\n\nWhen Saffan generates complete webpages, SVG graphics, or scripts, they will appear here side-by-side automatically.",
305
+ visible=True
306
+ )
307
+
308
+ # Tabs for Preview Render and Code Source
309
+ with gr.Tabs() as artifact_tabs:
310
+ with gr.Tab("Preview"):
311
+ artifact_render = gr.HTML(
312
+ value="",
313
+ visible=False
314
+ )
315
+ with gr.Tab("Source Code"):
316
+ artifact_code = gr.Code(
317
+ value="",
318
+ language="plaintext",
319
+ interactive=False,
320
+ wrap_lines=True,
321
+ visible=False
322
+ )
323
 
324
  # Define UI event linkages
325
 
 
330
  outputs=[model_dropdown]
331
  )
332
 
333
+ # Preset dropdown change updates the system prompt textbox content
334
+ system_preset_dropdown.change(
335
+ fn=lambda preset: SYSTEM_PROMPTS.get(preset, SYSTEM_PROMPT),
336
+ inputs=[system_preset_dropdown],
337
+ outputs=[system_prompt]
338
+ )
339
+
340
  # 2. Main submit event chain (for Enter key submit)
341
  submit_event = input_box.submit(
342
  fn=add_user_message,
 
356
  enable_search,
357
  hf_token
358
  ],
359
+ outputs=[chatbot, artifacts_state]
360
  )
361
 
362
  # 3. Submit button click event chain
 
378
  enable_search,
379
  hf_token
380
  ],
381
+ outputs=[chatbot, artifacts_state]
382
  )
383
 
384
+ # 4. State change triggers UI update for the Artifacts panel
385
+ artifacts_state.change(
386
+ fn=update_artifacts_ui,
387
+ inputs=[artifacts_state],
388
+ outputs=[artifact_selector, artifact_placeholder, artifact_code, artifact_render]
389
+ )
390
+
391
+ # 5. Dropdown change updates the content panel
392
+ def handle_selector_change(selected_title, artifacts):
393
+ if not selected_title or not artifacts:
394
+ return gr.update(value="", visible=False), gr.update(value="", visible=False)
395
+ code_content, render_html, lang = load_selected_artifact(selected_title, artifacts)
396
+ return (
397
+ gr.Code(value=code_content, language=lang, visible=True),
398
+ gr.HTML(value=render_html, visible=True)
399
+ )
400
 
401
+ artifact_selector.change(
402
+ fn=handle_selector_change,
403
+ inputs=[artifact_selector, artifacts_state],
404
+ outputs=[artifact_code, artifact_render]
405
+ )
406
 
407
+ # 6. Clear chat history button event (also clears artifacts state)
408
+ clear_btn.click(fn=lambda: ([], []), outputs=[chatbot, artifacts_state], queue=False)
409
+
410
+ # 7. Suggestion prompt buttons click events
411
  def load_suggestion(text):
412
  search_enabled = "Search the web" in text or "latest advancements" in text
413
  return text, search_enabled
414
 
 
 
415
  suggestion_1.click(
416
  fn=lambda: load_suggestion("Draft a clean Python function using asyncio to scrape web data."),
417
  outputs=[input_box, enable_search]