乐于分享
好东西不私藏

AI UI 生成器 - 简化版 Google Stitch

AI UI 生成器 - 简化版 Google Stitch

AI UI 生成器 - 简化版 Google Stitch

学在坚持公众号

学在坚持公众号

简介

基于大模型(Qwen3.5-397B)实现的文本转UI工具。输入自然语言描述,AI自动生成完整的HTML页面代码,并在浏览器中实时预览。支持对话式迭代修改,像和设计师对话一样调整界面。


核心原理

用户输入文字描述      ↓System Prompt(约束输出格式)+ 用户Prompt      ↓调用 Qwen3.5-397B API      ↓返回完整HTML代码      ↓保存为临时文件 → 浏览器打开预览      ↓用户提出修改 → 追加到对话历史 → 再次调用API → 更新预览

环境要求

项目
要求
Python
3.7+
依赖
requests(通常已自带)
网络
需要能访问API接口
浏览器
任意现代浏览器(用于预览)
pip install requests

运行方式

cd python/ngorkpython ai_ui_generator.py

功能说明

功能
说明
文本生成UI
输入描述,AI生成完整HTML+CSS页面
快捷模板
一键填入常用场景(登录页/仪表盘/商品卡片/简历)
对话式修改
基于当前代码提出修改要求,AI增量修改
浏览器预览
生成后自动在默认浏览器中打开
代码查看
左侧深色代码区实时显示生成的HTML
导出HTML
保存为本地HTML文件

界面布局

┌──────────────────────────────────────────────────────┐│  AI UI 生成器                                         │├────────────────────────┬─────────────────────────────┤│  描述你想要的UI         │                             ││  ┌──────────────────┐  │      实时预览               ││  │ 输入框(多行)    │  │                             ││  └──────────────────┘  │   (自动在浏览器中打开)      ││                        │                             ││  [登录页][仪表盘]...   │                             ││                        │                             ││  [生成UI][修改当前]    │      预览信息                ││  [浏览器预览][导出]    │   ✅ 代码已生成              ││                        │   💡 输入修改要求可迭代      ││  生成的代码            │                             ││  ┌──────────────────┐  │                             ││  │ <!DOCTYPE html>  │  │                             ││  │ <html>...        │  │                             ││  │ (深色代码区)      │  │                             ││  └──────────────────┘  │                             │└────────────────────────┴─────────────────────────────┘

测试步骤

测试1:基本生成

  1. 启动程序:python ai_ui_generator.py
  2. 在输入框中输入:一个现代风格的登录页面,有用户名和密码输入框,登录按钮
  3. 点击「🚀 生成UI」
  4. 等待10-30秒(API调用)
  5. 预期结果:
    • 状态栏显示 ✅ 生成完成 (xxx字符)
    • 左侧代码区显示完整HTML代码
    • 浏览器自动打开预览页面
    • 预览页面是一个美观的登录表单

测试2:快捷模板

  1. 点击顶部「仪表盘」按钮
  2. 输入框自动填入仪表盘描述
  3. 点击「🚀 生成UI」
  4. 预期结果:生成一个带导航栏、统计卡片、表格的仪表盘页面

测试3:对话式修改

  1. 先完成测试1(生成登录页)
  2. 在输入框中输入:把背景改成蓝紫渐变色,按钮改成圆角
  3. 点击「🔄 修改当前」
  4. 预期结果:
    • AI基于当前代码进行修改
    • 浏览器刷新后看到背景变成渐变色
    • 按钮变成圆角样式
    • 其他元素保持不变

测试4:连续迭代

  1. 接测试3,继续输入:增加一个"忘记密码"链接和第三方登录按钮(微信、QQ)
  2. 点击「🔄 修改当前」
  3. 预期结果:在原有基础上增加了新元素,之前的渐变背景和圆角按钮保持不变

测试5:导出

  1. 生成满意的UI后
  2. 点击「💾 导出HTML」
  3. 选择保存路径
  4. 用浏览器打开导出的文件,确认效果一致

测试6:错误处理

  1. 断开网络
  2. 点击「🚀 生成UI」
  3. 预期结果: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文件

关键设计

设计点
实现方式
原因
异步调用
threading子线程
API调用耗时长,不阻塞GUI
对话历史
self.history列表
修改时保持上下文,AI知道当前代码
代码提取
正则去除markdown包裹
模型有时会用html包裹代码
预览方式
临时文件+webbrowser
Tkinter无内嵌浏览器,用系统浏览器最可靠
System Prompt
严格约束输出格式
确保只输出代码,不输出解释文字

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对比

