CultriX commited on
Commit
0d9cafc
·
verified ·
1 Parent(s): 1674f91

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +461 -221
app.py CHANGED
@@ -1,18 +1,50 @@
1
- from run import create_agent, run_agent_with_streaming
2
  import gradio as gr
 
 
3
  import os
4
- import threading
5
  import time
6
- import base64
7
  import re # For regex to extract code blocks
8
-
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  from dotenv import load_dotenv
10
 
11
  load_dotenv()
12
  CONFIG_FILE = ".user_config.env"
13
 
14
- # --- Constants and Helper Functions for Code Playground ---
15
- # This is a simplified version of SystemPrompt, adapt as needed for your smolagents.CodeAgent
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  SYSTEM_PROMPT_CODE_GEN = """
17
  You are an expert web developer. Your task is to write a complete, single HTML file
18
  (including all necessary CSS and JavaScript within <style> and <script> tags, or as data URIs for images if any)
@@ -26,13 +58,15 @@ that directly solves the user's request.
26
  - Provide a brief reasoning *before* the code block, explaining your approach.
27
  """
28
 
29
- def save_env_vars_to_file(env_vars):
30
- print("[DEBUG] Saving user config to file")
31
- with open(CONFIG_FILE, "w") as f:
32
- for key, value in env_vars.items():
33
- f.write(f"{key}={value}\n")
 
34
 
35
- # --- Helper for Code Playground ---
 
36
  def remove_code_block(text):
