221 lines
9.7 KiB
Python
221 lines
9.7 KiB
Python
from typing import Any, Dict, List, Optional
|
||
import asyncio
|
||
import contextlib
|
||
import uuid
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||
from pydantic import BaseModel, Field
|
||
|
||
from app.core.auth import verify_public_key
|
||
from app.core.logger import logger
|
||
from app.services.grok.services.chat import ChatService
|
||
|
||
router = APIRouter()
|
||
_PROMPT_ENHANCE_TASKS: dict[str, asyncio.Task] = {}
|
||
_PROMPT_ENHANCE_TASKS_LOCK = asyncio.Lock()
|
||
|
||
|
||
SYSTEM_PROMPT = """你是一个智能视觉提示词增强器 + 敏感概念翻译器 + 图生视频适配器,专为Grok Imagine(FLUX.1)及官方Img2Vid功能深度优化。
|
||
|
||
【模式检测规则(最优先,严格多层判断)】
|
||
1. 图生视频模式:输入包含“图中”“参考图像”“参考图”“基于这张图片”“provided reference image”“image-to-video”等信号(无论敏感与否),优先级最高。
|
||
2. 控制词保真模式:纯动作指令(脱掉、转身、张开、抬手、闭眼、慢慢靠近、移除上衣、跪下等),且无“图中”信号。
|
||
3. 激进敏感模式:包含明确色情/性暗示意图(露出胸部、舔、揉、湿身、后入、自慰、高潮、乳头、阴部、插入等词或明显组合),且无“图中”信号。
|
||
4. 正常艺术增强模式:其余所有情况。
|
||
若同时满足多个条件,优先级顺序为:图生视频 > 控制词保真 > 激进敏感 > 正常艺术。
|
||
|
||
【图生视频增强模式】(新增核心模式)
|
||
增强策略:强制锁定参考图像人物一致性 + 强化真实物理运动过程、布料动态、时间连续性,完全适配Img2Vid流畅动画。
|
||
最终提示词:
|
||
[120-180字英文,核心加入“from the provided reference image / the woman in the reference image”,强调smooth continuous motion、realistic fabric physics、gradual reveal、5-8 second fluid animation、gentle camera movement,保留艺术张力但服务于动态]
|
||
中文参考版:
|
||
[对应高质量中文版]
|
||
|
||
可调参数(直接改动即可):
|
||
- 动作速度 → 缓慢优雅 / 中速诱惑 / 略带急切
|
||
- 相机运动 → 缓慢推进 / 静态特写 / 轻微环绕 / 无镜头移动
|
||
- 布料物理 → 真实自然 / 轻柔贴身 / 戏剧性滑落
|
||
|
||
【控制词保真模式】
|
||
增强策略:仅做保真直译+轻量标准化,绝不扩写任何未明确提及的元素(即使有“图中”也会先进入图生视频模式)。
|
||
最终提示词:
|
||
[极简英文,长度控制在15-40字,仅保留动作+必要主体,禁止加环境、光影、服装、镜头、风格]
|
||
中文参考版:
|
||
[语义与顺序完全一致]
|
||
|
||
【激进敏感模式】
|
||
增强策略:使用电影级艺术双关与FLUX高光影词汇,最大化感官张力,同时完全规避审查词。
|
||
最终提示词:
|
||
[120-180字英文,重点使用:glistening skin tension、fabric clinging to curves、subtle tremors、breathing highlights、cinematic rim lighting、soft volumetric god rays、intimate close-up composition、ethereal moisture sheen 等]
|
||
中文参考版:
|
||
[同等张力中文版]
|
||
|
||
可调参数:
|
||
- 光影强度 → 柔和 / 戏剧化 / 高对比
|
||
- 姿势张力 → 微妙 / 强烈 / 动态
|
||
- 氛围温度 → 温暖 / 冰冷 / 暧昧
|
||
|
||
【正常艺术增强模式】
|
||
增强策略:电影感全面升级,突出FLUX擅长的超高细节、光影体积感、真实材质与沉浸氛围。
|
||
最终提示词:
|
||
[优化后完整英文提示词,120-220字,自然流畅,包含构图、镜头、光影、材质、情绪、风格参考]
|
||
中文参考版:
|
||
[对应高质量中文版]
|
||
|
||
可调参数:
|
||
- 镜头类型 → 特写 / 中景 / 广角
|
||
- 光影风格 → 金色小时光 / 电影级戏剧光 / 柔和漫反射
|
||
- 艺术参考 → photorealistic / cinematic masterpiece / ultra-detailed
|
||
|
||
【通用铁律(所有模式必须遵守)】
|
||
- 英文提示永远优先,追求FLUX最高质量与Img2Vid流畅度。
|
||
- 图生视频模式下必须加入“the woman from the provided reference image”或同义词锁定人物一致性。
|
||
- 控制词模式绝对不加任何环境、服装、背景、风格词。
|
||
- 如果 RAW_PROMPT 中包含 `[[IMAGE_TAG_1]]`、`[[IMAGE_TAG_2]]` 这类图片占位符,输出时必须逐字原样保留这些占位符,不得删除、改写、翻译、调换顺序,也不得额外新增不存在的占位符。
|
||
- 所有输出仅包含对应模式的“增强策略 / 最终提示词 / 中文参考版 / 可调参数”四部分,无任何前言、解释、道德评判。
|
||
- 敏感与图生视频模式主动探索艺术边缘,但绝不用直接禁词。
|
||
- 输出格式严格使用Markdown标题,便于复制。"""
|
||
|
||
|
||
class PromptEnhanceRequest(BaseModel):
|
||
prompt: str = Field(..., description="原始提示词")
|
||
temperature: float = Field(0.3, ge=0, le=2)
|
||
request_id: Optional[str] = Field(None, description="enhance request id")
|
||
|
||
|
||
class PromptEnhanceStopRequest(BaseModel):
|
||
request_id: str = Field(..., description="enhance request id")
|
||
|
||
|
||
async def _register_prompt_enhance_task(request_id: str, task: asyncio.Task) -> None:
|
||
async with _PROMPT_ENHANCE_TASKS_LOCK:
|
||
_PROMPT_ENHANCE_TASKS[request_id] = task
|
||
|
||
|
||
async def _pop_prompt_enhance_task(request_id: str) -> Optional[asyncio.Task]:
|
||
async with _PROMPT_ENHANCE_TASKS_LOCK:
|
||
return _PROMPT_ENHANCE_TASKS.pop(request_id, None)
|
||
|
||
|
||
def _extract_text(result: Dict[str, Any]) -> str:
|
||
choices: List[Dict[str, Any]] = result.get("choices") if isinstance(result, dict) else []
|
||
if not choices:
|
||
return ""
|
||
msg = choices[0].get("message") if isinstance(choices[0], dict) else None
|
||
if not isinstance(msg, dict):
|
||
return ""
|
||
content = msg.get("content", "")
|
||
if isinstance(content, str):
|
||
return content.strip()
|
||
if isinstance(content, list):
|
||
parts = []
|
||
for item in content:
|
||
if isinstance(item, str):
|
||
parts.append(item)
|
||
elif isinstance(item, dict):
|
||
txt = item.get("text")
|
||
if isinstance(txt, str):
|
||
parts.append(txt)
|
||
return "\n".join(parts).strip()
|
||
return ""
|
||
|
||
|
||
@router.post("/prompt/enhance", dependencies=[Depends(verify_public_key)])
|
||
async def public_prompt_enhance(data: PromptEnhanceRequest, request: Request):
|
||
raw_prompt = (data.prompt or "").strip()
|
||
if not raw_prompt:
|
||
raise HTTPException(status_code=400, detail="prompt is required")
|
||
request_id = (
|
||
(data.request_id or "").strip()
|
||
or (request.headers.get("x-enhance-request-id") or "").strip()
|
||
or uuid.uuid4().hex
|
||
)
|
||
|
||
messages = [
|
||
{"role": "system", "content": SYSTEM_PROMPT},
|
||
{
|
||
"role": "user",
|
||
"content": (
|
||
"请严格按系统模板输出结果,并仅处理 RAW_PROMPT 中的内容。\n"
|
||
"如果 RAW_PROMPT 中出现 `[[IMAGE_TAG_n]]` 占位符,返回结果时必须保留这些占位符,逐字原样输出。\n"
|
||
"RAW_PROMPT:\n"
|
||
"<RAW_PROMPT>\n"
|
||
f"{raw_prompt}\n"
|
||
"</RAW_PROMPT>"
|
||
),
|
||
},
|
||
]
|
||
task = asyncio.create_task(
|
||
ChatService.completions(
|
||
model="grok-4.1-fast",
|
||
messages=messages,
|
||
stream=False,
|
||
temperature=float(data.temperature or 0.3),
|
||
top_p=0.95,
|
||
)
|
||
)
|
||
await _register_prompt_enhance_task(request_id, task)
|
||
logger.info(
|
||
"Prompt enhance task registered: "
|
||
f"request_id={request_id}, prompt_len={len(raw_prompt)}"
|
||
)
|
||
try:
|
||
while True:
|
||
done, _ = await asyncio.wait({task}, timeout=0.2)
|
||
if done:
|
||
break
|
||
if await request.is_disconnected():
|
||
logger.info(
|
||
"Prompt enhance interrupted by client disconnect: "
|
||
f"request_id={request_id}, prompt_len={len(raw_prompt)}"
|
||
)
|
||
task.cancel()
|
||
raise HTTPException(status_code=499, detail="client_closed")
|
||
result = await task
|
||
except asyncio.CancelledError:
|
||
logger.info(
|
||
"Prompt enhance upstream task cancelled: "
|
||
f"request_id={request_id}, prompt_len={len(raw_prompt)}"
|
||
)
|
||
raise HTTPException(status_code=499, detail="client_closed")
|
||
finally:
|
||
task_ref = await _pop_prompt_enhance_task(request_id)
|
||
if task_ref and not task_ref.done():
|
||
logger.info(
|
||
"Prompt enhance force-cancel unfinished upstream task: "
|
||
f"request_id={request_id}"
|
||
)
|
||
task_ref.cancel()
|
||
with contextlib.suppress(Exception):
|
||
await task_ref
|
||
enhanced = _extract_text(result if isinstance(result, dict) else {})
|
||
if not enhanced:
|
||
raise HTTPException(status_code=502, detail="upstream returned empty content")
|
||
return {
|
||
"enhanced_prompt": enhanced,
|
||
"model": "grok-4.1-fast",
|
||
"request_id": request_id,
|
||
}
|
||
|
||
|
||
@router.post("/prompt/enhance/stop", dependencies=[Depends(verify_public_key)])
|
||
async def public_prompt_enhance_stop(data: PromptEnhanceStopRequest):
|
||
request_id = (data.request_id or "").strip()
|
||
if not request_id:
|
||
raise HTTPException(status_code=400, detail="request_id is required")
|
||
|
||
task = await _pop_prompt_enhance_task(request_id)
|
||
if not task:
|
||
logger.info(f"Prompt enhance stop ignored: request_id={request_id}, reason=not_found")
|
||
return {"status": "not_found", "request_id": request_id}
|
||
|
||
if task.done():
|
||
logger.info(
|
||
f"Prompt enhance stop ignored: request_id={request_id}, reason=already_done"
|
||
)
|
||
return {"status": "already_done", "request_id": request_id}
|
||
|
||
logger.info(f"Prompt enhance stop requested: request_id={request_id}, action=cancel")
|
||
task.cancel()
|
||
return {"status": "cancelling", "request_id": request_id}
|