2026-01-20 09:23:15 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
import asyncio
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from fastapi import APIRouter, HTTPException
|
2026-01-20 09:38:29 +08:00
|
|
|
|
from fastapi.responses import StreamingResponse
|
2026-01-20 09:23:15 +08:00
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
from sqlalchemy.sql import text
|
|
|
|
|
|
|
|
|
|
|
|
from Util.BananaClient import BananaClient
|
|
|
|
|
|
from Util.LlmUtil import get_llm_response
|
2026-01-21 08:41:47 +08:00
|
|
|
|
from Model.HaiBaoModel import HaiBaoModel
|
2026-01-20 09:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/haibao")
|
|
|
|
|
|
logger = logging.getLogger("HaiBaoController")
|
|
|
|
|
|
|
|
|
|
|
|
class GenerateRequest(BaseModel):
|
|
|
|
|
|
prompt: str
|
|
|
|
|
|
width: int = 1024
|
|
|
|
|
|
height: int = 1024
|
|
|
|
|
|
|
|
|
|
|
|
@router.on_event("startup")
|
|
|
|
|
|
async def startup_event():
|
2026-01-21 08:41:47 +08:00
|
|
|
|
"""初始化时逻辑"""
|
|
|
|
|
|
logger.info("海报控制器启动成功")
|
2026-01-20 09:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
class RefineRequest(BaseModel):
|
|
|
|
|
|
prompt: str
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/refine")
|
|
|
|
|
|
async def refine_prompt(req: RefineRequest):
|
|
|
|
|
|
"""润色提示词"""
|
2026-01-20 09:38:29 +08:00
|
|
|
|
async def generate():
|
2026-01-20 09:23:15 +08:00
|
|
|
|
try:
|
2026-01-20 09:38:29 +08:00
|
|
|
|
extra_requirements = ""
|
|
|
|
|
|
if "春节" in req.prompt:
|
|
|
|
|
|
extra_requirements = "7. 画面氛围要极其温馨喜庆,体现春节团圆主题。可以尝试在画面中自然融入“过年了”、“回家看看”等字样(如果模型支持文字生成),或者通过红灯笼、春联、全家福等元素强烈暗示这一主题。"
|
|
|
|
|
|
|
|
|
|
|
|
refine_system_prompt = "你是一个资深的AI绘画提示词专家。你的任务是将用户简短的描述扩充为一段详细、高质量的画面描述提示词,用于生成宣传海报。"
|
|
|
|
|
|
refine_user_prompt = f"""
|
|
|
|
|
|
请根据以下主题,为充电企业“驿来特”设计一张宣传海报的画面描述。
|
|
|
|
|
|
|
|
|
|
|
|
主题:{req.prompt}
|
|
|
|
|
|
|
|
|
|
|
|
要求:
|
|
|
|
|
|
1. 描述画面主体、背景、光影、色彩、构图。
|
|
|
|
|
|
2. 风格要求:现代感、科技感、精美、3D渲染风格或高品质插画风格。
|
|
|
|
|
|
3. 融入新能源、绿色环保、充电桩等元素。
|
|
|
|
|
|
4. 关于品牌元素:画面中可自然融入品牌Logo的视觉元素(如配色、形状),能用多少就用多少,有元素体现即可,不必生搬硬套,保持画面自然和谐。
|
|
|
|
|
|
5. 直接输出提示词内容,不要包含“好的”、“以下是”等无关废话。
|
|
|
|
|
|
6. 字数在100-300字之间。
|
|
|
|
|
|
{extra_requirements}
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
async for chunk in get_llm_response(query_text=refine_user_prompt, system_prompt=refine_system_prompt, stream=True):
|
|
|
|
|
|
yield chunk
|
2026-01-20 09:23:15 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"提示词润色失败: {e}")
|
2026-01-20 09:38:29 +08:00
|
|
|
|
yield f"Error: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
return StreamingResponse(generate(), media_type="text/plain")
|
2026-01-20 09:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
@router.post("/generate")
|
|
|
|
|
|
async def generate_poster(req: GenerateRequest):
|
|
|
|
|
|
"""生成海报及文案"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 并行执行生图和生文
|
|
|
|
|
|
client = BananaClient()
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 构造生图任务 (包含智能润色判断)
|
|
|
|
|
|
async def generate_image_task():
|
|
|
|
|
|
final_prompt = req.prompt
|
|
|
|
|
|
|
|
|
|
|
|
# 智能判断:如果提示词太短(少于50字),则认为用户未进行润色,自动执行润色
|
|
|
|
|
|
# 如果用户使用了"一键扩写"功能,提示词通常会很长,这里就会跳过自动润色,尊重用户的修改
|
|
|
|
|
|
if len(final_prompt) < 50:
|
|
|
|
|
|
logger.info(f"提示词较短({len(final_prompt)}字),执行自动润色...")
|
2026-01-20 09:38:29 +08:00
|
|
|
|
|
|
|
|
|
|
extra_requirements = ""
|
|
|
|
|
|
if "春节" in req.prompt:
|
|
|
|
|
|
extra_requirements = "7. 画面氛围要极其温馨喜庆,体现春节团圆主题。可以尝试在画面中自然融入“过年了”、“回家看看”等字样(如果模型支持文字生成),或者通过红灯笼、春联、全家福等元素强烈暗示这一主题。"
|
|
|
|
|
|
|
2026-01-20 09:23:15 +08:00
|
|
|
|
refine_system_prompt = "你是一个资深的AI绘画提示词专家。你的任务是将用户简短的描述扩充为一段详细、高质量的画面描述提示词,用于生成宣传海报。"
|
|
|
|
|
|
refine_user_prompt = f"""
|
|
|
|
|
|
请根据以下主题,为充电企业“驿来特”设计一张宣传海报的画面描述。
|
|
|
|
|
|
|
|
|
|
|
|
主题:{req.prompt}
|
|
|
|
|
|
|
|
|
|
|
|
要求:
|
|
|
|
|
|
1. 描述画面主体、背景、光影、色彩、构图。
|
|
|
|
|
|
2. 风格要求:现代感、科技感、精美、3D渲染风格或高品质插画风格。
|
|
|
|
|
|
3. 融入新能源、绿色环保、充电桩等元素。
|
2026-01-20 09:26:42 +08:00
|
|
|
|
4. 关于品牌元素:画面中可自然融入品牌Logo的视觉元素(如配色、形状),能用多少就用多少,有元素体现即可,不必生搬硬套,保持画面自然和谐。
|
|
|
|
|
|
5. 直接输出提示词内容,不要包含“好的”、“以下是”等无关废话。
|
|
|
|
|
|
6. 字数在100-300字之间。
|
2026-01-20 09:38:29 +08:00
|
|
|
|
{extra_requirements}
|
2026-01-20 09:23:15 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
refined_prompt = ""
|
|
|
|
|
|
try:
|
|
|
|
|
|
async for chunk in get_llm_response(query_text=refine_user_prompt, system_prompt=refine_system_prompt, stream=False):
|
|
|
|
|
|
refined_prompt += chunk
|
|
|
|
|
|
if refined_prompt and refined_prompt.strip():
|
|
|
|
|
|
final_prompt = refined_prompt
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"自动润色失败,使用原始提示词: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"Final generation prompt: {final_prompt}")
|
|
|
|
|
|
|
|
|
|
|
|
# 1.2 调用生图
|
|
|
|
|
|
resp = await client.generate_image(prompt=final_prompt, size=f"{req.width}x{req.height}")
|
2026-01-20 09:26:42 +08:00
|
|
|
|
# 定义Logo路径
|
|
|
|
|
|
LOGO_PATH = r"d:\dsWork\aiData\static\Images\login_logo.png"
|
|
|
|
|
|
obs_urls = await client.download_and_upload_to_obs(resp, overlay_logo_path=LOGO_PATH)
|
2026-01-20 09:23:15 +08:00
|
|
|
|
if not obs_urls:
|
|
|
|
|
|
raise Exception("未获取到有效的图片URL")
|
|
|
|
|
|
return obs_urls[0]
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 构造生文任务
|
|
|
|
|
|
async def generate_text_task():
|
2026-01-20 09:38:29 +08:00
|
|
|
|
extra_text_req = ""
|
|
|
|
|
|
if "春节" in req.prompt:
|
|
|
|
|
|
extra_text_req = "6. 文案中必须包含“过年了,回家看看,记得提前充电噢”或类似的温馨提示。"
|
|
|
|
|
|
|
2026-01-20 09:23:15 +08:00
|
|
|
|
scheme_prompt = f"""
|
|
|
|
|
|
你是一个专业的社群运营专家。请为充电企业“驿来特”撰写一段发在微信群里的宣传文案。
|
|
|
|
|
|
|
|
|
|
|
|
主题:{req.prompt}
|
|
|
|
|
|
|
|
|
|
|
|
要求:
|
|
|
|
|
|
1. 语气亲切、有吸引力,适合微信社群传播。
|
|
|
|
|
|
2. 突出“驿来特”品牌,强调新能源、优惠、便利等特点(根据主题自由发挥)。
|
|
|
|
|
|
3. 包含适当的emoji表情,增加趣味性。
|
|
|
|
|
|
4. 字数控制在150字以内。
|
|
|
|
|
|
5. 格式清晰,分段合理。
|
2026-01-20 09:38:29 +08:00
|
|
|
|
{extra_text_req}
|
2026-01-20 09:23:15 +08:00
|
|
|
|
"""
|
|
|
|
|
|
# get_llm_response 是一个异步生成器 (stream=True by default) 或者直接返回 (stream=False)
|
|
|
|
|
|
# 这里我们强制 stream=False 获取完整文本
|
|
|
|
|
|
text_response = ""
|
|
|
|
|
|
# LlmUtil.get_llm_response 默认为 stream=True,我们需要修改调用方式或适配
|
|
|
|
|
|
# 查看 LlmUtil 源码,如果 stream=False,它 yield 内容。
|
|
|
|
|
|
# 所以我们需要迭代它
|
|
|
|
|
|
async for chunk in get_llm_response(query_text=scheme_prompt, stream=False):
|
|
|
|
|
|
text_response += chunk
|
|
|
|
|
|
return text_response
|
|
|
|
|
|
|
|
|
|
|
|
# 3. 并行执行
|
|
|
|
|
|
image_url, scheme_content = await asyncio.gather(generate_image_task(), generate_text_task())
|
|
|
|
|
|
|
|
|
|
|
|
# 4. 保存到数据库
|
2026-01-21 08:41:47 +08:00
|
|
|
|
model = HaiBaoModel()
|
2026-01-20 09:23:15 +08:00
|
|
|
|
record_id = str(uuid.uuid4())
|
|
|
|
|
|
created_at = datetime.now()
|
|
|
|
|
|
|
2026-01-21 08:41:47 +08:00
|
|
|
|
await model.insert_record(record_id, req.prompt, image_url, scheme_content, created_at)
|
2026-01-20 09:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": record_id,
|
|
|
|
|
|
"image_url": image_url,
|
|
|
|
|
|
"prompt": req.prompt,
|
|
|
|
|
|
"scheme_content": scheme_content,
|
|
|
|
|
|
"created_at": created_at.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"生成海报/文案失败: {e}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/history")
|
|
|
|
|
|
async def get_history():
|
|
|
|
|
|
"""获取海报生成历史"""
|
|
|
|
|
|
try:
|
2026-01-21 08:41:47 +08:00
|
|
|
|
model = HaiBaoModel()
|
|
|
|
|
|
result = await model.get_history(50)
|
2026-01-20 09:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
formatted_result = []
|
|
|
|
|
|
for item in result:
|
|
|
|
|
|
item_dict = dict(item) if not isinstance(item, dict) else item
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(item_dict.get('created_at'), datetime):
|
|
|
|
|
|
item_dict['created_at'] = item_dict['created_at'].strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
|
|
|
|
|
|
|
# 确保 scheme_content 存在
|
|
|
|
|
|
if 'scheme_content' not in item_dict:
|
|
|
|
|
|
item_dict['scheme_content'] = ""
|
|
|
|
|
|
|
|
|
|
|
|
formatted_result.append(item_dict)
|
|
|
|
|
|
|
|
|
|
|
|
return formatted_result
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"获取历史失败: {e}")
|
|
|
|
|
|
return []
|