37
  """
38
  Extracts the content of the first Markdown code block (```html ... ``` or ``` ... ```)
@@ -112,10 +146,216 @@ def send_to_sandbox(code):
112
  print("[DEBUG] Generated iframe for sandbox.")
113
  return iframe_html
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
  # --- Main Gradio Interface Launch Function ---
117
  def launch_interface():
118
- # --- Chatbot Tab Logic ---
119
  def setup_agent_streaming(question, model_id, hf_token, openai_api_key, serpapi_key, api_endpoint, use_custom_endpoint,
120
  custom_api_endpoint, custom_api_key, search_provider, search_api_key, custom_search_url):
121
  print("[DEBUG] Setting up agent with input question:", question)
@@ -164,7 +404,6 @@ def launch_interface():
164
  return f"<p><span style='color:#f59e0b;font-weight:bold;'>[STEP]</span> {text.strip()}</p>"
165
  elif text.strip():
166
  # Wrap regular steps in details tag for collapsing
167
- # Ensure each line is its own <pre> or paragraph if preferred, for distinct updates
168
  return f"<details><summary><span style='color:#f59e0b;'>Step</span></summary>\n<pre>{text.strip()}</pre>\n</details>"
169
  return ""
170
 
@@ -196,31 +435,32 @@ def launch_interface():
196
  # Join only the new content, or the entire buffer for cumulative display
197
  current_html_output = "".join(output_html_buffer)
198
  yield current_html_output, final_answer_text
199
- last_buffer_length = len(output_html_buffer)
200
  time.sleep(0.05) # Smaller delay for more responsive updates
201
 
202
  # Ensure final state is yielded
203
  final_html_output = "".join(output_html_buffer)
204
  yield final_html_output, final_answer_text
205
 
206
- # --- Code Playground Tab Logic ---
207
  def generate_code_streaming(query, model_id, hf_token, openai_api_key, serpapi_key, api_endpoint, use_custom_endpoint,
208
  custom_api_endpoint, custom_api_key):
209
  print(f"[DEBUG] Starting code generation with query: {query}")
210
 
211
  if query.strip() == "":
212
- # Reset outputs
213
- yield gr.update(value="", visible=True), gr.update(value=""), gr.update(value=""), gr.update(selected="empty", visible=True), gr.update(selected="reasoning", visible=False), gr.update(value="Enter your request to generate code")
 
 
 
 
 
 
214
  return
215
 
216
  endpoint = custom_api_endpoint if use_custom_endpoint else api_endpoint
217
  api_key = custom_api_key if use_custom_endpoint else openai_api_key
218
 
219
- # Create a CodeAgent specifically for this task
220
- # Note: We're reusing the create_agent function, but it needs to be aware
221
- # of the different system prompt for code generation.
222
- # A more robust solution might have a dedicated `create_code_agent` if prompts vary.
223
- # For now, we'll assume SYSTEM_PROMPT_CODE_GEN is handled by the model or passed implicitly.
224
  agent = create_agent(
225
  model_id=model_id,
226
  hf_token=hf_token,
@@ -229,147 +469,109 @@ def launch_interface():
229
  api_endpoint=api_endpoint,
230
  custom_api_endpoint=endpoint,
231
  custom_api_key=api_key,
232
- # search_provider and search_api_key are not relevant for code generation agent
233
  search_provider="none", # Explicitly set to none if not used
234
  search_api_key=None,
235
  custom_search_url=None
236
  )
237
 
238
- # Override the agent's system prompt for code generation
239
- # Based on the error, smolagents uses prompt_templates for system prompts.
240
- # The specific key might vary, but 'user_agent' or 'managed_agent' are common.
241
- # Assuming the CodeAgent also has a 'system_prompt' in its prompt_templates
242
- # or we can modify the one for its internal 'managed_agent' if applicable.
243
-
244
- # A common pattern for smolagents' CodeAgent might be to modify the 'user_agent'
245
- # or 'managed_agent' template that it uses internally.
246
- # Let's try modifying the 'managed_agent' task and prepending the system prompt.
247
- # If this doesn't fully capture the 'system' role, you might need to inspect
248
- # the exact structure of `agent.prompt_templates` in a debugger.
249
-
250
- # Safely try to modify the prompt template used by the CodeAgent.
251
- # The exact key for the system prompt might vary (e.g., 'system', 'default', 'user_agent').
252
- # A common structure is `agent.prompt_templates["user_agent"]["system_prompt"]`
253
- # or it might be directly injected in the `task` for agents that don't have a separate system role.
254
-
255
- # Let's assume the `CodeAgent` itself uses a "default" or similar template
256
- # that allows injecting a system prompt, or that its primary instruction
257
- # can be adjusted via its 'task' definition.
258
-
259
- # A more direct way to ensure the system prompt is used by LiteLLMModel
260
- # within smolagents is to pass it during agent creation, or if the agent
261
- # has an exposed way to set a 'system' message.
262
-
263
- # Given the error implies direct modification of `prompt_templates`,
264
- # let's try to find a suitable place.
265
- # Based on smolagents examples, `user_agent` or `managed_agent` templates are often customized.
266
- # The `CodeAgent` itself doesn't typically expose a direct `system_prompt` property
267
- # that it forwards to the LLM; instead, it uses the prompt templates.
268
-
269
- # The error specifically points to `self.prompt_templates["system_prompt"]`.
270
- # This implies there *is* a key "system_prompt" within prompt_templates.
271
- # Let's apply SYSTEM_PROMPT_CODE_GEN to this specific key.
272
- if hasattr(agent, 'prompt_templates') and "system_prompt" in agent.prompt_templates:
273
- # If the agent explicitly exposes a "system_prompt" template key
274
- agent.prompt_templates["system_prompt"] = SYSTEM_PROMPT_CODE_GEN
275
- print("[DEBUG] Set agent.prompt_templates['system_prompt'] for code generation.")
276
- elif hasattr(agent, 'prompt_templates') and 'user_agent' in agent.prompt_templates:
277
- # If it's a ToolCallingAgent (which CodeAgent inherits from)
278
- # and it has a 'user_agent' template, we can modify its system message.
279
- # This is a common pattern for defining the agent's core persona.
280
- agent.prompt_templates['user_agent']['system_message'] = SYSTEM_PROMPT_CODE_GEN
281
- print("[DEBUG] Set agent.prompt_templates['user_agent']['system_message'] for code generation.")
282
  else:
283
- print("[WARNING] Could not set system prompt for CodeAgent using known patterns. "
284
- "Agent might not follow code generation instructions optimally.")
285
- # Fallback: Prepend to the question for basic instruction if no proper system prompt mechanism
286
- # This is not ideal but ensures the instruction is conveyed.
287
  query = SYSTEM_PROMPT_CODE_GEN + "\n\n" + query
288
 
289
 
290
- reasoning_buffer = []
291
- code_buffer = []
292
- is_complete = False
293
-
294
- def code_gen_stream_callback(text):
295
- # This callback needs to differentiate between reasoning and code.
296
- # Smolagents typically logs reasoning and then the final output.
297
- # We'll assume anything before a detected code block is reasoning,
298
- # and the code block itself is the code.
299
-
300
- # This is a heuristic. A better approach would be if smolagents
301
- # could tag its output more clearly (e.g., [REASONING] or [CODE]).
302
-
303
- # For now, we'll append everything to reasoning_buffer
304
- # and then try to extract the final code at the end.
305
- reasoning_buffer.append(text)
306
 
307
- # Yield updates for the reasoning output
308
- yield gr.update(value="".join(reasoning_buffer), visible=True), \
309
- gr.update(value=""), \
310
- gr.update(value=""), \
311
- gr.update(selected="loading"), \
312
- gr.update(selected="reasoning", visible=True), \
313
- gr.update(value="Thinking and coding...")
314
- time.sleep(0.01) # Very small sleep for fine-grained updates
315
-
316
- # Run agent in a separate thread
317
- agent_thread = threading.Thread(target=lambda: run_agent_with_streaming(agent, query, code_gen_stream_callback))
 
 
 
 
318
  agent_thread.start()
319
 
320
- # Initial yield to show loading state
321
- yield gr.update(value="", visible=True), gr.update(value=""), gr.update(value=""), gr.update(selected="loading", visible=True), gr.update(selected="reasoning", visible=True), gr.update(value="Thinking and coding...")
322
-
323
- # Stream reasoning process
324
- last_reasoning_len = 0
325
- while agent_thread.is_alive() or not is_complete: # is_complete will be set by run_agent_async's finally
326
- current_reasoning_output = "".join(reasoning_buffer)
327
- if len(current_reasoning_output) > last_reasoning_len:
328
- yield gr.update(value="".join(reasoning_buffer), visible=True), \
329
- gr.update(value=""), \
330
- gr.update(value=""), \
331
- gr.update(selected="loading"), \
332
- gr.update(selected="reasoning", visible=True), \
333
- gr.update(value="Generating code...")
334
- last_reasoning_len = len(current_reasoning_output)
335
- time.sleep(0.05) # Adjust for streaming granularity
336
-
337
-
338
- # After thread finishes, get the final answer (which contains both reasoning and code)
339
- # Note: This requires run_agent_with_streaming to return the final answer.
340
- # Currently, it returns it, but the thread doesn't easily expose it back to the generator.
341
- # A more robust way is to pass a queue between the thread and the generator.
342
- # For simplicity, we'll assume the last content in reasoning_buffer
343
- # will contain the full output, and we'll parse it.
344
 
345
- full_agent_output = "".join(reasoning_buffer) # Contains everything from the agent
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
- # Extract code and reasoning
348
- generated_code_raw = remove_code_block(full_agent_output)
349
 
350
- # Heuristic: Remove the extracted code block from the full output to get just reasoning
351
- reasoning_only = full_agent_output.replace(f"```{generated_code_raw}```", "").strip()
352
- reasoning_only = reasoning_only.replace(f"```html\n{generated_code_raw}\n```", "").strip()
353
- reasoning_only = reasoning_only.replace(f"```HTML\n{generated_code_raw}\n```", "").strip()
354
-
355
-
356
- # Render the code in sandbox
357
- html_to_render = send_to_sandbox(generated_code_raw) if generated_code_raw else "<div>No code generated or code extraction failed.</div>"
 
358
 
359
- # Final yield to set all outputs
360
- yield gr.update(value=full_agent_output, visible=True), \
361
- gr.update(value=generated_code_raw, visible=True), \
362
  gr.update(value=html_to_render, visible=True), \
363
  gr.update(selected="render", visible=True), \
364
- gr.update(selected="code", visible=True), \
365
- gr.update(value="Done")
366
 
367
- # --- Gradio UI Layout ---
368
- with gr.Blocks(title="SmolAgent - Intelligent AI with Web Tools", theme="CultriX/gradio-theme") as demo:
 
369
  gr.Markdown("# SmolAgent - Intelligent AI with Web Tools")
370
 
371
- with gr.Tabs(): # Use gr.Tabs for multiple sections
372
  with gr.TabItem("Chatbot"):
 
373
  with gr.Row():
374
  with gr.Column(scale=1):
375
  question = gr.Textbox(label="Your Question", lines=3, placeholder="Enter your question or task for the AI agent...")
@@ -429,90 +631,97 @@ def launch_interface():
429
  )
430
 
431
  with gr.TabItem("Code Playground (WebDev)"):
432
- with gr.Row():
433
- with gr.Column(scale=1):
434
- code_query = gr.Textbox(label="Your Request", lines=3, placeholder="Describe the web application you want to generate...")
435
- model_id_code = gr.Textbox(label="Model ID", value="gpt-4o-mini", placeholder="e.g., gpt-4, claude-3-opus-20240229")
436
-
437
- with gr.Accordion("API Configuration (Code Gen)", open=False):
438
- hf_token_code = gr.Textbox(label="Hugging Face Token (Optional)", type="password", value=os.getenv("HF_TOKEN", ""), placeholder="Your Hugging Face token if using HF models")
439
- openai_api_key_code = gr.Textbox(label="OpenAI API Key (Optional)", type="password", value=os.getenv("OPENAI_API_KEY", ""), placeholder="Your OpenAI API key")
440
- api_endpoint_code = gr.Textbox(label="Default API Endpoint", value=os.getenv("API_ENDPOINT", "https://api.openai.com/v1"), placeholder="e.g., https://api.openai.com/v1")
441
- with gr.Group():
442
- use_custom_endpoint_code = gr.Checkbox(label="Use Custom API Endpoint")
443
- custom_api_endpoint_code = gr.Textbox(label="Custom API URL", visible=False, placeholder="URL for your custom API endpoint")
444
- custom_api_key_code = gr.Textbox(label="Custom API Key (Optional)", type="password", visible=False, placeholder="API key for the custom endpoint")
445
-
446
- generate_code_btn = gr.Button("Generate Code", variant="primary")
447
-
448
- with gr.Column(scale=2):
449
- with gr.Tabs(selected="empty") as code_output_tabs_container: # This will control empty/loading/render
450
- with gr.TabItem("🤔 Thinking Process", elem_id="reasoning_tab"):
451
- # This will display the streamed reasoning process
452
- reasoning_output = gr.HTML(label="Thinking Process")
453
- with gr.TabItem("💻 Generated Code", elem_id="code_tab"):
454
- # This will display the raw generated code block
455
- code_output_raw = gr.Code(label="Generated Code", language="html", interactive=False)
456
- with gr.TabItem("Output", elem_id="sandbox_tab"): # The actual iframe container
457
- sandbox_output = gr.HTML(label="Rendered Output") # This will hold the iframe
458
-
459
- # These are for controlling the visibility/state like the target app
460
- # We use gr.State to hold tips and other internal states if needed
461
- loading_status = gr.Textbox(label="Status", value="Ready", visible=False) # A hidden component to show "Thinking...", "Generating code..."
462
-
463
- # Placeholders for empty/loading states - controlled by js or updates
464
- with gr.Group(visible=True) as empty_state_group:
465
- gr.Markdown(
466
- """
467
- <div style="text-align: center; padding: 20px;">
468
- <h3>Enter your request to generate code</h3>
469
- <p>Describe the web component or application you want the AI to create.</p>
470
- </div>
471
- """
472
  )
473
- with gr.Group(visible=False) as loading_state_group:
474
- gr.Markdown(
475
- """
476
- <div style="text-align: center; padding: 20px;">
477
- <div class="gr-progres-bar-container" style="display: flex; justify-content: center; align-items: center; min-height: 150px;">
478
- <div class="gr-progress-bar" style="width: 50px; height: 50px; border: 5px solid #f3f3f3; border-top: 5px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite;"></div>
479
- <style>
480
- @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
481
- </style>
482
- </div>
483
- <p style="font-weight: bold; margin-top: 10px;">Thinking and coding...</p>
484
- </div>
485
- """
486
- )
487
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
 
489
- # --- Code Playground Interactions ---
490
  generate_code_btn.click(
 
 
 
 
 
 
 
 
 
 
 
 
491
  fn=generate_code_streaming,
492
  inputs=[
493
- code_query, model_id_code, hf_token_code, openai_api_key_code, serpapi_key_chatbot,
494
  api_endpoint_code, use_custom_endpoint_code, custom_api_endpoint_code, custom_api_key_code
495
  ],
496
- outputs=[reasoning_output, code_output_raw, sandbox_output, code_output_tabs_container, code_output_tabs_container, loading_status],
497
- show_progress="hidden" # We'll manage progress indicators manually
498
- ).success( # On successful completion, hide loading, show tabs
499
- fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
500
- outputs=[empty_state_group, code_output_tabs_container]
501
- ).then( # On any status (even during streaming), hide empty state
502
- fn=lambda: gr.update(visible=False),
503
- outputs=[empty_state_group]
504
- )
505
-
506
- # JavaScript to switch tabs and show loading/empty states
507
- # This needs to be carefully orchestrated with the Python yields.
508
- # A common pattern is to have a hidden state component that triggers JS.
509
- code_query.submit(
510
- fn=lambda: (gr.update(visible=True), gr.update(selected="loading")),
511
- outputs=[loading_state_group, code_output_tabs_container]
512
  )
513
 
514
- # Use a JS function to ensure scrolling to the bottom for streaming outputs
515
- # This needs to be triggered on updates to the streaming output component
 
516
  reasoning_output.change(
517
  fn=None,
518
  inputs=[],
@@ -520,17 +729,48 @@ def launch_interface():
520
  js="""
