重构run_pipeline和generate_layout
将辅助函数移动整合到utils.py当中
This commit is contained in:
parent
6da2bda08c
commit
20802db28a
597
API_文档.md
597
API_文档.md
@ -1,597 +0,0 @@
|
||||
# AI海报生成系统 API 文档
|
||||
|
||||
## 🎯 API概览
|
||||
|
||||
AI海报生成系统提供统一的REST API接口,一键生成Vue组件代码和PSD文件。
|
||||
|
||||
**基础URL**: `http://localhost:8000`
|
||||
|
||||
**主要特性**:
|
||||
- 🚀 一键生成Vue代码和PSD文件
|
||||
- 🎨 集成多个AI模型(DeepSeek + Kimi + ComfyUI)
|
||||
- 📁 会话管理和文件下载
|
||||
- 🔄 自动图片生成和合成
|
||||
|
||||
## 📋 API端点总览
|
||||
|
||||
| 端点 | 方法 | 描述 | 状态 |
|
||||
|------|------|------|------|
|
||||
| `/` | GET | 获取API信息 | ✅ |
|
||||
| `/health` | GET | 健康检查 | ✅ |
|
||||
| `/api/generate-poster` | POST | **主要接口** - 生成海报 | ✅ |
|
||||
| `/api/download/{file_type}` | GET | 下载文件 | ✅ |
|
||||
| `/api/status/{session_id}` | GET | 获取会话状态 | ✅ |
|
||||
|
||||
## 🔧 主要接口详情
|
||||
|
||||
### 1. 生成海报(核心接口)
|
||||
|
||||
**POST** `/api/generate-poster`
|
||||
|
||||
这是唯一需要的主要接口,一次调用完成所有生成任务。
|
||||
|
||||
**请求示例**:
|
||||
```json
|
||||
{
|
||||
"user_input": "端午节海报,传统风格,包含荷花和龙舟",
|
||||
"session_id": "可选 - 用于跟踪会话"
|
||||
}
|
||||
```
|
||||
|
||||
**完整响应示例**:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "海报生成完成",
|
||||
"data": {
|
||||
"vue_code": "完整的Vue 3组件代码",
|
||||
"suggestions": {
|
||||
"layer5_logo_content": {
|
||||
"text": "主办方",
|
||||
"color": "#000000"
|
||||
},
|
||||
"layer6_title_content": {
|
||||
"content": "端午节安康",
|
||||
"font_name": "SimHei",
|
||||
"color": "#7E0C6E"
|
||||
},
|
||||
"layer7_subtitle_content": {
|
||||
"content": "粽叶飘香,龙舟竞渡,共庆端午佳节",
|
||||
"font_name": "Microsoft YaHei",
|
||||
"color": "#000000"
|
||||
}
|
||||
},
|
||||
"analysis_result": {
|
||||
"analyzed_prompt": "端午节海报,传统风格",
|
||||
"main_theme": "端午节祝福",
|
||||
"style_preference": "传统",
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"keywords": ["端午节", "传统", "荷花", "龙舟"]
|
||||
},
|
||||
"psd_file_path": "/path/to/session_xxx/final_poster.psd",
|
||||
"file_size_mb": 5.93,
|
||||
"generated_images": 2,
|
||||
"files": {
|
||||
"vue_file": "/path/to/generated_code.vue",
|
||||
"psd_file": "/path/to/final_poster.psd"
|
||||
}
|
||||
},
|
||||
"session_id": "uuid-generated-session-id"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 文件下载
|
||||
|
||||
**GET** `/api/download/{file_type}?session_id={session_id}`
|
||||
|
||||
**参数**:
|
||||
- `file_type`: 文件类型
|
||||
- `vue` - Vue组件文件
|
||||
- `psd` - PSD文件
|
||||
- `json` - 文案建议JSON文件
|
||||
- `session_id`: 会话ID(必需)
|
||||
|
||||
**响应**: 直接返回文件流,浏览器会自动下载
|
||||
|
||||
### 3. 健康检查
|
||||
|
||||
**GET** `/health`
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2025-01-02T20:30:00.123456"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 会话状态查询
|
||||
|
||||
**GET** `/api/status/{session_id}`
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "状态获取成功",
|
||||
"data": {
|
||||
"user_input": "端午节海报,传统风格",
|
||||
"analysis_result": "...",
|
||||
"suggestions": "...",
|
||||
"vue_path": "/path/to/vue/file",
|
||||
"psd_path": "/path/to/psd/file",
|
||||
"created_at": "2025-01-02T20:30:00"
|
||||
},
|
||||
"session_id": "session-id"
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ 前端集成指南
|
||||
|
||||
### JavaScript ES6+ 示例
|
||||
|
||||
```javascript
|
||||
class PosterGenerator {
|
||||
constructor(baseUrl = 'http://localhost:8000') {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
// 主要方法:生成海报
|
||||
async generatePoster(userInput) {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/api/generate-poster`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_input: userInput
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
console.log('✅ 海报生成成功');
|
||||
console.log('Vue代码长度:', result.data.vue_code.length);
|
||||
console.log('PSD文件大小:', result.data.file_size_mb, 'MB');
|
||||
console.log('生成的图片数量:', result.data.generated_images);
|
||||
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(result.message || '生成失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 海报生成失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
downloadFile(sessionId, fileType) {
|
||||
const url = `${this.baseUrl}/api/download/${fileType}?session_id=${sessionId}`;
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = '';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
// 获取会话状态
|
||||
async getSessionStatus(sessionId) {
|
||||
const response = await fetch(`${this.baseUrl}/api/status/${sessionId}`);
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const generator = new PosterGenerator();
|
||||
|
||||
async function createPoster() {
|
||||
try {
|
||||
// 生成海报
|
||||
const result = await generator.generatePoster("春节海报,红色背景,现代风格");
|
||||
|
||||
// 显示Vue代码
|
||||
document.getElementById('vue-code').textContent = result.data.vue_code;
|
||||
|
||||
// 显示文案建议
|
||||
document.getElementById('suggestions').textContent =
|
||||
JSON.stringify(result.data.suggestions, null, 2);
|
||||
|
||||
// 设置下载按钮
|
||||
document.getElementById('download-vue').onclick = () =>
|
||||
generator.downloadFile(result.session_id, 'vue');
|
||||
document.getElementById('download-psd').onclick = () =>
|
||||
generator.downloadFile(result.session_id, 'psd');
|
||||
|
||||
} catch (error) {
|
||||
alert('生成失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Vue.js 组件示例
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="poster-generator">
|
||||
<div class="input-section">
|
||||
<h2>AI海报生成器</h2>
|
||||
<div class="form-group">
|
||||
<label>海报需求描述:</label>
|
||||
<textarea
|
||||
v-model="userInput"
|
||||
placeholder="请描述您的海报需求,例如:端午节海报,传统风格,包含荷花和龙舟"
|
||||
rows="4"
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
@click="generatePoster"
|
||||
:disabled="loading || !userInput.trim()"
|
||||
:class="{ loading: loading }"
|
||||
>
|
||||
{{ loading ? '生成中...' : '生成海报' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="result" class="result-section">
|
||||
<div class="tabs">
|
||||
<button
|
||||
@click="activeTab = 'vue'"
|
||||
:class="{ active: activeTab === 'vue' }"
|
||||
>
|
||||
Vue代码
|
||||
</button>
|
||||
<button
|
||||
@click="activeTab = 'suggestions'"
|
||||
:class="{ active: activeTab === 'suggestions' }"
|
||||
>
|
||||
文案建议
|
||||
</button>
|
||||
<button
|
||||
@click="activeTab = 'info'"
|
||||
:class="{ active: activeTab === 'info' }"
|
||||
>
|
||||
生成信息
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
<div v-if="activeTab === 'vue'" class="vue-code">
|
||||
<h3>Vue组件代码</h3>
|
||||
<pre><code>{{ result.data.vue_code }}</code></pre>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'suggestions'" class="suggestions">
|
||||
<h3>文案建议</h3>
|
||||
<div class="suggestion-item" v-for="(item, key) in result.data.suggestions" :key="key">
|
||||
<h4>{{ getSuggestionTitle(key) }}</h4>
|
||||
<p><strong>内容:</strong> {{ item.content || item.text }}</p>
|
||||
<p><strong>字体:</strong> {{ item.font_name || '未指定' }}</p>
|
||||
<p><strong>颜色:</strong> <span :style="{ color: item.color }">{{ item.color }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'info'" class="info">
|
||||
<h3>生成信息</h3>
|
||||
<p><strong>主题:</strong> {{ result.data.analysis_result.main_theme }}</p>
|
||||
<p><strong>风格:</strong> {{ result.data.analysis_result.style_preference }}</p>
|
||||
<p><strong>PSD文件大小:</strong> {{ result.data.file_size_mb }} MB</p>
|
||||
<p><strong>生成图片数量:</strong> {{ result.data.generated_images }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="download-section">
|
||||
<h3>下载文件</h3>
|
||||
<button @click="downloadFile('vue')" class="download-btn">
|
||||
📄 下载Vue文件
|
||||
</button>
|
||||
<button @click="downloadFile('psd')" class="download-btn">
|
||||
🎨 下载PSD文件
|
||||
</button>
|
||||
<button @click="downloadFile('json')" class="download-btn">
|
||||
📋 下载文案JSON
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error">
|
||||
<h3>错误信息</h3>
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const userInput = ref('')
|
||||
const loading = ref(false)
|
||||
const result = ref(null)
|
||||
const error = ref('')
|
||||
const activeTab = ref('vue')
|
||||
|
||||
const generatePoster = async () => {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:8000/api/generate-poster', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_input: userInput.value
|
||||
})
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (data.status === 'success') {
|
||||
result.value = data
|
||||
} else {
|
||||
error.value = data.message || '生成失败'
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = '网络错误: ' + err.message
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const downloadFile = (fileType) => {
|
||||
if (!result.value) return
|
||||
|
||||
const url = `http://localhost:8000/api/download/${fileType}?session_id=${result.value.session_id}`
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = ''
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
|
||||
const getSuggestionTitle = (key) => {
|
||||
const titles = {
|
||||
layer5_logo_content: 'Logo文字',
|
||||
layer6_title_content: '主标题',
|
||||
layer7_subtitle_content: '副标题'
|
||||
}
|
||||
return titles[key] || key
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.poster-generator {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.input-section {
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
button.loading {
|
||||
background: #ffc107;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.tabs button {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.tabs button.active {
|
||||
background: white;
|
||||
color: #007bff;
|
||||
border-bottom: 2px solid #007bff;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 20px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.vue-code pre {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.download-section {
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## ⚡ 快速开始
|
||||
|
||||
### 1. 启动服务器
|
||||
```bash
|
||||
cd E:\砚生\ai_service\scripts
|
||||
python run_pipeline.py
|
||||
# 选择: 2 (API服务器模式)
|
||||
```
|
||||
|
||||
### 2. 测试API
|
||||
```bash
|
||||
# 健康检查
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# 生成海报
|
||||
curl -X POST http://localhost:8000/api/generate-poster \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"user_input": "春节海报,红色背景,现代风格"}'
|
||||
```
|
||||
|
||||
### 3. 前端调用
|
||||
```javascript
|
||||
// 最简单的调用方式
|
||||
fetch('http://localhost:8000/api/generate-poster', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ user_input: '端午节海报,传统风格' })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
console.log('Vue代码:', data.data.vue_code);
|
||||
// 下载PSD文件
|
||||
window.open(`http://localhost:8000/api/download/psd?session_id=${data.session_id}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 🔧 错误处理
|
||||
|
||||
### 错误响应格式
|
||||
```json
|
||||
{
|
||||
"detail": "具体错误信息"
|
||||
}
|
||||
```
|
||||
|
||||
### 常见错误
|
||||
- **400 Bad Request**: 请求参数错误
|
||||
- **404 Not Found**: 会话不存在或文件不存在
|
||||
- **500 Internal Server Error**: 服务器内部错误
|
||||
|
||||
### 错误处理示例
|
||||
```javascript
|
||||
try {
|
||||
const response = await fetch('/api/generate-poster', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ user_input: 'test' })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || '请求失败');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
// 处理成功结果
|
||||
} catch (error) {
|
||||
console.error('API调用失败:', error.message);
|
||||
// 显示错误给用户
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 部署配置
|
||||
|
||||
### 开发环境
|
||||
```bash
|
||||
# 启动开发服务器
|
||||
python run_pipeline.py
|
||||
# 访问: http://localhost:8000
|
||||
```
|
||||
|
||||
### 生产环境
|
||||
```bash
|
||||
# 使用uvicorn直接运行
|
||||
uvicorn run_pipeline:app --host 0.0.0.0 --port 8000
|
||||
|
||||
# 或使用PM2管理
|
||||
pm2 start "uvicorn run_pipeline:app --host 0.0.0.0 --port 8000" --name poster-api
|
||||
```
|
||||
|
||||
### Docker部署
|
||||
```dockerfile
|
||||
FROM python:3.11
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN pip install -r requirements.txt
|
||||
EXPOSE 8000
|
||||
CMD ["uvicorn", "scripts.run_pipeline:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
```
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### v1.0.0 (2025-01-02)
|
||||
- ✅ 统一API接口设计
|
||||
- ✅ 集成DeepSeek + Kimi + ComfyUI
|
||||
- ✅ 支持Vue组件和PSD文件生成
|
||||
- ✅ 会话管理和文件下载
|
||||
- ✅ 完整的错误处理和文档
|
@ -1 +0,0 @@
|
||||
|
407
README.md
407
README.md
@ -1,38 +1,27 @@
|
||||
# AI海报生成系统
|
||||
|
||||
## 🎯 项目概述
|
||||
## 概述
|
||||
|
||||
AI海报生成系统是一个集成多个AI模型的智能海报生成平台,能够根据用户输入一键生成Vue组件代码和PSD文件。
|
||||
AI海报生成系统集成DeepSeek、Kimi、ComfyUI三大AI模型,一键生成Vue组件代码和PSD文件。
|
||||
|
||||
**核心特性**:
|
||||
- 🤖 **AI驱动**: 集成DeepSeek、Kimi、ComfyUI三大AI模型
|
||||
- 🎨 **一键生成**: 输入需求,自动生成Vue代码和PSD文件
|
||||
- 📱 **前端友好**: 提供统一的REST API接口
|
||||
- 🎭 **智能排版**: 自动生成响应式Vue组件布局
|
||||
- 🖼️ **图片合成**: 自动生成图片并合成PSD文件
|
||||
- ✍️ **文案优化**: AI生成适配的标题和文案内容
|
||||
**核心功能**:
|
||||
- 🤖 **AI驱动**: DeepSeek分析 + Kimi文案 + ComfyUI图片
|
||||
- 🎨 **一键生成**: 输入需求 → Vue代码 + PSD文件
|
||||
- 📱 **REST API**: 统一接口,支持前端集成
|
||||
- 🎭 **预定义模板**: lotus.jpg, nku.png, stamp.jpg, background.png
|
||||
- 🖼️ **自动合成**: 多图层PSD文件生成
|
||||
|
||||
## 🏗️ 系统架构
|
||||
## 系统架构
|
||||
|
||||
```
|
||||
┌─────────────┐ HTTP请求 ┌──────────────┐ 调用AI服务 ┌─────────────┐
|
||||
│ 前端 │ ─────────────→ │ FastAPI服务器 │ ─────────────→ │ AI模型集群 │
|
||||
│ (Vue/React) │ │ (8000端口) │ │ │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌─────────────┐
|
||||
│ 会话管理 │ │ ComfyUI图片 │
|
||||
│ 文件存储 │ │ 生成服务 │
|
||||
└──────────────┘ │ (101.201.50.90) │
|
||||
└─────────────┘
|
||||
前端 → FastAPI(8000) → AI模型集群
|
||||
↓
|
||||
Vue代码 + PSD文件
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
## 快速开始
|
||||
|
||||
### 环境准备
|
||||
|
||||
1. **Python环境**:
|
||||
```bash
|
||||
# 创建虚拟环境
|
||||
conda create -n ai_service python=3.11
|
||||
@ -40,235 +29,105 @@ conda activate ai_service
|
||||
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **环境配置**:
|
||||
```bash
|
||||
# 复制环境变量文件
|
||||
# 配置API密钥
|
||||
cp .env.example .env
|
||||
|
||||
# 配置API密钥(编辑.env文件)
|
||||
DEEPSEEK_API_KEY=your_deepseek_api_key
|
||||
MOONSHOT_API_KEY=your_kimi_api_key
|
||||
# 编辑 .env 文件,添加:
|
||||
# DEEPSEEK_API_KEY=your_key
|
||||
# MOONSHOT_API_KEY=your_key
|
||||
```
|
||||
|
||||
### 启动服务
|
||||
|
||||
#### 方式1: 交互式启动
|
||||
```bash
|
||||
cd scripts
|
||||
python run_pipeline.py
|
||||
# 选择: 2 (API服务器模式)
|
||||
```
|
||||
|
||||
#### 方式2: 直接启动API服务器
|
||||
```bash
|
||||
cd scripts
|
||||
uvicorn run_pipeline:app --host 0.0.0.0 --port 8000 --reload
|
||||
python run_pipeline.py # 选择: 2 (API服务器)
|
||||
```
|
||||
|
||||
### 验证服务
|
||||
|
||||
```bash
|
||||
# 健康检查
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# 查看API信息
|
||||
curl http://localhost:8000/
|
||||
```
|
||||
|
||||
## 📋 主要功能
|
||||
|
||||
### 1. 智能需求分析
|
||||
- 使用**DeepSeek**模型分析用户输入
|
||||
- 提取主题、风格、尺寸等关键信息
|
||||
- 生成结构化的设计参数
|
||||
|
||||
### 2. AI图片生成
|
||||
- 调用**ComfyUI**服务(101.201.50.90:8188)
|
||||
- 根据分析结果生成高质量图片
|
||||
- 支持多种风格和主题
|
||||
|
||||
### 3. 智能文案生成
|
||||
- 使用**Kimi**模型生成文案内容
|
||||
- 智能选择字体和颜色搭配
|
||||
- 支持多层级文案结构
|
||||
|
||||
### 4. Vue组件生成
|
||||
- 使用**DeepSeek**生成Vue 3组件代码
|
||||
- 响应式布局设计
|
||||
- 完整的template、script、style结构
|
||||
|
||||
### 5. PSD文件合成
|
||||
- 自动合成多图层PSD文件
|
||||
- 支持手动PSD模板优先
|
||||
- 包含预览图片生成
|
||||
|
||||
## 🛠️ 使用方法
|
||||
|
||||
### API调用
|
||||
|
||||
```javascript
|
||||
// 一键生成海报
|
||||
const response = await fetch('http://localhost:8000/api/generate-poster', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
user_input: "端午节海报,传统风格,包含荷花和龙舟"
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
// 获取Vue代码
|
||||
console.log('Vue代码:', result.data.vue_code);
|
||||
|
||||
// 获取文案建议
|
||||
console.log('文案建议:', result.data.suggestions);
|
||||
|
||||
// 下载PSD文件
|
||||
const downloadUrl = `http://localhost:8000/api/download/psd?session_id=${result.session_id}`;
|
||||
window.open(downloadUrl);
|
||||
}
|
||||
```
|
||||
|
||||
### 本地开发测试
|
||||
## API使用
|
||||
|
||||
### 生成海报
|
||||
```bash
|
||||
cd scripts
|
||||
python run_pipeline.py
|
||||
# 选择: 1 (本地测试模式)
|
||||
# 输入: "春节海报,红色背景,现代风格"
|
||||
curl -X POST http://localhost:8000/api/generate-poster \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"user_input": "端午节海报,传统风格"}'
|
||||
```
|
||||
|
||||
## 📁 项目结构
|
||||
### 下载文件
|
||||
```bash
|
||||
curl "http://localhost:8000/api/download/psd?session_id=SESSION_ID" -o poster.psd
|
||||
curl "http://localhost:8000/api/download/vue?session_id=SESSION_ID" -o poster.vue
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
ai_service/
|
||||
├── scripts/ # 核心脚本
|
||||
│ ├── run_pipeline.py # 主服务入口和API服务器
|
||||
│ ├── prompt_analysis.py # 用户输入分析 (DeepSeek)
|
||||
│ ├── flux_con.py # 图片生成 (ComfyUI)
|
||||
│ ├── generate_text.py # 文案生成 (Kimi)
|
||||
│ ├── generate_layout.py # Vue代码生成 (DeepSeek)
|
||||
│ └── export_psd_from_json.py # PSD文件合成
|
||||
├── configs/ # 配置文件
|
||||
│ ├── font.yaml # 字体配置
|
||||
│ └── example.json # 配置示例
|
||||
├── outputs/ # 输出目录
|
||||
│ ├── session_*/ # 会话相关文件
|
||||
│ ├── *.psd # 生成的PSD文件
|
||||
│ ├── *.vue # 生成的Vue组件
|
||||
│ └── *.png # 生成的图片
|
||||
├── workflows/ # ComfyUI工作流
|
||||
│ ├── flux_work.json # 主要工作流配置
|
||||
│ └── temp/ # 临时工作流文件
|
||||
├── README.md # 项目说明
|
||||
├── requirements.txt # Python依赖
|
||||
├── API_文档.md # API使用文档
|
||||
├── 接口文档.md # 接口技术文档
|
||||
└── 部署方案.md # 部署指南
|
||||
├── scripts/ # 核心脚本
|
||||
│ ├── run_pipeline.py # API服务器
|
||||
│ ├── utils.py # 工具函数
|
||||
│ ├── generate_layout.py # Vue代码生成
|
||||
│ ├── generate_text.py # 文案生成
|
||||
│ ├── flux_con.py # 图片生成
|
||||
│ └── export_psd_from_json.py # PSD合成
|
||||
├── configs/ # 配置文件
|
||||
│ ├── vue_templates.yaml # Vue模板
|
||||
│ └── font.yaml # 字体配置
|
||||
├── outputs/ # 输出目录
|
||||
└── workflows/ # ComfyUI工作流
|
||||
```
|
||||
|
||||
## 🔧 核心模块
|
||||
## 核心模块
|
||||
|
||||
### 1. prompt_analysis.py
|
||||
- **功能**: 使用DeepSeek分析用户输入
|
||||
- **输入**: 用户描述文本
|
||||
- **输出**: 结构化分析结果
|
||||
| 模块 | 功能 | AI模型 |
|
||||
|------|------|--------|
|
||||
| `utils.py` | 用户输入分析 | DeepSeek |
|
||||
| `flux_con.py` | 图片生成 | ComfyUI |
|
||||
| `generate_text.py` | 文案生成 | Kimi |
|
||||
| `generate_layout.py` | Vue代码生成 | DeepSeek |
|
||||
| `export_psd_from_json.py` | PSD合成 | - |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### JavaScript调用
|
||||
```javascript
|
||||
const response = await fetch('http://localhost:8000/api/generate-poster', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ user_input: "春节海报,红色背景" })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log('Vue代码:', result.data.vue_code);
|
||||
```
|
||||
|
||||
### Python调用
|
||||
```python
|
||||
user_input_analysis_result = llm_user_analysis("端午节海报,传统风格")
|
||||
import requests
|
||||
|
||||
response = requests.post('http://localhost:8000/api/generate-poster',
|
||||
json={"user_input": "端午节海报,传统风格"})
|
||||
result = response.json()
|
||||
|
||||
if result["status"] == "success":
|
||||
print("生成成功!")
|
||||
print("Vue代码:", result["data"]["vue_code"])
|
||||
```
|
||||
|
||||
### 2. flux_con.py
|
||||
- **功能**: 调用ComfyUI生成图片
|
||||
- **输入**: 分析结果和系统提示
|
||||
- **输出**: 图片文件列表
|
||||
```python
|
||||
parse_imglist = comfyui_img_info(user_input_analysis_result, system_prompt)
|
||||
```
|
||||
|
||||
### 3. generate_text.py
|
||||
- **功能**: 使用Kimi生成文案建议
|
||||
- **输入**: 分析后的提示词
|
||||
- **输出**: 分层文案内容
|
||||
```python
|
||||
suggestions = get_poster_content_suggestions(analyzed_prompt)
|
||||
```
|
||||
|
||||
### 4. generate_layout.py
|
||||
- **功能**: 使用DeepSeek生成Vue组件
|
||||
- **输入**: 布局提示词
|
||||
- **输出**: 完整Vue组件代码
|
||||
```python
|
||||
vue_code = generate_vue_code(prompt)
|
||||
```
|
||||
|
||||
### 5. export_psd_from_json.py
|
||||
- **功能**: 合成PSD文件
|
||||
- **输入**: 图片路径列表
|
||||
- **输出**: PSD文件
|
||||
```python
|
||||
create_psd_from_images(image_paths, output_path, canvas_size)
|
||||
```
|
||||
|
||||
## 🌐 API接口
|
||||
|
||||
### 主要接口
|
||||
|
||||
| 接口 | 方法 | 功能 | 状态 |
|
||||
|------|------|------|------|
|
||||
| `/api/generate-poster` | POST | 一键生成海报 | ✅ |
|
||||
| `/api/download/{type}` | GET | 文件下载 | ✅ |
|
||||
| `/api/status/{session_id}` | GET | 会话状态 | ✅ |
|
||||
| `/health` | GET | 健康检查 | ✅ |
|
||||
|
||||
### 请求示例
|
||||
|
||||
### 本地测试
|
||||
```bash
|
||||
# 生成海报
|
||||
curl -X POST http://localhost:8000/api/generate-poster \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"user_input": "春节海报,红色背景,现代风格"}'
|
||||
|
||||
# 下载PSD文件
|
||||
curl "http://localhost:8000/api/download/psd?session_id=SESSION_ID" \
|
||||
--output poster.psd
|
||||
cd scripts
|
||||
python run_pipeline.py # 选择: 1 (本地测试)
|
||||
# 输入: "春节海报,红色背景,现代风格"
|
||||
```
|
||||
|
||||
## 🎨 使用示例
|
||||
## 部署
|
||||
|
||||
### 1. 传统节日海报
|
||||
```
|
||||
输入: "端午节海报,传统风格,包含荷花和龙舟"
|
||||
输出: 传统中式风格的Vue组件 + 精美PSD文件
|
||||
```
|
||||
|
||||
### 2. 现代活动海报
|
||||
```
|
||||
输入: "科技大会海报,现代简约风格,蓝色主题"
|
||||
输出: 现代简约风格的Vue组件 + 科技感PSD文件
|
||||
```
|
||||
|
||||
### 3. 节庆祝福海报
|
||||
```
|
||||
输入: "春节祝福海报,红色背景,包含灯笼烟花"
|
||||
输出: 喜庆风格的Vue组件 + 节日氛围PSD文件
|
||||
```
|
||||
|
||||
## 🚀 部署方案
|
||||
|
||||
### 开发环境
|
||||
```bash
|
||||
# 本地启动
|
||||
python scripts/run_pipeline.py
|
||||
# 访问: http://localhost:8000
|
||||
```
|
||||
|
||||
### 生产环境
|
||||
|
||||
#### Docker部署
|
||||
### Docker部署
|
||||
```dockerfile
|
||||
FROM python:3.11
|
||||
WORKDIR /app
|
||||
@ -278,124 +137,44 @@ EXPOSE 8000
|
||||
CMD ["uvicorn", "scripts.run_pipeline:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
```
|
||||
|
||||
### PM2部署
|
||||
```bash
|
||||
# 构建和运行
|
||||
docker build -t ai-poster-generator .
|
||||
docker run -p 8000:8000 ai-poster-generator
|
||||
```
|
||||
|
||||
#### PM2部署
|
||||
```bash
|
||||
# 安装PM2
|
||||
npm install -g pm2
|
||||
|
||||
# 启动服务
|
||||
pm2 start "uvicorn scripts.run_pipeline:app --host 0.0.0.0 --port 8000" --name poster-api
|
||||
|
||||
# 开机自启
|
||||
pm2 startup
|
||||
pm2 save
|
||||
```
|
||||
|
||||
### 反向代理(Nginx)
|
||||
### Nginx反向代理
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 故障排查
|
||||
## 故障排查
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **API密钥错误**
|
||||
```bash
|
||||
# 检查环境变量
|
||||
# 检查API密钥
|
||||
echo $DEEPSEEK_API_KEY
|
||||
echo $MOONSHOT_API_KEY
|
||||
```
|
||||
|
||||
2. **ComfyUI连接失败**
|
||||
```bash
|
||||
# 测试ComfyUI连接
|
||||
curl http://101.201.50.90:8188/system_stats
|
||||
```
|
||||
|
||||
3. **PSD生成失败**
|
||||
```bash
|
||||
# 检查pytoshop安装
|
||||
pip show pytoshop psd-tools
|
||||
```
|
||||
|
||||
4. **端口被占用**
|
||||
```bash
|
||||
# 查找占用端口的进程
|
||||
# 检查端口占用
|
||||
netstat -tulpn | grep 8000
|
||||
# 或使用其他端口
|
||||
uvicorn run_pipeline:app --port 8001
|
||||
```
|
||||
|
||||
### 日志查看
|
||||
```bash
|
||||
# 查看实时日志
|
||||
tail -f logs/app.log
|
||||
## 更新日志
|
||||
|
||||
# 检查错误日志
|
||||
grep -i error logs/app.log
|
||||
```
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
### 开发环境搭建
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone <repository-url>
|
||||
cd ai_service
|
||||
|
||||
# 安装开发依赖
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
# 运行测试
|
||||
pytest tests/
|
||||
```
|
||||
|
||||
### 代码规范
|
||||
- 使用Python 3.11+
|
||||
- 遵循PEP 8代码风格
|
||||
- 添加类型注解
|
||||
- 编写单元测试
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT License - 详见 [LICENSE](LICENSE) 文件
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
- **项目维护**: AI海报生成系统团队
|
||||
- **技术支持**: 查看[API文档](API_文档.md)和[接口文档](接口文档.md)
|
||||
- **问题反馈**: 提交Issue到项目仓库
|
||||
|
||||
## 📝 更新日志
|
||||
### v2.0.0 (2025-07-03)
|
||||
- ✅ 重构代码结构,移除冗余代码
|
||||
- ✅ 新增预定义Vue模板系统
|
||||
- ✅ 优化工具函数模块化
|
||||
- ✅ 简化API接口设计
|
||||
|
||||
### v1.0.0 (2025-01-02)
|
||||
- ✅ 集成DeepSeek + Kimi + ComfyUI
|
||||
- ✅ 统一API接口设计
|
||||
- ✅ 支持Vue组件生成
|
||||
- ✅ 支持PSD文件合成
|
||||
- ✅ 完整的会话管理
|
||||
- ✅ 丰富的文档和示例
|
||||
|
||||
### 近期规划
|
||||
- 🔄 支持更多图片风格
|
||||
- 🔄 增加文案模板库
|
||||
- 🔄 优化PSD分层效果
|
||||
- 🔄 增加批量生成功能
|
||||
- ✅ 支持Vue组件和PSD文件生成
|
||||
|
538
configs/vue_templates.yaml
Normal file
538
configs/vue_templates.yaml
Normal file
@ -0,0 +1,538 @@
|
||||
# Vue模板配置文件
|
||||
# 为不同图片类型预定义Vue组件模板
|
||||
|
||||
vue_templates:
|
||||
lotus.jpg:
|
||||
theme: "荷花主题"
|
||||
style: "传统优雅"
|
||||
template: |
|
||||
<template>
|
||||
<div class="poster-container lotus-theme">
|
||||
<div class="background-layer">
|
||||
<img src="../outputs/lotus.jpg" alt="荷花背景" class="background-image" />
|
||||
</div>
|
||||
<div class="content-layer">
|
||||
<div class="title-section">
|
||||
<h1 class="main-title lotus-title">{{ title }}</h1>
|
||||
<h2 class="subtitle lotus-subtitle">{{ subtitle }}</h2>
|
||||
</div>
|
||||
<div class="main-content lotus-content">
|
||||
<div class="decoration-elements">
|
||||
<div class="lotus-decoration"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<div class="logo-area">
|
||||
<span class="logo-text">{{ logoText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const title = ref('{{ title_content }}')
|
||||
const subtitle = ref('{{ subtitle_content }}')
|
||||
const logoText = ref('{{ logo_content }}')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.poster-container {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #f8f4e6, #e8dcc0);
|
||||
}
|
||||
|
||||
.background-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.background-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.content-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.lotus-title {
|
||||
font-size: 64px;
|
||||
font-weight: bold;
|
||||
color: #2d5016;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
margin-bottom: 20px;
|
||||
font-family: 'SimSun', serif;
|
||||
}
|
||||
|
||||
.lotus-subtitle {
|
||||
font-size: 32px;
|
||||
color: #4a6741;
|
||||
margin-bottom: 40px;
|
||||
font-family: 'KaiTi', cursive;
|
||||
}
|
||||
|
||||
.lotus-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.lotus-decoration {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: radial-gradient(circle, rgba(255,192,203,0.6), rgba(255,182,193,0.3));
|
||||
border-radius: 50%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
position: absolute;
|
||||
bottom: 10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 24px;
|
||||
color: #2d5016;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
|
||||
nku.png:
|
||||
theme: "南开大学"
|
||||
style: "学术正式"
|
||||
template: |
|
||||
<template>
|
||||
<div class="poster-container nku-theme">
|
||||
<div class="background-layer">
|
||||
<img src="../outputs/background.png" alt="背景" class="background-image" />
|
||||
</div>
|
||||
<div class="content-layer">
|
||||
<div class="header-section">
|
||||
<img src="../outputs/nku.png" alt="南开大学logo" class="nku-logo" />
|
||||
</div>
|
||||
<div class="title-section">
|
||||
<h1 class="main-title nku-title">{{ title }}</h1>
|
||||
<h2 class="subtitle nku-subtitle">{{ subtitle }}</h2>
|
||||
</div>
|
||||
<div class="main-content nku-content">
|
||||
<div class="academic-decoration">
|
||||
<div class="knowledge-symbol"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<div class="university-info">
|
||||
<span class="university-name">{{ logoText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const title = ref('{{ title_content }}')
|
||||
const subtitle = ref('{{ subtitle_content }}')
|
||||
const logoText = ref('{{ logo_content }}')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.poster-container {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(180deg, #7E0C6E, #4a0845);
|
||||
}
|
||||
|
||||
.background-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.background-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.content-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.header-section {
|
||||
position: absolute;
|
||||
top: 8%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.nku-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
position: absolute;
|
||||
top: 25%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
.nku-title {
|
||||
font-size: 58px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
text-shadow: 3px 3px 6px rgba(0,0,0,0.5);
|
||||
margin-bottom: 25px;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
.nku-subtitle {
|
||||
font-size: 28px;
|
||||
color: #f0f0f0;
|
||||
margin-bottom: 40px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.nku-content {
|
||||
position: absolute;
|
||||
top: 55%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.knowledge-symbol {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: 3px solid rgba(255,255,255,0.3);
|
||||
border-radius: 50%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.knowledge-symbol::before {
|
||||
content: '知';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 48px;
|
||||
color: #ffffff;
|
||||
font-family: 'SimSun', serif;
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
position: absolute;
|
||||
bottom: 8%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.university-name {
|
||||
font-size: 22px;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
stamp.jpg:
|
||||
theme: "印章装饰"
|
||||
style: "传统文化"
|
||||
template: |
|
||||
<template>
|
||||
<div class="poster-container stamp-theme">
|
||||
<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 stamp-title">{{ title }}</h1>
|
||||
<h2 class="subtitle stamp-subtitle">{{ subtitle }}</h2>
|
||||
</div>
|
||||
<div class="main-content stamp-content">
|
||||
<div class="stamp-decoration">
|
||||
<img src="../outputs/stamp.jpg" alt="印章" class="stamp-image" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<div class="traditional-footer">
|
||||
<span class="logo-text">{{ logoText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const title = ref('{{ title_content }}')
|
||||
const subtitle = ref('{{ subtitle_content }}')
|
||||
const logoText = ref('{{ logo_content }}')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.poster-container {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(45deg, #faf0e6, #f5e6d3);
|
||||
}
|
||||
|
||||
.background-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.background-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.content-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
position: absolute;
|
||||
top: 18%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
.stamp-title {
|
||||
font-size: 56px;
|
||||
font-weight: bold;
|
||||
color: #8B4513;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
||||
margin-bottom: 20px;
|
||||
font-family: 'SimSun', serif;
|
||||
}
|
||||
|
||||
.stamp-subtitle {
|
||||
font-size: 26px;
|
||||
color: #A0522D;
|
||||
margin-bottom: 40px;
|
||||
line-height: 1.5;
|
||||
font-family: 'KaiTi', cursive;
|
||||
}
|
||||
|
||||
.stamp-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 60%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stamp-image {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(4px 4px 8px rgba(0,0,0,0.3));
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
position: absolute;
|
||||
bottom: 12%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 20px;
|
||||
color: #8B4513;
|
||||
font-weight: 500;
|
||||
font-family: 'SimSun', serif;
|
||||
}
|
||||
</style>
|
||||
|
||||
background.png:
|
||||
theme: "通用背景"
|
||||
style: "简约现代"
|
||||
template: |
|
||||
<template>
|
||||
<div class="poster-container modern-theme">
|
||||
<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 modern-title">{{ title }}</h1>
|
||||
<h2 class="subtitle modern-subtitle">{{ subtitle }}</h2>
|
||||
</div>
|
||||
<div class="main-content modern-content">
|
||||
<div class="modern-decoration">
|
||||
<div class="geometric-shape"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<div class="modern-footer">
|
||||
<span class="logo-text">{{ logoText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const title = ref('{{ title_content }}')
|
||||
const subtitle = ref('{{ subtitle_content }}')
|
||||
const logoText = ref('{{ logo_content }}')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.poster-container {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.background-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.background-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.content-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
position: absolute;
|
||||
top: 22%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.modern-title {
|
||||
font-size: 60px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-shadow: 2px 2px 8px rgba(0,0,0,0.4);
|
||||
margin-bottom: 25px;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
.modern-subtitle {
|
||||
font-size: 30px;
|
||||
color: #f0f0f0;
|
||||
margin-bottom: 40px;
|
||||
line-height: 1.4;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.modern-content {
|
||||
position: absolute;
|
||||
top: 55%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.geometric-shape {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
transform: rotate(45deg);
|
||||
margin: 0 auto;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
position: absolute;
|
||||
bottom: 10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 24px;
|
||||
color: #ffffff;
|
||||
font-weight: 400;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
</style>
|
BIN
outputs/80155705_20250703114134_0.png
Normal file
BIN
outputs/80155705_20250703114134_0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 MiB |
BIN
outputs/80155705_20250703114134_1.png
Normal file
BIN
outputs/80155705_20250703114134_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 MiB |
BIN
outputs/98856890_20250702220533_0.png
Normal file
BIN
outputs/98856890_20250702220533_0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 MiB |
BIN
outputs/98856890_20250702220533_1.png
Normal file
BIN
outputs/98856890_20250702220533_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 MiB |
@ -1,11 +1,23 @@
|
||||
<template>
|
||||
<div class="poster-container">
|
||||
<img class="background" :src="backgroundImage" alt="端午节背景">
|
||||
<div class="main-title">{{ title }}</div>
|
||||
<div class="sub-title">{{ subtitle }}</div>
|
||||
<img class="content-image" :src="contentImage" alt="端午节内容">
|
||||
<div class="logo-area">
|
||||
<img :src="logoImage" alt="Logo">
|
||||
<div class="poster-container lotus-theme">
|
||||
<div class="background-layer">
|
||||
<img src="../outputs/lotus.jpg" alt="荷花背景" class="background-image" />
|
||||
</div>
|
||||
<div class="content-layer">
|
||||
<div class="title-section">
|
||||
<h1 class="main-title lotus-title">{{ title }}</h1>
|
||||
<h2 class="subtitle lotus-subtitle">{{ subtitle }}</h2>
|
||||
</div>
|
||||
<div class="main-content lotus-content">
|
||||
<div class="decoration-elements">
|
||||
<div class="lotus-decoration"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<div class="logo-area">
|
||||
<span class="logo-text">{{ logoText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -13,88 +25,96 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const backgroundImage = ref('https://example.com/background.jpg')
|
||||
const contentImage = ref('https://example.com/content.jpg')
|
||||
const logoImage = ref('https://example.com/logo.png')
|
||||
const title = ref('端午节安康')
|
||||
const subtitle = ref('粽叶飘香,龙舟竞渡,共庆端午佳节')
|
||||
const title = ref('端午节')
|
||||
const subtitle = ref('粽叶飘香,龙舟竞渡')
|
||||
const logoText = ref('')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.poster-container {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 1080px;
|
||||
aspect-ratio: 9/16;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
background: linear-gradient(135deg, #f8f4e6, #e8dcc0);
|
||||
}
|
||||
|
||||
.background {
|
||||
.background-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.background-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
filter: brightness(0.9);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
.content-layer {
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: clamp(40px, 8vw, 80px);
|
||||
color: #fff;
|
||||
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.7);
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
width: 90%;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
.title-section {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: clamp(20px, 4vw, 36px);
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5);
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
width: 80%;
|
||||
line-height: 1.5;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
.content-image {
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 80%;
|
||||
max-height: 40%;
|
||||
z-index: 2;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.logo-area {
|
||||
position: absolute;
|
||||
bottom: 5%;
|
||||
top: 20%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 20%;
|
||||
max-width: 200px;
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.logo-area img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
|
||||
.lotus-title {
|
||||
font-size: 64px;
|
||||
font-weight: bold;
|
||||
color: #2d5016;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
margin-bottom: 20px;
|
||||
font-family: 'SimSun', serif;
|
||||
}
|
||||
|
||||
.lotus-subtitle {
|
||||
font-size: 32px;
|
||||
color: #4a6741;
|
||||
margin-bottom: 40px;
|
||||
font-family: 'KaiTi', cursive;
|
||||
}
|
||||
|
||||
.lotus-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.lotus-decoration {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: radial-gradient(circle, rgba(255,192,203,0.6), rgba(255,182,193,0.3));
|
||||
border-radius: 50%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
position: absolute;
|
||||
bottom: 10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 24px;
|
||||
color: #2d5016;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
@ -4,13 +4,13 @@
|
||||
"color": "#000000"
|
||||
},
|
||||
"layer6_title_content": {
|
||||
"content": "端午节安康",
|
||||
"content": "端午节",
|
||||
"font_name": "SimHei",
|
||||
"color": "#000000"
|
||||
},
|
||||
"layer7_subtitle_content": {
|
||||
"content": "粽叶飘香,龙舟竞渡,共庆端午佳节。",
|
||||
"font_name": "Microsoft YaHei",
|
||||
"content": "粽叶飘香,龙舟竞渡",
|
||||
"font_name": "SimHei",
|
||||
"color": "#7E0C6E"
|
||||
}
|
||||
}
|
37
outputs/temp_config.yaml
Normal file
37
outputs/temp_config.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
available_fonts:
|
||||
- displayName: 微软雅黑
|
||||
name: Microsoft YaHei
|
||||
roles:
|
||||
- title
|
||||
- subtitle
|
||||
- content
|
||||
tags:
|
||||
- 现代
|
||||
- 清晰
|
||||
- displayName: 黑体
|
||||
name: SimHei
|
||||
roles:
|
||||
- title
|
||||
- subtitle
|
||||
- content
|
||||
tags:
|
||||
- 通用
|
||||
- 标准
|
||||
default_logo_text: ''
|
||||
vue_templates:
|
||||
background.png:
|
||||
style: 简约现代
|
||||
template_name: background_template
|
||||
theme: 通用背景
|
||||
lotus.jpg:
|
||||
style: 传统优雅
|
||||
template_name: lotus_template
|
||||
theme: 荷花主题
|
||||
nku.png:
|
||||
style: 学术正式
|
||||
template_name: nku_template
|
||||
theme: 南开大学
|
||||
stamp.jpg:
|
||||
style: 传统文化
|
||||
template_name: stamp_template
|
||||
theme: 印章装饰
|
BIN
requirments.txt
BIN
requirments.txt
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
scripts/__pycache__/utils.cpython-311.pyc
Normal file
BIN
scripts/__pycache__/utils.cpython-311.pyc
Normal file
Binary file not shown.
@ -1,43 +1,44 @@
|
||||
"""
|
||||
@file generate_layout.py
|
||||
@brief Vue组件代码生成模块
|
||||
基于DeepSeek API生成响应式Vue 3组件布局代码
|
||||
基于DeepSeek API生成响应式Vue 3组件布局代码,支持预定义模板
|
||||
|
||||
@author 王秀强 (2310460@mail.nankai.edu.cn)
|
||||
@date 2025.5.19
|
||||
@version v1.0.0
|
||||
@version v2.0.0
|
||||
|
||||
@details
|
||||
本文件主要实现:
|
||||
- DeepSeek API调用封装和错误处理
|
||||
- Vue 3 Composition API组件代码生成
|
||||
- 海报布局的动态排版和样式生成
|
||||
- 预定义Vue模板系统
|
||||
- 增强Vue代码生成逻辑
|
||||
- 代码清理和格式化处理
|
||||
- 备用Vue模板生成机制
|
||||
|
||||
@note
|
||||
- 需要配置DEEPSEEK_API_KEY环境变量
|
||||
- 支持流式和非流式响应模式
|
||||
- 生成的Vue代码包含完整的template、script和style部分
|
||||
- 具备指数退避重试机制处理API限流
|
||||
- 支持基于图片类型的预定义模板
|
||||
|
||||
@usage
|
||||
# 生成Vue组件代码
|
||||
vue_code = generate_vue_code("生成端午节海报Vue组件")
|
||||
vue_code = generate_vue_code_enhanced("生成端午节海报Vue组件", parse_imglist, suggestions)
|
||||
save_code(vue_code, "../outputs/poster.vue")
|
||||
|
||||
# 调用DeepSeek API
|
||||
result, usage = call_deepseek(prompt="请生成代码", temperature=0.6)
|
||||
|
||||
@copyright
|
||||
(c) 2025 砚生项目组
|
||||
"""
|
||||
|
||||
import os
|
||||
import yaml
|
||||
from openai import OpenAI
|
||||
from dotenv import load_dotenv
|
||||
import time
|
||||
from colorama import init, Fore, Back, Style
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
# 初始化colorama
|
||||
init(autoreset=True)
|
||||
@ -125,9 +126,193 @@ def call_deepseek(
|
||||
raise Exception("达到最大重试次数,API 调用失败")
|
||||
|
||||
|
||||
def load_vue_templates() -> Dict:
|
||||
"""加载预定义的Vue模板配置"""
|
||||
template_path = "../configs/vue_templates.yaml"
|
||||
|
||||
try:
|
||||
with open(template_path, 'r', encoding='utf-8') as f:
|
||||
templates = yaml.safe_load(f)
|
||||
print(f"{Fore.GREEN}✅ Vue模板配置加载成功{Style.RESET_ALL}")
|
||||
return templates.get('vue_templates', {})
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}⚠️ Vue模板配置加载失败: {e},使用默认模板{Style.RESET_ALL}")
|
||||
return {}
|
||||
|
||||
|
||||
def get_template_by_images(parse_imglist: List[Dict]) -> Optional[str]:
|
||||
"""
|
||||
根据图片列表选择合适的预定义模板
|
||||
|
||||
参数:
|
||||
parse_imglist: 图片信息列表
|
||||
|
||||
返回:
|
||||
选中的模板代码,如果没有匹配的模板则返回None
|
||||
"""
|
||||
templates = load_vue_templates()
|
||||
|
||||
if not templates or not parse_imglist:
|
||||
return None
|
||||
|
||||
# 检查固定的图片文件
|
||||
fixed_images = ["lotus.jpg", "nku.png", "stamp.jpg", "background.png"]
|
||||
|
||||
for img_name in fixed_images:
|
||||
img_path = f"../outputs/{img_name}"
|
||||
if os.path.exists(img_path) and img_name in templates:
|
||||
print(f"{Fore.CYAN}📋 找到匹配的预定义模板: {img_name}{Style.RESET_ALL}")
|
||||
return templates[img_name]['template']
|
||||
|
||||
print(f"{Fore.YELLOW}⚠️ 未找到匹配的预定义模板,将使用AI生成{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
|
||||
def generate_layout_prompt(user_input_analysis_result: Dict, parse_imglist: List[Dict], suggestions: Dict = None) -> str:
|
||||
"""
|
||||
生成增强的Vue布局提示,包含文案内容
|
||||
这个函数从run_pipeline.py移动到这里
|
||||
"""
|
||||
width = user_input_analysis_result.get("width", 1080)
|
||||
height = user_input_analysis_result.get("height", 1920)
|
||||
theme = user_input_analysis_result.get("main_theme", "活动海报")
|
||||
|
||||
# 构造图片信息字符串
|
||||
images_info = "\n".join(
|
||||
[f"- {img['picture_name']} ({img['picture_description']})" for img in parse_imglist]
|
||||
)
|
||||
|
||||
# 构造文案信息
|
||||
content_info = ""
|
||||
if suggestions:
|
||||
try:
|
||||
if 'layer6_title_content' in suggestions:
|
||||
title = suggestions['layer6_title_content'].get('content', theme)
|
||||
content_info += f"- 主标题: {title}\n"
|
||||
|
||||
if 'layer7_subtitle_content' in suggestions:
|
||||
subtitle = suggestions['layer7_subtitle_content'].get('content', '精彩活动,敬请参与')
|
||||
content_info += f"- 副标题: {subtitle}\n"
|
||||
|
||||
if 'layer5_logo_content' in suggestions:
|
||||
logo = suggestions['layer5_logo_content'].get('text', '主办方')
|
||||
content_info += f"- Logo文字: {logo}\n"
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}⚠️ 文案信息解析错误: {e}{Style.RESET_ALL}")
|
||||
content_info = f"- 主标题: {theme}\n- 副标题: 精彩活动,敬请参与\n"
|
||||
|
||||
# 调用DeepSeek生成动态排版Prompt
|
||||
system_prompt = "你是一个擅长前端开发的AI,专注于生成Vue.js代码。请根据提供的信息生成完整的Vue组件,包含所有必要的HTML结构和基础定位样式。"
|
||||
|
||||
prompt = f"""
|
||||
请生成一个Vue.js组件代码,用于{theme}海报,要求如下:
|
||||
|
||||
组件尺寸: {width}x{height}px
|
||||
|
||||
图片资源:
|
||||
{images_info}
|
||||
|
||||
文案内容:
|
||||
{content_info}
|
||||
|
||||
布局要求:
|
||||
1. 背景图层: 使用第一张图片作为背景,占据整个组件区域
|
||||
2. 主标题: 位于画布上方1/3处,居中显示
|
||||
3. 副标题: 位于主标题下方,居中显示
|
||||
4. 内容区域: 使用剩余图片,合理布局在中间区域
|
||||
5. Logo区域: 位于底部,居中显示
|
||||
|
||||
技术要求:
|
||||
- 使用Vue 3 Composition API
|
||||
- 使用absolute定位进行精确布局
|
||||
- 包含完整的template、script和style部分
|
||||
- 确保所有文本内容都正确显示
|
||||
- 图片使用相对路径引用
|
||||
|
||||
请生成完整可用的Vue组件代码,不要包含任何说明文字。
|
||||
"""
|
||||
|
||||
try:
|
||||
result, _ = call_deepseek(prompt=prompt, system_prompt=system_prompt, temperature=0.4)
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}❌ 布局提示生成失败: {e}{Style.RESET_ALL}")
|
||||
return generate_fallback_vue_code(theme, width, height)
|
||||
|
||||
|
||||
def fill_template_content(template: str, suggestions: Dict) -> str:
|
||||
"""
|
||||
填充模板中的动态内容
|
||||
|
||||
参数:
|
||||
template: Vue模板代码
|
||||
suggestions: 文案建议
|
||||
|
||||
返回:
|
||||
填充后的Vue代码
|
||||
"""
|
||||
filled_template = template
|
||||
|
||||
try:
|
||||
# 提取文案内容
|
||||
title_content = suggestions.get('layer6_title_content', {}).get('content', '默认标题')
|
||||
subtitle_content = suggestions.get('layer7_subtitle_content', {}).get('content', '默认副标题')
|
||||
logo_content = suggestions.get('layer5_logo_content', {}).get('text', '主办方')
|
||||
|
||||
# 替换模板占位符
|
||||
filled_template = filled_template.replace('{{ title_content }}', title_content)
|
||||
filled_template = filled_template.replace('{{ subtitle_content }}', subtitle_content)
|
||||
filled_template = filled_template.replace('{{ logo_content }}', logo_content)
|
||||
|
||||
print(f"{Fore.GREEN}✅ 模板内容填充完成{Style.RESET_ALL}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}⚠️ 模板填充出错: {e},使用默认内容{Style.RESET_ALL}")
|
||||
|
||||
return filled_template
|
||||
|
||||
|
||||
def generate_vue_code_enhanced(
|
||||
user_input_analysis_result: Dict,
|
||||
parse_imglist: List[Dict],
|
||||
suggestions: Dict = None,
|
||||
prompt: str = None
|
||||
) -> str:
|
||||
"""
|
||||
增强的Vue代码生成函数
|
||||
优先使用预定义模板,如果没有匹配的模板则使用AI生成
|
||||
|
||||
参数:
|
||||
user_input_analysis_result: 用户输入分析结果
|
||||
parse_imglist: 图片信息列表
|
||||
suggestions: 文案建议
|
||||
prompt: 自定义提示词(可选)
|
||||
|
||||
返回:
|
||||
Vue组件代码
|
||||
"""
|
||||
print(f"{Fore.CYAN}🎨 开始增强Vue代码生成...{Style.RESET_ALL}")
|
||||
|
||||
# 1. 尝试使用预定义模板
|
||||
template_code = get_template_by_images(parse_imglist)
|
||||
|
||||
if template_code and suggestions:
|
||||
print(f"{Fore.GREEN}✅ 使用预定义模板生成Vue代码{Style.RESET_ALL}")
|
||||
vue_code = fill_template_content(template_code, suggestions)
|
||||
return vue_code
|
||||
|
||||
# 2. 如果没有合适的模板,使用AI生成
|
||||
print(f"{Fore.BLUE}🤖 使用AI生成Vue代码{Style.RESET_ALL}")
|
||||
|
||||
if not prompt:
|
||||
prompt = generate_layout_prompt(user_input_analysis_result, parse_imglist, suggestions)
|
||||
|
||||
return generate_vue_code(prompt)
|
||||
|
||||
|
||||
def generate_vue_code(prompt=None):
|
||||
"""
|
||||
生成Vue组件代码
|
||||
原有的Vue代码生成函数(保持兼容性)
|
||||
"""
|
||||
if not prompt:
|
||||
prompt = (
|
||||
@ -170,46 +355,32 @@ def generate_vue_code(prompt=None):
|
||||
print(f"{Fore.RED}❌ Vue代码生成异常: {str(e)}{Style.RESET_ALL}")
|
||||
return generate_fallback_vue_code()
|
||||
|
||||
def generate_fallback_vue_code():
|
||||
def generate_fallback_vue_code(theme="默认主题", width=1080, height=1920):
|
||||
"""
|
||||
生成备用的Vue代码
|
||||
"""
|
||||
print(f"{Fore.YELLOW}⚠️ 使用备用Vue模板{Style.RESET_ALL}")
|
||||
return """<template>
|
||||
<div class="poster-container">
|
||||
return f"""<template>
|
||||
<div class="poster-container" :style="containerStyle">
|
||||
<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>
|
||||
<h1 class="main-title">{theme}</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 class="image-gallery">
|
||||
<img src="../outputs/image1.png" alt="活动图片" class="content-image" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<div class="logo-area">
|
||||
<span class="logo-text">主办单位</span>
|
||||
<span class="logo-text">主办方</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -217,124 +388,91 @@ def generate_fallback_vue_code():
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import {{ computed }} from 'vue'
|
||||
|
||||
// 组件数据
|
||||
const posterData = ref({
|
||||
title: '端午节活动',
|
||||
subtitle: '传统文化,现代体验',
|
||||
highlights: [
|
||||
{ title: '包粽子体验', description: '亲手制作传统粽子' },
|
||||
{ title: '龙舟竞赛', description: '激情龙舟比赛' },
|
||||
{ title: '文化表演', description: '精彩传统表演' }
|
||||
]
|
||||
})
|
||||
const containerStyle = computed(() => ({{
|
||||
width: '{width}px',
|
||||
height: '{height}px',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.poster-container {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
.poster-container {{
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
}
|
||||
}}
|
||||
|
||||
.background-layer {
|
||||
.background-layer {{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
}}
|
||||
|
||||
.background-image {
|
||||
.background-image {{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}}
|
||||
|
||||
.content-layer {
|
||||
.content-layer {{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
}}
|
||||
|
||||
.title-section {
|
||||
.title-section {{
|
||||
position: absolute;
|
||||
top: 25%;
|
||||
top: 20%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
}
|
||||
}}
|
||||
|
||||
.main-title {
|
||||
font-size: 60px;
|
||||
.main-title {{
|
||||
font-size: 48px;
|
||||
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;
|
||||
.subtitle {{
|
||||
font-size: 24px;
|
||||
color: #666;
|
||||
}
|
||||
margin-bottom: 40px;
|
||||
}}
|
||||
|
||||
.footer-section {
|
||||
.main-content {{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}}
|
||||
|
||||
.content-image {{
|
||||
max-width: 400px;
|
||||
max-height: 300px;
|
||||
object-fit: cover;
|
||||
}}
|
||||
|
||||
.footer-section {{
|
||||
position: absolute;
|
||||
bottom: 10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}}
|
||||
|
||||
.logo-text {
|
||||
font-size: 20px;
|
||||
.logo-text {{
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
}}
|
||||
</style>"""
|
||||
|
||||
def save_code(code, file_path="../outputs/generated_code.vue"):
|
||||
@ -363,6 +501,25 @@ def save_code(code, file_path="../outputs/generated_code.vue"):
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"{Fore.MAGENTA}🚀 开始生成Vue组件...{Style.RESET_ALL}")
|
||||
vue_code = generate_vue_code()
|
||||
|
||||
# 测试增强的Vue代码生成
|
||||
test_analysis = {
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"main_theme": "端午节海报"
|
||||
}
|
||||
|
||||
test_imglist = [
|
||||
{"picture_name": "background", "picture_description": "背景图片"},
|
||||
{"picture_name": "lotus", "picture_description": "荷花装饰"}
|
||||
]
|
||||
|
||||
test_suggestions = {
|
||||
"layer6_title_content": {"content": "端午安康"},
|
||||
"layer7_subtitle_content": {"content": "粽叶飘香,龙舟竞渡"},
|
||||
"layer5_logo_content": {"text": "主办方"}
|
||||
}
|
||||
|
||||
vue_code = generate_vue_code_enhanced(test_analysis, test_imglist, test_suggestions)
|
||||
save_code(vue_code)
|
||||
print(f"{Fore.GREEN}✅ Vue组件代码生成完成{Style.RESET_ALL}")
|
||||
|
@ -1,90 +0,0 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from generate_layout import call_deepseek
|
||||
import json
|
||||
from colorama import init, Fore, Back, Style
|
||||
|
||||
# 初始化colorama
|
||||
init(autoreset=True)
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
def llm_user_analysis(user_input):
|
||||
"""
|
||||
使用DeepSeek动态分析用户输入,提取海报设计参数
|
||||
"""
|
||||
if not user_input:
|
||||
user_input = "端午节海报,包含背景、活动亮点和图标"
|
||||
|
||||
print(f"{Fore.CYAN}🔍 正在分析用户输入: {Style.BRIGHT}{user_input}{Style.RESET_ALL}")
|
||||
|
||||
# 构建分析提示词
|
||||
analysis_prompt = f"""
|
||||
请分析以下用户输入的海报需求,提取关键信息并以JSON格式返回:
|
||||
|
||||
用户输入:{user_input}
|
||||
|
||||
请严格按照以下JSON格式返回:
|
||||
{{
|
||||
"analyzed_prompt": "原始用户输入",
|
||||
"keywords": ["提取的关键词1", "关键词2", "关键词3"],
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"batch_size": 2,
|
||||
"poster_type": "海报类型(如:节日海报、活动海报、产品海报等)",
|
||||
"main_theme": "主要主题",
|
||||
"style_preference": "风格偏好(如:现代、传统、简约等)",
|
||||
"color_preference": "颜色偏好(如:暖色调、冷色调、单色等)"
|
||||
}}
|
||||
|
||||
注意:
|
||||
1. keywords应该包含3-5个最重要的关键词
|
||||
2. 根据用户输入推断合适的海报类型
|
||||
3. 尺寸默认为1080x1920,除非用户明确指定
|
||||
4. batch_size根据需求调整,通常为1-4
|
||||
5. 分析用户的风格和颜色偏好
|
||||
"""
|
||||
|
||||
system_prompt = "你是一个专业的设计需求分析师,擅长从用户描述中提取海报设计的关键参数。请严格按照JSON格式返回结果,确保输出的JSON格式正确且完整。"
|
||||
|
||||
try:
|
||||
result, _ = call_deepseek(prompt=analysis_prompt, system_prompt=system_prompt, temperature=0.3)
|
||||
|
||||
# 解析JSON
|
||||
json_str = result.strip()
|
||||
if "```json" in json_str:
|
||||
json_str = json_str.split("```json")[1].split("```")[0].strip()
|
||||
elif json_str.startswith("```") and json_str.endswith("```"):
|
||||
json_str = json_str[3:-3].strip()
|
||||
|
||||
analysis_result = json.loads(json_str)
|
||||
|
||||
print(f"{Fore.GREEN}✅ 分析完成! {Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}📊 主题: {Style.BRIGHT}{analysis_result.get('main_theme', '未知')}{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}🎨 风格: {analysis_result.get('style_preference', '未设置')}")
|
||||
print(f"{Fore.YELLOW}🔖 关键词: {', '.join(analysis_result.get('keywords', []))}{Style.RESET_ALL}")
|
||||
|
||||
return analysis_result
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}❌ 分析失败: {str(e)}{Style.RESET_ALL}")
|
||||
# 返回默认值
|
||||
return {
|
||||
"analyzed_prompt": user_input,
|
||||
"keywords": ["海报", "设计", "活动"],
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"batch_size": 2,
|
||||
"poster_type": "通用海报",
|
||||
"main_theme": "默认主题",
|
||||
"style_preference": "现代",
|
||||
"color_preference": "暖色调"
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试函数
|
||||
test_input = "春节海报,红色背景,包含灯笼和烟花,现代风格"
|
||||
result = llm_user_analysis(test_input)
|
||||
print(f"\n{Fore.GREEN}测试结果:")
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
@ -5,7 +5,7 @@
|
||||
*
|
||||
* @author 王秀强 (2310460@mail.nankai.edu.cn)
|
||||
* @date 2025.6.9
|
||||
* @version v1.0.0
|
||||
* @version v2.0.0
|
||||
*
|
||||
* @details
|
||||
* 本文件主要实现:
|
||||
@ -34,38 +34,49 @@
|
||||
* (c) 2025 砚生项目组
|
||||
*/
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import yaml
|
||||
from prompt_analysis import llm_user_analysis
|
||||
from generate_layout import call_deepseek, generate_vue_code, save_code
|
||||
from generate_text import load_config_from_file, get_poster_content_suggestions
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
from flux_con import comfyui_img_info
|
||||
from export_psd_from_json import create_psd_from_images as create_psd_impl
|
||||
from colorama import init, Fore, Style
|
||||
import json
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
# 导入工具函数
|
||||
from utils import (
|
||||
print_step, print_result, get_session_folder,
|
||||
llm_user_analysis, save_json_file, save_vue_file,
|
||||
create_temp_config, CONFIG_PATHS
|
||||
)
|
||||
|
||||
# 导入核心模块
|
||||
from generate_layout import generate_vue_code_enhanced, save_code
|
||||
from generate_text import load_config_from_file, get_poster_content_suggestions
|
||||
from flux_con import comfyui_img_info
|
||||
from export_psd_from_json import create_psd_from_images as create_psd_impl
|
||||
|
||||
# FastAPI相关
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
|
||||
from colorama import init, Fore, Style
|
||||
|
||||
# 初始化colorama
|
||||
init(autoreset=True)
|
||||
|
||||
# 配置路径
|
||||
config_paths = {
|
||||
"font": "../configs/font.yaml",
|
||||
"output_folder": "../outputs/",
|
||||
}
|
||||
|
||||
# 加载环境变量和配置
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
app = FastAPI(title="AI海报生成系统API", version="1.0.0")
|
||||
|
||||
# 添加CORS中间件,允许前端跨域访问
|
||||
# FastAPI应用
|
||||
app = FastAPI(title="AI海报生成系统API", version="2.0.0")
|
||||
|
||||
# 添加CORS中间件
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # 在生产环境中应该设置具体的域名
|
||||
@ -74,143 +85,40 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
# 请求模型
|
||||
class PosterRequest(BaseModel):
|
||||
user_input: str
|
||||
session_id: str = None # 可选的会话ID,用于跟踪同一个用户的请求
|
||||
|
||||
|
||||
class GenerateVueRequest(BaseModel):
|
||||
user_input: str
|
||||
session_id: str = None
|
||||
|
||||
|
||||
class GeneratePSDRequest(BaseModel):
|
||||
user_input: str
|
||||
session_id: str = None
|
||||
use_manual_psd: bool = False # 是否使用手动创建的PSD文件
|
||||
|
||||
|
||||
# 响应模型
|
||||
class ApiResponse(BaseModel):
|
||||
status: str
|
||||
message: str
|
||||
data: dict = None
|
||||
session_id: str = None
|
||||
|
||||
|
||||
# 全局变量存储会话数据
|
||||
sessions = {}
|
||||
|
||||
# 加载字体配置
|
||||
try:
|
||||
with open(config_paths["font"], "r", encoding="utf-8") as f:
|
||||
fonts_config = yaml.safe_load(f)
|
||||
print(f"{Fore.GREEN}✅ 字体配置加载成功{Style.RESET_ALL}")
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}⚠️ 字体配置加载失败: {e},使用默认配置{Style.RESET_ALL}")
|
||||
fonts_config = {}
|
||||
|
||||
|
||||
# 辅助函数
|
||||
def print_step(step_num, description, status="进行中"):
|
||||
"""打印带颜色的步骤信息"""
|
||||
if status == "进行中":
|
||||
print(f"{Fore.BLUE}📋 步骤{step_num}: {description}...{Style.RESET_ALL}")
|
||||
elif status == "完成":
|
||||
print(f"{Fore.GREEN}✅ 步骤{step_num}: {description} - 完成{Style.RESET_ALL}")
|
||||
elif status == "错误":
|
||||
print(f"{Fore.RED}❌ 步骤{step_num}: {description} - 出错{Style.RESET_ALL}")
|
||||
|
||||
|
||||
def print_result(key, value):
|
||||
"""打印结果信息"""
|
||||
print(f"{Fore.CYAN}📊 {key}: {value}{Style.RESET_ALL}")
|
||||
|
||||
|
||||
def get_session_folder(session_id):
|
||||
"""获取会话专用的输出文件夹"""
|
||||
if not session_id:
|
||||
session_id = str(uuid.uuid4())
|
||||
|
||||
session_folder = os.path.join(config_paths["output_folder"], f"session_{session_id}")
|
||||
os.makedirs(session_folder, exist_ok=True)
|
||||
return session_folder, session_id
|
||||
|
||||
|
||||
# 生成prompts.yaml的函数
|
||||
def generate_prompts_yaml(user_input=None):
|
||||
def create_psd_from_fixed_images(output_path: str) -> Optional[str]:
|
||||
"""
|
||||
动态生成prompts.yaml配置文件
|
||||
使用固定的图片文件创建PSD文件
|
||||
简化的PSD创建函数,直接使用预定义的四个图片
|
||||
"""
|
||||
if not user_input:
|
||||
user_input = "端午节海报,包含背景、活动亮点和图标"
|
||||
|
||||
prompts_config = {
|
||||
"default_logo_text": "",
|
||||
"available_fonts": [
|
||||
{
|
||||
"name": "Microsoft YaHei",
|
||||
"displayName": "微软雅黑",
|
||||
"tags": ["现代", "清晰"],
|
||||
"roles": ["title", "subtitle", "content"]
|
||||
},
|
||||
{
|
||||
"name": "SimHei",
|
||||
"displayName": "黑体",
|
||||
"tags": ["通用", "标准"],
|
||||
"roles": ["title", "subtitle", "content"]
|
||||
}
|
||||
],
|
||||
"NAMING_COLORS": {
|
||||
"primary": "#1976D2",
|
||||
"secondary": "#424242",
|
||||
"accent": "#FF5722"
|
||||
},
|
||||
"STYLE_RULES": {
|
||||
"modern": {
|
||||
"primary_font": "Microsoft YaHei",
|
||||
"secondary_font": "SimHei"
|
||||
}
|
||||
},
|
||||
"LOGO_RULES": {
|
||||
"default_position": "bottom",
|
||||
"fallback_text": "活动主办方"
|
||||
}
|
||||
}
|
||||
|
||||
# 保存到临时文件
|
||||
temp_prompts_path = os.path.join(config_paths["output_folder"], "temp_prompts.yaml")
|
||||
os.makedirs(os.path.dirname(temp_prompts_path), exist_ok=True)
|
||||
|
||||
with open(temp_prompts_path, 'w', encoding='utf-8') as f:
|
||||
yaml.dump(prompts_config, f, allow_unicode=True, default_flow_style=False)
|
||||
|
||||
print(f"{Fore.GREEN}✅ 临时prompts.yaml已生成: {temp_prompts_path}{Style.RESET_ALL}")
|
||||
return temp_prompts_path
|
||||
|
||||
|
||||
# 修复PSD合成接口 - 使用临时固定图片列表
|
||||
def create_psd_from_images_wrapper(img_list, vue_layout_path, output_path):
|
||||
"""
|
||||
临时接口:使用固定的图片文件创建PSD文件
|
||||
使用outputs目录下的: lotus.jpg, nankai.png, stamp.jpg, background.png
|
||||
"""
|
||||
print(f"{Fore.CYAN}🎨 开始合成PSD文件(临时接口)...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}🎨 开始合成PSD文件...{Style.RESET_ALL}")
|
||||
|
||||
try:
|
||||
# 使用固定的图片文件列表
|
||||
fixed_image_files = ["background.png", "lotus.jpg", "nankai.png", "stamp.jpg"]
|
||||
fixed_image_files = ["background.png", "lotus.jpg", "nku.png", "stamp.jpg"]
|
||||
image_paths = []
|
||||
|
||||
for img_file in fixed_image_files:
|
||||
img_path = os.path.join(config_paths["output_folder"], img_file)
|
||||
img_path = os.path.join(CONFIG_PATHS["output_folder"], img_file)
|
||||
if os.path.exists(img_path):
|
||||
image_paths.append(img_path)
|
||||
print(f"{Fore.GREEN}✓ 找到图片: {img_file}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.YELLOW}⚠️ 图片不存在: {img_file} (路径: {img_path}){Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}⚠️ 图片不存在: {img_file}{Style.RESET_ALL}")
|
||||
|
||||
if not image_paths:
|
||||
print(f"{Fore.RED}❌ 没有找到任何指定的图片文件{Style.RESET_ALL}")
|
||||
@ -223,19 +131,19 @@ def create_psd_from_images_wrapper(img_list, vue_layout_path, output_path):
|
||||
# 确保输出目录存在
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
# 调用export_psd中的PSD创建函数
|
||||
# 调用PSD创建函数
|
||||
create_psd_impl(
|
||||
image_paths=image_paths,
|
||||
output_path=output_path,
|
||||
canvas_size=(1080, 1920), # 使用标准海报尺寸
|
||||
canvas_size=(1080, 1920),
|
||||
mode='RGB'
|
||||
)
|
||||
|
||||
print(f"{Fore.GREEN}✅ PSD文件创建成功: {output_path}{Style.RESET_ALL}")
|
||||
|
||||
# 验证PSD文件是否成功创建
|
||||
# 验证PSD文件
|
||||
if os.path.exists(output_path):
|
||||
file_size = os.path.getsize(output_path) / (1024 * 1024) # 转换为MB
|
||||
file_size = os.path.getsize(output_path) / (1024 * 1024)
|
||||
print(f"{Fore.CYAN}📁 PSD文件大小: {file_size:.2f} MB{Style.RESET_ALL}")
|
||||
|
||||
return output_path
|
||||
@ -247,212 +155,19 @@ def create_psd_from_images_wrapper(img_list, vue_layout_path, output_path):
|
||||
return None
|
||||
|
||||
|
||||
# 增强Vue代码生成,确保包含文案内容
|
||||
def generate_layout_prompt(user_input_analysis_result, parse_imglist, suggestions=None):
|
||||
def run_pipeline(user_input: str = None) -> Optional[str]:
|
||||
"""
|
||||
生成更完整的Vue布局提示,包含文案内容
|
||||
"""
|
||||
width = user_input_analysis_result["width"]
|
||||
height = user_input_analysis_result["height"]
|
||||
theme = user_input_analysis_result.get("main_theme", "活动海报")
|
||||
|
||||
# 构造图片信息字符串
|
||||
images_info = "\n".join(
|
||||
[f"- {img['picture_name']} ({img['picture_description']})" for img in parse_imglist]
|
||||
)
|
||||
|
||||
# 构造文案信息
|
||||
content_info = ""
|
||||
if suggestions:
|
||||
try:
|
||||
if 'layer6_title_content' in suggestions:
|
||||
title = suggestions['layer6_title_content'].get('content', theme)
|
||||
content_info += f"- 主标题: {title}\n"
|
||||
|
||||
if 'layer7_subtitle_content' in suggestions:
|
||||
subtitle = suggestions['layer7_subtitle_content'].get('content', '精彩活动,敬请参与')
|
||||
content_info += f"- 副标题: {subtitle}\n"
|
||||
|
||||
if 'layer5_logo_content' in suggestions:
|
||||
logo = suggestions['layer5_logo_content'].get('text', '主办方')
|
||||
content_info += f"- Logo文字: {logo}\n"
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}⚠️ 文案信息解析错误: {e}{Style.RESET_ALL}")
|
||||
content_info = f"- 主标题: {theme}\n- 副标题: 精彩活动,敬请参与\n"
|
||||
|
||||
# 调用DeepSeek生成动态排版Prompt
|
||||
system_prompt = "你是一个擅长前端开发的AI,专注于生成Vue.js代码。请根据提供的信息生成完整的Vue组件,包含所有必要的HTML结构和基础定位样式。"
|
||||
|
||||
prompt = f"""
|
||||
请生成一个Vue.js组件代码,用于{theme}海报,要求如下:
|
||||
|
||||
组件尺寸: {width}x{height}px
|
||||
|
||||
图片资源:
|
||||
{images_info}
|
||||
|
||||
文案内容:
|
||||
{content_info}
|
||||
|
||||
布局要求:
|
||||
1. 背景图层: 使用第一张图片作为背景,占据整个组件区域
|
||||
2. 主标题: 位于画布上方1/3处,居中显示
|
||||
3. 副标题: 位于主标题下方,居中显示
|
||||
4. 内容区域: 使用剩余图片,合理布局在中间区域
|
||||
5. Logo区域: 位于底部,居中显示
|
||||
|
||||
技术要求:
|
||||
- 使用Vue 3 Composition API
|
||||
- 使用absolute定位进行精确布局
|
||||
- 包含完整的template、script和style部分
|
||||
- 确保所有文本内容都正确显示
|
||||
- 图片使用相对路径引用
|
||||
|
||||
请生成完整可用的Vue组件代码,不要包含任何说明文字。
|
||||
"""
|
||||
|
||||
try:
|
||||
result, _ = call_deepseek(prompt=prompt, system_prompt=system_prompt, temperature=0.4)
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}❌ 布局提示生成失败: {e}{Style.RESET_ALL}")
|
||||
return generate_fallback_vue_code(theme, width, height)
|
||||
|
||||
|
||||
def generate_fallback_vue_code(theme, width=1080, height=1920):
|
||||
"""
|
||||
生成备用的Vue代码
|
||||
"""
|
||||
return f"""<template>
|
||||
<div class="poster-container" :style="containerStyle">
|
||||
<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">{theme}</h1>
|
||||
<h2 class="subtitle">精彩活动,敬请参与</h2>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="image-gallery">
|
||||
<img src="../outputs/image1.png" alt="活动图片" class="content-image" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<div class="logo-area">
|
||||
<span class="logo-text">主办方</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {{ computed }} from 'vue'
|
||||
|
||||
const containerStyle = computed(() => ({{
|
||||
width: '{width}px',
|
||||
height: '{height}px',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.poster-container {{
|
||||
position: relative;
|
||||
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: 20%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
}}
|
||||
|
||||
.main-title {{
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}}
|
||||
|
||||
.subtitle {{
|
||||
font-size: 24px;
|
||||
color: #666;
|
||||
margin-bottom: 40px;
|
||||
}}
|
||||
|
||||
.main-content {{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}}
|
||||
|
||||
.content-image {{
|
||||
max-width: 400px;
|
||||
max-height: 300px;
|
||||
object-fit: cover;
|
||||
}}
|
||||
|
||||
.footer-section {{
|
||||
position: absolute;
|
||||
bottom: 10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}}
|
||||
|
||||
.logo-text {{
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
}}
|
||||
</style>"""
|
||||
|
||||
|
||||
# 一键执行流程
|
||||
def run_pipeline(user_input=None):
|
||||
"""
|
||||
自动执行海报生成流程
|
||||
简化的海报生成流程
|
||||
"""
|
||||
try:
|
||||
print(f"{Fore.MAGENTA}{'=' * 50}")
|
||||
print(f"{Fore.MAGENTA}🎨 海报生成流程启动 🎨")
|
||||
print(f"{'=' * 50}{Style.RESET_ALL}")
|
||||
|
||||
print_step(1, "加载配置文件")
|
||||
prompts_yaml_path = generate_prompts_yaml(user_input)
|
||||
load_config_from_file(prompts_yaml_path)
|
||||
print_step(1, "加载配置文件", "完成")
|
||||
print_step(1, "生成临时配置")
|
||||
temp_config_path = create_temp_config(user_input)
|
||||
load_config_from_file(temp_config_path)
|
||||
print_step(1, "生成临时配置", "完成")
|
||||
|
||||
print_step(2, "分析用户输入")
|
||||
user_input_analysis_result = llm_user_analysis(user_input)
|
||||
@ -467,40 +182,27 @@ def run_pipeline(user_input=None):
|
||||
|
||||
print_step(4, "生成文案建议")
|
||||
suggestions = get_poster_content_suggestions(user_input_analysis_result["analyzed_prompt"])
|
||||
print(f"{Fore.CYAN}文案生成结果:")
|
||||
print(json.dumps(suggestions, indent=2, ensure_ascii=False))
|
||||
|
||||
# 保存文案到文件
|
||||
suggestions_path = os.path.join(config_paths["output_folder"], "poster_content.json")
|
||||
with open(suggestions_path, "w", encoding="utf-8") as f:
|
||||
json.dump(suggestions, f, indent=2, ensure_ascii=False)
|
||||
suggestions_path = os.path.join(CONFIG_PATHS["output_folder"], "poster_content.json")
|
||||
save_json_file(suggestions, suggestions_path)
|
||||
print_step(4, "生成文案建议", "完成")
|
||||
|
||||
print_step(5, "生成Vue排版")
|
||||
dynamic_prompt = generate_layout_prompt(user_input_analysis_result, parse_imglist, suggestions)
|
||||
vue_code = generate_vue_code(dynamic_prompt)
|
||||
vue_path = os.path.join(config_paths["output_folder"], "generated_code.vue")
|
||||
save_code(vue_code, file_path=vue_path)
|
||||
print_step(5, "生成Vue组件")
|
||||
# 使用增强的Vue代码生成(已移至generate_layout模块)
|
||||
vue_code = generate_vue_code_enhanced(
|
||||
user_input_analysis_result,
|
||||
parse_imglist,
|
||||
suggestions
|
||||
)
|
||||
|
||||
# 验证Vue文件是否成功生成
|
||||
if os.path.exists(vue_path):
|
||||
print(f"{Fore.GREEN}✅ Vue文件已生成: {vue_path}{Style.RESET_ALL}")
|
||||
# 显示Vue代码的前几行用于验证
|
||||
with open(vue_path, 'r', encoding='utf-8') as f:
|
||||
preview = f.read()[:500]
|
||||
print(f"{Fore.CYAN}Vue代码预览:\n{preview}...{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}❌ Vue文件生成失败{Style.RESET_ALL}")
|
||||
print_step(5, "生成Vue排版", "完成")
|
||||
vue_path = os.path.join(CONFIG_PATHS["output_folder"], "generated_code.vue")
|
||||
save_vue_file(vue_code, vue_path)
|
||||
print_step(5, "生成Vue组件", "完成")
|
||||
|
||||
print_step(6, "合成PSD文件")
|
||||
img_list = [(pic["picture_name"], pic["picture_type"]) for pic in parse_imglist]
|
||||
psd_path = os.path.join(config_paths["output_folder"], "final_poster.psd")
|
||||
result_path = create_psd_from_images_wrapper(
|
||||
img_list=img_list,
|
||||
vue_layout_path=vue_path,
|
||||
output_path=psd_path
|
||||
)
|
||||
psd_path = os.path.join(CONFIG_PATHS["output_folder"], "final_poster.psd")
|
||||
result_path = create_psd_from_fixed_images(psd_path)
|
||||
|
||||
if result_path:
|
||||
print_step(6, "合成PSD文件", "完成")
|
||||
@ -511,7 +213,7 @@ def run_pipeline(user_input=None):
|
||||
print(f"✅ 流程执行完成!")
|
||||
print(f"{'=' * 50}{Style.RESET_ALL}")
|
||||
|
||||
return os.path.join(config_paths["output_folder"], "final_poster.png")
|
||||
return result_path
|
||||
|
||||
except Exception as e:
|
||||
print_step("X", f"Pipeline执行", "错误")
|
||||
@ -521,33 +223,38 @@ def run_pipeline(user_input=None):
|
||||
return None
|
||||
|
||||
|
||||
# 本地运行函数
|
||||
def run_local_pipeline(user_input=None):
|
||||
def run_local_pipeline(user_input: str = None):
|
||||
"""
|
||||
本地运行整个管道流程,输出结果到控制台和文件系统。
|
||||
本地运行整个管道流程
|
||||
"""
|
||||
print(f"{Fore.CYAN}🎬 Starting local pipeline with input: {Style.BRIGHT}{user_input}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}🎬 启动本地流程,输入: {Style.BRIGHT}{user_input}{Style.RESET_ALL}")
|
||||
output_path = run_pipeline(user_input)
|
||||
|
||||
if output_path:
|
||||
print(f"{Fore.GREEN}🎊 Pipeline completed successfully!")
|
||||
print(f"{Fore.YELLOW}📁 Results saved to:")
|
||||
print(f" - Vue layout: {os.path.join(config_paths['output_folder'], 'generated_code.vue')}")
|
||||
print(f" - PSD file: {os.path.join(config_paths['output_folder'], 'final_poster.psd')}")
|
||||
print(f" - Content JSON: {os.path.join(config_paths['output_folder'], 'poster_content.json')}")
|
||||
print(f"{Fore.CYAN}💡 Check the outputs/ directory for generated files.{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}🎊 流程执行成功!")
|
||||
print(f"{Fore.YELLOW}📁 生成文件:")
|
||||
print(f" - Vue组件: {os.path.join(CONFIG_PATHS['output_folder'], 'generated_code.vue')}")
|
||||
print(f" - PSD文件: {os.path.join(CONFIG_PATHS['output_folder'], 'final_poster.psd')}")
|
||||
print(f" - 文案JSON: {os.path.join(CONFIG_PATHS['output_folder'], 'poster_content.json')}")
|
||||
print(f"{Fore.CYAN}💡 查看 outputs/ 目录获取生成的文件{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}❌ Pipeline执行失败{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}❌ 流程执行失败{Style.RESET_ALL}")
|
||||
|
||||
|
||||
# API路由
|
||||
# === API路由 ===
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {
|
||||
"message": "AI海报生成系统API",
|
||||
"version": "1.0.0",
|
||||
"message": "AI海报生成系统API v2.0",
|
||||
"version": "2.0.0",
|
||||
"features": [
|
||||
"预定义Vue模板支持",
|
||||
"简化的代码结构",
|
||||
"优化的流程管理"
|
||||
],
|
||||
"endpoints": {
|
||||
"generate_poster": "/api/generate-poster", # 主要接口
|
||||
"generate_poster": "/api/generate-poster",
|
||||
"download": "/api/download/{file_type}",
|
||||
"health": "/health",
|
||||
"status": "/api/status/{session_id}"
|
||||
@ -557,14 +264,13 @@ def read_root():
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
"""健康检查接口"""
|
||||
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
|
||||
|
||||
|
||||
@app.post("/api/generate-poster", response_model=ApiResponse)
|
||||
async def generate_poster_api(request: PosterRequest):
|
||||
"""
|
||||
一键生成完整海报(包含Vue代码和PSD文件)的主要API接口
|
||||
简化的海报生成API接口
|
||||
"""
|
||||
try:
|
||||
session_folder, session_id = get_session_folder(request.session_id)
|
||||
@ -573,93 +279,35 @@ async def generate_poster_api(request: PosterRequest):
|
||||
print(f"{Fore.CYAN}用户输入: {request.user_input}{Style.RESET_ALL}")
|
||||
|
||||
# === 步骤1: 生成配置文件 ===
|
||||
print(f"{Fore.BLUE}📋 步骤1: 生成配置文件{Style.RESET_ALL}")
|
||||
temp_prompts_path = os.path.join(session_folder, "temp_prompts.yaml")
|
||||
prompts_config = {
|
||||
"default_logo_text": "",
|
||||
"available_fonts": [
|
||||
{
|
||||
"name": "Microsoft YaHei",
|
||||
"displayName": "微软雅黑",
|
||||
"tags": ["现代", "清晰"],
|
||||
"roles": ["title", "subtitle", "content"]
|
||||
},
|
||||
{
|
||||
"name": "SimHei",
|
||||
"displayName": "黑体",
|
||||
"tags": ["通用", "标准"],
|
||||
"roles": ["title", "subtitle", "content"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
with open(temp_prompts_path, 'w', encoding='utf-8') as f:
|
||||
yaml.dump(prompts_config, f, allow_unicode=True, default_flow_style=False)
|
||||
|
||||
load_config_from_file(temp_prompts_path)
|
||||
temp_config_path = create_temp_config(request.user_input)
|
||||
load_config_from_file(temp_config_path)
|
||||
|
||||
# === 步骤2: 分析用户输入 ===
|
||||
print(f"{Fore.BLUE}📋 步骤2: 分析用户输入{Style.RESET_ALL}")
|
||||
user_input_analysis_result = llm_user_analysis(request.user_input)
|
||||
|
||||
# === 步骤3: 生成图片信息 ===
|
||||
print(f"{Fore.BLUE}📋 步骤3: 生成图片信息{Style.RESET_ALL}")
|
||||
system_prompt = user_input_analysis_result["analyzed_prompt"]
|
||||
parse_imglist = comfyui_img_info(user_input_analysis_result, system_prompt)
|
||||
|
||||
# === 步骤4: 生成文案建议 ===
|
||||
print(f"{Fore.BLUE}📋 步骤4: 生成文案建议{Style.RESET_ALL}")
|
||||
suggestions = get_poster_content_suggestions(user_input_analysis_result["analyzed_prompt"])
|
||||
|
||||
# 保存文案到会话文件夹
|
||||
suggestions_path = os.path.join(session_folder, "poster_content.json")
|
||||
with open(suggestions_path, "w", encoding="utf-8") as f:
|
||||
json.dump(suggestions, f, indent=2, ensure_ascii=False)
|
||||
save_json_file(suggestions, suggestions_path)
|
||||
|
||||
# === 步骤5: 生成Vue排版 ===
|
||||
print(f"{Fore.BLUE}📋 步骤5: 生成Vue排版{Style.RESET_ALL}")
|
||||
dynamic_prompt = generate_layout_prompt(user_input_analysis_result, parse_imglist, suggestions)
|
||||
vue_code = generate_vue_code(dynamic_prompt)
|
||||
# === 步骤5: 生成Vue组件 ===
|
||||
vue_code = generate_vue_code_enhanced(
|
||||
user_input_analysis_result,
|
||||
parse_imglist,
|
||||
suggestions
|
||||
)
|
||||
vue_path = os.path.join(session_folder, "generated_code.vue")
|
||||
save_code(vue_code, file_path=vue_path)
|
||||
save_vue_file(vue_code, vue_path)
|
||||
|
||||
# === 步骤6: 合成PSD文件 ===
|
||||
print(f"{Fore.BLUE}📋 步骤6: 合成PSD文件{Style.RESET_ALL}")
|
||||
img_list = [(pic["picture_name"], pic["picture_type"]) for pic in parse_imglist]
|
||||
psd_path = os.path.join(session_folder, "final_poster.psd")
|
||||
|
||||
# 修复PSD创建调用
|
||||
try:
|
||||
# 使用固定的图片文件列表
|
||||
fixed_image_files = ["background.png", "lotus.jpg", "nankai.png", "stamp.jpg"]
|
||||
image_paths = []
|
||||
|
||||
for img_file in fixed_image_files:
|
||||
img_path = os.path.join(config_paths["output_folder"], img_file)
|
||||
if os.path.exists(img_path):
|
||||
image_paths.append(img_path)
|
||||
|
||||
if image_paths:
|
||||
# 确保输出目录存在
|
||||
os.makedirs(os.path.dirname(psd_path), exist_ok=True)
|
||||
|
||||
# 调用PSD创建函数
|
||||
create_psd_impl(
|
||||
image_paths=image_paths,
|
||||
output_path=psd_path,
|
||||
canvas_size=(1080, 1920),
|
||||
mode='RGB'
|
||||
)
|
||||
|
||||
print(f"{Fore.GREEN}✅ PSD文件创建成功: {psd_path}{Style.RESET_ALL}")
|
||||
psd_created = True
|
||||
else:
|
||||
print(f"{Fore.YELLOW}⚠️ 没有找到图片文件,跳过PSD创建{Style.RESET_ALL}")
|
||||
psd_created = False
|
||||
|
||||
except Exception as psd_error:
|
||||
print(f"{Fore.RED}❌ PSD文件创建失败: {str(psd_error)}{Style.RESET_ALL}")
|
||||
psd_created = False
|
||||
psd_created = create_psd_from_fixed_images(psd_path)
|
||||
|
||||
# 返回API响应
|
||||
return ApiResponse(
|
||||
@ -671,7 +319,10 @@ async def generate_poster_api(request: PosterRequest):
|
||||
"content_file": suggestions_path,
|
||||
"analysis_result": user_input_analysis_result,
|
||||
"images_info": parse_imglist,
|
||||
"suggestions": suggestions
|
||||
"suggestions": suggestions,
|
||||
"vue_code": vue_code,
|
||||
"file_size_mb": round(os.path.getsize(psd_path) / (1024 * 1024), 2) if psd_created else 0,
|
||||
"generated_images": len(parse_imglist)
|
||||
},
|
||||
session_id=session_id
|
||||
)
|
||||
@ -691,62 +342,46 @@ async def generate_poster_api(request: PosterRequest):
|
||||
|
||||
@app.get("/api/download/{file_type}")
|
||||
async def download_file(file_type: str, session_id: str = None):
|
||||
"""
|
||||
下载生成的文件
|
||||
"""
|
||||
"""下载生成的文件"""
|
||||
try:
|
||||
if session_id:
|
||||
session_folder = os.path.join(config_paths["output_folder"], f"session_{session_id}")
|
||||
session_folder = os.path.join(CONFIG_PATHS["output_folder"], f"session_{session_id}")
|
||||
else:
|
||||
session_folder = config_paths["output_folder"]
|
||||
session_folder = CONFIG_PATHS["output_folder"]
|
||||
|
||||
if file_type == "vue":
|
||||
file_path = os.path.join(session_folder, "generated_code.vue")
|
||||
media_type = "text/plain"
|
||||
elif file_type == "psd":
|
||||
file_path = os.path.join(session_folder, "final_poster.psd")
|
||||
media_type = "application/octet-stream"
|
||||
elif file_type == "json":
|
||||
file_path = os.path.join(session_folder, "poster_content.json")
|
||||
media_type = "application/json"
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"error": "不支持的文件类型"}
|
||||
)
|
||||
file_mapping = {
|
||||
"vue": ("generated_code.vue", "text/plain"),
|
||||
"psd": ("final_poster.psd", "application/octet-stream"),
|
||||
"json": ("poster_content.json", "application/json")
|
||||
}
|
||||
|
||||
if file_type not in file_mapping:
|
||||
return JSONResponse(status_code=400, content={"error": "不支持的文件类型"})
|
||||
|
||||
filename, media_type = file_mapping[file_type]
|
||||
file_path = os.path.join(session_folder, filename)
|
||||
|
||||
if os.path.exists(file_path):
|
||||
return FileResponse(
|
||||
path=file_path,
|
||||
media_type=media_type,
|
||||
filename=os.path.basename(file_path)
|
||||
filename=filename
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={"error": "文件不存在"}
|
||||
)
|
||||
return JSONResponse(status_code=404, content={"error": "文件不存在"})
|
||||
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"error": f"下载失败: {str(e)}"}
|
||||
)
|
||||
return JSONResponse(status_code=500, content={"error": f"下载失败: {str(e)}"})
|
||||
|
||||
|
||||
@app.get("/api/status/{session_id}")
|
||||
async def get_session_status(session_id: str):
|
||||
"""
|
||||
获取会话状态
|
||||
"""
|
||||
"""获取会话状态"""
|
||||
try:
|
||||
session_folder = os.path.join(config_paths["output_folder"], f"session_{session_id}")
|
||||
session_folder = os.path.join(CONFIG_PATHS["output_folder"], f"session_{session_id}")
|
||||
|
||||
if not os.path.exists(session_folder):
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={"error": "会话不存在"}
|
||||
)
|
||||
return JSONResponse(status_code=404, content={"error": "会话不存在"})
|
||||
|
||||
files_status = {
|
||||
"vue_file": os.path.exists(os.path.join(session_folder, "generated_code.vue")),
|
||||
@ -761,16 +396,12 @@ async def get_session_status(session_id: str):
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"error": f"状态查询失败: {str(e)}"}
|
||||
)
|
||||
return JSONResponse(status_code=500, content={"error": f"状态查询失败: {str(e)}"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
# 启动本地运行(可选)
|
||||
print(f"{Fore.BLUE}🔧 运行模式选择:{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}1. 本地测试模式")
|
||||
print(f"2. API服务器模式{Style.RESET_ALL}")
|
||||
@ -781,9 +412,9 @@ if __name__ == "__main__":
|
||||
# 本地测试
|
||||
test_input = input("请输入海报需求 (留空使用默认): ").strip()
|
||||
if not test_input:
|
||||
test_input = "端午节安康,"
|
||||
test_input = "端午节海报,传统风格"
|
||||
run_local_pipeline(test_input)
|
||||
else:
|
||||
# 启动API服务器
|
||||
print(f"{Fore.GREEN}🚀 启动API服务器...{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}🚀 启动API服务器 v2.0...{Style.RESET_ALL}")
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
|
266
scripts/utils.py
Normal file
266
scripts/utils.py
Normal file
@ -0,0 +1,266 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
工具函数模块
|
||||
整合所有辅助函数和公共功能
|
||||
"""
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
from colorama import init, Fore, Style
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
# 初始化colorama
|
||||
init(autoreset=True)
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
# 配置路径
|
||||
CONFIG_PATHS = {
|
||||
"font": "../configs/font.yaml",
|
||||
"output_folder": "../outputs/",
|
||||
"workflows": "../workflows/",
|
||||
"templates": "../configs/templates.yaml"
|
||||
}
|
||||
|
||||
|
||||
def print_step(step_num: int, description: str, status: str = "进行中"):
|
||||
"""打印带颜色的步骤信息"""
|
||||
if status == "进行中":
|
||||
print(f"{Fore.BLUE}📋 步骤{step_num}: {description}...{Style.RESET_ALL}")
|
||||
elif status == "完成":
|
||||
print(f"{Fore.GREEN}✅ 步骤{step_num}: {description} - 完成{Style.RESET_ALL}")
|
||||
elif status == "错误":
|
||||
print(f"{Fore.RED}❌ 步骤{step_num}: {description} - 出错{Style.RESET_ALL}")
|
||||
|
||||
|
||||
def print_result(key: str, value: str):
|
||||
"""打印结果信息"""
|
||||
print(f"{Fore.CYAN}📊 {key}: {value}{Style.RESET_ALL}")
|
||||
|
||||
|
||||
def get_session_folder(session_id: Optional[str] = None) -> Tuple[str, str]:
|
||||
"""获取会话专用的输出文件夹"""
|
||||
if not session_id:
|
||||
session_id = str(uuid.uuid4())
|
||||
|
||||
session_folder = os.path.join(CONFIG_PATHS["output_folder"], f"session_{session_id}")
|
||||
os.makedirs(session_folder, exist_ok=True)
|
||||
return session_folder, session_id
|
||||
|
||||
|
||||
def load_config_file(config_type: str) -> Dict:
|
||||
"""加载配置文件"""
|
||||
config_path = CONFIG_PATHS.get(config_type)
|
||||
if not config_path or not os.path.exists(config_path):
|
||||
print(f"{Fore.YELLOW}⚠️ 配置文件不存在: {config_path},使用默认配置{Style.RESET_ALL}")
|
||||
return {}
|
||||
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
if config_path.endswith('.yaml') or config_path.endswith('.yml'):
|
||||
config = yaml.safe_load(f)
|
||||
else:
|
||||
config = json.load(f)
|
||||
print(f"{Fore.GREEN}✅ 配置文件加载成功: {config_path}{Style.RESET_ALL}")
|
||||
return config
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}❌ 配置文件加载失败: {e}{Style.RESET_ALL}")
|
||||
return {}
|
||||
|
||||
|
||||
def save_json_file(data: Dict, file_path: str):
|
||||
"""保存JSON文件"""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
print(f"{Fore.GREEN}✅ JSON文件已保存: {file_path}{Style.RESET_ALL}")
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}❌ JSON文件保存失败: {e}{Style.RESET_ALL}")
|
||||
|
||||
|
||||
def save_vue_file(vue_code: str, file_path: str):
|
||||
"""保存Vue文件"""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(vue_code)
|
||||
|
||||
if os.path.exists(file_path):
|
||||
file_size = os.path.getsize(file_path)
|
||||
print(f"{Fore.GREEN}✅ Vue文件已保存: {file_path} ({file_size} 字节){Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}❌ Vue文件保存失败{Style.RESET_ALL}")
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}❌ Vue文件保存失败: {e}{Style.RESET_ALL}")
|
||||
|
||||
|
||||
def create_temp_config(user_input: str = None) -> str:
|
||||
"""动态生成临时配置文件"""
|
||||
if not user_input:
|
||||
user_input = "默认海报配置"
|
||||
|
||||
temp_config = {
|
||||
"default_logo_text": "",
|
||||
"available_fonts": [
|
||||
{
|
||||
"name": "Microsoft YaHei",
|
||||
"displayName": "微软雅黑",
|
||||
"tags": ["现代", "清晰"],
|
||||
"roles": ["title", "subtitle", "content"]
|
||||
},
|
||||
{
|
||||
"name": "SimHei",
|
||||
"displayName": "黑体",
|
||||
"tags": ["通用", "标准"],
|
||||
"roles": ["title", "subtitle", "content"]
|
||||
}
|
||||
],
|
||||
"vue_templates": {
|
||||
"lotus.jpg": {
|
||||
"theme": "荷花主题",
|
||||
"style": "传统优雅",
|
||||
"template_name": "lotus_template"
|
||||
},
|
||||
"nku.png": {
|
||||
"theme": "南开大学",
|
||||
"style": "学术正式",
|
||||
"template_name": "nku_template"
|
||||
},
|
||||
"stamp.jpg": {
|
||||
"theme": "印章装饰",
|
||||
"style": "传统文化",
|
||||
"template_name": "stamp_template"
|
||||
},
|
||||
"background.png": {
|
||||
"theme": "通用背景",
|
||||
"style": "简约现代",
|
||||
"template_name": "background_template"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
temp_config_path = os.path.join(CONFIG_PATHS["output_folder"], "temp_config.yaml")
|
||||
with open(temp_config_path, 'w', encoding='utf-8') as f:
|
||||
yaml.dump(temp_config, f, allow_unicode=True, default_flow_style=False)
|
||||
|
||||
print(f"{Fore.GREEN}✅ 临时配置文件已生成: {temp_config_path}{Style.RESET_ALL}")
|
||||
return temp_config_path
|
||||
|
||||
|
||||
# === 用户输入分析模块 (从 prompt_analysis.py 整合) ===
|
||||
|
||||
from generate_layout import call_deepseek
|
||||
|
||||
def llm_user_analysis(user_input: str) -> Dict:
|
||||
"""
|
||||
使用DeepSeek动态分析用户输入,提取海报设计参数
|
||||
"""
|
||||
if not user_input:
|
||||
user_input = "端午节海报,包含背景、活动亮点和图标"
|
||||
|
||||
print(f"{Fore.CYAN}🔍 正在分析用户输入: {Style.BRIGHT}{user_input}{Style.RESET_ALL}")
|
||||
|
||||
# 构建分析提示词
|
||||
analysis_prompt = f"""
|
||||
请分析以下用户输入的海报需求,提取关键信息并以JSON格式返回:
|
||||
|
||||
用户输入:{user_input}
|
||||
|
||||
请严格按照以下JSON格式返回:
|
||||
{{
|
||||
"analyzed_prompt": "原始用户输入",
|
||||
"keywords": ["提取的关键词1", "关键词2", "关键词3"],
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"batch_size": 2,
|
||||
"poster_type": "海报类型(如:节日海报、活动海报、产品海报等)",
|
||||
"main_theme": "主要主题",
|
||||
"style_preference": "风格偏好(如:现代、传统、简约等)",
|
||||
"color_preference": "颜色偏好(如:暖色调、冷色调、单色等)"
|
||||
}}
|
||||
|
||||
注意:
|
||||
1. keywords应该包含3-5个最重要的关键词
|
||||
2. 根据用户输入推断合适的海报类型
|
||||
3. 尺寸默认为1080x1920,除非用户明确指定
|
||||
4. batch_size根据需求调整,通常为1-4
|
||||
5. 分析用户的风格和颜色偏好
|
||||
"""
|
||||
|
||||
system_prompt = "你是一个专业的设计需求分析师,擅长从用户描述中提取海报设计的关键参数。请严格按照JSON格式返回结果,确保输出的JSON格式正确且完整。"
|
||||
|
||||
try:
|
||||
result, _ = call_deepseek(prompt=analysis_prompt, system_prompt=system_prompt, temperature=0.3)
|
||||
|
||||
# 解析JSON
|
||||
json_str = result.strip()
|
||||
if "```json" in json_str:
|
||||
json_str = json_str.split("```json")[1].split("```")[0].strip()
|
||||
elif json_str.startswith("```") and json_str.endswith("```"):
|
||||
json_str = json_str[3:-3].strip()
|
||||
|
||||
analysis_result = json.loads(json_str)
|
||||
|
||||
print(f"{Fore.GREEN}✅ 分析完成! {Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}📊 主题: {Style.BRIGHT}{analysis_result.get('main_theme', '未知')}{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}🎨 风格: {analysis_result.get('style_preference', '未设置')}")
|
||||
print(f"{Fore.YELLOW}🔖 关键词: {', '.join(analysis_result.get('keywords', []))}{Style.RESET_ALL}")
|
||||
|
||||
return analysis_result
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}❌ 分析失败: {str(e)}{Style.RESET_ALL}")
|
||||
# 返回默认值
|
||||
return {
|
||||
"analyzed_prompt": user_input,
|
||||
"keywords": ["海报", "设计", "活动"],
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"batch_size": 2,
|
||||
"poster_type": "通用海报",
|
||||
"main_theme": "默认主题",
|
||||
"style_preference": "现代",
|
||||
"color_preference": "暖色调"
|
||||
}
|
||||
|
||||
|
||||
def validate_file_exists(file_path: str) -> bool:
|
||||
"""验证文件是否存在"""
|
||||
exists = os.path.exists(file_path)
|
||||
if exists:
|
||||
print(f"{Fore.GREEN}✓ 文件存在: {os.path.basename(file_path)}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.YELLOW}⚠️ 文件不存在: {os.path.basename(file_path)}{Style.RESET_ALL}")
|
||||
return exists
|
||||
|
||||
|
||||
def get_file_info(file_path: str) -> Dict:
|
||||
"""获取文件信息"""
|
||||
if not os.path.exists(file_path):
|
||||
return {"exists": False}
|
||||
|
||||
stat = os.stat(file_path)
|
||||
return {
|
||||
"exists": True,
|
||||
"size": stat.st_size,
|
||||
"size_mb": round(stat.st_size / (1024 * 1024), 2),
|
||||
"modified": datetime.fromtimestamp(stat.st_mtime).isoformat()
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试工具函数
|
||||
print(f"{Fore.MAGENTA}🧪 测试工具函数{Style.RESET_ALL}")
|
||||
|
||||
# 测试用户输入分析
|
||||
test_input = "春节海报,红色背景,现代风格"
|
||||
result = llm_user_analysis(test_input)
|
||||
print(f"\n{Fore.GREEN}分析结果:")
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
@ -0,0 +1,226 @@
|
||||
{
|
||||
"6": {
|
||||
"inputs": {
|
||||
"text": [
|
||||
"31",
|
||||
0
|
||||
],
|
||||
"clip": [
|
||||
"11",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": {
|
||||
"title": "CLIP Text Encode (Positive Prompt)"
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"inputs": {
|
||||
"samples": [
|
||||
"13",
|
||||
0
|
||||
],
|
||||
"vae": [
|
||||
"10",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "VAEDecode",
|
||||
"_meta": {
|
||||
"title": "VAE解码"
|
||||
}
|
||||
},
|
||||
"9": {
|
||||
"inputs": {
|
||||
"filename_prefix": "ComfyUI",
|
||||
"images": [
|
||||
"8",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "SaveImage",
|
||||
"_meta": {
|
||||
"title": "保存图像"
|
||||
}
|
||||
},
|
||||
"10": {
|
||||
"inputs": {
|
||||
"vae_name": "ae.safetensors"
|
||||
},
|
||||
"class_type": "VAELoader",
|
||||
"_meta": {
|
||||
"title": "加载VAE"
|
||||
}
|
||||
},
|
||||
"11": {
|
||||
"inputs": {
|
||||
"clip_name1": "t5xxl_fp16.safetensors",
|
||||
"clip_name2": "clip_l.safetensors",
|
||||
"type": "flux",
|
||||
"device": "default"
|
||||
},
|
||||
"class_type": "DualCLIPLoader",
|
||||
"_meta": {
|
||||
"title": "双CLIP加载器"
|
||||
}
|
||||
},
|
||||
"12": {
|
||||
"inputs": {
|
||||
"unet_name": "flux1-dev.safetensors",
|
||||
"weight_dtype": "default"
|
||||
},
|
||||
"class_type": "UNETLoader",
|
||||
"_meta": {
|
||||
"title": "UNet加载器"
|
||||
}
|
||||
},
|
||||
"13": {
|
||||
"inputs": {
|
||||
"noise": [
|
||||
"25",
|
||||
0
|
||||
],
|
||||
"guider": [
|
||||
"22",
|
||||
0
|
||||
],
|
||||
"sampler": [
|
||||
"16",
|
||||
0
|
||||
],
|
||||
"sigmas": [
|
||||
"17",
|
||||
0
|
||||
],
|
||||
"latent_image": [
|
||||
"27",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "SamplerCustomAdvanced",
|
||||
"_meta": {
|
||||
"title": "自定义采样器(高级)"
|
||||
}
|
||||
},
|
||||
"16": {
|
||||
"inputs": {
|
||||
"sampler_name": "euler"
|
||||
},
|
||||
"class_type": "KSamplerSelect",
|
||||
"_meta": {
|
||||
"title": "K采样器选择"
|
||||
}
|
||||
},
|
||||
"17": {
|
||||
"inputs": {
|
||||
"scheduler": "simple",
|
||||
"steps": 25,
|
||||
"denoise": 1,
|
||||
"model": [
|
||||
"30",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "BasicScheduler",
|
||||
"_meta": {
|
||||
"title": "基本调度器"
|
||||
}
|
||||
},
|
||||
"22": {
|
||||
"inputs": {
|
||||
"model": [
|
||||
"30",
|
||||
0
|
||||
],
|
||||
"conditioning": [
|
||||
"26",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "BasicGuider",
|
||||
"_meta": {
|
||||
"title": "基本引导器"
|
||||
}
|
||||
},
|
||||
"25": {
|
||||
"inputs": {
|
||||
"noise_seed": 142213168350829
|
||||
},
|
||||
"class_type": "RandomNoise",
|
||||
"_meta": {
|
||||
"title": "随机噪波"
|
||||
}
|
||||
},
|
||||
"26": {
|
||||
"inputs": {
|
||||
"guidance": 3.5,
|
||||
"conditioning": [
|
||||
"6",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "FluxGuidance",
|
||||
"_meta": {
|
||||
"title": "Flux引导"
|
||||
}
|
||||
},
|
||||
"27": {
|
||||
"inputs": {
|
||||
"width": "1080",
|
||||
"height": "1920",
|
||||
"batch_size": "2"
|
||||
},
|
||||
"class_type": "EmptySD3LatentImage",
|
||||
"_meta": {
|
||||
"title": "空Latent图像(SD3)"
|
||||
}
|
||||
},
|
||||
"30": {
|
||||
"inputs": {
|
||||
"max_shift": 1.1500000000000001,
|
||||
"base_shift": 0.5000000000000001,
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"model": [
|
||||
"12",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "ModelSamplingFlux",
|
||||
"_meta": {
|
||||
"title": "采样算法(Flux)"
|
||||
}
|
||||
},
|
||||
"31": {
|
||||
"inputs": {
|
||||
"user_prompt": "作为一个AI提示词专家,请你仿照范例,根据我给出的主题,生成一条符合下列要求的提示词,来让CLIP模型可以更好地理解画面主体。注意:你需要仿照下面的示例详细分析(仅仿照写法,而不仿照任何内容),将用户的需求转化为详细的提示词。\\n 要求共六条,请严格遵守:\\n 1: 用自然语言简单句来描述画面,请避免出现过于长的,或者格式过于复杂的句子,句子中不要出现*等特殊符号。\\n 2.用英语表达。 \\n 3.直接给出prompt内容即可,不需要任何解释和说明。\\n 4. 每条prompt至少50词,不超过200词。\\n 5.避免模棱两可的说法。\\n 6.描述的最开始加入:“no text, no AI style”\\n 例如:\nCartoon-Style Nankai University Main Building,\nvividly depicted with rounded edges and pastel gradients, the iconic Gothic-Revival structure stands majestically. Crimson brick façade contrasts with golden-glowing arched windows, while whimsical cloud-shaped eaves drip melted clock details. A giant smiling sun hangs low, casting honey-golden rays through simplified pine trees, creating striped shadows dancing on marble stairs.\n\nTranslucent ghostly scholars from 1919 float near pillars holding glowing books, their outlines shimmering like liquid mercury. Oversized autumn leaves (stylized as maple-red origami) spiral around twin bell towers chiming visible musical notes. Puddles on the ground mirror upside-down building reflections rippling with calculus formulas.\n\nEnvironment:\nSurreal candy-pink sunset gradients blend into starry indigo sky above. Playful squirrels wearing tiny graduation caps scamper across emerald lawns textured like green velvet.",
|
||||
"system_prompt": "端午节海报,传统风格",
|
||||
"enable_history": false,
|
||||
"max_history": 10,
|
||||
"history_json": "",
|
||||
"save_path": "./chat_history.json",
|
||||
"api_config": [
|
||||
"32",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "DeepSeekChat",
|
||||
"_meta": {
|
||||
"title": "LLM Model Input Box"
|
||||
}
|
||||
},
|
||||
"32": {
|
||||
"inputs": {
|
||||
"config_name": "DeepSeek官方Chat API",
|
||||
"temperature": 1,
|
||||
"max_tokens": 512,
|
||||
"stream": false,
|
||||
"api_key": ""
|
||||
},
|
||||
"class_type": "DeepSeekAPIConfig",
|
||||
"_meta": {
|
||||
"title": "LLM API Model Selector"
|
||||
}
|
||||
}
|
||||
}
|
293
接口文档.md
293
接口文档.md
@ -1,26 +1,18 @@
|
||||
# AI海报生成系统 接口文档
|
||||
|
||||
## 概述
|
||||
|
||||
AI海报生成系统是一个基于FastAPI的Web服务,集成了多个AI模型和图像处理功能,能够一键生成Vue组件代码和PSD文件。
|
||||
# AI海报生成系统 API文档
|
||||
|
||||
## 系统架构
|
||||
|
||||
```
|
||||
前端 → FastAPI服务器(8000端口) → ComfyUI服务器(101.201.50.90:8188)
|
||||
前端 → FastAPI(8000) → ComfyUI(101.201.50.90:8188)
|
||||
↓
|
||||
Vue代码生成 + PSD文件合成
|
||||
Vue代码 + PSD文件
|
||||
```
|
||||
|
||||
## API端点
|
||||
|
||||
### 1. 健康检查
|
||||
|
||||
**GET** `/health`
|
||||
|
||||
**功能**: 检查服务器状态
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
@ -28,17 +20,14 @@ AI海报生成系统是一个基于FastAPI的Web服务,集成了多个AI模型
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 主要接口 - 生成海报
|
||||
|
||||
### 2. 生成海报
|
||||
**POST** `/api/generate-poster`
|
||||
|
||||
**功能**: 一键生成完整海报(Vue代码 + PSD文件)
|
||||
|
||||
**请求体**:
|
||||
**请求**:
|
||||
```json
|
||||
{
|
||||
"user_input": "端午节海报,传统风格,包含荷花和龙舟",
|
||||
"session_id": "可选-用于跟踪会话"
|
||||
"user_input": "端午节海报,传统风格",
|
||||
"session_id": "可选"
|
||||
}
|
||||
```
|
||||
|
||||
@ -48,14 +37,14 @@ AI海报生成系统是一个基于FastAPI的Web服务,集成了多个AI模型
|
||||
"status": "success",
|
||||
"message": "海报生成完成",
|
||||
"data": {
|
||||
"vue_code": "完整的Vue组件代码",
|
||||
"vue_code": "完整Vue组件代码",
|
||||
"suggestions": {
|
||||
"layer5_logo_content": {
|
||||
"text": "主办方",
|
||||
"color": "#000000"
|
||||
},
|
||||
"layer6_title_content": {
|
||||
"content": "端午节安康",
|
||||
"content": "端午安康",
|
||||
"font_name": "SimHei",
|
||||
"color": "#7E0C6E"
|
||||
},
|
||||
@ -66,259 +55,97 @@ AI海报生成系统是一个基于FastAPI的Web服务,集成了多个AI模型
|
||||
}
|
||||
},
|
||||
"analysis_result": {
|
||||
"analyzed_prompt": "端午节海报,传统风格",
|
||||
"main_theme": "端午节祝福",
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"keywords": ["端午节", "传统", "荷花", "龙舟"]
|
||||
"keywords": ["端午节", "传统", "荷花"]
|
||||
},
|
||||
"psd_file_path": "生成的PSD文件路径",
|
||||
"file_size_mb": 5.93,
|
||||
"generated_images": 2,
|
||||
"files": {
|
||||
"vue_file": "Vue文件路径",
|
||||
"psd_file": "PSD文件路径"
|
||||
}
|
||||
"generated_images": 2
|
||||
},
|
||||
"session_id": "会话ID"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 文件下载
|
||||
|
||||
**GET** `/api/download/{file_type}?session_id={session_id}`
|
||||
|
||||
**功能**: 下载生成的文件
|
||||
|
||||
**参数**:
|
||||
- `file_type`: 文件类型 (`vue` | `psd` | `json`)
|
||||
- `session_id`: 会话ID(查询参数)
|
||||
|
||||
**响应**: 直接返回文件下载
|
||||
|
||||
### 4. 获取生成状态
|
||||
参数: `file_type` = `vue` | `psd` | `json`
|
||||
|
||||
### 4. 会话状态
|
||||
**GET** `/api/status/{session_id}`
|
||||
|
||||
**功能**: 获取指定会话的生成状态和信息
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "状态获取成功",
|
||||
"data": {
|
||||
"user_input": "用户输入",
|
||||
"analysis_result": "分析结果",
|
||||
"suggestions": "文案建议",
|
||||
"vue_path": "Vue文件路径",
|
||||
"psd_path": "PSD文件路径",
|
||||
"psd_size_mb": 5.93,
|
||||
"created_at": "2025-01-02T20:30:00"
|
||||
"session_id": "会话ID",
|
||||
"files": {
|
||||
"vue_file": true,
|
||||
"psd_file": true,
|
||||
"content_file": true
|
||||
},
|
||||
"session_id": "会话ID"
|
||||
"folder": "会话文件夹路径"
|
||||
}
|
||||
```
|
||||
|
||||
## 内部模块接口
|
||||
|
||||
### 1. prompt_analysis
|
||||
|
||||
**函数**: `llm_user_analysis(user_input)`
|
||||
|
||||
**功能**: 使用DeepSeek模型分析用户输入
|
||||
|
||||
**参数**:
|
||||
- `user_input` (str): 用户输入的海报需求
|
||||
|
||||
**返回值**:
|
||||
```json
|
||||
{
|
||||
"analyzed_prompt": "原始用户输入",
|
||||
"keywords": ["关键词1", "关键词2"],
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"batch_size": 2,
|
||||
"poster_type": "海报类型",
|
||||
"main_theme": "主要主题",
|
||||
"style_preference": "风格偏好",
|
||||
"color_preference": "颜色偏好"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. flux_con
|
||||
|
||||
**函数**: `comfyui_img_info(user_input_analysis_result, system_prompt)`
|
||||
|
||||
**功能**: 调用ComfyUI生成图片
|
||||
|
||||
**参数**:
|
||||
- `user_input_analysis_result`: 用户输入分析结果
|
||||
- `system_prompt`: 系统提示词
|
||||
|
||||
**返回值**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"picture_name": "图片文件名",
|
||||
"picture_type": "png",
|
||||
"picture_description": "图片描述",
|
||||
"picture_size": "1080x1920"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 3. generate_text
|
||||
|
||||
**函数**: `get_poster_content_suggestions(analyzed_prompt)`
|
||||
|
||||
**功能**: 使用Kimi生成文案建议
|
||||
|
||||
**参数**:
|
||||
- `analyzed_prompt` (str): 分析后的提示词
|
||||
|
||||
**返回值**:
|
||||
```json
|
||||
{
|
||||
"layer5_logo_content": {
|
||||
"text": "Logo文字",
|
||||
"color": "#000000"
|
||||
},
|
||||
"layer6_title_content": {
|
||||
"content": "主标题",
|
||||
"font_name": "字体名称",
|
||||
"color": "#颜色代码"
|
||||
},
|
||||
"layer7_subtitle_content": {
|
||||
"content": "副标题",
|
||||
"font_name": "字体名称",
|
||||
"color": "#颜色代码"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. generate_layout
|
||||
|
||||
**函数**: `generate_vue_code(prompt)`
|
||||
|
||||
**功能**: 使用DeepSeek生成Vue组件代码
|
||||
|
||||
**参数**:
|
||||
- `prompt` (str): 包含布局要求的提示词
|
||||
|
||||
**返回值**: Vue组件代码字符串
|
||||
|
||||
### 5. export_psd_from_json
|
||||
|
||||
**函数**: `create_psd_from_images(image_paths, output_path, canvas_size, mode)`
|
||||
|
||||
**功能**: 创建PSD文件
|
||||
|
||||
**参数**:
|
||||
- `image_paths` (List[str]): 图片路径列表
|
||||
- `output_path` (str): 输出PSD文件路径
|
||||
- `canvas_size` (Tuple[int, int]): 画布大小
|
||||
- `mode` (str): 颜色模式
|
||||
|
||||
## 错误处理
|
||||
|
||||
**错误响应格式**:
|
||||
```json
|
||||
{
|
||||
"detail": "错误描述信息"
|
||||
}
|
||||
```
|
||||
|
||||
**常见错误码**:
|
||||
- `400`: 请求参数错误
|
||||
- `404`: 资源不存在(会话不存在、文件不存在)
|
||||
- `500`: 服务器内部错误
|
||||
|
||||
## 使用示例
|
||||
|
||||
### JavaScript调用示例
|
||||
|
||||
### JavaScript
|
||||
```javascript
|
||||
// 生成海报
|
||||
async function generatePoster(userInput) {
|
||||
const response = await fetch('http://localhost:8000/api/generate-poster', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ user_input: userInput })
|
||||
});
|
||||
const response = await fetch('http://localhost:8000/api/generate-poster', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ user_input: "春节海报,红色背景" })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
console.log('Vue代码:', result.data.vue_code);
|
||||
console.log('文案建议:', result.data.suggestions);
|
||||
|
||||
// 下载文件
|
||||
downloadFile(result.session_id, 'vue');
|
||||
downloadFile(result.session_id, 'psd');
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
// 下载文件
|
||||
const url = `http://localhost:8000/api/download/psd?session_id=${result.session_id}`;
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
function downloadFile(sessionId, fileType) {
|
||||
const url = `http://localhost:8000/api/download/${fileType}?session_id=${sessionId}`;
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
generatePoster("春节海报,红色背景,现代风格");
|
||||
```
|
||||
|
||||
### Python调用示例
|
||||
|
||||
### Python
|
||||
```python
|
||||
import requests
|
||||
|
||||
# 生成海报
|
||||
def generate_poster(user_input):
|
||||
url = "http://localhost:8000/api/generate-poster"
|
||||
data = {"user_input": user_input}
|
||||
response = requests.post('http://localhost:8000/api/generate-poster',
|
||||
json={"user_input": "端午节海报,传统风格"})
|
||||
result = response.json()
|
||||
|
||||
response = requests.post(url, json=data)
|
||||
result = response.json()
|
||||
|
||||
if result["status"] == "success":
|
||||
print("Vue代码:", result["data"]["vue_code"])
|
||||
session_id = result["session_id"]
|
||||
|
||||
# 下载PSD文件
|
||||
download_url = f"http://localhost:8000/api/download/psd?session_id={session_id}"
|
||||
psd_response = requests.get(download_url)
|
||||
|
||||
with open("poster.psd", "wb") as f:
|
||||
f.write(psd_response.content)
|
||||
|
||||
# 使用示例
|
||||
generate_poster("端午节海报,传统风格")
|
||||
if result["status"] == "success":
|
||||
session_id = result["session_id"]
|
||||
# 下载PSD文件
|
||||
psd_url = f"http://localhost:8000/api/download/psd?session_id={session_id}"
|
||||
psd_response = requests.get(psd_url)
|
||||
with open("poster.psd", "wb") as f:
|
||||
f.write(psd_response.content)
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
## 错误处理
|
||||
|
||||
1. **接收请求**: 前端发送用户输入到 `/api/generate-poster`
|
||||
2. **分析输入**: 使用DeepSeek分析用户需求
|
||||
3. **生成图片**: 调用ComfyUI(101.201.50.90:8188)生成图片
|
||||
4. **生成文案**: 使用Kimi生成文案建议
|
||||
5. **生成Vue代码**: 使用DeepSeek生成Vue组件
|
||||
6. **创建PSD**: 合成PSD文件(优先使用手动模板)
|
||||
7. **返回结果**: 一次性返回所有生成内容
|
||||
8. **文件下载**: 前端可按需下载生成的文件
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "错误描述",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 部署说明
|
||||
常见错误码:
|
||||
- `400`: 请求参数错误
|
||||
- `404`: 资源不存在
|
||||
- `500`: 服务器内部错误
|
||||
|
||||
## 部署
|
||||
|
||||
### 开发环境
|
||||
```bash
|
||||
cd E:\砚生\ai_service\scripts
|
||||
python run_pipeline.py
|
||||
# 选择: 2 (API服务器模式)
|
||||
cd scripts
|
||||
python run_pipeline.py # 选择2
|
||||
```
|
||||
|
||||
### 生产环境
|
||||
- 使用PM2管理进程
|
||||
- 配置Nginx反向代理
|
||||
- 设置CORS允许的域名
|
||||
- 配置HTTPS
|
||||
```bash
|
||||
uvicorn scripts.run_pipeline:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
243
部署方案.md
243
部署方案.md
@ -1,243 +0,0 @@
|
||||
# AI海报生成系统 - 简化部署方案
|
||||
|
||||
## 🎯 核心架构
|
||||
|
||||
```
|
||||
前端 → API服务器(8000端口) → ComfyUI服务器(101.201.50.90:8188)
|
||||
```
|
||||
|
||||
## 🚀 简化API设计
|
||||
|
||||
### 主要接口(只需要这一个)
|
||||
|
||||
**POST** `/api/generate-poster`
|
||||
|
||||
**请求:**
|
||||
```json
|
||||
{
|
||||
"user_input": "端午节海报,传统风格,包含荷花和龙舟"
|
||||
}
|
||||
```
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "海报生成完成",
|
||||
"data": {
|
||||
"vue_code": "完整的Vue组件代码",
|
||||
"suggestions": {
|
||||
"layer6_title_content": {"content": "端午节安康", "font_name": "SimHei"},
|
||||
"layer7_subtitle_content": {"content": "粽叶飘香,龙舟竞渡"}
|
||||
},
|
||||
"psd_file_path": "生成的PSD文件路径",
|
||||
"file_size_mb": 5.93,
|
||||
"generated_images": 2
|
||||
},
|
||||
"session_id": "会话ID用于下载文件"
|
||||
}
|
||||
```
|
||||
|
||||
### 文件下载接口
|
||||
|
||||
**GET** `/api/download/{file_type}?session_id={session_id}`
|
||||
|
||||
- `file_type`: `vue` | `psd` | `json`
|
||||
|
||||
## 🔧 本地开发部署
|
||||
|
||||
### 1. 启动API服务器
|
||||
```bash
|
||||
cd E:\砚生\ai_service\scripts
|
||||
python run_pipeline.py
|
||||
# 选择: 2 (API服务器模式)
|
||||
```
|
||||
|
||||
服务器启动在: `http://localhost:8000`
|
||||
|
||||
### 2. 前端调用示例
|
||||
|
||||
```javascript
|
||||
// 一键生成海报
|
||||
async function generatePoster(userInput) {
|
||||
try {
|
||||
const response = await fetch('http://localhost:8000/api/generate-poster', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_input: userInput
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
console.log('Vue代码:', result.data.vue_code);
|
||||
console.log('PSD文件大小:', result.data.file_size_mb, 'MB');
|
||||
|
||||
// 下载Vue文件
|
||||
downloadFile(result.session_id, 'vue');
|
||||
// 下载PSD文件
|
||||
downloadFile(result.session_id, 'psd');
|
||||
|
||||
return result;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('生成失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
function downloadFile(sessionId, fileType) {
|
||||
const url = `http://localhost:8000/api/download/${fileType}?session_id=${sessionId}`;
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = '';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
generatePoster("春节海报,红色背景,包含灯笼");
|
||||
```
|
||||
|
||||
## 🌐 生产环境部署
|
||||
|
||||
### 方案1: 单服务器部署(推荐)
|
||||
|
||||
```bash
|
||||
# 1. 安装依赖
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 2. 配置环境变量
|
||||
cp .env.example .env
|
||||
# 编辑.env文件,设置API密钥
|
||||
|
||||
# 3. 启动服务
|
||||
python run_pipeline.py
|
||||
# 选择: 2 (API服务器模式)
|
||||
|
||||
# 4. 使用PM2管理进程(可选)
|
||||
npm install -g pm2
|
||||
pm2 start "python run_pipeline.py --mode=api" --name="poster-api"
|
||||
```
|
||||
|
||||
### 方案2: Docker部署
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM python:3.11
|
||||
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["python", "scripts/run_pipeline.py", "--mode=api"]
|
||||
```
|
||||
|
||||
```bash
|
||||
# 构建和运行
|
||||
docker build -t poster-api .
|
||||
docker run -p 8000:8000 poster-api
|
||||
```
|
||||
|
||||
### 方案3: 云服务器部署
|
||||
|
||||
```bash
|
||||
# 在云服务器上
|
||||
git clone <你的仓库>
|
||||
cd ai_service
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 配置防火墙开放8000端口
|
||||
sudo ufw allow 8000
|
||||
|
||||
# 启动服务
|
||||
python scripts/run_pipeline.py
|
||||
```
|
||||
|
||||
## 🔒 生产环境配置
|
||||
|
||||
### 1. 修改CORS设置
|
||||
```python
|
||||
# 在run_pipeline.py中修改
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["https://your-frontend-domain.com"], # 改为你的前端域名
|
||||
allow_credentials=True,
|
||||
allow_methods=["GET", "POST"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
```
|
||||
|
||||
### 2. 添加API认证(可选)
|
||||
```python
|
||||
from fastapi.security import HTTPBearer
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
@app.post("/api/generate-poster")
|
||||
async def generate_poster_api(request: PosterRequest, token: str = Depends(security)):
|
||||
# 验证token
|
||||
if not verify_token(token.credentials):
|
||||
raise HTTPException(status_code=401, detail="Invalid token")
|
||||
# ...existing code...
|
||||
```
|
||||
|
||||
### 3. 配置HTTPS(Nginx反向代理)
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 性能监控
|
||||
|
||||
### 健康检查
|
||||
```bash
|
||||
curl http://localhost:8000/health
|
||||
```
|
||||
|
||||
### 状态监控
|
||||
```javascript
|
||||
// 检查生成状态
|
||||
async function checkStatus(sessionId) {
|
||||
const response = await fetch(`http://localhost:8000/api/status/${sessionId}`);
|
||||
return await response.json();
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 为什么使用单一API?
|
||||
|
||||
1. **简化前端调用** - 一次请求获得所有结果
|
||||
2. **减少网络延迟** - 避免多次HTTP请求
|
||||
3. **统一错误处理** - 所有错误在一个地方处理
|
||||
4. **会话管理** - 自动管理文件和状态
|
||||
5. **部署简单** - 只需要一个服务端点
|
||||
|
||||
## 🔄 工作流程
|
||||
|
||||
1. **前端发送请求** → `POST /api/generate-poster`
|
||||
2. **API自动执行**:
|
||||
- 调用DeepSeek分析用户输入
|
||||
- 调用ComfyUI(101.201.50.90:8188)生成图片
|
||||
- 调用Kimi生成文案
|
||||
- 生成Vue代码
|
||||
- 创建PSD文件(使用手动模板)
|
||||
3. **返回完整结果** → 前端获得所有内容
|
||||
4. **前端按需下载** → 使用session_id下载文件
|
||||
|
||||
这样设计的好处是前端只需要调用一个接口,等待完成后就能获得Vue代码和下载PSD文件,非常简单高效!
|
Loading…
Reference in New Issue
Block a user