├── requirements.txt ├── README.md ├── .gitignore ├── app.py ├── static └── css │ └── style.css └── templates └── index.html /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | requests 3 | python-dotenv -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image Generate Demo 2 | 3 | 这是一个使用 端脑云 DeepSeek R1 蒸馏模型和 Flux 模型生成图片的 Web 应用演示项目。 4 | 5 | 粉丝福利:现在用我的邀请链接 https://cephalon.cloud/share/register-landing?invite_id=X46Dzv 或者邀请码 X46Dzv,还有我专属的注册奖励,还可以额外免费送5000端脑值 6 | 7 | ## 功能特点 8 | 9 | - 基于文本提示生成图片 10 | - 可自定义图片尺寸 11 | 12 | ## 技术栈 13 | 14 | - Python Flask 作为后端框架 15 | - HTML/CSS 用于前端界面 16 | - ComfyUI 用于图片生成 17 | 18 | ## 安装和运行 19 | 20 | 1. 克隆项目仓库 21 | 2. 安装依赖: 22 | ```bash 23 | pip install -r requirements.txt 24 | ``` 25 | 3. 运行应用: 26 | ```bash 27 | python app.py 28 | ``` 29 | 4. 在浏览器中访问 `http://localhost:5000` 30 | 31 | ## 使用说明 32 | 33 | 1. 在文本框中输入你想要生成的图片描述 34 | 2. 根据需要调整参数 35 | 3. 点击生成按钮 36 | 4. 等待图片生成完成 37 | 38 | ## 目录结构 39 | 40 | ``` 41 | . 42 | ├── app.py # Flask 应用主文件 43 | ├── requirements.txt # 项目依赖 44 | ├── static/ # 静态资源目录 45 | │ ├── css/ # CSS 样式文件 46 | │ └── images/ # 生成的图片存储目录 47 | └── templates/ # HTML 模板目录 48 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Distribution / packaging 7 | .Python 8 | env/ 9 | venv/ 10 | ENV/ 11 | .env 12 | *.egg 13 | *.egg-info 14 | dist/ 15 | build/ 16 | eggs/ 17 | parts/ 18 | bin/ 19 | lib/ 20 | share/ 21 | sdist* 22 | *.whl 23 | *.zip 24 | *.tar.gz 25 | *.tar.bz2 26 | *.tar.xz 27 | 28 | # Installer logs 29 | pip-log.txt 30 | pip-delete-this.txt 31 | pip-wheel-metadata/ 32 | pip-download-cache/ 33 | *.dist-info/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.pyd 37 | *.so 38 | 39 | # Environments 40 | .env 41 | .venv 42 | venv 43 | ENV 44 | environment/ 45 | conda 46 | conda-env/ 47 | .conda-env/ 48 | pyvenv.cfg 49 | *.envrc 50 | *.direnv 51 | 52 | # Jupyter Notebook 53 | .ipynb_checkpoints 54 | *.ipynb 55 | 56 | # pyenv 57 | .python-version 58 | *.python-version 59 | 60 | # Editor/IDE backups, autosaves, and temporary files 61 | *~ 62 | *.swp 63 | .DS_Store 64 | .vscode/ 65 | .idea/ 66 | *.secret 67 | *.log 68 | *.sqlite3 69 | *.db 70 | 71 | # Node.js 72 | node_modules/ 73 | package-lock.json 74 | npm-debug.log 75 | yarn-error.log 76 | 77 | # Static files 78 | static/images/* -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, jsonify, send_file 2 | import requests 3 | import json 4 | import os 5 | from datetime import datetime 6 | import re 7 | import time 8 | 9 | app = Flask(__name__) 10 | 11 | # API配置 12 | API_TOKEN = "your key" 13 | CHAT_API_URL = "https://cephalon.cloud/user-center/v1/model/chat/completions" 14 | IMAGE_API_URL = "https://cephalon.cloud/user-center/v1/model/comfyui" 15 | 16 | # 确保存储图片的目录存在 17 | UPLOAD_FOLDER = 'static/images' 18 | if not os.path.exists(UPLOAD_FOLDER): 19 | os.makedirs(UPLOAD_FOLDER) 20 | 21 | def extract_think_and_prompt(text): 22 | """从AI响应中提取思考过程和最终提示词""" 23 | think_match = re.search(r'(.*?)', text, re.DOTALL) 24 | think_content = think_match.group(1).strip() if think_match else "" 25 | 26 | # 提取最终提示词(在标签后的内容) 27 | final_prompt = re.sub(r'.*\s*', '', text, flags=re.DOTALL).strip() 28 | 29 | return think_content, final_prompt 30 | 31 | @app.route('/') 32 | def index(): 33 | return render_template('index.html') 34 | 35 | @app.route('/generate-prompt', methods=['POST']) 36 | def generate_prompt(): 37 | max_retries = 2 38 | retry_count = 0 39 | 40 | while retry_count <= max_retries: 41 | try: 42 | user_prompt = request.form.get('prompt', '') 43 | if not user_prompt: 44 | return jsonify({'error': '请输入描述文字'}), 400 45 | 46 | # 调用AI生成提示词 47 | chat_headers = { 48 | 'Content-Type': 'application/json', 49 | 'Authorization': f'Bearer {API_TOKEN}', 50 | } 51 | 52 | chat_data = { 53 | "model": "DeepSeek-R1-Distill-Qwen-32B", 54 | "messages": [ 55 | { 56 | "role": "system", 57 | "content": "你是一个专业的AI绘图提示词专家。请帮助用户将中文描述转换为详细的AI绘图英文提示词,用逗号分隔关键词" 58 | }, 59 | { 60 | "role": "user", 61 | "content": user_prompt 62 | } 63 | ], 64 | "stream": False 65 | } 66 | 67 | chat_response = requests.post(CHAT_API_URL, headers=chat_headers, json=chat_data) 68 | chat_response.raise_for_status() 69 | 70 | # 解析AI返回的内容 71 | ai_response = chat_response.json()['choices'][0]['message']['content'].strip() 72 | think_content, final_prompt = extract_think_and_prompt(ai_response) 73 | 74 | return jsonify({ 75 | 'success': True, 76 | 'think_content': think_content, 77 | 'final_prompt': final_prompt 78 | }) 79 | 80 | except requests.exceptions.RequestException as e: 81 | retry_count += 1 82 | if retry_count > max_retries: 83 | return jsonify({'error': f'生成提示词过程中出错: {str(e)}'}), 500 84 | # 如果还有重试次数,等待一秒后继续 85 | time.sleep(1) 86 | continue 87 | 88 | return jsonify({'error': '服务器响应超时,请重试'}), 500 89 | 90 | @app.route('/generate-image', methods=['POST']) 91 | def generate_image(): 92 | max_retries = 2 93 | retry_count = 0 94 | 95 | while retry_count <= max_retries: 96 | try: 97 | ai_prompt = request.form.get('ai_prompt', '') 98 | aspect_ratio = request.form.get('aspect_ratio', '1:1') 99 | 100 | if not ai_prompt: 101 | return jsonify({'error': '缺少AI提示词'}), 400 102 | 103 | # 根据选择的比例设置宽高 104 | aspect_ratios = { 105 | '1:1': (1024, 1024), 106 | '16:9': (1344, 768), 107 | '9:16': (768, 1344), 108 | '4:3': (1024, 768), 109 | '3:4': (768, 1024) 110 | } 111 | 112 | width, height = aspect_ratios.get(aspect_ratio, (1024, 1024)) 113 | 114 | # 调用图片生成API 115 | image_headers = { 116 | 'Authorization': f'Bearer {API_TOKEN}', 117 | 'Model-Id': '1854732937730371541', 118 | 'Content-Type': 'application/json' 119 | } 120 | 121 | image_data = { 122 | "prompt": ai_prompt, 123 | "width": width, 124 | "height": height, 125 | "guidance_scale": 3.5, 126 | "seed": 320521890, 127 | "steps": 25 128 | } 129 | 130 | image_response = requests.post(IMAGE_API_URL, headers=image_headers, json=image_data) 131 | image_response.raise_for_status() 132 | 133 | # 保存生成的图片 134 | timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') 135 | image_filename = f'generated_{timestamp}.png' 136 | image_path = os.path.join(UPLOAD_FOLDER, image_filename) 137 | 138 | with open(image_path, 'wb') as f: 139 | f.write(image_response.content) 140 | 141 | return jsonify({ 142 | 'success': True, 143 | 'message': '图片生成成功', 144 | 'image_url': f'/static/images/{image_filename}', 145 | 'image_filename': image_filename 146 | }) 147 | 148 | except requests.exceptions.RequestException as e: 149 | retry_count += 1 150 | if retry_count > max_retries: 151 | return jsonify({'error': f'生成图片过程中出错: {str(e)}'}), 500 152 | # 如果还有重试次数,等待一秒后继续 153 | time.sleep(1) 154 | continue 155 | 156 | return jsonify({'error': '服务器响应超时,请重试'}), 500 157 | 158 | @app.route('/download/') 159 | def download_file(filename): 160 | try: 161 | return send_file( 162 | os.path.join(UPLOAD_FOLDER, filename), 163 | as_attachment=True, 164 | download_name=filename 165 | ) 166 | except Exception as e: 167 | return jsonify({'error': f'下载文件时出错: {str(e)}'}), 500 168 | 169 | if __name__ == '__main__': 170 | app.run(debug=True) -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; 9 | line-height: 1.6; 10 | color: #333; 11 | background-color: #f5f5f5; 12 | } 13 | 14 | .container { 15 | max-width: 1000px; 16 | margin: 2rem auto; 17 | padding: 0 1rem; 18 | } 19 | 20 | h1 { 21 | text-align: center; 22 | color: #2c3e50; 23 | margin-bottom: 2rem; 24 | } 25 | 26 | h2 { 27 | color: #2c3e50; 28 | margin-bottom: 1rem; 29 | font-size: 1.5rem; 30 | border-bottom: 2px solid #3498db; 31 | padding-bottom: 0.5rem; 32 | } 33 | 34 | h3 { 35 | color: #2c3e50; 36 | margin: 1.5rem 0 1rem; 37 | font-size: 1.2rem; 38 | } 39 | 40 | .step-section { 41 | background: white; 42 | padding: 2rem; 43 | border-radius: 8px; 44 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 45 | margin-bottom: 2rem; 46 | } 47 | 48 | .input-section { 49 | margin-bottom: 1rem; 50 | } 51 | 52 | textarea { 53 | width: 100%; 54 | min-height: 120px; 55 | padding: 1rem; 56 | margin-bottom: 1rem; 57 | border: 1px solid #ddd; 58 | border-radius: 4px; 59 | resize: vertical; 60 | font-size: 1rem; 61 | font-family: inherit; 62 | } 63 | 64 | textarea:focus { 65 | outline: none; 66 | border-color: #3498db; 67 | box-shadow: 0 0 0 2px rgba(52,152,219,0.2); 68 | } 69 | 70 | /* AI思考过程样式 */ 71 | .think-section { 72 | margin-bottom: 2rem; 73 | padding-bottom: 2rem; 74 | border-bottom: 1px solid #eee; 75 | } 76 | 77 | .think-box { 78 | background: #f8f9fa; 79 | padding: 1.5rem; 80 | border-radius: 4px; 81 | border-left: 4px solid #2ecc71; 82 | margin-bottom: 1.5rem; 83 | font-size: 0.95rem; 84 | color: #2c3e50; 85 | } 86 | 87 | .think-box p { 88 | margin-bottom: 0.8rem; 89 | } 90 | 91 | .think-box p:last-child { 92 | margin-bottom: 0; 93 | } 94 | 95 | /* 最终提示词样式 */ 96 | .prompt-section { 97 | margin-bottom: 2rem; 98 | } 99 | 100 | .prompt-box { 101 | background: #f8f9fa; 102 | padding: 1.5rem; 103 | border-radius: 4px; 104 | border-left: 4px solid #3498db; 105 | margin-bottom: 1.5rem; 106 | white-space: pre-wrap; 107 | font-family: 'Courier New', Courier, monospace; 108 | line-height: 1.6; 109 | font-size: 0.95rem; 110 | } 111 | 112 | /* 按钮样式 */ 113 | button, .button { 114 | display: inline-block; 115 | background-color: #3498db; 116 | color: white; 117 | padding: 0.8rem 2rem; 118 | border: none; 119 | border-radius: 4px; 120 | cursor: pointer; 121 | font-size: 1rem; 122 | transition: all 0.3s ease; 123 | text-decoration: none; 124 | text-align: center; 125 | } 126 | 127 | button:hover, .button:hover { 128 | background-color: #2980b9; 129 | transform: translateY(-1px); 130 | } 131 | 132 | button:disabled { 133 | background-color: #95a5a6; 134 | cursor: not-allowed; 135 | } 136 | 137 | button.secondary { 138 | background-color: #95a5a6; 139 | margin-left: 1rem; 140 | } 141 | 142 | button.secondary:hover { 143 | background-color: #7f8c8d; 144 | } 145 | 146 | .action-buttons { 147 | display: flex; 148 | gap: 1rem; 149 | margin-top: 1.5rem; 150 | } 151 | 152 | .hidden { 153 | display: none; 154 | } 155 | 156 | /* 加载提示样式 */ 157 | #loadingIndicator { 158 | text-align: center; 159 | padding: 2rem; 160 | background: white; 161 | border-radius: 8px; 162 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 163 | margin-bottom: 2rem; 164 | } 165 | 166 | .spinner { 167 | width: 50px; 168 | height: 50px; 169 | border: 5px solid #f3f3f3; 170 | border-top: 5px solid #3498db; 171 | border-radius: 50%; 172 | animation: spin 1s linear infinite; 173 | margin: 0 auto 1rem; 174 | } 175 | 176 | @keyframes spin { 177 | 0% { transform: rotate(0deg); } 178 | 100% { transform: rotate(360deg); } 179 | } 180 | 181 | /* 图片结果样式 */ 182 | .image-section { 183 | text-align: center; 184 | } 185 | 186 | #generatedImage { 187 | max-width: 100%; 188 | height: auto; 189 | border-radius: 4px; 190 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 191 | margin-bottom: 1.5rem; 192 | } 193 | 194 | .image-actions { 195 | display: flex; 196 | justify-content: center; 197 | gap: 1rem; 198 | margin-top: 1rem; 199 | } 200 | 201 | #loadingText { 202 | color: #666; 203 | font-size: 1.1rem; 204 | } 205 | 206 | /* 响应式设计 */ 207 | @media (max-width: 768px) { 208 | .container { 209 | margin: 1rem auto; 210 | } 211 | 212 | .step-section { 213 | padding: 1rem; 214 | } 215 | 216 | .action-buttons { 217 | flex-direction: column; 218 | } 219 | 220 | button, .button { 221 | width: 100%; 222 | margin: 0.5rem 0; 223 | } 224 | 225 | button.secondary { 226 | margin-left: 0; 227 | } 228 | 229 | .image-actions { 230 | flex-direction: column; 231 | } 232 | 233 | .think-box, .prompt-box { 234 | padding: 1rem; 235 | font-size: 0.9rem; 236 | } 237 | } 238 | 239 | .aspect-ratio-group { 240 | margin: 1rem 0; 241 | } 242 | 243 | .aspect-ratio-options { 244 | display: flex; 245 | gap: 1rem; 246 | flex-wrap: wrap; 247 | justify-content: center; 248 | margin-top: 0.5rem; 249 | } 250 | 251 | .aspect-ratio-option { 252 | display: flex; 253 | flex-direction: column; 254 | align-items: center; 255 | cursor: pointer; 256 | padding: 0.5rem; 257 | border: 2px solid transparent; 258 | border-radius: 8px; 259 | transition: all 0.2s; 260 | } 261 | 262 | .aspect-ratio-option:hover { 263 | background: rgba(0,0,0,0.05); 264 | } 265 | 266 | .aspect-ratio-option input[type="radio"] { 267 | display: none; 268 | } 269 | 270 | .aspect-ratio-option input[type="radio"]:checked + .ratio-box { 271 | border-color: #2196F3; 272 | } 273 | 274 | .aspect-ratio-option input[type="radio"]:checked + .ratio-box + .ratio-text { 275 | color: #2196F3; 276 | font-weight: bold; 277 | } 278 | 279 | .ratio-box { 280 | width: 60px; 281 | height: 60px; 282 | border: 2px solid #ccc; 283 | margin-bottom: 0.5rem; 284 | border-radius: 4px; 285 | position: relative; 286 | } 287 | 288 | .ratio-box.square { 289 | /* 1:1 */ 290 | width: 60px; 291 | height: 60px; 292 | } 293 | 294 | .ratio-box.landscape { 295 | /* 16:9 */ 296 | width: 64px; 297 | height: 36px; 298 | } 299 | 300 | .ratio-box.portrait { 301 | /* 9:16 */ 302 | width: 36px; 303 | height: 64px; 304 | } 305 | 306 | .ratio-box.landscape-43 { 307 | /* 4:3 */ 308 | width: 60px; 309 | height: 45px; 310 | } 311 | 312 | .ratio-box.portrait-34 { 313 | /* 3:4 */ 314 | width: 45px; 315 | height: 60px; 316 | } 317 | 318 | .ratio-text { 319 | font-size: 0.9rem; 320 | text-align: center; 321 | color: #666; 322 | } 323 | 324 | .mode-switch { 325 | margin-bottom: 1rem; 326 | display: flex; 327 | gap: 1.5rem; 328 | } 329 | 330 | .mode-option { 331 | display: flex; 332 | align-items: center; 333 | gap: 0.5rem; 334 | cursor: pointer; 335 | } 336 | 337 | .mode-option input[type="radio"] { 338 | margin: 0; 339 | } 340 | 341 | .mode-option span { 342 | font-size: 0.95rem; 343 | color: #333; 344 | } -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AI图片生成器 7 | 8 | 9 | 10 |
11 |

AI图片生成器

12 | 13 | 14 |
15 |

第一步:输入描述,生成AI提示词

16 |
17 |
18 | 22 | 26 |
27 |
28 | 33 |
34 | 35 |
36 | 41 | 46 | 51 | 56 | 61 |
62 |
63 | 64 |
65 |
66 |
67 | 68 | 69 | 87 | 88 | 89 | 93 | 94 | 95 | 105 |
106 | 107 | 226 | 227 | --------------------------------------------------------------------------------