521
  function() {
522
  setTimeout(() => {
523
- const elem = document.querySelector('#reasoning_tab .markdown-container, #reasoning_tab .gr-html-container');
524
- if (elem) {
525
- elem.scrollTop = elem.scrollHeight;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
  }
527
  }, 100);
528
  }
529
  """
530
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
531
 
532
  print("[DEBUG] Launching updated Gradio interface")
533
- demo.launch()
534
 
535
  if __name__ == "__main__":
536
  launch_interface()
 
1
+ import base64
2
  import gradio as gr
3
+ import json
4
+ import mimetypes # Used in MiniMax template for base64 encoding, though not directly in my code for now
5
  import os
6
+ import requests # MiniMax template uses requests for its API calls
7
  import time
 
8
  import re # For regex to extract code blocks
9
+ import threading # For running agent asynchronously
10
+
11
+ # Import modelscope_studio components
12
+ import modelscope_studio.components.antd as antd
13
+ import modelscope_studio.components.antdx as antdx
14
+ import modelscope_studio.components.base as ms
15
+ import modelscope_studio.components.pro as pro # pro.Chatbot etc.
16
+ from modelscope_studio.components.pro.chatbot import (
17
+ ChatbotActionConfig, ChatbotBotConfig, ChatbotMarkdownConfig,
18
+ ChatbotPromptsConfig, ChatbotUserConfig, ChatbotWelcomeConfig
19
+ )
20
+
21
+ # Your existing smolagents imports
22
+ from run import create_agent, run_agent_with_streaming
23
  from dotenv import load_dotenv
24
 
25
  load_dotenv()
26
  CONFIG_FILE = ".user_config.env"
27
 
28
+ # --- Constants and Helper Functions from MiniMaxAI template ---
29
+ # (Adapt paths and values as per your project structure)
30
+
31
+ # Dummy EXAMPLES and DEFAULT_PROMPTS for the Code Playground (replace with your actual data)
32
+ EXAMPLES = {
33
+ "UI Components": [
34
+ {"title": "Simple Button", "description": "Generate a simple HTML button with hover effect."},
35
+ {"title": "Responsive Nav Bar", "description": "Create a responsive navigation bar using HTML and CSS."},
36
+ ],
37
+ "Games & Visualizations": [
38
+ {"title": "Maze Generator and Pathfinding Visualizer", "description": "Create a maze generator and pathfinding visualizer. Randomly generate a maze and visualize A* algorithm solving it step by step. Use canvas and animations. Make it visually appealing."},
39
+ {"title": "Particle Explosion Effect", "description": "Implement a particle explosion effect when the user clicks anywhere on the page."},
40
+ ],
41
+ "Interactive Apps": [
42
+ {"title": "Typing Speed Game", "description": "Build a typing speed test web app. Randomly show a sentence, and track the user's typing speed in WPM (words per minute). Provide live feedback with colors and accuracy."},
43
+ {"title": "Simple Calculator", "description": "Generate a basic four-function calculator with a user-friendly interface."},
44
+ ],
45
+ }
46
+
47
+ # The SYSTEM_PROMPT for code generation, now as a constant
48
  SYSTEM_PROMPT_CODE_GEN = """
