lastmass commited on
Commit
15d6775
·
1 Parent(s): 08e88a0
Files changed (2) hide show
  1. app.py +373 -0
  2. requirements.txt +6 -0
app.py ADDED
@@ -0,0 +1,373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import base64
3
+ import io
4
+ import json
5
+ from PIL import Image
6
+ import httpx
7
+ import html
8
+ from lzstring import LZString
9
+ import google.generativeai as genai
10
+
11
+ # --- 默认API配置 ---
12
+ DEFAULT_GEMINI_API_KEY = ""
13
+ DEFAULT_DEEPSEEK_API_KEY = ""
14
+ DEEPSEEK_BASE_URL = "https://api.deepseek.com"
15
+
16
+ # --- 核心工具函数 ---
17
+ def analyze_image(image: Image.Image, gemini_api_key: str = "") -> str:
18
+ """
19
+ Analyze an uploaded image and provide a detailed description of its content and layout.
20
+
21
+ Args:
22
+ image: The PIL Image object to analyze
23
+ gemini_api_key: Gemini API key for image analysis
24
+
25
+ Returns:
26
+ A detailed description of the image content, layout, and website type
27
+ """
28
+ if image is None:
29
+ return "Error: No image provided"
30
+
31
+ # 使用提供的API密钥或默认密钥
32
+ api_key = gemini_api_key.strip() if gemini_api_key.strip() else DEFAULT_GEMINI_API_KEY
33
+
34
+ if not api_key:
35
+ return "Error: Gemini API key not provided"
36
+
37
+ try:
38
+ # 配置Gemini API
39
+ genai.configure(api_key=api_key)
40
+
41
+ # 转换图片为base64
42
+ buffered = io.BytesIO()
43
+ image.save(buffered, format="PNG")
44
+ img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
45
+
46
+ # 创建模型和提示
47
+ model = genai.GenerativeModel(model_name="gemini-2.5-flash-preview-05-20")
48
+ prompt = """
49
+ Analyze this image and provide a concise description.
50
+ Describe the main elements, colors, layout, and UI components.
51
+ Identify what type of website or application this resembles.
52
+ Focus on structural and visual elements that would be important for recreating the design.
53
+ """
54
+
55
+ image_part = {"mime_type": "image/png", "data": img_str}
56
+ contents = [prompt, image_part]
57
+
58
+ # 生成描述
59
+ response = model.generate_content(contents)
60
+ return response.text
61
+
62
+ except Exception as e:
63
+ return f"Error analyzing image: {str(e)}"
64
+
65
+ def generate_html_code(description: str, deepseek_api_key: str = "") -> str:
66
+ """
67
+ Generate HTML/CSS/JavaScript code based on a website description.
68
+
69
+ Args:
70
+ description: Detailed description of the website to generate
71
+ deepseek_api_key: DeepSeek API key for code generation
72
+
73
+ Returns:
74
+ Complete HTML code with embedded CSS and JavaScript
75
+ """
76
+ if not description or description.startswith("Error"):
77
+ return "Error: Invalid or missing description"
78
+
79
+ # 使用提供的API密钥或默认密钥
80
+ api_key = deepseek_api_key.strip() if deepseek_api_key.strip() else DEFAULT_DEEPSEEK_API_KEY
81
+
82
+ if not api_key:
83
+ return "Error: DeepSeek API key not provided"
84
+
85
+ prompt = f"""
86
+ Generate a complete, responsive webpage based on this description:
87
+
88
+ {description}
89
+
90
+ Requirements:
91
+ - Use modern HTML5, CSS3, and vanilla JavaScript only
92
+ - Include TailwindCSS via CDN for styling
93
+ - Make it responsive and visually appealing
94
+ - Use placeholder images from https://unsplash.com/ if needed
95
+ - Include proper semantic HTML structure
96
+ - Add interactive elements where appropriate
97
+ - Ensure the design matches the described layout and style
98
+
99
+ Return only the complete HTML code starting with <!DOCTYPE html> and ending with </html>.
100
+ """
101
+
102
+ headers = {
103
+ "Authorization": f"Bearer {api_key}",
104
+ "Content-Type": "application/json"
105
+ }
106
+
107
+ data = {
108
+ "model": "deepseek-chat",
109
+ "messages": [{"role": "user", "content": prompt}],
110
+ "temperature": 0.7,
111
+ "max_tokens": 8000
112
+ }
113
+
114
+ try:
115
+ import requests
116
+ response = requests.post(
117
+ f"{DEEPSEEK_BASE_URL}/chat/completions",
118
+ headers=headers,
119
+ json=data,
120
+ timeout=60
121
+ )
122
+
123
+ if response.status_code == 200:
124
+ result = response.json()
125
+ html_code = result['choices'][0]['message']['content']
126
+
127
+ # 清理代码格式
128
+ if html_code.strip().startswith("```html"):
129
+ html_code = html_code.split("```html", 1)[1].strip()
130
+ if html_code.strip().endswith("```"):
131
+ html_code = html_code.rsplit("```", 1)[0].strip()
132
+
133
+ # 确保代码完整性
134
+ if "<!DOCTYPE html>" in html_code and "</html>" in html_code:
135
+ start = html_code.find("<!DOCTYPE html>")
136
+ end = html_code.rfind("</html>") + 7
137
+ return html_code[start:end]
138
+ else:
139
+ return html_code
140
+ else:
141
+ return f"Error: API request failed with status {response.status_code}"
142
+
143
+ except Exception as e:
144
+ return f"Error generating HTML code: {str(e)}"
145
+
146
+ def create_codesandbox(html_code: str) -> str:
147
+ """
148
+ Create a CodeSandbox project from HTML code.
149
+
150
+ Args:
151
+ html_code: Complete HTML code to upload to CodeSandbox
152
+
153
+ Returns:
154
+ CodeSandbox URL or error message
155
+ """
156
+ if not html_code or html_code.startswith("Error"):
157
+ return "Error: No valid HTML code provided"
158
+
159
+ try:
160
+ # 准备文件结构
161
+ files = {
162
+ "index.html": {
163
+ "content": html_code,
164
+ "isBinary": False
165
+ },
166
+ "package.json": {
167
+ "content": json.dumps({
168
+ "name": "ai-generated-website",
169
+ "version": "1.0.0",
170
+ "description": "Website generated from image analysis",
171
+ "main": "index.html",
172
+ "scripts": {
173
+ "start": "serve .",
174
+ "build": "echo 'No build required'"
175
+ },
176
+ "devDependencies": {
177
+ "serve": "^14.0.0"
178
+ }
179
+ }, indent=2),
180
+ "isBinary": False
181
+ }
182
+ }
183
+
184
+ # 准备参数
185
+ parameters = {
186
+ "files": files,
187
+ "template": "static"
188
+ }
189
+
190
+ # 压缩数据
191
+ json_str = json.dumps(parameters, separators=(',', ':'))
192
+ lz = LZString()
193
+ compressed = lz.compressToBase64(json_str)
194
+ compressed = compressed.replace('+', '-').replace('/', '_').rstrip('=')
195
+
196
+ # 生成URL
197
+ codesandbox_url = f"https://codesandbox.io/api/v1/sandboxes/define?parameters={compressed}"
198
+
199
+ # 尝试创建sandbox
200
+ import requests
201
+ response = requests.post(codesandbox_url, timeout=30)
202
+
203
+ if response.status_code == 200:
204
+ result = response.json()
205
+ sandbox_id = result.get("sandbox_id")
206
+ if sandbox_id:
207
+ return f"https://codesandbox.io/s/{sandbox_id}"
208
+
209
+ # 如果POST失败,返回GET URL
210
+ return codesandbox_url
211
+
212
+ except Exception as e:
213
+ return f"Error creating CodeSandbox: {str(e)}"
214
+
215
+ def screenshot_to_code(image: Image.Image, gemini_api_key: str = "", deepseek_api_key: str = "") -> tuple:
216
+ """
217
+ Complete pipeline: analyze image and generate corresponding HTML code.
218
+
219
+ Args:
220
+ image: Screenshot image to analyze
221
+ gemini_api_key: Gemini API key for image analysis
222
+ deepseek_api_key: DeepSeek API key for code generation
223
+
224
+ Returns:
225
+ Tuple of (description, html_code)
226
+ """
227
+ # 分析图片
228
+ description = analyze_image(image, gemini_api_key)
229
+
230
+ if description.startswith("Error"):
231
+ return description, "Error: Cannot generate code due to image analysis failure"
232
+
233
+ # 生成代码
234
+ html_code = generate_html_code(description, deepseek_api_key)
235
+
236
+ return description, html_code
237
+
238
+ # --- Gradio界面 ---
239
+ with gr.Blocks(
240
+ theme=gr.themes.Soft(),
241
+ title="AI Website Generator - MCP Compatible",
242
+ css="""
243
+ .api-section { background: #f8f9fa; padding: 15px; border-radius: 8px; margin: 10px 0; }
244
+ .tool-section { border: 1px solid #e0e0e0; padding: 15px; border-radius: 8px; margin: 10px 0; }
245
+ """
246
+ ) as app:
247
+
248
+ gr.Markdown("""
249
+ # 🚀 AI Website Generator (MCP Compatible)
250
+
251
+ Transform website screenshots into functional HTML code using AI.
252
+
253
+ **Features:**
254
+ - 📸 Image analysis with Gemini AI
255
+ - 💻 HTML/CSS/JS code generation with DeepSeek
256
+ - 🌐 Direct CodeSandbox deployment
257
+ - 🔧 MCP (Model Context Protocol) compatible
258
+
259
+ **Tools Available:**
260
+ - `analyze_image`: Analyze website screenshots
261
+ - `generate_html_code`: Generate HTML from descriptions
262
+ - `create_codesandbox`: Deploy to CodeSandbox
263
+ - `screenshot_to_code`: Complete pipeline
264
+ """)
265
+
266
+ with gr.Tab("🎯 Quick Generate"):
267
+ with gr.Row():
268
+ with gr.Column(scale=1):
269
+ gr.Markdown("### 📤 Input", elem_classes=["tool-section"])
270
+
271
+ # API配置
272
+ with gr.Group():
273
+ gr.Markdown("**API Keys (Optional)**")
274
+ gemini_key = gr.Textbox(
275
+ label="Gemini API Key",
276
+ type="password",
277
+ placeholder="Leave empty to use default",
278
+ value=""
279
+ )
280
+ deepseek_key = gr.Textbox(
281
+ label="DeepSeek API Key",
282
+ type="password",
283
+ placeholder="Leave empty to use default",
284
+ value=""
285
+ )
286
+
287
+ # 图片上传
288
+ image_input = gr.Image(
289
+ type="pil",
290
+ label="Upload Website Screenshot",
291
+ sources=["upload", "clipboard"]
292
+ )
293
+
294
+ generate_btn = gr.Button(
295
+ "🎨 Generate Website",
296
+ variant="primary",
297
+ size="lg"
298
+ )
299
+
300
+ with gr.Column(scale=2):
301
+ gr.Markdown("### 📋 Results", elem_classes=["tool-section"])
302
+
303
+ description_output = gr.Textbox(
304
+ label="📝 Image Analysis",
305
+ lines=6,
306
+ interactive=False
307
+ )
308
+
309
+ html_output = gr.Code(
310
+ label="💻 Generated HTML Code",
311
+ language="html",
312
+ lines=15
313
+ )
314
+
315
+ with gr.Row():
316
+ codesandbox_btn = gr.Button("🚀 Deploy to CodeSandbox")
317
+ codesandbox_output = gr.Textbox(
318
+ label="CodeSandbox URL",
319
+ interactive=False
320
+ )
321
+
322
+ with gr.Tab("🔧 Individual Tools"):
323
+ gr.Markdown("### Use individual MCP tools")
324
+
325
+ with gr.Row():
326
+ with gr.Column():
327
+ gr.Markdown("#### 📸 Image Analysis Tool")
328
+ img_tool = gr.Image(type="pil", label="Image")
329
+ gemini_key_tool = gr.Textbox(label="Gemini API Key", type="password")
330
+ analyze_btn = gr.Button("Analyze Image")
331
+ analysis_result = gr.Textbox(label="Analysis Result", lines=5)
332
+
333
+ with gr.Column():
334
+ gr.Markdown("#### 💻 Code Generation Tool")
335
+ desc_input = gr.Textbox(label="Description", lines=3)
336
+ deepseek_key_tool = gr.Textbox(label="DeepSeek API Key", type="password")
337
+ code_btn = gr.Button("Generate Code")
338
+ code_result = gr.Code(label="Generated Code", language="html")
339
+
340
+ # 事件绑定
341
+ generate_btn.click(
342
+ fn=screenshot_to_code,
343
+ inputs=[image_input, gemini_key, deepseek_key],
344
+ outputs=[description_output, html_output]
345
+ )
346
+
347
+ codesandbox_btn.click(
348
+ fn=create_codesandbox,
349
+ inputs=[html_output],
350
+ outputs=[codesandbox_output]
351
+ )
352
+
353
+ analyze_btn.click(
354
+ fn=analyze_image,
355
+ inputs=[img_tool, gemini_key_tool],
356
+ outputs=[analysis_result]
357
+ )
358
+
359
+ code_btn.click(
360
+ fn=generate_html_code,
361
+ inputs=[desc_input, deepseek_key_tool],
362
+ outputs=[code_result]
363
+ )
364
+
365
+ # 示例
366
+ gr.Examples(
367
+ examples=[["1.jpg"]],
368
+ inputs=[image_input],
369
+ label="📷 Example Screenshots"
370
+ )
371
+
372
+ if __name__ == "__main__":
373
+ app.launch(mcp_server=True, share=False)
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio>=4.19.0
2
+ pillow>=9.0.0
3
+ httpx>=0.24.0
4
+ google-generativeai>=0.3.0
5
+ lzstring>=1.0.4
6
+ requests>=2.28.0