feat(ai): 支持双模型多视角AI设计生图与后台管理系统
- 实现AI多视角设计图生成功能,支持6个可选设计参数配置 - 集成SiliconFlow FLUX.1与火山引擎Seedream 4.5双模型切换 - 构建专业中文转英文prompt系统,提升AI生成质量 - 前端设计预览支持多视角切换与视角指示器展示 - 增加多视角设计图片DesignImage模型关联及存储 - 后端设计服务异步调用AI接口,失败时降级生成mock图 - 新增管理员后台管理路由及完整的权限校验机制 - 实现后台模块:仪表盘、系统配置、用户/品类/设计管理 - 配置数据库系统配置表,支持动态AI配置及热更新 - 增加用户管理员标识字段,管理后台登录鉴权支持 - 更新API接口支持多视角设计参数及后台管理接口 - 优化设计删除逻辑,删除多视角相关图片文件 - 前端新增管理后台页面与路由,布局样式独立分离 - 更新环境变量增加AI模型相关Key与参数配置说明 - 引入httpx异步HTTP客户端用于AI接口调用及图片下载 - README文档完善AI多视角生图与后台管理详细功能与流程说明
This commit is contained in:
144
backend/app/services/ai_generator.py
Normal file
144
backend/app/services/ai_generator.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""
|
||||
AI 生图服务
|
||||
支持双模型:SiliconFlow FLUX.1 [dev] 和 火山引擎 Seedream 4.5
|
||||
"""
|
||||
import os
|
||||
import uuid
|
||||
import logging
|
||||
import httpx
|
||||
from typing import Optional
|
||||
|
||||
from ..config import settings
|
||||
from .config_service import get_ai_config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 超时设置(秒)
|
||||
REQUEST_TIMEOUT = 90
|
||||
# 最大重试次数
|
||||
MAX_RETRIES = 3
|
||||
|
||||
|
||||
async def _call_siliconflow(prompt: str, size: int = 1024, ai_config: dict = None) -> str:
|
||||
"""
|
||||
调用 SiliconFlow FLUX.1 [dev] 生图 API
|
||||
|
||||
Returns:
|
||||
远程图片 URL
|
||||
"""
|
||||
cfg = ai_config or get_ai_config()
|
||||
url = f"{cfg['SILICONFLOW_BASE_URL']}/images/generations"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {cfg['SILICONFLOW_API_KEY']}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload = {
|
||||
"model": "black-forest-labs/FLUX.1-dev",
|
||||
"prompt": prompt,
|
||||
"image_size": f"{size}x{size}",
|
||||
"num_inference_steps": 20,
|
||||
}
|
||||
|
||||
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://..."}]}
|
||||
images = data.get("images", [])
|
||||
if not images:
|
||||
raise ValueError("SiliconFlow 返回空图片列表")
|
||||
return images[0]["url"]
|
||||
|
||||
|
||||
async def _call_seedream(prompt: str, size: int = 1024, ai_config: dict = None) -> str:
|
||||
"""
|
||||
调用火山引擎 Seedream 4.5 生图 API
|
||||
|
||||
Returns:
|
||||
远程图片 URL
|
||||
"""
|
||||
cfg = ai_config or get_ai_config()
|
||||
url = f"{cfg['VOLCENGINE_BASE_URL']}/images/generations"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {cfg['VOLCENGINE_API_KEY']}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload = {
|
||||
"model": "doubao-seedream-4.5-t2i-250528",
|
||||
"prompt": prompt,
|
||||
"size": f"{size}x{size}",
|
||||
"response_format": "url",
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
# Seedream 响应格式: {"data": [{"url": "https://..."}]}
|
||||
items = data.get("data", [])
|
||||
if not items:
|
||||
raise ValueError("Seedream 返回空图片列表")
|
||||
return items[0]["url"]
|
||||
|
||||
|
||||
async def generate_image(prompt: str, model: Optional[str] = None) -> str:
|
||||
"""
|
||||
统一生图接口,带重试机制
|
||||
|
||||
Args:
|
||||
prompt: 英文提示词
|
||||
model: 模型名称 (flux-dev / seedream-4.5),为空则使用配置默认值
|
||||
|
||||
Returns:
|
||||
远程图片 URL
|
||||
|
||||
Raises:
|
||||
Exception: 所有重试失败后抛出
|
||||
"""
|
||||
ai_config = get_ai_config()
|
||||
model = model or ai_config.get("AI_IMAGE_MODEL", "flux-dev")
|
||||
size = ai_config.get("AI_IMAGE_SIZE", 1024)
|
||||
|
||||
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)
|
||||
else:
|
||||
image_url = await _call_siliconflow(prompt, size, ai_config)
|
||||
logger.info(f"AI 生图成功 (model={model}, attempt={attempt})")
|
||||
return image_url
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
logger.warning(f"AI 生图失败 (model={model}, attempt={attempt}/{MAX_RETRIES}): {e}")
|
||||
if attempt < MAX_RETRIES:
|
||||
import asyncio
|
||||
await asyncio.sleep(2 * attempt) # 指数退避
|
||||
|
||||
raise RuntimeError(f"AI 生图在 {MAX_RETRIES} 次重试后仍然失败: {last_error}")
|
||||
|
||||
|
||||
async def download_and_save(image_url: str, save_path: str) -> str:
|
||||
"""
|
||||
下载远程图片并保存到本地
|
||||
|
||||
Args:
|
||||
image_url: 远程图片 URL
|
||||
save_path: 本地保存路径(如 uploads/designs/1001_效果图.png)
|
||||
|
||||
Returns:
|
||||
本地文件相对路径(以 / 开头,如 /uploads/designs/1001_效果图.png)
|
||||
"""
|
||||
# 确保目录存在
|
||||
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||
|
||||
async with httpx.AsyncClient(timeout=60, follow_redirects=True) as client:
|
||||
resp = await client.get(image_url)
|
||||
resp.raise_for_status()
|
||||
with open(save_path, "wb") as f:
|
||||
f.write(resp.content)
|
||||
|
||||
logger.info(f"图片已下载保存: {save_path}")
|
||||
return f"/{save_path}"
|
||||
58
backend/app/services/config_service.py
Normal file
58
backend/app/services/config_service.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
配置服务
|
||||
优先从数据库 system_configs 表读取配置,数据库无值时回退到 .env
|
||||
"""
|
||||
import logging
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..database import SessionLocal
|
||||
from ..models.system_config import SystemConfig
|
||||
from ..config import settings as env_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_config_value(key: str, default: Optional[str] = None) -> Optional[str]:
|
||||
"""
|
||||
获取配置值(数据库优先,.env 兜底)
|
||||
|
||||
Args:
|
||||
key: 配置键名(如 SILICONFLOW_API_KEY)
|
||||
default: 默认值
|
||||
Returns:
|
||||
配置值字符串
|
||||
"""
|
||||
# 1. 尝试从数据库读取
|
||||
try:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
config = db.query(SystemConfig).filter(SystemConfig.config_key == key).first()
|
||||
if config and config.config_value:
|
||||
return config.config_value
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"从数据库读取配置 {key} 失败: {e}")
|
||||
|
||||
# 2. 回退到 .env / Settings
|
||||
env_value = getattr(env_settings, key, None)
|
||||
if env_value is not None and env_value != "":
|
||||
return str(env_value)
|
||||
|
||||
return default
|
||||
|
||||
|
||||
def get_ai_config() -> dict:
|
||||
"""
|
||||
获取所有 AI 相关配置
|
||||
返回字典,方便 ai_generator 使用
|
||||
"""
|
||||
return {
|
||||
"SILICONFLOW_API_KEY": get_config_value("SILICONFLOW_API_KEY", ""),
|
||||
"SILICONFLOW_BASE_URL": get_config_value("SILICONFLOW_BASE_URL", "https://api.siliconflow.cn/v1"),
|
||||
"VOLCENGINE_API_KEY": get_config_value("VOLCENGINE_API_KEY", ""),
|
||||
"VOLCENGINE_BASE_URL": get_config_value("VOLCENGINE_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3"),
|
||||
"AI_IMAGE_MODEL": get_config_value("AI_IMAGE_MODEL", "flux-dev"),
|
||||
"AI_IMAGE_SIZE": int(get_config_value("AI_IMAGE_SIZE", "1024")),
|
||||
}
|
||||
@@ -1,40 +1,56 @@
|
||||
"""
|
||||
设计服务
|
||||
处理设计相关的业务逻辑
|
||||
处理设计相关的业务逻辑,支持 AI 多视角生图 + mock 降级
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
from typing import List, Optional, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import desc
|
||||
|
||||
from ..models import Design, Category, SubType, Color
|
||||
from ..models import Design, DesignImage, Category, SubType, Color
|
||||
from ..schemas import DesignCreate
|
||||
from ..config import settings
|
||||
from .mock_generator import generate_mock_design
|
||||
from .prompt_builder import get_views_for_category, build_prompt
|
||||
from . import ai_generator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_design(db: Session, user_id: int, design_data: DesignCreate) -> Design:
|
||||
def _has_ai_key() -> bool:
|
||||
"""检查是否配置了 AI API Key"""
|
||||
model = settings.AI_IMAGE_MODEL
|
||||
if model == "seedream-4.5":
|
||||
return bool(settings.VOLCENGINE_API_KEY)
|
||||
return bool(settings.SILICONFLOW_API_KEY)
|
||||
|
||||
|
||||
async def create_design_async(db: Session, user_id: int, design_data: DesignCreate) -> Design:
|
||||
"""
|
||||
创建设计记录
|
||||
|
||||
1. 创建设计记录(status=generating)
|
||||
2. 调用 mock_generator 生成图片
|
||||
3. 更新设计记录(status=completed, image_url)
|
||||
4. 返回设计对象
|
||||
创建设计记录(异步版本,支持 AI 多视角生图)
|
||||
|
||||
流程:
|
||||
1. 创建 Design 记录(status=generating)
|
||||
2. 获取品类视角列表
|
||||
3. 循环每个视角:构建 prompt → 调用 AI 生图 → 下载保存 → 创建 DesignImage
|
||||
4. 第一张效果图 URL 存入 design.image_url(兼容旧逻辑)
|
||||
5. 更新 status=completed
|
||||
6. 失败时降级到 mock_generator
|
||||
"""
|
||||
# 获取关联信息
|
||||
category = db.query(Category).filter(Category.id == design_data.category_id).first()
|
||||
if not category:
|
||||
raise ValueError(f"品类不存在: {design_data.category_id}")
|
||||
|
||||
|
||||
sub_type = None
|
||||
if design_data.sub_type_id:
|
||||
sub_type = db.query(SubType).filter(SubType.id == design_data.sub_type_id).first()
|
||||
|
||||
|
||||
color = None
|
||||
if design_data.color_id:
|
||||
color = db.query(Color).filter(Color.id == design_data.color_id).first()
|
||||
|
||||
|
||||
# 创建设计记录
|
||||
design = Design(
|
||||
user_id=user_id,
|
||||
@@ -52,8 +68,109 @@ def create_design(db: Session, user_id: int, design_data: DesignCreate) -> Desig
|
||||
)
|
||||
db.add(design)
|
||||
db.flush() # 获取 ID
|
||||
|
||||
# 生成图片
|
||||
|
||||
# 尝试 AI 生图
|
||||
if _has_ai_key():
|
||||
try:
|
||||
await _generate_ai_images(db, design, category, sub_type, color, design_data)
|
||||
db.commit()
|
||||
db.refresh(design)
|
||||
return design
|
||||
except Exception as e:
|
||||
logger.error(f"AI 生图全部失败,降级到 mock: {e}")
|
||||
db.rollback()
|
||||
# 重新查询,因为 rollback 后 ORM 对象可能失效
|
||||
design = db.query(Design).filter(Design.id == design.id).first()
|
||||
if not design:
|
||||
# rollback 导致 design 也没了,重新创建
|
||||
design = Design(
|
||||
user_id=user_id,
|
||||
category_id=design_data.category_id,
|
||||
sub_type_id=design_data.sub_type_id,
|
||||
color_id=design_data.color_id,
|
||||
prompt=design_data.prompt,
|
||||
carving_technique=design_data.carving_technique,
|
||||
design_style=design_data.design_style,
|
||||
motif=design_data.motif,
|
||||
size_spec=design_data.size_spec,
|
||||
surface_finish=design_data.surface_finish,
|
||||
usage_scene=design_data.usage_scene,
|
||||
status="generating"
|
||||
)
|
||||
db.add(design)
|
||||
db.flush()
|
||||
|
||||
# 降级到 mock 生成
|
||||
_generate_mock_fallback(db, design, category, sub_type, color, design_data)
|
||||
db.commit()
|
||||
db.refresh(design)
|
||||
return design
|
||||
|
||||
|
||||
async def _generate_ai_images(
|
||||
db: Session,
|
||||
design: Design,
|
||||
category,
|
||||
sub_type,
|
||||
color,
|
||||
design_data: DesignCreate,
|
||||
) -> None:
|
||||
"""使用 AI 模型为每个视角生成图片"""
|
||||
views = get_views_for_category(category.name)
|
||||
model = settings.AI_IMAGE_MODEL
|
||||
|
||||
for idx, view_name in enumerate(views):
|
||||
# 构建 prompt
|
||||
prompt_text = build_prompt(
|
||||
category_name=category.name,
|
||||
view_name=view_name,
|
||||
sub_type_name=sub_type.name if sub_type else None,
|
||||
color_name=color.name if color else None,
|
||||
user_prompt=design_data.prompt,
|
||||
carving_technique=design_data.carving_technique,
|
||||
design_style=design_data.design_style,
|
||||
motif=design_data.motif,
|
||||
size_spec=design_data.size_spec,
|
||||
surface_finish=design_data.surface_finish,
|
||||
usage_scene=design_data.usage_scene,
|
||||
)
|
||||
|
||||
# 调用 AI 生图
|
||||
remote_url = await ai_generator.generate_image(prompt_text, model)
|
||||
|
||||
# 下载保存到本地
|
||||
save_path = os.path.join(
|
||||
settings.UPLOAD_DIR, "designs", f"{design.id}_{view_name}.png"
|
||||
)
|
||||
local_url = await ai_generator.download_and_save(remote_url, save_path)
|
||||
|
||||
# 创建 DesignImage 记录
|
||||
design_image = DesignImage(
|
||||
design_id=design.id,
|
||||
view_name=view_name,
|
||||
image_url=local_url,
|
||||
model_used=model,
|
||||
prompt_used=prompt_text,
|
||||
sort_order=idx,
|
||||
)
|
||||
db.add(design_image)
|
||||
|
||||
# 第一张图(效果图)存入 design.image_url 兼容旧逻辑
|
||||
if idx == 0:
|
||||
design.image_url = local_url
|
||||
|
||||
design.status = "completed"
|
||||
|
||||
|
||||
def _generate_mock_fallback(
|
||||
db: Session,
|
||||
design: Design,
|
||||
category,
|
||||
sub_type,
|
||||
color,
|
||||
design_data: DesignCreate,
|
||||
) -> None:
|
||||
"""降级使用 mock 生成器"""
|
||||
save_path = os.path.join(settings.UPLOAD_DIR, "designs", f"{design.id}.png")
|
||||
image_url = generate_mock_design(
|
||||
category_name=category.name,
|
||||
@@ -68,13 +185,47 @@ def create_design(db: Session, user_id: int, design_data: DesignCreate) -> Desig
|
||||
surface_finish=design_data.surface_finish,
|
||||
usage_scene=design_data.usage_scene,
|
||||
)
|
||||
|
||||
# 更新设计记录
|
||||
design.image_url = image_url
|
||||
design.status = "completed"
|
||||
logger.info(f"Mock 降级生成完成: design_id={design.id}")
|
||||
|
||||
|
||||
def create_design(db: Session, user_id: int, design_data: DesignCreate) -> Design:
|
||||
"""
|
||||
同步版本创建设计(兼容旧调用,仅用 mock)
|
||||
"""
|
||||
category = db.query(Category).filter(Category.id == design_data.category_id).first()
|
||||
if not category:
|
||||
raise ValueError(f"品类不存在: {design_data.category_id}")
|
||||
|
||||
sub_type = None
|
||||
if design_data.sub_type_id:
|
||||
sub_type = db.query(SubType).filter(SubType.id == design_data.sub_type_id).first()
|
||||
|
||||
color = None
|
||||
if design_data.color_id:
|
||||
color = db.query(Color).filter(Color.id == design_data.color_id).first()
|
||||
|
||||
design = Design(
|
||||
user_id=user_id,
|
||||
category_id=design_data.category_id,
|
||||
sub_type_id=design_data.sub_type_id,
|
||||
color_id=design_data.color_id,
|
||||
prompt=design_data.prompt,
|
||||
carving_technique=design_data.carving_technique,
|
||||
design_style=design_data.design_style,
|
||||
motif=design_data.motif,
|
||||
size_spec=design_data.size_spec,
|
||||
surface_finish=design_data.surface_finish,
|
||||
usage_scene=design_data.usage_scene,
|
||||
status="generating"
|
||||
)
|
||||
db.add(design)
|
||||
db.flush()
|
||||
|
||||
_generate_mock_fallback(db, design, category, sub_type, color, design_data)
|
||||
db.commit()
|
||||
db.refresh(design)
|
||||
|
||||
return design
|
||||
|
||||
|
||||
@@ -132,16 +283,24 @@ def delete_design(db: Session, design_id: int, user_id: int) -> bool:
|
||||
if not design:
|
||||
return False
|
||||
|
||||
# 删除图片文件
|
||||
# 删除主图片文件
|
||||
if design.image_url:
|
||||
# image_url 格式: /uploads/designs/1001.png
|
||||
# 转换为实际文件路径
|
||||
file_path = design.image_url.lstrip("/")
|
||||
if os.path.exists(file_path):
|
||||
try:
|
||||
os.remove(file_path)
|
||||
except Exception:
|
||||
pass # 忽略删除失败
|
||||
pass
|
||||
|
||||
# 删除多视角图片文件
|
||||
for img in design.images:
|
||||
if img.image_url:
|
||||
fp = img.image_url.lstrip("/")
|
||||
if os.path.exists(fp):
|
||||
try:
|
||||
os.remove(fp)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 删除数据库记录
|
||||
db.delete(design)
|
||||
|
||||
164
backend/app/services/prompt_builder.py
Normal file
164
backend/app/services/prompt_builder.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
专业玉雕设计提示词构建器(数据库版)
|
||||
从数据库 prompt_templates + prompt_mappings 读取配置,支持后台热更新
|
||||
"""
|
||||
import logging
|
||||
from typing import Optional, Dict, List
|
||||
|
||||
from ..database import SessionLocal
|
||||
from ..models.prompt_template import PromptTemplate, PromptMapping
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ============================================================
|
||||
# 品类视角配置(保留硬编码,因为与业务流程强关联)
|
||||
# ============================================================
|
||||
CATEGORY_VIEWS: Dict[str, List[str]] = {
|
||||
"牌子": ["效果图", "正面图", "背面图"],
|
||||
"珠子": ["效果图", "正面图"],
|
||||
"手把件": ["效果图", "正面图", "侧面图", "背面图"],
|
||||
"雕刻件": ["效果图", "正面图", "侧面图", "背面图"],
|
||||
"摆件": ["效果图", "正面图", "侧面图", "背面图"],
|
||||
"手镯": ["效果图", "正面图", "侧面图"],
|
||||
"耳钉": ["效果图", "正面图"],
|
||||
"耳饰": ["效果图", "正面图"],
|
||||
"手链": ["效果图", "正面图"],
|
||||
"项链": ["效果图", "正面图"],
|
||||
"戒指": ["效果图", "正面图", "侧面图"],
|
||||
"表带": ["效果图", "正面图"],
|
||||
}
|
||||
|
||||
|
||||
def _load_mappings(mapping_type: str) -> Dict[str, str]:
|
||||
"""从数据库加载指定类型的映射字典"""
|
||||
try:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
rows = db.query(PromptMapping).filter(
|
||||
PromptMapping.mapping_type == mapping_type
|
||||
).order_by(PromptMapping.sort_order).all()
|
||||
return {r.cn_key: r.en_value for r in rows}
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"加载映射 {mapping_type} 失败: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
def _load_template(template_key: str, default: str = "") -> str:
|
||||
"""从数据库加载模板"""
|
||||
try:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
tpl = db.query(PromptTemplate).filter(
|
||||
PromptTemplate.template_key == template_key
|
||||
).first()
|
||||
if tpl:
|
||||
return tpl.template_value
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"加载模板 {template_key} 失败: {e}")
|
||||
return default
|
||||
|
||||
|
||||
def get_views_for_category(category_name: str) -> List[str]:
|
||||
"""获取品类对应的视角列表"""
|
||||
return CATEGORY_VIEWS.get(category_name, ["效果图", "正面图"])
|
||||
|
||||
|
||||
def build_prompt(
|
||||
category_name: str,
|
||||
view_name: str,
|
||||
sub_type_name: Optional[str] = None,
|
||||
color_name: Optional[str] = None,
|
||||
user_prompt: Optional[str] = None,
|
||||
carving_technique: Optional[str] = None,
|
||||
design_style: Optional[str] = None,
|
||||
motif: Optional[str] = None,
|
||||
size_spec: Optional[str] = None,
|
||||
surface_finish: Optional[str] = None,
|
||||
usage_scene: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
构建专业英文生图提示词(从数据库读取映射和模板)
|
||||
|
||||
业务逻辑:用户参数 → 中英映射 → 填入模板 → 最终prompt
|
||||
"""
|
||||
# 从数据库加载所有映射
|
||||
category_map = _load_mappings("category")
|
||||
color_map = _load_mappings("color")
|
||||
view_map = _load_mappings("view")
|
||||
carving_map = _load_mappings("carving")
|
||||
style_map = _load_mappings("style")
|
||||
motif_map = _load_mappings("motif")
|
||||
finish_map = _load_mappings("finish")
|
||||
scene_map = _load_mappings("scene")
|
||||
sub_type_map = _load_mappings("sub_type")
|
||||
|
||||
# 加载模板
|
||||
quality_suffix = _load_template("quality_suffix",
|
||||
"professional jewelry product photography, studio lighting setup, pure white background, ultra-detailed, sharp focus, 8K resolution, photorealistic rendering, high-end commercial quality")
|
||||
default_color = _load_template("default_color",
|
||||
"natural Hetian nephrite jade with warm luster")
|
||||
|
||||
# 构建各部分
|
||||
parts = []
|
||||
|
||||
# 1. 品类主体
|
||||
subject = category_map.get(category_name, f"Chinese Hetian nephrite jade {category_name}")
|
||||
parts.append(subject)
|
||||
|
||||
# 2. 子类型
|
||||
if sub_type_name:
|
||||
sub_detail = sub_type_map.get(sub_type_name, sub_type_name)
|
||||
parts.append(sub_detail)
|
||||
|
||||
# 3. 颜色
|
||||
if color_name:
|
||||
color_desc = color_map.get(color_name, f"{color_name} colored nephrite jade")
|
||||
parts.append(color_desc)
|
||||
else:
|
||||
parts.append(default_color)
|
||||
|
||||
# 4. 题材纹样
|
||||
if motif:
|
||||
motif_desc = motif_map.get(motif, f"{motif} themed design")
|
||||
parts.append(f"featuring {motif_desc}")
|
||||
|
||||
# 5. 雕刻工艺
|
||||
if carving_technique:
|
||||
tech_desc = carving_map.get(carving_technique, carving_technique)
|
||||
parts.append(tech_desc)
|
||||
|
||||
# 6. 设计风格
|
||||
if design_style:
|
||||
style_desc = style_map.get(design_style, design_style)
|
||||
parts.append(style_desc)
|
||||
|
||||
# 7. 表面处理
|
||||
if surface_finish:
|
||||
finish_desc = finish_map.get(surface_finish, surface_finish)
|
||||
parts.append(finish_desc)
|
||||
|
||||
# 8. 用途场景
|
||||
if usage_scene:
|
||||
scene_desc = scene_map.get(usage_scene, usage_scene)
|
||||
parts.append(scene_desc)
|
||||
|
||||
# 9. 尺寸
|
||||
if size_spec:
|
||||
parts.append(f"size approximately {size_spec}")
|
||||
|
||||
# 10. 用户描述
|
||||
if user_prompt:
|
||||
parts.append(f"design concept: {user_prompt}")
|
||||
|
||||
# 11. 视角
|
||||
view_desc = view_map.get(view_name, "three-quarter view")
|
||||
parts.append(view_desc)
|
||||
|
||||
# 12. 质量后缀
|
||||
parts.append(quality_suffix)
|
||||
|
||||
return ", ".join(parts)
|
||||
Reference in New Issue
Block a user