49
  You are an expert web developer. Your task is to write a complete, single HTML file
50
  (including all necessary CSS and JavaScript within <style> and <script> tags, or as data URIs for images if any)
 
58
  - Provide a brief reasoning *before* the code block, explaining your approach.
59
  """
60
 
61
+ # Dummy DEFAULT_PROMPTS for the Chatbot (if your chatbot uses them)
62
+ DEFAULT_PROMPTS = [
63
+ {"description": "What is the capital of France?"},
64
+ {"description": "Explain quantum entanglement in simple terms."},
65
+ {"description": "Write a short story about a brave knight."},
66
+ ]
67
 
68
+
69
+ # --- Helper Functions from MiniMaxAI Template (adapted for your app) ---
70
  def remove_code_block(text):
71
  """
72
  Extracts the content of the first Markdown code block (```html ... ``` or ``` ... ```)
 
146
  print("[DEBUG] Generated iframe for sandbox.")
147
  return iframe_html
148
 
149
+ def select_example(example_state):
150
+ """Function to set the input textbox value from an example card."""
151
+ # Assuming example_state is a dictionary with a 'description' key
152
+ return gr.update(value=example_state.get("description", ""))
153
+
154
+
155
+ # --- Your existing save_env_vars_to_file (from your original code) ---
156
+ def save_env_vars_to_file(env_vars):
157
+ print("[DEBUG] Saving user config to file")
158
+ with open(CONFIG_FILE, "w") as f:
159
+ for key, value in env_vars.items():
160
+ f.write(f"{key}={value}\n")
161
+
162
+ # --- CSS from MiniMaxAI template ---
163
+ CUSTOM_CSS = """
164
+ /* Add styles for the main container */
165
+ .ant-tabs-content {
166
+ height: calc(100vh - 200px);
167
+ overflow: hidden;
168
+ }
169
+ .ant-tabs-tabpane {
170
+ height: 100%;
171
+ overflow-y: auto;
172
+ }
173
+ /* Modify existing styles */
174
+ .output-empty,.output-loading {
175
+ display: flex;
176
+ flex-direction: column;
177
+ align-items: center;
178
+ justify-content: center;
179
+ width: 100%;
180
+ min-height: 680px;
181
+ position: relative;
182
+ }
183
+ .output-html {
184
+ display: flex;
185
+ flex-direction: column;
186
+ width: 100%;
187
+ min-height: 680px;
188
+ }
189
+ .output-html > iframe {
190
+ flex: 1;
191
+ }
192
+ .right_content {
193
+ display: flex;
194
+ flex-direction: column;
195
+ align-items: center;
196
+ justify-content: center;
197
+ width: 100%;
198
+ height: 100%;
199
+ min-height: unset;
200
+ background: #fff;
201
+ border-radius: 8px;
202
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
203
+ }
204
+ /* Add styles for the code playground container */
205
+ .code-playground-container {
206
+ height: 100%;
207
+ overflow-y: auto;
208
+ padding-right: 8px;
209
+ }
210
+ .code-playground-container::-webkit-scrollbar {
211
+ width: 6px;
212
+ }
213
+ .code-playground-container::-webkit-scrollbar-track {
214
+ background: #f1f1f1;
215
+ border-radius: 3px;
216
+ }
217
+ .code-playground-container::-webkit-scrollbar-thumb {
218
+ background: #888;
219
+ border-radius: 3px;
220
+ }
221
+ .code-playground-container::-webkit-scrollbar-thumb:hover {
222
+ background: #555;
223
+ }
224
+ .render_header {
225
+ display: flex;
226
+ align-items: center;
227
+ padding: 8px 16px;
228
+ background: #f5f5f5;
229
+ border-bottom: 1px solid #e8e8e8;
230
+ border-top-left-radius: 8px;
231
+ border-top-right-radius: 8px;
232
+ }
233
+ .header_btn {
234
+ width: 12px;
235
+ height: 12px;
236
+ border-radius: 50%;
237
+ margin-right: 8px;
238
+ display: inline-block;
239
+ }
240
+ .header_btn:nth-child(1) {
241
+ background: #ff5f56;
242
+ }
243
+ .header_btn:nth-child(2) {
244
+ background: #ffbd2e;
245
+ }
246
+ .header_btn:nth-child(3) {
247
+ background: #27c93f;
248
+ }
249
+ .output-html > iframe {
250
+ flex: 1;
251
+ border: none;
252
+ background: #fff;
253
+ }
254
+ .reasoning-box {
255
+ max-height: 300px;
256
+ overflow-y: auto;
257
+ border-radius: 4px;
258
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
259
+ font-size: 14px;
260
+ line-height: 1.6;
261
+ width: 100%;
262
+ scroll-behavior: smooth;
263
+ display: flex;
264
+ flex-direction: column-reverse;
265
+ }
266
+ .reasoning-box .ms-markdown { /* Targeting markdown within the box for modelscope */
267
+ padding: 0 12px;
268
+ }
269
+ .reasoning-box::-webkit-scrollbar {
270
+ width: 6px;
271
+ }
272
+ .reasoning-box::-webkit-scrollbar-track {
273
+ background: #f1f1f1;
274
+ border-radius: 3px;
275
+ }
276
+ .reasoning-box::-webkit-scrollbar-thumb {
277
+ background: #888;
278
+ border-radius: 3px;
279
+ }
280
+ .reasoning-box::-webkit-scrollbar-thumb:hover {
281
+ background: #555;
282
+ }
283
+ .markdown-container {
284
+ max-height: 300px;
285
+ overflow-y: auto;
286
+ border-radius: 4px;
287
+ font-family: -apple-system, BlinkMacMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
288
+ font-size: 14px;
289
+ line-height: 1.6;
290
+ width: 100%;
291
+ scroll-behavior: smooth;
292
+ display: flex;
293
+ flex-direction: column-reverse;
294
+ }
295
+ /* Example card styles */
296
+ .example-card {
297
+ flex: 1 1 calc(50% - 20px);
298
+ max-width: calc(50% - 20px);
299
+ margin: 6px;
300
+ transition: all 0.3s;
301
+ cursor: pointer;
302
+ border: 1px solid #e8e8e8;
303
+ border-radius: 8px;
304
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
305
+ }
306
+ .example-card:hover {
307
+ transform: translateY(-4px);
308
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
309
+ border-color: #d9d9d9;
310
+ }
311
+ .example-card .ant-card-meta-title {
312
+ font-size: 16px;
313
+ font-weight: 500;
314
+ margin-bottom: 8px;
315
+ color: #262626;
316
+ }
317
+ .example-card .ant-card-meta-description {
318
+ color: #666;
319
+ font-size: 14px;
320
+ line-height: 1.5;
321
+ }
322
+ /* Example tabs styles */
323
+ .example-tabs .ant-tabs-nav {
324
+ margin-bottom: 16px;
325
+ }
326
+ .example-tabs .ant-tabs-tab {
327
+ padding: 8px 16px;
328
+ font-size: 15px;
329
+ }
330
+ .example-tabs .ant-tabs-tab-active {
331
+ font-weight: 500;
332
+ }
333
+ /* Empty state styles */
334
+ /* Corrected to match the target's `.right_content` for empty state */
335
+ .right_content .output-empty {
336
+ display: flex;
337
+ flex-direction: column;
338
+ align-items: center;
339
+ justify-content: center;
340
+ width: 100%;
341
+ min-height: 620px; /* Adjusted to match original */
342
+ background: #fff;
343
+ border-radius: 8px;
344
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
345
+ }
346
+ /* Add styles for the example cards container */
347
+ .example-tabs .ant-tabs-content {
348
+ padding: 0 8px;
349
+ }
350
+ .example-tabs .ant-flex {
351
+ margin: 0 -8px;
352
+ width: calc(100% + 16px);
353
+ }
354
+ """
355
 
