Spaces:
Running
Running
GitHub Action
commited on
Commit
·
3f80c6a
1
Parent(s):
40b06b6
Sync ling-space changes from GitHub commit 2ca584b
Browse files- README.md +1 -1
- app.py +6 -5
- code_kit/__init__.py +0 -0
- code_kit/agent_code_generator.py +146 -0
- code_kit/agent_style_generator.py +102 -0
- code_kit/code_examples.py +38 -0
- requirements.txt +1 -1
- tab_chat.py +2 -2
- tab_code.py +59 -136
README.md
CHANGED
|
@@ -4,7 +4,7 @@ emoji: 💍
|
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
python_version: 3.13.7
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 6.0.1
|
| 8 |
python_version: 3.13.7
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import uuid
|
| 3 |
from datetime import datetime
|
|
|
|
| 4 |
import pandas as pd
|
| 5 |
from model_handler import ModelHandler
|
| 6 |
from tab_chat import create_chat_tab
|
|
@@ -64,10 +65,7 @@ if __name__ == "__main__":
|
|
| 64 |
# Instantiate the model handler with the configuration
|
| 65 |
model_handler = ModelHandler()
|
| 66 |
|
| 67 |
-
with gr.Blocks(
|
| 68 |
-
css=CSS,
|
| 69 |
-
head="",
|
| 70 |
-
head_paths=['./static/toastify.html', './static/app.html'],
|
| 71 |
fill_height=True,
|
| 72 |
fill_width=True) as demo:
|
| 73 |
with gr.Tabs(elem_id='indicator-space-app') as tabs:
|
|
@@ -112,4 +110,7 @@ if __name__ == "__main__":
|
|
| 112 |
)
|
| 113 |
|
| 114 |
# Launch the Gradio application
|
| 115 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import uuid
|
| 3 |
from datetime import datetime
|
| 4 |
+
from gradio.analytics import ANALYTICS_URL
|
| 5 |
import pandas as pd
|
| 6 |
from model_handler import ModelHandler
|
| 7 |
from tab_chat import create_chat_tab
|
|
|
|
| 65 |
# Instantiate the model handler with the configuration
|
| 66 |
model_handler = ModelHandler()
|
| 67 |
|
| 68 |
+
with gr.Blocks(analytics_enabled=False,
|
|
|
|
|
|
|
|
|
|
| 69 |
fill_height=True,
|
| 70 |
fill_width=True) as demo:
|
| 71 |
with gr.Tabs(elem_id='indicator-space-app') as tabs:
|
|
|
|
| 110 |
)
|
| 111 |
|
| 112 |
# Launch the Gradio application
|
| 113 |
+
demo.launch(theme=gr.themes.Default(),
|
| 114 |
+
css=CSS,
|
| 115 |
+
head="",
|
| 116 |
+
head_paths=['./static/toastify.html', './static/app.html'])
|
code_kit/__init__.py
ADDED
|
File without changes
|
code_kit/agent_code_generator.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import time
|
| 3 |
+
import logging
|
| 4 |
+
import os
|
| 5 |
+
from model_handler import ModelHandler
|
| 6 |
+
from tab_code_prompts.html_system_prompt import get_html_system_prompt
|
| 7 |
+
|
| 8 |
+
# Configure logging
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
# Read the content of the JavaScript file for error catching
|
| 12 |
+
# Assuming the script is run from the project root or ling-space root.
|
| 13 |
+
try:
|
| 14 |
+
with open("static/catch-error.js", "r", encoding="utf-8") as f:
|
| 15 |
+
CATCH_ERROR_JS_SCRIPT = f.read()
|
| 16 |
+
except FileNotFoundError:
|
| 17 |
+
logger.error("Error: static/catch-error.js not found. The error catching overlay will not work.")
|
| 18 |
+
CATCH_ERROR_JS_SCRIPT = ""
|
| 19 |
+
|
| 20 |
+
def get_spinner_html():
|
| 21 |
+
"""Return HTML with a CSS spinner animation"""
|
| 22 |
+
return """
|
| 23 |
+
<div style="width: 100%; height: 600px; display: flex; justify-content: center; align-items: center; border: 1px solid #ddd; background-color: #f9f9f9;">
|
| 24 |
+
<div class="spinner"></div>
|
| 25 |
+
</div>
|
| 26 |
+
<style>
|
| 27 |
+
.spinner {
|
| 28 |
+
border: 4px solid rgba(0, 0, 0, 0.1);
|
| 29 |
+
width: 36px;
|
| 30 |
+
height: 36px;
|
| 31 |
+
border-radius: 50%;
|
| 32 |
+
border-left-color: #09f;
|
| 33 |
+
animation: spin 1s ease infinite;
|
| 34 |
+
}
|
| 35 |
+
@keyframes spin {
|
| 36 |
+
0% { transform: rotate(0deg); }
|
| 37 |
+
100% { transform: rotate(360deg); }
|
| 38 |
+
}
|
| 39 |
+
</style>
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def code_generation_agent(code_type, model_choice, user_prompt, color_palette, decoration_style, overall_style, chatbot_history):
|
| 43 |
+
"""Generate code and provide a preview, updating a log stream chatbot."""
|
| 44 |
+
logger.info(f"--- [Code Generation] Start ---")
|
| 45 |
+
logger.info(f"Code Type: {code_type}, Model: {model_choice}, Prompt: '{user_prompt}'")
|
| 46 |
+
|
| 47 |
+
if not user_prompt:
|
| 48 |
+
chatbot_history.append({"role": "assistant", "content": "🚨 **错误**: 请输入提示词。"})
|
| 49 |
+
yield "", gr.update(value="<p>预览将在此处显示。</p>"), chatbot_history, gr.update()
|
| 50 |
+
return
|
| 51 |
+
|
| 52 |
+
chatbot_history.append({"role": "assistant", "content": "⏳ 开始生成代码..."})
|
| 53 |
+
yield "", gr.HTML(get_spinner_html()), chatbot_history, gr.update()
|
| 54 |
+
|
| 55 |
+
if user_prompt == "create an error" or user_prompt == "创建一个报错示例":
|
| 56 |
+
error_code = f"""<h1>This will create an error</h1><script>{CATCH_ERROR_JS_SCRIPT}</script><script>nonExistentFunction();</script>"""
|
| 57 |
+
escaped_code = error_code.replace("'", "'").replace('"', '"')
|
| 58 |
+
final_preview_html = f"""
|
| 59 |
+
<div style="width: 100%; height: 600px; border: 1px solid #ddd; overflow: hidden; position: relative; background-color: #f9f9f9;">
|
| 60 |
+
<iframe srcdoc='{escaped_code}'
|
| 61 |
+
style="position: absolute; top: 0; left: 0; width: 200%; height: 200%; transform: scale(0.5); transform-origin: 0 0; border: none;">
|
| 62 |
+
</iframe>
|
| 63 |
+
</div>
|
| 64 |
+
"""
|
| 65 |
+
chatbot_history.append({"role": "assistant", "content": "✅ **成功**: 已生成一个用于测试的错误页面。"})
|
| 66 |
+
yield error_code, gr.update(value=final_preview_html), chatbot_history, gr.Tabs(selected=0)
|
| 67 |
+
return
|
| 68 |
+
|
| 69 |
+
# --- Append Style Prompt ---
|
| 70 |
+
full_user_prompt = user_prompt
|
| 71 |
+
|
| 72 |
+
if color_palette or decoration_style or overall_style:
|
| 73 |
+
full_user_prompt += "\n\n--- Visual Style Requirements (Strictly Follow These) ---\n"
|
| 74 |
+
if overall_style:
|
| 75 |
+
full_user_prompt += f"Overall Theme/Vibe: {overall_style}\n"
|
| 76 |
+
if decoration_style:
|
| 77 |
+
full_user_prompt += f"UI Decoration Style: {decoration_style}\n"
|
| 78 |
+
if color_palette:
|
| 79 |
+
full_user_prompt += f"Color Palette Mapping (Use these colors for their assigned roles): {color_palette}\n"
|
| 80 |
+
|
| 81 |
+
logger.info(f"Full Prompt with Style: {full_user_prompt}")
|
| 82 |
+
# ---------------------------
|
| 83 |
+
|
| 84 |
+
start_time = time.time()
|
| 85 |
+
model_handler = ModelHandler()
|
| 86 |
+
|
| 87 |
+
if code_type == "静态页面":
|
| 88 |
+
system_prompt = get_html_system_prompt()
|
| 89 |
+
full_code_with_think = ""
|
| 90 |
+
full_code_for_preview = ""
|
| 91 |
+
buffer = ""
|
| 92 |
+
is_thinking = False
|
| 93 |
+
|
| 94 |
+
for code_chunk in model_handler.generate_code(system_prompt, full_user_prompt, model_choice):
|
| 95 |
+
full_code_with_think += code_chunk
|
| 96 |
+
buffer += code_chunk
|
| 97 |
+
|
| 98 |
+
while True:
|
| 99 |
+
if is_thinking:
|
| 100 |
+
end_index = buffer.find("</think>")
|
| 101 |
+
if end_index != -1:
|
| 102 |
+
is_thinking = False
|
| 103 |
+
buffer = buffer[end_index + len("</think>"):]
|
| 104 |
+
else:
|
| 105 |
+
break
|
| 106 |
+
else:
|
| 107 |
+
start_index = buffer.find("<think>")
|
| 108 |
+
if start_index != -1:
|
| 109 |
+
part_to_add = buffer[:start_index]
|
| 110 |
+
full_code_for_preview += part_to_add
|
| 111 |
+
is_thinking = True
|
| 112 |
+
buffer = buffer[start_index:]
|
| 113 |
+
else:
|
| 114 |
+
full_code_for_preview += buffer
|
| 115 |
+
buffer = ""
|
| 116 |
+
break
|
| 117 |
+
|
| 118 |
+
elapsed_time = time.time() - start_time
|
| 119 |
+
generated_length = len(full_code_with_think)
|
| 120 |
+
speed = generated_length / elapsed_time if elapsed_time > 0 else 0
|
| 121 |
+
|
| 122 |
+
log_message = f"""
|
| 123 |
+
**⏳ 正在生成中...**
|
| 124 |
+
- **时间:** {elapsed_time:.2f}s
|
| 125 |
+
- **长度:** {generated_length} chars
|
| 126 |
+
- **速度:** {speed:.2f} char/s
|
| 127 |
+
"""
|
| 128 |
+
|
| 129 |
+
if len(chatbot_history) > 0 and "正在生成中" in chatbot_history[-1]["content"]:
|
| 130 |
+
chatbot_history[-1] = {"role": "assistant", "content": log_message}
|
| 131 |
+
else:
|
| 132 |
+
chatbot_history.append({"role": "assistant", "content": log_message})
|
| 133 |
+
|
| 134 |
+
yield full_code_with_think, gr.update(), chatbot_history, gr.update()
|
| 135 |
+
|
| 136 |
+
escaped_code = full_code_for_preview.replace("'", "'").replace('"', '"')
|
| 137 |
+
final_preview_html = f"""
|
| 138 |
+
<div style="width: 100%; height: 600px; border: 1px solid #ddd; overflow: hidden; position: relative; background-color: #f9f9f9;">
|
| 139 |
+
<iframe srcdoc='{escaped_code}'
|
| 140 |
+
style="position: absolute; top: 0; left: 0; width: 200%; height: 200%; transform: scale(0.5); transform-origin: 0 0; border: none;">
|
| 141 |
+
</iframe>
|
| 142 |
+
</div>
|
| 143 |
+
"""
|
| 144 |
+
chatbot_history.append({"role": "assistant", "content": "✅ **成功**: 代码生成完成!"})
|
| 145 |
+
yield full_code_with_think, gr.HTML(final_preview_html), chatbot_history, gr.Tabs(selected=0)
|
| 146 |
+
logger.info("Static page streaming finished.")
|
code_kit/agent_style_generator.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import gradio as gr
|
| 3 |
+
from model_handler import ModelHandler
|
| 4 |
+
from config import LING_FLASH_2_0
|
| 5 |
+
|
| 6 |
+
def format_palette_html(palette_data):
|
| 7 |
+
"""
|
| 8 |
+
Generates an HTML string to display color boxes with roles.
|
| 9 |
+
palette_data: List of dicts {'role': str, 'hex': str} or list of strings (fallback)
|
| 10 |
+
"""
|
| 11 |
+
html = '<div style="display: flex; gap: 10px; margin-top: 5px; flex-wrap: wrap;">'
|
| 12 |
+
|
| 13 |
+
for item in palette_data:
|
| 14 |
+
if isinstance(item, dict):
|
| 15 |
+
color = item.get("hex", "#000000")
|
| 16 |
+
role = item.get("role", "")
|
| 17 |
+
else:
|
| 18 |
+
color = item
|
| 19 |
+
role = ""
|
| 20 |
+
|
| 21 |
+
html += f'''
|
| 22 |
+
<div style="display: flex; flex-direction: column; align-items: center; width: 60px;">
|
| 23 |
+
<div style="width: 40px; height: 40px; background-color: {color}; border-radius: 8px; border: 1px solid #ccc; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" title="{role}: {color}"></div>
|
| 24 |
+
<span style="font-size: 9px; color: #666; margin-top: 4px; text-align: center; line-height: 1.1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%;">{role}</span>
|
| 25 |
+
<span style="font-size: 9px; color: #999; text-align: center;">{color}</span>
|
| 26 |
+
</div>
|
| 27 |
+
'''
|
| 28 |
+
html += '</div>'
|
| 29 |
+
return html
|
| 30 |
+
|
| 31 |
+
def generate_random_style(current_model_display_name):
|
| 32 |
+
"""
|
| 33 |
+
Generates a random web design style using the LLM.
|
| 34 |
+
"""
|
| 35 |
+
model_choice = LING_FLASH_2_0
|
| 36 |
+
|
| 37 |
+
system_prompt = """
|
| 38 |
+
You are a creative Design Director. Your task is to generate a unique, cohesive visual style for a web interface.
|
| 39 |
+
|
| 40 |
+
Return ONLY a valid JSON object with the following keys:
|
| 41 |
+
1. "palette": A list of 6 to 8 color objects, where each object has:
|
| 42 |
+
- "role": A descriptive name for the color usage (e.g., "Page Background", "Card/Surface", "Primary Text", "Secondary Text", "Brand/Action Color", "Accent/Highlight", "Border/Divider").
|
| 43 |
+
- "hex": The hexadecimal color code.
|
| 44 |
+
2. "decoration": A concise description (10-20 words) of the UI decoration style (e.g., "Neo-brutalism with thick black borders and hard shadows", "Glassmorphism with high blur and white transparency").
|
| 45 |
+
3. "theme": A creative, metaphorical description (5-10 words) of the overall vibe (e.g., "Cyberpunk Neon City", "Minimalist Zen Garden").
|
| 46 |
+
|
| 47 |
+
Ensure the colors contrast well (especially text on background) and the roles make sense for a standard web layout.
|
| 48 |
+
Do not include any markdown formatting (like ```json). Just the raw JSON string.
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
user_prompt = "Generate a new random design style now."
|
| 52 |
+
|
| 53 |
+
model_handler = ModelHandler()
|
| 54 |
+
|
| 55 |
+
full_response = ""
|
| 56 |
+
for chunk in model_handler.generate_code(system_prompt, user_prompt, model_choice):
|
| 57 |
+
full_response += chunk
|
| 58 |
+
|
| 59 |
+
clean_response = full_response.strip()
|
| 60 |
+
if clean_response.startswith("```json"):
|
| 61 |
+
clean_response = clean_response[7:]
|
| 62 |
+
if clean_response.startswith("```"):
|
| 63 |
+
clean_response = clean_response[3:]
|
| 64 |
+
if clean_response.endswith("```"):
|
| 65 |
+
clean_response = clean_response[:-3]
|
| 66 |
+
clean_response = clean_response.strip()
|
| 67 |
+
|
| 68 |
+
try:
|
| 69 |
+
style_data = json.loads(clean_response)
|
| 70 |
+
palette = style_data.get("palette", [])
|
| 71 |
+
|
| 72 |
+
# Validate palette structure, fallback if it's just a list of strings (old format)
|
| 73 |
+
if palette and isinstance(palette[0], str):
|
| 74 |
+
palette = [{"role": f"Color {i+1}", "hex": c} for i, c in enumerate(palette)]
|
| 75 |
+
|
| 76 |
+
if not palette:
|
| 77 |
+
palette = [
|
| 78 |
+
{"role": "Background", "hex": "#F0F0F0"},
|
| 79 |
+
{"role": "Text", "hex": "#333333"},
|
| 80 |
+
{"role": "Accent", "hex": "#007BFF"}
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
decoration = style_data.get("decoration", "Standard modern web style.")
|
| 84 |
+
theme = style_data.get("theme", "Default Theme")
|
| 85 |
+
|
| 86 |
+
palette_html = format_palette_html(palette)
|
| 87 |
+
|
| 88 |
+
# Create a structured string for the prompt
|
| 89 |
+
# e.g., "Page Background: #FFF, Primary Text: #000, ..."
|
| 90 |
+
palette_str = ", ".join([f"{p['role']}: {p['hex']}" for p in palette])
|
| 91 |
+
|
| 92 |
+
return palette_html, palette_str, decoration, theme
|
| 93 |
+
|
| 94 |
+
except json.JSONDecodeError as e:
|
| 95 |
+
print(f"Error parsing style JSON: {e}. Response: {full_response}")
|
| 96 |
+
fallback_palette = [
|
| 97 |
+
{"role": "Background", "hex": "#FFFFFF"},
|
| 98 |
+
{"role": "Primary Text", "hex": "#000000"},
|
| 99 |
+
{"role": "Action", "hex": "#007BFF"},
|
| 100 |
+
{"role": "Error", "hex": "#DC3545"}
|
| 101 |
+
]
|
| 102 |
+
return format_palette_html(fallback_palette), ", ".join([f"{p['role']}: {p['hex']}" for p in fallback_palette]), "Simple and clean.", "Fallback Default"
|
code_kit/code_examples.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from config import LING_1T, LING_FLASH_2_0, get_model_display_name
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
This file contains the recommended initial inputs for the code generation tab.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
CODE_EXAMPLES = [
|
| 8 |
+
{
|
| 9 |
+
"task": "知识讲解",
|
| 10 |
+
"model": get_model_display_name(LING_1T),
|
| 11 |
+
"user_prompt": "创建一个关于‘深度学习基础’的交互式学习网页。包含:1. 核心概念卡片(神经网络、反向传播等);2. 一个简单的交互式图解;3. 常见问题解答部分。"
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"task": "行业分析",
|
| 15 |
+
"model": get_model_display_name(LING_1T),
|
| 16 |
+
"user_prompt": "生成一份 2025 年新能源汽车行业分析报告网页。页面应包含:1. 带有数据可视化的市场趋势概览;2. 主要厂商的对比表格;3. 行业挑战与机遇的模块化展示。"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"task": "Hello World",
|
| 20 |
+
"model": get_model_display_name(LING_FLASH_2_0),
|
| 21 |
+
"user_prompt": "编写一个 Hello World 弹窗"
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"task": "烟花动画",
|
| 25 |
+
"model": get_model_display_name(LING_1T),
|
| 26 |
+
"user_prompt": "创建一个黑色背景上五彩烟花连续绽放的 Canvas 动画"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"task": "流光特效",
|
| 30 |
+
"model": get_model_display_name(LING_1T),
|
| 31 |
+
"user_prompt": "生成一个带有五彩流光的 Canvas 特效"
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"task": "报错测试",
|
| 35 |
+
"model": get_model_display_name(LING_FLASH_2_0),
|
| 36 |
+
"user_prompt": "创建一个报错示例"
|
| 37 |
+
}
|
| 38 |
+
]
|
requirements.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
gradio==
|
| 2 |
python-dotenv
|
| 3 |
httpx
|
| 4 |
# transformers
|
|
|
|
| 1 |
+
gradio==6.0.1
|
| 2 |
python-dotenv
|
| 3 |
httpx
|
| 4 |
# transformers
|
tab_chat.py
CHANGED
|
@@ -69,7 +69,7 @@ def create_chat_tab():
|
|
| 69 |
)
|
| 70 |
|
| 71 |
with gr.Column(scale=4):
|
| 72 |
-
chatbot = gr.Chatbot(height=500
|
| 73 |
with gr.Row():
|
| 74 |
textbox = gr.Textbox(placeholder="输入消息...", container=False, scale=7)
|
| 75 |
submit_btn = gr.Button("发送", scale=1)
|
|
@@ -157,4 +157,4 @@ def create_chat_tab():
|
|
| 157 |
new_chat_btn.click(handle_new_chat, inputs=[conversation_store, current_conversation_id], outputs=[current_conversation_id, conversation_store, chatbot, history_df])
|
| 158 |
history_df.select(load_conversation_from_df, inputs=[history_df, conversation_store], outputs=[current_conversation_id, chatbot])
|
| 159 |
|
| 160 |
-
return conversation_store, current_conversation_id, history_df, chatbot
|
|
|
|
| 69 |
)
|
| 70 |
|
| 71 |
with gr.Column(scale=4):
|
| 72 |
+
chatbot = gr.Chatbot(height=500)
|
| 73 |
with gr.Row():
|
| 74 |
textbox = gr.Textbox(placeholder="输入消息...", container=False, scale=7)
|
| 75 |
submit_btn = gr.Button("发送", scale=1)
|
|
|
|
| 157 |
new_chat_btn.click(handle_new_chat, inputs=[conversation_store, current_conversation_id], outputs=[current_conversation_id, conversation_store, chatbot, history_df])
|
| 158 |
history_df.select(load_conversation_from_df, inputs=[history_df, conversation_store], outputs=[current_conversation_id, chatbot])
|
| 159 |
|
| 160 |
+
return conversation_store, current_conversation_id, history_df, chatbot
|
tab_code.py
CHANGED
|
@@ -1,137 +1,15 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
import time
|
| 3 |
import logging
|
| 4 |
-
from model_handler import ModelHandler
|
| 5 |
from config import CHAT_MODEL_SPECS, LING_1T, CODE_FRAMEWORK_SPECS, STATIC_PAGE
|
| 6 |
from ui_components.model_selector import create_model_selector
|
| 7 |
from ui_components.code_framework_selector import create_code_framework_selector
|
| 8 |
-
from
|
|
|
|
|
|
|
| 9 |
|
| 10 |
# Configure logging
|
| 11 |
logger = logging.getLogger(__name__)
|
| 12 |
|
| 13 |
-
# Read the content of the JavaScript file for error catching
|
| 14 |
-
try:
|
| 15 |
-
with open("static/catch-error.js", "r", encoding="utf-8") as f:
|
| 16 |
-
CATCH_ERROR_JS_SCRIPT = f.read()
|
| 17 |
-
except FileNotFoundError:
|
| 18 |
-
logger.error("Error: static/catch-error.js not found. The error catching overlay will not work.")
|
| 19 |
-
CATCH_ERROR_JS_SCRIPT = ""
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
def get_spinner_html():
|
| 23 |
-
"""Return HTML with a CSS spinner animation"""
|
| 24 |
-
return """
|
| 25 |
-
<div style="width: 100%; height: 600px; display: flex; justify-content: center; align-items: center; border: 1px solid #ddd; background-color: #f9f9f9;">
|
| 26 |
-
<div class="spinner"></div>
|
| 27 |
-
</div>
|
| 28 |
-
<style>
|
| 29 |
-
.spinner {
|
| 30 |
-
border: 4px solid rgba(0, 0, 0, 0.1);
|
| 31 |
-
width: 36px;
|
| 32 |
-
height: 36px;
|
| 33 |
-
border-radius: 50%;
|
| 34 |
-
border-left-color: #09f;
|
| 35 |
-
animation: spin 1s ease infinite;
|
| 36 |
-
}
|
| 37 |
-
@keyframes spin {
|
| 38 |
-
0% { transform: rotate(0deg); }
|
| 39 |
-
100% { transform: rotate(360deg); }
|
| 40 |
-
}
|
| 41 |
-
</style>
|
| 42 |
-
"""
|
| 43 |
-
|
| 44 |
-
def generate_code(code_type, model_choice, user_prompt, chatbot_history):
|
| 45 |
-
"""Generate code and provide a preview, updating a log stream chatbot."""
|
| 46 |
-
logger.info(f"--- [Code Generation] Start ---")
|
| 47 |
-
logger.info(f"Code Type: {code_type}, Model: {model_choice}, Prompt: '{user_prompt}'")
|
| 48 |
-
|
| 49 |
-
if not user_prompt:
|
| 50 |
-
chatbot_history.append({"role": "assistant", "content": "🚨 **错误**: 请输入提示词。"})
|
| 51 |
-
yield "", gr.update(value="<p>预览将在此处显示。</p>"), chatbot_history, gr.update()
|
| 52 |
-
return
|
| 53 |
-
|
| 54 |
-
chatbot_history.append({"role": "assistant", "content": "⏳ 开始生成代码..."})
|
| 55 |
-
yield "", gr.HTML(get_spinner_html()), chatbot_history, gr.update()
|
| 56 |
-
|
| 57 |
-
if user_prompt == "create an error":
|
| 58 |
-
error_code = f"""<h1>This will create an error</h1><script>{CATCH_ERROR_JS_SCRIPT}</script><script>nonExistentFunction();</script>""";
|
| 59 |
-
escaped_code = error_code.replace("'", "'").replace('"', '"')
|
| 60 |
-
final_preview_html = f"""
|
| 61 |
-
<div style="width: 100%; height: 600px; border: 1px solid #ddd; overflow: hidden; position: relative; background-color: #f9f9f9;">
|
| 62 |
-
<iframe srcdoc='{escaped_code}'
|
| 63 |
-
style="position: absolute; top: 0; left: 0; width: 200%; height: 200%; transform: scale(0.5); transform-origin: 0 0; border: none;">
|
| 64 |
-
</iframe>
|
| 65 |
-
</div>
|
| 66 |
-
"""
|
| 67 |
-
chatbot_history.append({"role": "assistant", "content": "✅ **成功**: 已生成一个用于测试的错误页面。"})
|
| 68 |
-
yield error_code, gr.update(value=final_preview_html), chatbot_history, gr.Tabs(selected=0)
|
| 69 |
-
return
|
| 70 |
-
|
| 71 |
-
start_time = time.time()
|
| 72 |
-
model_handler = ModelHandler()
|
| 73 |
-
|
| 74 |
-
if code_type == "静态页面":
|
| 75 |
-
system_prompt = get_html_system_prompt()
|
| 76 |
-
full_code_with_think = ""
|
| 77 |
-
full_code_for_preview = ""
|
| 78 |
-
buffer = ""
|
| 79 |
-
is_thinking = False
|
| 80 |
-
|
| 81 |
-
for code_chunk in model_handler.generate_code(system_prompt, user_prompt, model_choice):
|
| 82 |
-
full_code_with_think += code_chunk
|
| 83 |
-
buffer += code_chunk
|
| 84 |
-
|
| 85 |
-
while True:
|
| 86 |
-
if is_thinking:
|
| 87 |
-
end_index = buffer.find("</think>")
|
| 88 |
-
if end_index != -1:
|
| 89 |
-
is_thinking = False
|
| 90 |
-
buffer = buffer[end_index + len("</think>"):]
|
| 91 |
-
else:
|
| 92 |
-
break
|
| 93 |
-
else:
|
| 94 |
-
start_index = buffer.find("<think>")
|
| 95 |
-
if start_index != -1:
|
| 96 |
-
part_to_add = buffer[:start_index]
|
| 97 |
-
full_code_for_preview += part_to_add
|
| 98 |
-
is_thinking = True
|
| 99 |
-
buffer = buffer[start_index:]
|
| 100 |
-
else:
|
| 101 |
-
full_code_for_preview += buffer
|
| 102 |
-
buffer = ""
|
| 103 |
-
break
|
| 104 |
-
|
| 105 |
-
elapsed_time = time.time() - start_time
|
| 106 |
-
generated_length = len(full_code_with_think)
|
| 107 |
-
speed = generated_length / elapsed_time if elapsed_time > 0 else 0
|
| 108 |
-
|
| 109 |
-
log_message = f"""
|
| 110 |
-
**⏳ 正在生成中...**
|
| 111 |
-
- **时间:** {elapsed_time:.2f}s
|
| 112 |
-
- **长度:** {generated_length} chars
|
| 113 |
-
- **速度:** {speed:.2f} char/s
|
| 114 |
-
"""
|
| 115 |
-
|
| 116 |
-
if len(chatbot_history) > 0 and "正在生成中" in chatbot_history[-1]["content"]:
|
| 117 |
-
chatbot_history[-1] = {"role": "assistant", "content": log_message}
|
| 118 |
-
else:
|
| 119 |
-
chatbot_history.append({"role": "assistant", "content": log_message})
|
| 120 |
-
|
| 121 |
-
yield full_code_with_think, gr.update(), chatbot_history, gr.update()
|
| 122 |
-
|
| 123 |
-
escaped_code = full_code_for_preview.replace("'", "'").replace('"', '"')
|
| 124 |
-
final_preview_html = f"""
|
| 125 |
-
<div style="width: 100%; height: 600px; border: 1px solid #ddd; overflow: hidden; position: relative; background-color: #f9f9f9;">
|
| 126 |
-
<iframe srcdoc='{escaped_code}'
|
| 127 |
-
style="position: absolute; top: 0; left: 0; width: 200%; height: 200%; transform: scale(0.5); transform-origin: 0 0; border: none;">
|
| 128 |
-
</iframe>
|
| 129 |
-
</div>
|
| 130 |
-
"""
|
| 131 |
-
chatbot_history.append({"role": "assistant", "content": "✅ **成功**: 代码生成完成!"})
|
| 132 |
-
yield full_code_with_think, gr.HTML(final_preview_html), chatbot_history, gr.Tabs(selected=0)
|
| 133 |
-
logger.info("Static page streaming finished.")
|
| 134 |
-
|
| 135 |
def refresh_preview(code_type, current_code, chatbot_history):
|
| 136 |
"""Refresh the preview and add a log entry."""
|
| 137 |
logger.info(f"--- [Manual Refresh] Start ---")
|
|
@@ -175,13 +53,6 @@ def log_js_error(error_text, chatbot_history):
|
|
| 175 |
|
| 176 |
def create_code_tab():
|
| 177 |
fullscreen_state = gr.State(False)
|
| 178 |
-
|
| 179 |
-
html_examples = [
|
| 180 |
-
"Write a hello world alert",
|
| 181 |
-
"Create a Canvas animation of continuous colorful fireworks blooming on a black background.",
|
| 182 |
-
"Generate a Canvas special effect with iridescent light streams.",
|
| 183 |
-
"create an error"
|
| 184 |
-
]
|
| 185 |
|
| 186 |
with gr.Row(elem_id="indicator-code-tab"):
|
| 187 |
with gr.Column(scale=1) as left_panel:
|
|
@@ -195,8 +66,29 @@ def create_code_tab():
|
|
| 195 |
default_model_constant=LING_1T
|
| 196 |
)
|
| 197 |
prompt_input = gr.Textbox(lines=5, placeholder="例如:创建一个带标题和按钮的简单页面", label="提示词")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
with gr.Column():
|
| 199 |
-
gr.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
generate_button = gr.Button("生成代码", variant="primary")
|
| 201 |
|
| 202 |
with gr.Column(scale=4):
|
|
@@ -212,10 +104,33 @@ def create_code_tab():
|
|
| 212 |
refresh_button = gr.Button("刷新预览")
|
| 213 |
|
| 214 |
with gr.Column(scale=1):
|
| 215 |
-
log_chatbot = gr.Chatbot(label="生成日志", height=300
|
| 216 |
|
| 217 |
js_error_channel = gr.Textbox(visible=True, elem_classes=["js_error_channel"], label="Debug Error Channel", interactive=False)
|
| 218 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
refresh_button.click(
|
| 220 |
fn=refresh_preview,
|
| 221 |
inputs=[code_framework_dropdown, code_output, log_chatbot],
|
|
@@ -223,8 +138,16 @@ def create_code_tab():
|
|
| 223 |
)
|
| 224 |
|
| 225 |
generate_button.click(
|
| 226 |
-
fn=
|
| 227 |
-
inputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
outputs=[code_output, preview_output, log_chatbot, result_tabs]
|
| 229 |
)
|
| 230 |
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
| 2 |
import logging
|
|
|
|
| 3 |
from config import CHAT_MODEL_SPECS, LING_1T, CODE_FRAMEWORK_SPECS, STATIC_PAGE
|
| 4 |
from ui_components.model_selector import create_model_selector
|
| 5 |
from ui_components.code_framework_selector import create_code_framework_selector
|
| 6 |
+
from code_kit.agent_code_generator import code_generation_agent
|
| 7 |
+
from code_kit.agent_style_generator import generate_random_style
|
| 8 |
+
from code_kit.code_examples import CODE_EXAMPLES
|
| 9 |
|
| 10 |
# Configure logging
|
| 11 |
logger = logging.getLogger(__name__)
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
def refresh_preview(code_type, current_code, chatbot_history):
|
| 14 |
"""Refresh the preview and add a log entry."""
|
| 15 |
logger.info(f"--- [Manual Refresh] Start ---")
|
|
|
|
| 53 |
|
| 54 |
def create_code_tab():
|
| 55 |
fullscreen_state = gr.State(False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
with gr.Row(elem_id="indicator-code-tab"):
|
| 58 |
with gr.Column(scale=1) as left_panel:
|
|
|
|
| 66 |
default_model_constant=LING_1T
|
| 67 |
)
|
| 68 |
prompt_input = gr.Textbox(lines=5, placeholder="例如:创建一个带标题和按钮的简单页面", label="提示词")
|
| 69 |
+
|
| 70 |
+
# --- Style Settings Section ---
|
| 71 |
+
with gr.Group(visible=True, elem_classes="style-settings-group"):
|
| 72 |
+
gr.Markdown("#### 🎨 设置网页风格")
|
| 73 |
+
with gr.Row():
|
| 74 |
+
palette_display = gr.HTML(value='<div style="color: #999; font-size: 12px;">暂无配色</div>', label="配色预览")
|
| 75 |
+
# Hidden textbox to store the raw palette string for the prompt
|
| 76 |
+
palette_input = gr.Textbox(visible=False, label="Palette (Raw)")
|
| 77 |
+
|
| 78 |
+
decoration_input = gr.Textbox(label="装饰风格", placeholder="例如:粗边框,无圆角", lines=1)
|
| 79 |
+
overall_style_input = gr.Textbox(label="整体风格", placeholder="例如:类似一本羊皮纸的书", lines=1)
|
| 80 |
+
|
| 81 |
+
generate_style_btn = gr.Button("🎲 随机生成风格", size="sm")
|
| 82 |
+
# ------------------------------
|
| 83 |
+
|
| 84 |
with gr.Column():
|
| 85 |
+
gr.Markdown("### 示例")
|
| 86 |
+
examples_dataset = gr.Dataset(
|
| 87 |
+
components=[gr.Textbox(visible=False)],
|
| 88 |
+
samples=[[item["task"]] for item in CODE_EXAMPLES],
|
| 89 |
+
label="示例",
|
| 90 |
+
headers=["选择一个示例"],
|
| 91 |
+
)
|
| 92 |
generate_button = gr.Button("生成代码", variant="primary")
|
| 93 |
|
| 94 |
with gr.Column(scale=4):
|
|
|
|
| 104 |
refresh_button = gr.Button("刷新预览")
|
| 105 |
|
| 106 |
with gr.Column(scale=1):
|
| 107 |
+
log_chatbot = gr.Chatbot(label="生成日志", height=300)
|
| 108 |
|
| 109 |
js_error_channel = gr.Textbox(visible=True, elem_classes=["js_error_channel"], label="Debug Error Channel", interactive=False)
|
| 110 |
|
| 111 |
+
# Event Handler for Example Selection
|
| 112 |
+
def on_select_example(evt: gr.SelectData):
|
| 113 |
+
selected_task = evt.value[0]
|
| 114 |
+
item = next((i for i in CODE_EXAMPLES if i["task"] == selected_task), None)
|
| 115 |
+
if not item:
|
| 116 |
+
return gr.update(), gr.update()
|
| 117 |
+
|
| 118 |
+
return gr.update(value=item["user_prompt"]), gr.update(value=item["model"])
|
| 119 |
+
|
| 120 |
+
examples_dataset.select(
|
| 121 |
+
fn=on_select_example,
|
| 122 |
+
inputs=None,
|
| 123 |
+
outputs=[prompt_input, model_choice_dropdown],
|
| 124 |
+
show_progress="none"
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
# Event Handler for Style Generation
|
| 128 |
+
generate_style_btn.click(
|
| 129 |
+
fn=generate_random_style,
|
| 130 |
+
inputs=[model_choice_dropdown],
|
| 131 |
+
outputs=[palette_display, palette_input, decoration_input, overall_style_input]
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
refresh_button.click(
|
| 135 |
fn=refresh_preview,
|
| 136 |
inputs=[code_framework_dropdown, code_output, log_chatbot],
|
|
|
|
| 138 |
)
|
| 139 |
|
| 140 |
generate_button.click(
|
| 141 |
+
fn=code_generation_agent,
|
| 142 |
+
inputs=[
|
| 143 |
+
code_framework_dropdown,
|
| 144 |
+
model_choice_dropdown,
|
| 145 |
+
prompt_input,
|
| 146 |
+
palette_input, # Pass the raw palette string
|
| 147 |
+
decoration_input,
|
| 148 |
+
overall_style_input,
|
| 149 |
+
log_chatbot
|
| 150 |
+
],
|
| 151 |
outputs=[code_output, preview_output, log_chatbot, result_tabs]
|
| 152 |
)
|
| 153 |
|