功能
Google Stitch
本工具
文本→UI
✅ Gemini驱动
✅ Qwen3.5驱动
图片→UI
✅ 多模态
❌ 纯文本模型
实时预览
✅ 内嵌iframe
✅ 浏览器打开
对话修改
语音输入
✅ Voice Canvas
多屏原型
✅ Flow
设计系统
✅ Material 3
⚠️ 靠Prompt约束
导出代码
✅ MCP协议
✅ HTML文件
费用
免费(有配额)
免费(API额度内)

后续可扩展

  • [ ] 支持指定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>配色要协调,间距要合理&quot;&quot;&quot;</section></li></ol><p>class AIUIGenerator:def <strong>init</strong>(self):self.root = tk.Tk()self.root.title(&quot;AI UI 生成器 - 文本转界面&quot;)self.root.geometry(&quot;1200x750&quot;)</p><pre><code>    self.current_html = &quot;&quot;    self.history = []  # 对话历史    self.generating = False    self.preview_file = os.path.join(tempfile.gettempdir(), &quot;ai_ui_preview.html&quot;)    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=&quot;AI UI 生成器&quot;, font=('', 14, 'bold')).pack(anchor='w', padx=10, pady=(10, 2))    ttk.Label(left, text=&quot;描述你想要的界面,AI帮你生成代码&quot;, foreground='#666').pack(anchor='w', padx=10, pady=(0, 10))    # 输入区    input_frame = ttk.LabelFrame(left, text=&quot;描述你想要的UI&quot;, 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, &quot;一个现代风格的登录页面,有用户名和密码输入框,登录按钮,底部有注册链接&quot;)    # 快捷模板    template_frame = ttk.Frame(left)    template_frame.pack(fill=tk.X, padx=10, pady=5)    ttk.Label(template_frame, text=&quot;快捷模板:&quot;, foreground='#888').pack(side=tk.LEFT)    templates = [        (&quot;登录页&quot;&quot;一个现代简约的登录页面,渐变背景,卡片式表单,有用户名密码输入框和登录按钮&quot;),        (&quot;仪表盘&quot;&quot;一个数据仪表盘页面,顶部导航栏,左侧菜单,右侧有4个统计卡片和一个表格&quot;),        (&quot;商品卡片&quot;&quot;一个电商商品展示页,网格布局展示6个商品卡片,每个有图片占位、标题、价格、购买按钮&quot;),        (&quot;个人简历&quot;&quot;一个在线简历页面,左侧头像和联系方式,右侧工作经历和技能条&quot;),    ]    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=&quot;🚀 生成UI&quot;, command=self._generate)    self.btn_generate.pack(side=tk.LEFT, padx=3)    ttk.Button(btn_frame, text=&quot;🔄 修改当前&quot;, command=self._modify).pack(side=tk.LEFT, padx=3)    ttk.Button(btn_frame, text=&quot;🌐 浏览器预览&quot;, command=self._preview_browser).pack(side=tk.LEFT, padx=3)    ttk.Button(btn_frame, text=&quot;💾 导出HTML&quot;, command=self._export).pack(side=tk.LEFT, padx=3)    # 状态    self.status_var = tk.StringVar(value=&quot;就绪&quot;)    ttk.Label(btn_frame, textvariable=self.status_var, foreground='#888').pack(side=tk.RIGHT)    # 代码区    code_frame = ttk.LabelFrame(left, text=&quot;生成的代码&quot;, 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=&quot;实时预览&quot;, 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=&quot;点击「生成UI」后,预览将在浏览器中打开\n\n&quot;                                         &quot;或点击「浏览器预览」查看当前效果&quot;,                                    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, &quot;预览信息将在这里显示...\n\n提示:生成后会自动在浏览器中打开预览&quot;)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:        return    prompt = self.input_text.get(1.0, tk.END).strip()    if not prompt:        messagebox.showinfo(&quot;提示&quot;&quot;请输入UI描述&quot;)        return    self.generating = True    self.status_var.set(&quot;⏳ 正在生成...&quot;)    self.btn_generate.config(state='disabled')    # 新对话    self.history = [        {&quot;role&quot;&quot;system&quot;&quot;content&quot;: SYSTEM_PROMPT},        {&quot;role&quot;&quot;user&quot;&quot;content&quot;: prompt}    ]    threading.Thread(target=self._call_api, daemon=True).start()def _modify(self):    if not self.current_html:        messagebox.showinfo(&quot;提示&quot;&quot;请先生成一个UI&quot;)        return    if self.generating:        return    prompt = self.input_text.get(1.0, tk.END).strip()    if not prompt:        messagebox.showinfo(&quot;提示&quot;&quot;请输入修改要求&quot;)        return    self.generating = True    self.status_var.set(&quot;⏳ 正在修改...&quot;)    self.btn_generate.config(state='disabled')    # 追加修改请求到历史    self.history.append({&quot;role&quot;&quot;user&quot;&quot;content&quot;: f&quot;请修改当前页面:{prompt}\n只输出修改后的完整HTML代码。&quot;})    threading.Thread(target=self._call_api, daemon=True).start()def _call_api(self):    try:        resp = requests.post(            API_CONFIG['endpoint'],            headers={                'Authorization': f&quot;Bearer {API_CONFIG['api_key']}&quot;,                '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&quot;API错误: {resp.status_code} {resp.text[:200]}&quot;)            return        result = resp.json()        content = result['choices'][0]['message']['content']        # 清理代码(去掉可能的markdown包裹)        html = self._extract_html(content)        if html:            self.current_html = html            self.history.append({&quot;role&quot;&quot;assistant&quot;&quot;content&quot;: html})            self.root.after(0, lambda: self._on_success(html))        else:            self._on_error(&quot;未能提取到有效的HTML代码&quot;)    except requests.Timeout:        self._on_error(&quot;请求超时(90秒),请重试&quot;)    except Exception as e:        self._on_error(f&quot;请求失败: {str(e)}&quot;)def _extract_html(self, text):    &quot;&quot;&quot;从响应中提取HTML代码&quot;&quot;&quot;    if not text:        return &quot;&quot;    # 去掉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())    # 确保是HTML    if '&lt;!DOCTYPE' in text or '&lt;html' in text or '&lt;body' in text:        return text    # 如果只有body内容,包裹完整HTML    if '&lt;div' in text or '&lt;form' in text or '&lt;h1' in text:        return f&quot;&quot;&quot;&lt;!DOCTYPE html&gt;</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 = False    self.status_var.set(f&quot;✅ 生成完成 ({len(html)}字符)&quot;)    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&quot;✅ 代码已生成 ({len(html)} 字符)\n\n&quot;)    self.preview_info.insert(tk.END, f&quot;已自动在浏览器中打开预览\n\n&quot;)    self.preview_info.insert(tk.END, f&quot;💡 提示:在左侧输入修改要求,点击「修改当前」可迭代优化\n&quot;)    self.preview_info.insert(tk.END, f&quot;   例如:'把背景改成渐变蓝色' '增加一个忘记密码链接'\n&quot;)    # 自动预览    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 = False    self.status_var.set(f&quot;❌ {msg[:50]}&quot;)    self.btn_generate.config(state='normal')    messagebox.showerror(&quot;错误&quot;, msg)def _save_and_preview(self, html):    &quot;&quot;&quot;保存到临时文件并在浏览器中打开&quot;&quot;&quot;    with open(self.preview_file, 'w', encoding='utf-8') as f:        f.write(html)    webbrowser.open(f'file:///{self.preview_file.replace(os.sep, &quot;/&quot;)}')def _preview_browser(self):    if not self.current_html:        messagebox.showinfo(&quot;提示&quot;&quot;还没有生成内容&quot;)        return    self._save_and_preview(self.current_html)def _export(self):    if not self.current_html:        messagebox.showinfo(&quot;提示&quot;&quot;还没有生成内容&quot;)        return    filepath = filedialog.asksaveasfilename(        title=&quot;导出HTML&quot;,        defaultextension=&quot;.html&quot;,        filetypes=[(&quot;HTML文件&quot;&quot;*.html&quot;), (&quot;所有文件&quot;&quot;*.*&quot;)],        initialfile=&quot;ai_generated_ui.html&quot;    )    if filepath:        with open(filepath, 'w', encoding='utf-8') as f:            f.write(self.current_html)        messagebox.showinfo(&quot;成功&quot;, f&quot;已导出到:\n{filepath}&quot;)def run(self):    self.root.mainloop()</code></pre><p>if <strong>name</strong> == '<strong>main</strong>':app = AIUIGenerator()app.run()</p>