356
  # --- Main Gradio Interface Launch Function ---
357
  def launch_interface():
358
+ # --- Chatbot Tab Logic (Your existing logic, using gr.gr components) ---
359
  def setup_agent_streaming(question, model_id, hf_token, openai_api_key, serpapi_key, api_endpoint, use_custom_endpoint,
360
  custom_api_endpoint, custom_api_key, search_provider, search_api_key, custom_search_url):
361
  print("[DEBUG] Setting up agent with input question:", question)
 
404
  return f"<p><span style='color:#f59e0b;font-weight:bold;'>[STEP]</span> {text.strip()}</p>"
405
  elif text.strip():
406
  # Wrap regular steps in details tag for collapsing
 
407
  return f"<details><summary><span style='color:#f59e0b;'>Step</span></summary>\n<pre>{text.strip()}</pre>\n</details>"
408
  return ""
409
 
 
435
  # Join only the new content, or the entire buffer for cumulative display
436
  current_html_output = "".join(output_html_buffer)
437
  yield current_html_output, final_answer_text
438
+ last_buffer_length = len(current_html_output)
439
  time.sleep(0.05) # Smaller delay for more responsive updates
440
 
441
  # Ensure final state is yielded
442
  final_html_output = "".join(output_html_buffer)
443
  yield final_html_output, final_answer_text
