feat(ai): 升级AI生图模型及多视角一致性支持

- 将默认AI生图模型升级为flux-dev及seedream-5.0版本
- SiliconFlow模型由FLUX.1-dev切换为Kolors,优化调用参数和返回值
- 火山引擎Seedream升级至5.0 lite版本,支持多视角参考图传入
- 设计图片字段由字符串改为Text扩展URL长度限制
- 设计图下载支持远程URL重定向和本地文件兼容
- 生成AI图片时多视角保持风格一致,SiliconFlow复用seed,Seedream传参考图
- 后台配置界面更改模型名称及价格显示,新增API Key状态检测
- 前端照片下载从链接改为按钮,远程文件新窗口打开
- 设计相关接口支持较长请求超时,下载走API路径无/api前缀
- 前端页面兼容驼峰与下划线格式URL参数识别
- 用户中心设计图下载支持本地文件Token授权下载
- 初始化数据库新增完整表结构与约束,适配新版设计业务逻辑
This commit is contained in:
2026-03-27 17:39:01 +08:00
parent 032c43525a
commit bb84747917
21 changed files with 645 additions and 414 deletions

View File

@@ -1,12 +1,12 @@
"""
AI 生图服务
支持双模型SiliconFlow FLUX.1 [dev] 和 火山引擎 Seedream 4.5
支持双模型SiliconFlow Kolors 和 火山引擎 Seedream 5.0 lite
"""
import os
import uuid
import logging
import httpx
from typing import Optional
from typing import Optional, Tuple
from ..config import settings
from .config_service import get_ai_config
@@ -19,12 +19,15 @@ REQUEST_TIMEOUT = 90
MAX_RETRIES = 3
async def _call_siliconflow(prompt: str, size: int = 1024, ai_config: dict = None) -> str:
async def _call_siliconflow(prompt: str, size: int = 1024, ai_config: dict = None, seed: Optional[int] = None) -> Tuple[str, Optional[int]]:
"""
调用 SiliconFlow FLUX.1 [dev] 生图 API
调用 SiliconFlow 生图 APIKolors 模型)
Args:
seed: 随机种子,传入相同 seed 可保持多视角图片风格一致
Returns:
远程图片 URL
(远程图片 URL, 使用的 seed)
"""
cfg = ai_config or get_ai_config()
url = f"{cfg['SILICONFLOW_BASE_URL']}/images/generations"
@@ -33,30 +36,38 @@ async def _call_siliconflow(prompt: str, size: int = 1024, ai_config: dict = Non
"Content-Type": "application/json",
}
payload = {
"model": "black-forest-labs/FLUX.1-dev",
"model": "Kwai-Kolors/Kolors",
"prompt": prompt,
"image_size": f"{size}x{size}",
"batch_size": 1,
"num_inference_steps": 20,
"guidance_scale": 7.5,
}
if seed is not None:
payload["seed"] = seed
async with httpx.AsyncClient(timeout=REQUEST_TIMEOUT) as client:
resp = await client.post(url, json=payload, headers=headers)
resp.raise_for_status()
data = resp.json()
# SiliconFlow 响应格式: {"images": [{"url": "https://..."}]}
# SiliconFlow 响应格式: {"images": [{"url": "https://..."}], "seed": 12345}
images = data.get("images", [])
if not images:
raise ValueError("SiliconFlow 返回空图片列表")
return images[0]["url"]
returned_seed = data.get("seed")
return images[0]["url"], returned_seed
async def _call_seedream(prompt: str, size: int = 1024, ai_config: dict = None) -> str:
async def _call_seedream(prompt: str, size: int = 1024, ai_config: dict = None, seed: Optional[int] = None, ref_image_url: Optional[str] = None) -> Tuple[str, Optional[int]]:
"""
调用火山引擎 Seedream 4.5 生图 API
调用火山引擎 Seedream 5.0 lite 生图 API
Args:
ref_image_url: 参考图 URL用于多视角一致性将第一张图作为参考传入后续视角
Returns:
远程图片 URL
(远程图片 URL, seed)
"""
cfg = ai_config or get_ai_config()
url = f"{cfg['VOLCENGINE_BASE_URL']}/images/generations"
@@ -65,37 +76,42 @@ async def _call_seedream(prompt: str, size: int = 1024, ai_config: dict = None)
"Content-Type": "application/json",
}
payload = {
"model": "doubao-seedream-4.5-t2i-250528",
"model": "doubao-seedream-5-0-260128",
"prompt": prompt,
"size": f"{size}x{size}",
"size": "2K",
"response_format": "url",
"watermark": False,
}
# 传入参考图保持多视角一致性API 要求数组格式)
if ref_image_url:
payload["image"] = [ref_image_url]
async with httpx.AsyncClient(timeout=REQUEST_TIMEOUT) as client:
resp = await client.post(url, json=payload, headers=headers)
resp.raise_for_status()
if resp.status_code != 200:
logger.error(f"Seedream API 错误: status={resp.status_code}, body={resp.text[:500]}")
resp.raise_for_status()
data = resp.json()
# Seedream 响应格式: {"data": [{"url": "https://..."}]}
items = data.get("data", [])
if not items:
raise ValueError("Seedream 返回空图片列表")
return items[0]["url"]
return items[0]["url"], seed
async def generate_image(prompt: str, model: Optional[str] = None) -> str:
async def generate_image(prompt: str, model: Optional[str] = None, seed: Optional[int] = None, ref_image_url: Optional[str] = None) -> Tuple[str, Optional[int]]:
"""
统一生图接口,带重试机制
Args:
prompt: 英文提示词
model: 模型名称 (flux-dev / seedream-4.5),为空则使用配置默认值
prompt: 提示词
model: 模型名称 (flux-dev / seedream-5.0)
seed: 随机种子SiliconFlow Kolors 支持)
ref_image_url: 参考图 URLSeedream 5.0 支持,用于多视角一致性)
Returns:
远程图片 URL
Raises:
Exception: 所有重试失败后抛出
(远程图片 URL, 使用的 seed)
"""
ai_config = get_ai_config()
model = model or ai_config.get("AI_IMAGE_MODEL", "flux-dev")
@@ -104,12 +120,12 @@ async def generate_image(prompt: str, model: Optional[str] = None) -> str:
last_error: Optional[Exception] = None
for attempt in range(1, MAX_RETRIES + 1):
try:
if model == "seedream-4.5":
image_url = await _call_seedream(prompt, size, ai_config)
if model in ("seedream-5.0", "seedream-4.5"):
image_url, returned_seed = await _call_seedream(prompt, size, ai_config, seed, ref_image_url)
else:
image_url = await _call_siliconflow(prompt, size, ai_config)
logger.info(f"AI 生图成功 (model={model}, attempt={attempt})")
return image_url
image_url, returned_seed = await _call_siliconflow(prompt, size, ai_config, seed)
logger.info(f"AI 生图成功 (model={model}, seed={returned_seed}, attempt={attempt})")
return image_url, returned_seed
except Exception as e:
last_error = e
logger.warning(f"AI 生图失败 (model={model}, attempt={attempt}/{MAX_RETRIES}): {e}")