ai_service/scripts/generate_layout.py

369 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
@file generate_layout.py
@brief Vue组件代码生成模块
基于DeepSeek API生成响应式Vue 3组件布局代码
@author 王秀强 (2310460@mail.nankai.edu.cn)
@date 2025.5.19
@version v1.0.0
@details
本文件主要实现:
- DeepSeek API调用封装和错误处理
- Vue 3 Composition API组件代码生成
- 海报布局的动态排版和样式生成
- 代码清理和格式化处理
- 备用Vue模板生成机制
@note
- 需要配置DEEPSEEK_API_KEY环境变量
- 支持流式和非流式响应模式
- 生成的Vue代码包含完整的template、script和style部分
- 具备指数退避重试机制处理API限流
@usage
# 生成Vue组件代码
vue_code = generate_vue_code("生成端午节海报Vue组件")
save_code(vue_code, "../outputs/poster.vue")
# 调用DeepSeek API
result, usage = call_deepseek(prompt="请生成代码", temperature=0.6)
@copyright
(c) 2025 砚生项目组
"""
import os
from openai import OpenAI
from dotenv import load_dotenv
import time
from colorama import init, Fore, Back, Style
# 初始化colorama
init(autoreset=True)
# === Config LLM call ===
load_dotenv()
deepseek_url = 'https://api.deepseek.com/v1' # set to be compatible with the OpenAI API
deepseek_api = os.getenv("DEEPSEEK_API_KEY")
if not deepseek_api:
raise ValueError("DEEPSEEK_API_KEY not set!")
def call_deepseek(
messages=None,
system_prompt="你是一个擅长前端开发的AI专注于生成Vue.js代码。",
prompt=None,
model='deepseek-chat',
temperature=0.6,
max_tokens=2000, # 增加token数量
stream=False,
max_retries=3,
):
"""
调用 DeepSeek API,支持多轮对话和流式/非流式响应
"""
# 初始化 OpenAI 客户端
client = OpenAI(
api_key=deepseek_api,
base_url=deepseek_url,
)
# 参数验证
if messages is None:
if not prompt:
raise ValueError("必须提供 message 或 prompt 参数")
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
elif not isinstance(messages, list) or not messages:
raise ValueError("message 参数必须是非空列表")
# 模型验证
models = ["deepseek-chat", "deepseek-reasoner"]
if model not in models:
raise ValueError(f"无效的模型名称: {model},可用模型: {models}")
# 调用 API
for attempt in range(max_retries):
try:
print(f"{Fore.BLUE}📡 正在调用DeepSeek API (尝试 {attempt + 1}/{max_retries})...{Style.RESET_ALL}")
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
stream=stream
)
# 流式响应
if stream:
def stream_generator():
usage = None
for chunk in response:
if chunk.choices[0].delta.content is not None:
yield chunk.choices[0].delta.content
return usage
return stream_generator(), None
else:
# 非流式响应
content = response.choices[0].message.content
print(f"{Fore.GREEN}✅ API调用成功返回内容长度: {len(content)}{Style.RESET_ALL}")
return content, response.usage
except Exception as e:
print(f"{Fore.RED}❌ API调用失败 (尝试 {attempt + 1}/{max_retries}): {str(e)}{Style.RESET_ALL}")
if hasattr(e, 'status_code') and e.status_code == 429: # 限流
print(f"{Fore.YELLOW}⏳ 请求过于频繁,等待重试...{Style.RESET_ALL}")
time.sleep(2 ** attempt) # 指数退避
elif attempt == max_retries - 1:
raise
else:
time.sleep(1)
raise Exception("达到最大重试次数API 调用失败")
def generate_vue_code(prompt=None):
"""
生成Vue组件代码
"""
if not prompt:
prompt = (
"生成一个Vue组件代码用于端午节活动海报包含以下部分并指定排版位置"
"1. 背景图层div占据整个组件区域。"
"2. 主体图层div位于顶部1/4处居中包含标题和副标题。"
"3. 活动亮点div位于底部1/4处居中使用网格布局展示三项活动每项包含图标、标题和描述"
"4. 页脚div位于底部居中包含主办单位信息和logo图片。"
"组件尺寸为1080x1920px布局使用absolute定位生成完整可用的Vue 3代码。"
)
system_prompt = (
"你是一个擅长前端开发的AI专注于生成Vue.js代码。"
"请生成完整的Vue 3组件包含template、script setup和style部分。"
"确保代码结构清晰,语法正确,可以直接使用。"
"不要包含任何解释文字,只返回纯代码。"
)
try:
print(f"{Fore.CYAN}🎨 正在生成Vue组件代码...{Style.RESET_ALL}")
result, usage = call_deepseek(prompt=prompt, system_prompt=system_prompt, temperature=0.4)
# 清理代码移除可能的markdown标记
if result:
# 移除markdown代码块标记
if "```vue" in result:
result = result.split("```vue")[1].split("```")[0].strip()
elif "```html" in result:
result = result.split("```html")[1].split("```")[0].strip()
elif result.startswith("```") and result.endswith("```"):
result = result[3:-3].strip()
print(f"{Fore.GREEN}✅ Vue代码生成成功长度: {len(result)} 字符{Style.RESET_ALL}")
return result
else:
print(f"{Fore.RED}❌ Vue代码生成失败返回空内容{Style.RESET_ALL}")
return generate_fallback_vue_code()
except Exception as e:
print(f"{Fore.RED}❌ Vue代码生成异常: {str(e)}{Style.RESET_ALL}")
return generate_fallback_vue_code()
def generate_fallback_vue_code():
"""
生成备用的Vue代码
"""
print(f"{Fore.YELLOW}⚠️ 使用备用Vue模板{Style.RESET_ALL}")
return """<template>
<div class="poster-container">
<div class="background-layer">
<img src="../outputs/background.png" alt="背景" class="background-image" />
</div>
<div class="content-layer">
<div class="title-section">
<h1 class="main-title">端午节活动</h1>
<h2 class="subtitle">传统文化,现代体验</h2>
</div>
<div class="main-content">
<div class="highlight-grid">
<div class="highlight-item">
<img src="../outputs/activity1.png" alt="活动1" class="activity-image" />
<h3>包粽子体验</h3>
<p>亲手制作传统粽子</p>
</div>
<div class="highlight-item">
<img src="../outputs/activity2.png" alt="活动2" class="activity-image" />
<h3>龙舟竞赛</h3>
<p>激情龙舟比赛</p>
</div>
<div class="highlight-item">
<img src="../outputs/activity3.png" alt="活动3" class="activity-image" />
<h3>文化表演</h3>
<p>精彩传统表演</p>
</div>
</div>
</div>
<div class="footer-section">
<div class="logo-area">
<span class="logo-text">主办单位</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 组件数据
const posterData = ref({
title: '端午节活动',
subtitle: '传统文化,现代体验',
highlights: [
{ title: '包粽子体验', description: '亲手制作传统粽子' },
{ title: '龙舟竞赛', description: '激情龙舟比赛' },
{ title: '文化表演', description: '精彩传统表演' }
]
})
</script>
<style scoped>
.poster-container {
width: 1080px;
height: 1920px;
position: relative;
overflow: hidden;
background: #ffffff;
}
.background-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.background-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.content-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
}
.title-section {
position: absolute;
top: 25%;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
.main-title {
font-size: 60px;
font-weight: bold;
margin-bottom: 20px;
color: #d32f2f;
}
.subtitle {
font-size: 28px;
color: #424242;
margin-bottom: 40px;
}
.main-content {
position: absolute;
top: 45%;
left: 50%;
transform: translateX(-50%);
width: 80%;
}
.highlight-grid {
display: grid;
grid-template-columns: 1fr;
gap: 30px;
}
.highlight-item {
text-align: center;
padding: 20px;
}
.activity-image {
width: 200px;
height: 150px;
object-fit: cover;
border-radius: 8px;
margin-bottom: 15px;
}
.highlight-item h3 {
font-size: 24px;
margin-bottom: 10px;
color: #333;
}
.highlight-item p {
font-size: 18px;
color: #666;
}
.footer-section {
position: absolute;
bottom: 10%;
left: 50%;
transform: translateX(-50%);
}
.logo-text {
font-size: 20px;
color: #666;
font-weight: 500;
}
</style>"""
def save_code(code, file_path="../outputs/generated_code.vue"):
"""
保存代码到文件
"""
try:
# 确保目录存在
os.makedirs(os.path.dirname(file_path), exist_ok=True)
# 写入文件
with open(file_path, "w", encoding="utf-8") as f:
f.write(code)
print(f"{Fore.GREEN}✅ Vue代码已保存到: {file_path}{Style.RESET_ALL}")
# 验证文件是否成功创建
if os.path.exists(file_path):
file_size = os.path.getsize(file_path)
print(f"{Fore.CYAN}📁 文件大小: {file_size} 字节{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 文件保存失败{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}❌ 保存代码时出错: {str(e)}{Style.RESET_ALL}")
if __name__ == "__main__":
print(f"{Fore.MAGENTA}🚀 开始生成Vue组件...{Style.RESET_ALL}")
vue_code = generate_vue_code()
save_code(vue_code)
print(f"{Fore.GREEN}✅ Vue组件代码生成完成{Style.RESET_ALL}")