444
 
445
+ # --- Code Playground Tab Logic (Using modelscope_studio components) ---
446
  def generate_code_streaming(query, model_id, hf_token, openai_api_key, serpapi_key, api_endpoint, use_custom_endpoint,
447
  custom_api_endpoint, custom_api_key):
448
  print(f"[DEBUG] Starting code generation with query: {query}")
449
 
450
  if query.strip() == "":
451
+ # Reset outputs and show empty state
452
+ # Yield for reasoning_output (Markdown), code_output_raw (Code), sandbox_output (HTML)
453
+ # code_output_tabs_container (antd.Tabs, for active_key and visibility)
454
+ # loading_state_group (gr.Group, for visibility)
455
+ # loading_tip (gr.State, for value and visibility)
456
+ yield gr.update(value=""), gr.update(value=""), gr.update(value=""), \
457
+ gr.update(selected="empty", visible=False), gr.update(visible=True), \
458
+ gr.update(value="Enter your request to generate code", visible=False)
459
  return
460
 
461
  endpoint = custom_api_endpoint if use_custom_endpoint else api_endpoint
462
  api_key = custom_api_key if use_custom_endpoint else openai_api_key
463
 
 
 
 
 
 
464
  agent = create_agent(
465
  model_id=model_id,
466
  hf_token=hf_token,
 
469
  api_endpoint=api_endpoint,
470
  custom_api_endpoint=endpoint,
471
  custom_api_key=api_key,
 
472
  search_provider="none", # Explicitly set to none if not used
473
  search_api_key=None,
474
  custom_search_url=None
475
  )
476
 
477
+ # Corrected: Set the system prompt using prompt_templates as per the error message.
478
+ if hasattr(agent, 'prompt_templates'):
479
+ if "system_prompt" in agent.prompt_templates:
480
+ agent.prompt_templates["system_prompt"] = SYSTEM_PROMPT_CODE_GEN
481
+ print("[DEBUG] Set agent.prompt_templates['system_prompt'] for code generation.")
482
+ elif 'user_agent' in agent.prompt_templates and 'system_message' in agent.prompt_templates['user_agent']:
483
+ agent.prompt_templates['user_agent']['system_message'] = SYSTEM_PROMPT_CODE_GEN
484
+ print("[DEBUG] Set agent.prompt_templates['user_agent']['system_message'] for code generation.")
485
+ else:
486
+ print("[WARNING] Could not set system prompt for CodeAgent using known patterns. "
487
+ "Agent might not follow code generation instructions optimally.")
488
+ # Fallback: Prepend to the question if no proper system prompt mechanism
489
+ query = SYSTEM_PROMPT_CODE_GEN + "\n\n" + query
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  else:
491
+ print("[WARNING] Agent has no 'prompt_templates' attribute. Cannot set system prompt.")
 
 
 
492
  query = SYSTEM_PROMPT_CODE_GEN + "\n\n" + query
493
 
494
 
495
+ reasoning_text_buffer = [] # Buffer for the raw text of reasoning/code combined
496
+ final_generated_code_content = "" # Store the final extracted code
497
+ is_agent_run_complete = False # Flag for the async agent run completion
498
+
499
+ # Callback for the run_agent_with_streaming
500
+ def code_gen_stream_callback(text_chunk):
501
+ nonlocal reasoning_text_buffer
502
+ reasoning_text_buffer.append(text_chunk)
 
 
 
 
 
 
 
 
503
 
504
+ # Function to run the agent asynchronously
505
+ def run_agent_async_for_codegen():
506
+ nonlocal is_agent_run_complete, final_generated_code_content
507
+ try:
508
+ # The run_agent_with_streaming returns the final answer
509
+ final_answer_from_agent = run_agent_with_streaming(agent, query, code_gen_stream_callback)
510
+ # Ensure the final answer from agent.run is captured
511
+ final_generated_code_content = final_answer_from_agent
512
+ except Exception as e:
513
+ reasoning_text_buffer.append(f"[ERROR] {str(e)}\n")
514
+ finally:
515
+ is_agent_run_complete = True
516
+
517
+ # Start agent in background thread
518
+ agent_thread = threading.Thread(target=run_agent_async_for_codegen)
519
  agent_thread.start()
