feat: 故事生成时自动用豆包 Seedream 4.5 文生图模型生成封面

- llm_service: 添加 _generate_and_upload_cover,调用 doubao-seedream-4-5-251128
  生成 1920x1920 儿童绘本风格封面,下载后上传至 OSS,URL 随 done 事件返回
- SSE 新增 cover 阶段(progress=90),非致命异常不影响故事主流程
- settings: LLM_CONFIG 新增 IMAGE_MODEL_NAME / IMAGE_SIZE,可通过环境变量覆盖

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
repair-agent 2026-03-02 15:32:54 +08:00
parent 487b258bbe
commit cbfe747553
2 changed files with 76 additions and 0 deletions

View File

@ -122,12 +122,28 @@ def generate_story_stream(characters, scenes, props):
result = _parse_story_json(full_content)
# ── Generate cover image ──
yield _sse_event('stage', {
'stage': 'cover',
'progress': 90,
'message': '正在绘制故事封面...',
})
cover_url = ''
try:
cover_url = _generate_and_upload_cover(
result['title'], characters, scenes, props, config
)
except Exception as cover_err:
logger.warning(f'Cover generation failed (non-fatal): {cover_err}')
yield _sse_event('done', {
'stage': 'done',
'progress': 100,
'message': '大功告成!',
'title': result['title'],
'content': result['content'],
'cover_url': cover_url,
})
except Exception as e:
@ -157,6 +173,64 @@ def _parse_story_json(text):
}
def _generate_and_upload_cover(title, characters, scenes, props, config):
"""
使用豆包文生图模型生成故事封面上传到 OSS 并返回 URL
失败时抛出异常由调用方捕获不影响主流程
"""
import uuid
import requests as req_lib
from datetime import datetime
from django.conf import settings
from volcenginesdkarkruntime import Ark
# Build a concise Chinese image prompt
elements = []
if characters:
elements.append(''.join(characters))
if scenes:
elements.append(''.join(scenes))
if props:
elements.append(''.join(props))
element_str = ''.join(elements) if elements else title
image_prompt = (
f"儿童绘本封面插画,{title}{element_str}"
"卡通可爱风格色彩明亮鲜艳温馨有趣适合3-8岁儿童高质量插画"
)
client = Ark(api_key=config['API_KEY'])
image_model = config.get('IMAGE_MODEL_NAME', 'doubao-seedream-4-5-251128')
image_size = config.get('IMAGE_SIZE', '1920x1920')
result = client.images.generate(
model=image_model,
prompt=image_prompt,
size=image_size,
response_format='url',
watermark=False,
)
image_url = result.data[0].url
# Download from temporary URL and upload to OSS
resp = req_lib.get(image_url, timeout=60)
resp.raise_for_status()
from utils.oss import get_oss_client
oss_client = get_oss_client()
key = f"stories/covers/{datetime.now().strftime('%Y%m%d')}/{uuid.uuid4().hex}.jpg"
oss_client.bucket.put_object(
key, resp.content,
headers={'Content-Type': 'image/jpeg'},
)
oss_config = settings.ALIYUN_OSS
if oss_config.get('CUSTOM_DOMAIN'):
return f"https://{oss_config['CUSTOM_DOMAIN']}/{key}"
return f"https://{oss_config['BUCKET_NAME']}.{oss_config['ENDPOINT']}/{key}"
def _sse_event(event, data):
"""格式化 SSE 事件"""
return f"event: {event}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n"

View File

@ -198,6 +198,8 @@ ALIYUN_PHONE_AUTH = {
LLM_CONFIG = {
'API_KEY': os.environ.get('VOLCENGINE_API_KEY', ''),
'MODEL_NAME': os.environ.get('VOLCENGINE_MODEL_NAME', 'doubao-seed-1-6-lite-251015'),
'IMAGE_MODEL_NAME': os.environ.get('VOLCENGINE_IMAGE_MODEL_NAME', 'doubao-seedream-4-5-251128'),
'IMAGE_SIZE': os.environ.get('VOLCENGINE_IMAGE_SIZE', '1920x1920'),
}
# Swagger/OpenAPI Settings