当前位置:首页>python>PythonAI UI 生成器 - 简化版 Google Stitch

PythonAI UI 生成器 - 简化版 Google Stitch

  • 2026-10-11 07:38:11
PythonAI 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预览(不依赖外部浏览器)
  • [ ] 组件库模式(生成可复用组件)
"""ChatFlask - AI对话Web应用Flask后端:API代理(解决CORS)+ 流式输出(SSE)+ 页面路由"""from flask import Flask, request, Response, render_template, jsonify, stream_with_contextimport requestsimport jsonimport timeimport uuidapp = Flask(name)内存存储对话历史conversations = {}  # {conv_id: {"title": str, "messages": [], "created_at": float}}@app.route('/')def index():return render_template('index.html')@app.route('/api/chat', methods=['POST'])def chat():"""API代理 - 流式输出"""data = request.jsonendpoint = data.get('endpoint', '')api_key = data.get('api_key', 'sk-')model = data.get('model', 'qwen-plus')messages = data.get('messages', [])temperature = data.get('temperature', 0.7)max_tokens = data.get('max_tokens', 4000)top_p = data.get('top_p', 0.9)stream = data.get('stream', True)if not endpoint or not api_key:    return jsonify({'error': '请配置API地址和Key'}), 400headers = {    'Authorization': f'Bearer {api_key}',    'Content-Type': 'application/json',}payload = {    'model': model,    'messages': messages,    'temperature': temperature,    'max_tokens': max_tokens,    'top_p': top_p,    'stream': stream,}if stream:    return Response(        stream_with_context(_stream_response(endpoint, headers, payload)),        content_type='text/event-stream',        headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'}    )else:    try:        resp = requests.post(endpoint, headers=headers, json=payload, timeout=90)        if resp.status_code != 200:            return jsonify({'error': f'API错误: {resp.status_code}{resp.text[:200]}'}), resp.status_code        result = resp.json()        content = result['choices'][0]['message']['content']        return jsonify({'content': content})    except requests.Timeout:        return jsonify({'error': '请求超时'}), 504    except Exception as e:        return jsonify({'error': str(e)}), 500def _stream_response(endpoint, headers, payload):"""流式代理转发"""try:resp = requests.post(endpoint, headers=headers, json=payload, stream=True, timeout=90)if resp.status_code != 200:yield f"data: {json.dumps({'error': f'API错误: {resp.status_code}'})}\n\n"return    for line in resp.iter_lines():        if line:            line_str = line.decode('utf-8')            if line_str.startswith('data: '):                data_str = line_str[6:]                if data_str.strip() == '[DONE]':                    yield f"data: {json.dumps({'content': '', 'done': True})}\n\n"                    break                try:                    chunk = json.loads(data_str)                    delta = chunk.get('choices', [{}])[0].get('delta', {})                    content = delta.get('content', '')                    if content:                        yield f"data: {json.dumps({'content': content, 'done': False})}\n\n"                except json.JSONDecodeError:                    continueexcept requests.Timeout:    yield f"data: {json.dumps({'error': '请求超时'})}\n\n"except Exception as e:    yield f"data: {json.dumps({'error': str(e)})}\n\n"@app.route('/api/chat/sync', methods=['POST'])def chat_sync():"""非流式调用(备用)"""data = request.jsonendpoint = data.get('endpoint', '')api_key = data.get('api_key', '')headers = {'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json'}payload = {    'model': data.get('model', 'qwen-plus'),    'messages': data.get('messages', []),    'temperature': data.get('temperature', 0.7),    'max_tokens': data.get('max_tokens', 4000),    'top_p': data.get('top_p', 0.9),    'stream': False,}try:    resp = requests.post(endpoint, headers=headers, json=payload, timeout=90)    result = resp.json()    content = result.get('choices', [{}])[0].get('message', {}).get('content', '')    # 有些模型把内容放在reasoning_content里    if not content:        content = result.get('choices', [{}])[0].get('message', {}).get('reasoning_content', '')    return jsonify({'content': content})except Exception as e:    return jsonify({'error': str(e)}), 500=== 对话历史管理 ===@app.route('/api/conversations', methods=['GET'])def list_conversations():conv_list = [{'id': k, 'title': v['title'], 'created_at': v['created_at']}for k, v in conversations.items()]conv_list.sort(key=lambda x: x['created_at'], reverse=True)return jsonify(conv_list)@app.route('/api/conversations', methods=['POST'])def create_conversation():conv_id = str(uuid.uuid4())[:8]conversations[conv_id] = {'title': '新对话', 'messages': [], 'created_at': time.time()}return jsonify({'id': conv_id})@app.route('/api/conversations/<conv_id>', methods=['GET'])def get_conversation(conv_id):if conv_id in conversations:return jsonify(conversations[conv_id])return jsonify({'error': '对话不存在'}), 404@app.route('/api/conversations/<conv_id>', methods=['PUT'])def update_conversation(conv_id):if conv_id not in conversations:return jsonify({'error': '对话不存在'}), 404data = request.jsonif 'title' in data:conversations[conv_id]['title'] = data['title']if 'messages' in data:conversations[conv_id]['messages'] = data['messages']return jsonify({'status': 'ok'})@app.route('/api/conversations/<conv_id>', methods=['DELETE'])def delete_conversation(conv_id):if conv_id in conversations:del conversations[conv_id]return jsonify({'status': 'ok'})if name == 'main':app.run(host='0.0.0.0', port=5000, debug=True)

最新文章

随机文章