AI UI 生成器 - 简化版 Google Stitch
学在坚持公众号
学在坚持公众号



简介
基于大模型(Qwen3.5-397B)实现的文本转UI工具。输入自然语言描述,AI自动生成完整的HTML页面代码,并在浏览器中实时预览。支持对话式迭代修改,像和设计师对话一样调整界面。
核心原理
用户输入文字描述 ↓System Prompt(约束输出格式)+ 用户Prompt ↓调用 Qwen3.5-397B API ↓返回完整HTML代码 ↓保存为临时文件 → 浏览器打开预览 ↓用户提出修改 → 追加到对话历史 → 再次调用API → 更新预览环境要求
pip install requests运行方式
cd python/ngorkpython ai_ui_generator.py功能说明
界面布局
┌──────────────────────────────────────────────────────┐│ AI UI 生成器 │├────────────────────────┬─────────────────────────────┤│ 描述你想要的UI │ ││ ┌──────────────────┐ │ 实时预览 ││ │ 输入框(多行) │ │ ││ └──────────────────┘ │ (自动在浏览器中打开) ││ │ ││ [登录页][仪表盘]... │ ││ │ ││ [生成UI][修改当前] │ 预览信息 ││ [浏览器预览][导出] │ ✅ 代码已生成 ││ │ 💡 输入修改要求可迭代 ││ 生成的代码 │ ││ ┌──────────────────┐ │ ││ │ <!DOCTYPE html> │ │ ││ │ <html>... │ │ ││ │ (深色代码区) │ │ ││ └──────────────────┘ │ │└────────────────────────┴─────────────────────────────┘测试步骤
测试1:基本生成
启动程序: python ai_ui_generator.py在输入框中输入: 一个现代风格的登录页面,有用户名和密码输入框,登录按钮点击「🚀 生成UI」 等待10-30秒(API调用) 预期结果: 状态栏显示 ✅ 生成完成 (xxx字符)左侧代码区显示完整HTML代码 浏览器自动打开预览页面 预览页面是一个美观的登录表单
测试2:快捷模板
点击顶部「仪表盘」按钮 输入框自动填入仪表盘描述 点击「🚀 生成UI」 预期结果:生成一个带导航栏、统计卡片、表格的仪表盘页面
测试3:对话式修改
先完成测试1(生成登录页) 在输入框中输入: 把背景改成蓝紫渐变色,按钮改成圆角点击「🔄 修改当前」 预期结果: AI基于当前代码进行修改 浏览器刷新后看到背景变成渐变色 按钮变成圆角样式 其他元素保持不变
测试4:连续迭代
接测试3,继续输入: 增加一个"忘记密码"链接和第三方登录按钮(微信、QQ)点击「🔄 修改当前」 预期结果:在原有基础上增加了新元素,之前的渐变背景和圆角按钮保持不变
测试5:导出
生成满意的UI后 点击「💾 导出HTML」 选择保存路径 用浏览器打开导出的文件,确认效果一致
测试6:错误处理
断开网络 点击「🚀 生成UI」 预期结果:90秒后提示"请求超时",不会崩溃
代码架构
ai_ui_generator.py├── API_CONFIG # API配置(endpoint/key/model)├── SYSTEM_PROMPT # 系统提示词(约束输出格式)└── AIUIGenerator # 主类 ├── _create_ui() # 构建GUI界面 ├── _generate() # 新建生成(清空历史) ├── _modify() # 迭代修改(追加历史) ├── _call_api() # 调用LLM API(子线程) ├── _extract_html() # 从响应中提取HTML代码 ├── _on_success() # 生成成功回调 ├── _on_error() # 错误处理 ├── _save_and_preview() # 保存临时文件+浏览器打开 └── _export() # 导出HTML文件关键设计
html包裹代码 | ||
System Prompt 设计
你是一个专业的前端UI设计师和开发者。根据用户的描述生成完整的HTML页面代码。规则:1. 生成完整的HTML文件(包含<!DOCTYPE html>、<head>、<body>)2. 使用内联CSS或<style>标签,不依赖外部文件3. 设计要现代、美观、响应式4. 只输出HTML代码,不要任何解释文字5. 不要用```html```包裹,直接输出代码6. 中文界面优先使用 -apple-system, 'Microsoft YaHei' 字体7. 配色要协调,间距要合理这个Prompt的关键约束:
"完整HTML文件":确保生成的代码可以直接在浏览器运行 "不依赖外部文件":不引用CDN,离线也能预览 "只输出代码":避免模型输出解释文字污染代码 "不要markdown包裹":减少后处理工作
API调用示例
import requestsresp = requests.post("https://xxxx/shop/v1/chat/completions", headers={"Authorization": "Bearer 4wWwsCTKCn4wtctz","Content-Type": "application/json" }, json={"model": "qwen3.5-397b","messages": [ {"role": "system", "content": "你是前端专家,只输出HTML代码..."}, {"role": "user", "content": "生成一个登录页面"} ],"temperature": 0.7,"max_tokens": 4000 }, timeout=90)html = resp.json()['choices'][0]['message']['content']对话式修改原理
# 第一次生成history = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": "生成登录页面"},]# API返回HTML → 追加到historyhistory.append({"role": "assistant", "content": "<html>..."})# 第二次修改history.append({"role": "user", "content": "把背景改成蓝色"})# API看到完整对话历史,知道当前代码是什么,只修改背景色# 返回修改后的完整HTML关键:每次修改都带上完整对话历史,AI能"记住"当前代码状态,做增量修改而不是重新生成。
常见问题
Q: 生成速度慢?
A: 397B参数模型推理较慢,通常需要10-30秒。这是正常的。
Q: 生成的代码不完整?
A: max_tokens 设为4000,复杂页面可能被截断。可以在代码中调大到8000。
Q: 修改时AI重新生成了整个页面?
A: 在修改提示中明确说"只修改xxx,其他保持不变"效果更好。
Q: 预览页面空白?
A: 检查代码区是否有有效HTML。如果API返回了非HTML内容,_extract_html() 会尝试修复。
Q: 能生成React/Vue代码吗?
A: 可以在输入中指定,如"用React组件方式生成",但预览需要额外构建步骤。
与Google Stitch对比
后续可扩展
[ ] 支持指定CSS框架(Tailwind/Bootstrap) [ ] 增加历史记录面板(回退到之前的版本) [ ] 支持多页面项目生成 [ ] 接入多模态模型支持图片输入 [ ] 流式输出(边生成边显示) [ ] 内嵌WebView预览(不依赖外部浏览器) [ ] 组件库模式(生成可复用组件)
"""AI UI 生成器 - 简化版 Google Stitch功能:输入文字描述 → AI生成HTML代码 → 实时预览 → 对话式迭代修改 → 导出"""import tkinter as tkfrom tkinter import ttk, scrolledtext, filedialog, messageboximport threadingimport requestsimport jsonimport tempfileimport webbrowserimport osimport reAPI配置API_CONFIG = {"endpoint": "https://aix-backup.hismarttv.com/shop/v1/chat/completions","api_key": "4wWwsCTKCn4wtctz","model": "qwen3.5-397b"}SYSTEM_PROMPT = """你是一个专业的前端UI设计师和开发者。根据用户的描述生成完整的HTML页面代码。规则:生成完整的HTML文件(包含、、)使用内联CSS或标签,不依赖外部文件</section></li><li><section>设计要现代、美观、响应式</section></li><li><section>只输出HTML代码,不要任何解释文字</section></li><li><section>不要用<code>html</code>包裹,直接输出代码</section></li><li><section>中文界面优先使用 -apple-system, 'Microsoft YaHei' 字体</section></li><li><section>配色要协调,间距要合理"""</section></li></ol><p>class AIUIGenerator:def <strong>init</strong>(self):self.root = tk.Tk()self.root.title("AI UI 生成器 - 文本转界面")self.root.geometry("1200x750")</p><pre><code> self.current_html = ""self.history = [] # 对话历史self.generating = Falseself.preview_file = os.path.join(tempfile.gettempdir(), "ai_ui_preview.html")self._create_ui()def _create_ui(self):# 主布局:左右分栏main = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)main.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)# === 左侧:输入+控制 ===left = ttk.Frame(main, width=450)main.add(left, weight=2)# 标题ttk.Label(left, text="AI UI 生成器", font=('', 14, 'bold')).pack(anchor='w', padx=10, pady=(10, 2))ttk.Label(left, text="描述你想要的界面,AI帮你生成代码", foreground='#666').pack(anchor='w', padx=10, pady=(0, 10))# 输入区input_frame = ttk.LabelFrame(left, text="描述你想要的UI", padding=8)input_frame.pack(fill=tk.X, padx=10, pady=5)self.input_text = scrolledtext.ScrolledText(input_frame, height=5, font=('', 10), wrap=tk.WORD)self.input_text.pack(fill=tk.X)self.input_text.insert(tk.END, "一个现代风格的登录页面,有用户名和密码输入框,登录按钮,底部有注册链接")# 快捷模板template_frame = ttk.Frame(left)template_frame.pack(fill=tk.X, padx=10, pady=5)ttk.Label(template_frame, text="快捷模板:", foreground='#888').pack(side=tk.LEFT)templates = [("登录页", "一个现代简约的登录页面,渐变背景,卡片式表单,有用户名密码输入框和登录按钮"),("仪表盘", "一个数据仪表盘页面,顶部导航栏,左侧菜单,右侧有4个统计卡片和一个表格"),("商品卡片", "一个电商商品展示页,网格布局展示6个商品卡片,每个有图片占位、标题、价格、购买按钮"),("个人简历", "一个在线简历页面,左侧头像和联系方式,右侧工作经历和技能条"),]for name, prompt in templates:btn = ttk.Button(template_frame, text=name,command=lambda p=prompt: self._set_prompt(p))btn.pack(side=tk.LEFT, padx=2)# 按钮区btn_frame = ttk.Frame(left)btn_frame.pack(fill=tk.X, padx=10, pady=8)self.btn_generate = ttk.Button(btn_frame, text="🚀 生成UI", command=self._generate)self.btn_generate.pack(side=tk.LEFT, padx=3)ttk.Button(btn_frame, text="🔄 修改当前", command=self._modify).pack(side=tk.LEFT, padx=3)ttk.Button(btn_frame, text="🌐 浏览器预览", command=self._preview_browser).pack(side=tk.LEFT, padx=3)ttk.Button(btn_frame, text="💾 导出HTML", command=self._export).pack(side=tk.LEFT, padx=3)# 状态self.status_var = tk.StringVar(value="就绪")ttk.Label(btn_frame, textvariable=self.status_var, foreground='#888').pack(side=tk.RIGHT)# 代码区code_frame = ttk.LabelFrame(left, text="生成的代码", padding=5)code_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)self.code_text = scrolledtext.ScrolledText(code_frame, font=('Consolas', 9),bg='#1e1e1e', fg='#d4d4d4',insertbackground='white')self.code_text.pack(fill=tk.BOTH, expand=True)# === 右侧:预览 ===right = ttk.Frame(main)main.add(right, weight=3)preview_frame = ttk.LabelFrame(right, text="实时预览", padding=5)preview_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)# 使用tkinter的Text模拟预览(显示HTML源码渲染提示)# 真正的预览通过浏览器打开self.preview_label = ttk.Label(preview_frame,text="点击「生成UI」后,预览将在浏览器中打开\n\n""或点击「浏览器预览」查看当前效果",font=('', 12), foreground='#999',justify='center', anchor='center')self.preview_label.pack(expand=True)# 内嵌预览(使用tkhtml或简单展示)self.preview_info = scrolledtext.ScrolledText(preview_frame, height=8,font=('', 10), wrap=tk.WORD,bg='#fafafa')self.preview_info.pack(fill=tk.BOTH, expand=True, pady=(10, 0))self.preview_info.insert(tk.END, "预览信息将在这里显示...\n\n提示:生成后会自动在浏览器中打开预览")def _set_prompt(self, prompt):self.input_text.delete(1.0, tk.END)self.input_text.insert(tk.END, prompt)def _generate(self):if self.generating:returnprompt = self.input_text.get(1.0, tk.END).strip()if not prompt:messagebox.showinfo("提示", "请输入UI描述")returnself.generating = Trueself.status_var.set("⏳ 正在生成...")self.btn_generate.config(state='disabled')# 新对话self.history = [{"role": "system", "content": SYSTEM_PROMPT},{"role": "user", "content": prompt}]threading.Thread(target=self._call_api, daemon=True).start()def _modify(self):if not self.current_html:messagebox.showinfo("提示", "请先生成一个UI")returnif self.generating:returnprompt = self.input_text.get(1.0, tk.END).strip()if not prompt:messagebox.showinfo("提示", "请输入修改要求")returnself.generating = Trueself.status_var.set("⏳ 正在修改...")self.btn_generate.config(state='disabled')# 追加修改请求到历史self.history.append({"role": "user", "content": f"请修改当前页面:{prompt}\n只输出修改后的完整HTML代码。"})threading.Thread(target=self._call_api, daemon=True).start()def _call_api(self):try:resp = requests.post(API_CONFIG['endpoint'],headers={'Authorization': f"Bearer {API_CONFIG['api_key']}",'Content-Type': 'application/json'},json={'model': API_CONFIG['model'],'messages': self.history,'temperature': 0.7,'max_tokens': 4000,},timeout=90)if resp.status_code != 200:self._on_error(f"API错误: {resp.status_code} {resp.text[:200]}")returnresult = resp.json()content = result['choices'][0]['message']['content']# 清理代码(去掉可能的markdown包裹)html = self._extract_html(content)if html:self.current_html = htmlself.history.append({"role": "assistant", "content": html})self.root.after(0, lambda: self._on_success(html))else:self._on_error("未能提取到有效的HTML代码")except requests.Timeout:self._on_error("请求超时(90秒),请重试")except Exception as e:self._on_error(f"请求失败: {str(e)}")def _extract_html(self, text):"""从响应中提取HTML代码"""if not text:return ""# 去掉markdown代码块包裹text = re.sub(r'^```html\s*\n?', '', text.strip())text = re.sub(r'^```\s*\n?', '', text.strip())text = re.sub(r'\n?```\s*$', '', text.strip())# 确保是HTMLif '<!DOCTYPE' in text or '<html' in text or '<body' in text:return text# 如果只有body内容,包裹完整HTMLif '<div' in text or '<form' in text or '<h1' in text:return f"""<!DOCTYPE html></code></pre><html><head><metacharset="utf-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>AI Generated UI</title></head><body>{text}</body></html>"""return text<pre><code>def _on_success(self, html):self.generating = Falseself.status_var.set(f"✅ 生成完成 ({len(html)}字符)")self.btn_generate.config(state='normal')# 更新代码区self.code_text.delete(1.0, tk.END)self.code_text.insert(tk.END, html)# 更新预览信息self.preview_info.delete(1.0, tk.END)self.preview_info.insert(tk.END, f"✅ 代码已生成 ({len(html)} 字符)\n\n")self.preview_info.insert(tk.END, f"已自动在浏览器中打开预览\n\n")self.preview_info.insert(tk.END, f"💡 提示:在左侧输入修改要求,点击「修改当前」可迭代优化\n")self.preview_info.insert(tk.END, f" 例如:'把背景改成渐变蓝色' '增加一个忘记密码链接'\n")# 自动预览self._save_and_preview(html)def _on_error(self, msg):self.root.after(0, lambda: self._show_error(msg))def _show_error(self, msg):self.generating = Falseself.status_var.set(f"❌ {msg[:50]}")self.btn_generate.config(state='normal')messagebox.showerror("错误", msg)def _save_and_preview(self, html):"""保存到临时文件并在浏览器中打开"""with open(self.preview_file, 'w', encoding='utf-8') as f:f.write(html)webbrowser.open(f'file:///{self.preview_file.replace(os.sep, "/")}')def _preview_browser(self):if not self.current_html:messagebox.showinfo("提示", "还没有生成内容")returnself._save_and_preview(self.current_html)def _export(self):if not self.current_html:messagebox.showinfo("提示", "还没有生成内容")returnfilepath = filedialog.asksaveasfilename(title="导出HTML",defaultextension=".html",filetypes=[("HTML文件", "*.html"), ("所有文件", "*.*")],initialfile="ai_generated_ui.html")if filepath:with open(filepath, 'w', encoding='utf-8') as f:f.write(self.current_html)messagebox.showinfo("成功", f"已导出到:\n{filepath}")def run(self):self.root.mainloop()</code></pre><p>if <strong>name</strong> == '<strong>main</strong>':app = AIUIGenerator()app.run()</p>
夜雨聆风