lastmass's picture
Add file
15d6775
raw
history blame
12.6 kB
import gradio as gr
import base64
import io
import json
from PIL import Image
import httpx
import html
from lzstring import LZString
import google.generativeai as genai
# --- 默认API配置 ---
DEFAULT_GEMINI_API_KEY = ""
DEFAULT_DEEPSEEK_API_KEY = ""
DEEPSEEK_BASE_URL = "https://api.deepseek.com"
# --- 核心工具函数 ---
def analyze_image(image: Image.Image, gemini_api_key: str = "") -> str:
"""
Analyze an uploaded image and provide a detailed description of its content and layout.
Args:
image: The PIL Image object to analyze
gemini_api_key: Gemini API key for image analysis
Returns:
A detailed description of the image content, layout, and website type
"""
if image is None:
return "Error: No image provided"
# 使用提供的API密钥或默认密钥
api_key = gemini_api_key.strip() if gemini_api_key.strip() else DEFAULT_GEMINI_API_KEY
if not api_key:
return "Error: Gemini API key not provided"
try:
# 配置Gemini API
genai.configure(api_key=api_key)
# 转换图片为base64
buffered = io.BytesIO()
image.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
# 创建模型和提示
model = genai.GenerativeModel(model_name="gemini-2.5-flash-preview-05-20")
prompt = """
Analyze this image and provide a concise description.
Describe the main elements, colors, layout, and UI components.
Identify what type of website or application this resembles.
Focus on structural and visual elements that would be important for recreating the design.
"""
image_part = {"mime_type": "image/png", "data": img_str}
contents = [prompt, image_part]
# 生成描述
response = model.generate_content(contents)
return response.text
except Exception as e:
return f"Error analyzing image: {str(e)}"
def generate_html_code(description: str, deepseek_api_key: str = "") -> str:
"""
Generate HTML/CSS/JavaScript code based on a website description.
Args:
description: Detailed description of the website to generate
deepseek_api_key: DeepSeek API key for code generation
Returns:
Complete HTML code with embedded CSS and JavaScript
"""
if not description or description.startswith("Error"):
return "Error: Invalid or missing description"
# 使用提供的API密钥或默认密钥
api_key = deepseek_api_key.strip() if deepseek_api_key.strip() else DEFAULT_DEEPSEEK_API_KEY
if not api_key:
return "Error: DeepSeek API key not provided"
prompt = f"""
Generate a complete, responsive webpage based on this description:
{description}
Requirements:
- Use modern HTML5, CSS3, and vanilla JavaScript only
- Include TailwindCSS via CDN for styling
- Make it responsive and visually appealing
- Use placeholder images from https://unsplash.com/ if needed
- Include proper semantic HTML structure
- Add interactive elements where appropriate
- Ensure the design matches the described layout and style
Return only the complete HTML code starting with <!DOCTYPE html> and ending with </html>.
"""
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
data = {
"model": "deepseek-chat",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.7,
"max_tokens": 8000
}
try:
import requests
response = requests.post(
f"{DEEPSEEK_BASE_URL}/chat/completions",
headers=headers,
json=data,
timeout=60
)
if response.status_code == 200:
result = response.json()
html_code = result['choices'][0]['message']['content']
# 清理代码格式
if html_code.strip().startswith("```html"):
html_code = html_code.split("```html", 1)[1].strip()
if html_code.strip().endswith("```"):
html_code = html_code.rsplit("```", 1)[0].strip()
# 确保代码完整性
if "<!DOCTYPE html>" in html_code and "</html>" in html_code:
start = html_code.find("<!DOCTYPE html>")
end = html_code.rfind("</html>") + 7
return html_code[start:end]
else:
return html_code
else:
return f"Error: API request failed with status {response.status_code}"
except Exception as e:
return f"Error generating HTML code: {str(e)}"
def create_codesandbox(html_code: str) -> str:
"""
Create a CodeSandbox project from HTML code.
Args:
html_code: Complete HTML code to upload to CodeSandbox
Returns:
CodeSandbox URL or error message
"""
if not html_code or html_code.startswith("Error"):
return "Error: No valid HTML code provided"
try:
# 准备文件结构
files = {
"index.html": {
"content": html_code,
"isBinary": False
},
"package.json": {
"content": json.dumps({
"name": "ai-generated-website",
"version": "1.0.0",
"description": "Website generated from image analysis",
"main": "index.html",
"scripts": {
"start": "serve .",
"build": "echo 'No build required'"
},
"devDependencies": {
"serve": "^14.0.0"
}
}, indent=2),
"isBinary": False
}
}
# 准备参数
parameters = {
"files": files,
"template": "static"
}
# 压缩数据
json_str = json.dumps(parameters, separators=(',', ':'))
lz = LZString()
compressed = lz.compressToBase64(json_str)
compressed = compressed.replace('+', '-').replace('/', '_').rstrip('=')
# 生成URL
codesandbox_url = f"https://codesandbox.io/api/v1/sandboxes/define?parameters={compressed}"
# 尝试创建sandbox
import requests
response = requests.post(codesandbox_url, timeout=30)
if response.status_code == 200:
result = response.json()
sandbox_id = result.get("sandbox_id")
if sandbox_id:
return f"https://codesandbox.io/s/{sandbox_id}"
# 如果POST失败,返回GET URL
return codesandbox_url
except Exception as e:
return f"Error creating CodeSandbox: {str(e)}"
def screenshot_to_code(image: Image.Image, gemini_api_key: str = "", deepseek_api_key: str = "") -> tuple:
"""
Complete pipeline: analyze image and generate corresponding HTML code.
Args:
image: Screenshot image to analyze
gemini_api_key: Gemini API key for image analysis
deepseek_api_key: DeepSeek API key for code generation
Returns:
Tuple of (description, html_code)
"""
# 分析图片
description = analyze_image(image, gemini_api_key)
if description.startswith("Error"):
return description, "Error: Cannot generate code due to image analysis failure"
# 生成代码
html_code = generate_html_code(description, deepseek_api_key)
return description, html_code
# --- Gradio界面 ---
with gr.Blocks(
theme=gr.themes.Soft(),
title="AI Website Generator - MCP Compatible",
css="""
.api-section { background: #f8f9fa; padding: 15px; border-radius: 8px; margin: 10px 0; }
.tool-section { border: 1px solid #e0e0e0; padding: 15px; border-radius: 8px; margin: 10px 0; }
"""
) as app:
gr.Markdown("""
# 🚀 AI Website Generator (MCP Compatible)
Transform website screenshots into functional HTML code using AI.
**Features:**
- 📸 Image analysis with Gemini AI
- 💻 HTML/CSS/JS code generation with DeepSeek
- 🌐 Direct CodeSandbox deployment
- 🔧 MCP (Model Context Protocol) compatible
**Tools Available:**
- `analyze_image`: Analyze website screenshots
- `generate_html_code`: Generate HTML from descriptions
- `create_codesandbox`: Deploy to CodeSandbox
- `screenshot_to_code`: Complete pipeline
""")
with gr.Tab("🎯 Quick Generate"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 📤 Input", elem_classes=["tool-section"])
# API配置
with gr.Group():
gr.Markdown("**API Keys (Optional)**")
gemini_key = gr.Textbox(
label="Gemini API Key",
type="password",
placeholder="Leave empty to use default",
value=""
)
deepseek_key = gr.Textbox(
label="DeepSeek API Key",
type="password",
placeholder="Leave empty to use default",
value=""
)
# 图片上传
image_input = gr.Image(
type="pil",
label="Upload Website Screenshot",
sources=["upload", "clipboard"]
)
generate_btn = gr.Button(
"🎨 Generate Website",
variant="primary",
size="lg"
)
with gr.Column(scale=2):
gr.Markdown("### 📋 Results", elem_classes=["tool-section"])
description_output = gr.Textbox(
label="📝 Image Analysis",
lines=6,
interactive=False
)
html_output = gr.Code(
label="💻 Generated HTML Code",
language="html",
lines=15
)
with gr.Row():
codesandbox_btn = gr.Button("🚀 Deploy to CodeSandbox")
codesandbox_output = gr.Textbox(
label="CodeSandbox URL",
interactive=False
)
with gr.Tab("🔧 Individual Tools"):
gr.Markdown("### Use individual MCP tools")
with gr.Row():
with gr.Column():
gr.Markdown("#### 📸 Image Analysis Tool")
img_tool = gr.Image(type="pil", label="Image")
gemini_key_tool = gr.Textbox(label="Gemini API Key", type="password")
analyze_btn = gr.Button("Analyze Image")
analysis_result = gr.Textbox(label="Analysis Result", lines=5)
with gr.Column():
gr.Markdown("#### 💻 Code Generation Tool")
desc_input = gr.Textbox(label="Description", lines=3)
deepseek_key_tool = gr.Textbox(label="DeepSeek API Key", type="password")
code_btn = gr.Button("Generate Code")
code_result = gr.Code(label="Generated Code", language="html")
# 事件绑定
generate_btn.click(
fn=screenshot_to_code,
inputs=[image_input, gemini_key, deepseek_key],
outputs=[description_output, html_output]
)
codesandbox_btn.click(
fn=create_codesandbox,
inputs=[html_output],
outputs=[codesandbox_output]
)
analyze_btn.click(
fn=analyze_image,
inputs=[img_tool, gemini_key_tool],
outputs=[analysis_result]
)
code_btn.click(
fn=generate_html_code,
inputs=[desc_input, deepseek_key_tool],
outputs=[code_result]
)
# 示例
gr.Examples(
examples=[["1.jpg"]],
inputs=[image_input],
label="📷 Example Screenshots"
)
if __name__ == "__main__":
app.launch(mcp_server=True, share=False)