520
 
521
+ # --- Initial yield to show loading state ---
522
+ # Hide empty, show loading, show reasoning tab initially
523
+ yield gr.update(value="", visible=True), gr.update(value="", visible=False), gr.update(value="", visible=False), \
524
+ gr.update(selected="reasoning", visible=True), gr.update(visible=True), \
525
+ gr.update(value="Thinking and coding...", visible=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
 
527
+ # --- Streaming loop for Gradio UI ---
528
+ last_buffer_len = 0
529
+ while not is_agent_run_complete or agent_thread.is_alive() or len(reasoning_text_buffer) > last_buffer_len:
530
+ current_full_output = "".join(reasoning_text_buffer)
531
+ if len(current_full_output) > last_buffer_len:
532
+ # Update reasoning output with accumulated text
533
+ yield gr.update(value=current_full_output, visible=True), \
534
+ gr.update(value="", visible=False), \
535
+ gr.update(value="", visible=False), \
536
+ gr.update(selected="reasoning"), \
537
+ gr.update(visible=False), \
538
+ gr.update(value="Generating code...", visible=True) # Update loading status
539
+ last_buffer_len = len(current_full_output)
540
+ time.sleep(0.05) # Small delay for UI updates
541
+
542
+ # After the agent run completes and all buffered text is processed:
543
+ # Use the actual final answer from the agent's run method if available, otherwise buffer.
544
+ # This is important if the final_answer_from_agent is more concise than the full buffer.
545
+ final_output_for_parsing = final_generated_code_content if final_generated_code_content else "".join(reasoning_text_buffer)
546
 
547
+ generated_code_extracted = remove_code_block(final_output_for_parsing)
 
548
 
549
+ # Try to refine reasoning if code was extracted
550
+ reasoning_only_display = final_output_for_parsing
551
+ if generated_code_extracted:
552
+ # Simple heuristic to remove code block from reasoning for display
553
+ reasoning_only_display = reasoning_only_display.replace(f"```{generated_code_extracted}```", "").strip()
554
+ reasoning_only_display = reasoning_only_display.replace(f"```html\n{generated_code_extracted}\n```", "").strip()
555
+ reasoning_only_display = reasoning_only_display.replace(f"```HTML\n{generated_code_extracted}\n```", "").strip()
556
+
557
+ html_to_render = send_to_sandbox(generated_code_extracted) if generated_code_extracted else "<div>No valid HTML code was generated or extracted.</div>"
558
 
559
+ # Final yield to show the code and rendered output
560
+ yield gr.update(value=reasoning_only_display, visible=True), \
561
+ gr.update(value=generated_code_extracted, visible=True), \
562
  gr.update(value=html_to_render, visible=True), \
563
  gr.update(selected="render", visible=True), \
564
+ gr.update(visible=True), \
565
+ gr.update(value="Done", visible=False) # Hide loading status
566
 
567
+ # --- Gradio UI Layout (Combining your original with MiniMaxAI template) ---
568
+ # Use gr.Blocks, ms.Application, antdx.XProvider, ms.AutoLoading for modelscope theming
569
+ with gr.Blocks(css=CUSTOM_CSS) as demo, ms.Application(), antdx.XProvider(), ms.AutoLoading():
570
  gr.Markdown("# SmolAgent - Intelligent AI with Web Tools")
571
 
572
+ with gr.Tabs() as main_tabs: # Main tabs for Chatbot and Code Playground
573
  with gr.TabItem("Chatbot"):
574
+ # Your existing chatbot tab using standard gr components
575
  with gr.Row():
576
  with gr.Column(scale=1):
577
  question = gr.Textbox(label="Your Question", lines=3, placeholder="Enter your question or task for the AI agent...")
 
631
  )
632
 
633
  with gr.TabItem("Code Playground (WebDev)"):
634
+ # This section uses modelscope_studio.components.antd/antdx/ms
635
+ with antd.Row(gutter=[32, 12], elem_classes="code-playground-container"):
636
+ with antd.Col(span=24, md=12):
637
+ with antd.Flex(vertical=True, gap="middle"):
638
+ code_query = antd.Input.Textarea(
639
+ size="large",
640
+ allow_clear=True,
641
+ auto_size=dict(minRows=2, maxRows=6),
642
+ placeholder="Please enter what kind of application you want or choose an example below and click the button"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
643
  )
644
+ generate_code_btn = antd.Button("Generate Code", type="primary", size="large")
645
+
646
+ # Output tabs for Reasoning and Generated Code
647
+ with antd.Tabs(active_key="reasoning", visible=False) as output_tabs_code_gen: # Matches target's output_tabs
648
+ with antd.Tabs.Item(key="reasoning", label="🤔 Thinking Process"):
649
+ reasoning_output = ms.Markdown(elem_classes="reasoning-box") # Use ms.Markdown
650
+ with antd.Tabs.Item(key="code", label="💻 Generated Code"):
651
+ # Gradio's gr.Code is suitable here, as modelscope doesn't have a direct equivalent for code display
652
+ code_output_raw = gr.Code(label="Generated Code", language="html", interactive=False, lines=20)
653
+
654
+ antd.Divider("Examples")
655
+ # Examples with categories
656
+ with antd.Tabs(elem_classes="example-tabs") as example_tabs:
657
+ for category, examples_list in EXAMPLES.items(): # Renamed 'examples' to 'examples_list' to avoid conflict
658
+ with antd.Tabs.Item(key=category, label=category):
659
+ with antd.Flex(gap="small", wrap=True):
660
+ for example in examples_list:
661
+ with antd.Card(
662
+ elem_classes="example-card",
663
+ hoverable=True
664
+ ) as example_card:
665
+ antd.Card.Meta(
666
+ title=example['title'],
667
+ description=example['description'])
668
+ # Use gr.State to pass the example data, and then select_example
669
+ example_card.click(
670
+ fn=select_example,
671
+ inputs=[gr.State(example)],
672
+ outputs=[code_query]
673
+ )
674
+
675
+ with antd.Col(span=24, md=12):
676
+ # This column will contain the output display: empty, loading, or rendered HTML
677
+ with antd.Card(title="Output", elem_style=dict(height="100%"), styles=dict(body=dict(height="100%")), elem_id="output-container"):
678
+ # This internal Tabs component will control the main right panel's state (empty/loading/render)
679
+ with antd.Tabs(active_key="empty", render_tab_bar="() => null") as state_tab: # Matches target's state_tab
680
+ with antd.Tabs.Item(key="empty"):
681
+ empty = antd.Empty(
682
+ description="Enter your request to generate code",
683
+ elem_classes="output-empty" # Matches target's CSS class
684
+ )
685
+ with antd.Tabs.Item(key="loading"):
686
+ # The Spin component from antd
687
+ with antd.Spin(True, tip="Thinking and coding...", size="large", elem_classes="output-loading") as loading_spinner: # Matches target's loading
688
+ ms.Div() # Placeholder for content inside spin
689
+ with antd.Tabs.Item(key="render"):
690
+ sandbox_output = gr.HTML(elem_classes="output-html") # Matches target's sandbox
691
+
692
+ # --- Interactions for Code Playground ---
693
+ # `loading_tip` is now a gr.State and used for JS triggers and Python updates.
694
+ loading_tip = gr.State("Ready")
695
 
696
+ # Initial setup when code_query is submitted or button clicked
697
  generate_code_btn.click(
698
+ fn=lambda: (
699
+ gr.update(selected="loading"), # Switch to loading tab in the right panel
700
+ gr.update(visible=False), # Hide the empty state component
701
+ gr.update(visible=True), # Show the loading state component
702
+ gr.update(value="Thinking and coding...", visible=True), # Update loading tip text
703
+ gr.update(value="", visible=True), # Clear reasoning output, make it visible
704
+ gr.update(value="", visible=False), # Clear raw code output, hide it
705
+ gr.update(value="", visible=False) # Clear sandbox output, hide it
706
+ ),
707
+ outputs=[state_tab, empty_state_group, loading_spinner, loading_tip, reasoning_output, code_output_raw, sandbox_output],
708
+ queue=False # This pre-processing step should not be queued
709
+ ).then(
710
  fn=generate_code_streaming,
711
  inputs=[
712
+ code_query, model_id_code, hf_token_code, openai_api_key_code, serpapi_key_chatbot, # Re-using chatbot's serpapi
713
  api_endpoint_code, use_custom_endpoint_code, custom_api_endpoint_code, custom_api_key_code
714
  ],
715
+ outputs=[reasoning_output, code_output_raw, sandbox_output, state_tab, output_tabs_code_gen, loading_tip],
716
+ show_progress="hidden" # Manage progress via loading_tip and state_tab
717
+ ).then(
718
+ fn=lambda: (gr.update(visible=False)), # Hide the loading spinner after the process completes
719
+ outputs=[loading_spinner]
 
 
 
 
 
 
 
 
 
 
 
720
  )
721
 
722
+ # Auto-scroll functionality from MiniMaxAI template
723
+ # This needs to target ms.Markdown components.
724
+ # Note: `elem_classes` for ms.Markdown might be different from raw Gradio.
725
  reasoning_output.change(
726
  fn=None,
727
  inputs=[],
 
729
  js="""
730
  function() {
731
  setTimeout(() => {
732
+ const reasoningBox = document.querySelector('.reasoning-box');
733
+ if (reasoningBox) {
734
+ reasoningBox.scrollTop = reasoningBox.scrollHeight;
735
+ }
736
+ }, 100);
737
+ }
738
+ """
739
+ )
740
+ code_output_raw.change( # This is gr.Code, might need different selector
741
+ fn=None,
742
+ inputs=[],
743
+ outputs=[],
744
+ js="""
745
+ function() {
746
+ setTimeout(() => {
747
+ // Gradio's gr.Code output is often within a <textarea> or <pre> inside a div
748
+ const codeBox = document.querySelector('.markdown-container pre, .markdown-container textarea');
749
+ if (codeBox) {
750
+ codeBox.scrollTop = codeBox.scrollHeight;
751
  }
752
  }, 100);
753
  }
754
  """
755
  )
756
+
757
+ # Handling tab changes to ensure correct visibility as in MiniMaxAI
758
+ def on_output_tabs_change(tab_key):
759
+ # This function is not directly used in the current streaming yield flow
760
+ # but is provided in the original template for programmatic tab changes.
761
+ # In our streaming, we set `selected` directly in the yields.
762
+ return gr.update(active_key=tab_key)
763
+
764
+ # The original MiniMaxAI app had a `output_tabs.change` event.
765
+ # In our setup, `output_tabs_code_gen` (the Reasoning/Code tabs)
766
+ # visibility and selected tab are controlled directly by the `generate_code_streaming`
767
+ # function's yields. `state_tab` (empty/loading/render) is the main outer control.
768
+ # If you need specific behavior when a user manually switches 'Thinking Process' vs 'Generated Code'
769
+ # after the process starts, you'd enable this.
770
+ # output_tabs_code_gen.change(fn=on_output_tabs_change, inputs=output_tabs_code_gen, outputs=[output_tabs_code_gen])
771
 
772
  print("[DEBUG] Launching updated Gradio interface")
773
+ demo.queue(default_concurrency_limit=50).launch(ssr_mode=False) # Keep queue and ssr_mode if relevant to your setup
774
 
775
  if __name__ == "__main__":
776
  launch_interface()