✨ feat(analytics): 增强 MCP 数据解析兼容性
- 优化用户资料和笔记详情的数据提取逻辑,优先从 `raw["raw"]["content"]` 获取内容,并回退到 `raw["content"]` - 在笔记详情解析中,增加从 `result["text"]` 提取文本的备用路径 - 在用户动态流解析中,优先从 `f["id"]` 获取笔记 ID,并增加无 ID 条目的日志警告 ✨ feat(persona): 扩展人设池并集成视觉风格配置 - 新增“赛博AI虚拟博主”和“性感福利主播”人设及其对应的主题与关键词 - 在 `sd_service.py` 中新增 `PERSONA_SD_PROFILES` 字典,为每个人设定义视觉增强词、风格后缀和 LLM 绘图指导 - 新增 `get_persona_sd_profile` 函数,根据人设文本匹配对应的视觉配置 ♻️ refactor(llm): 重构 SD 绘图提示词生成以支持人设 - 修改 `LLMService.get_sd_prompt_guide` 函数签名,新增 `persona` 参数 - 在生成的绘图指南中,根据匹配到的人设追加特定的视觉风格指导文本 - 针对“赛博AI虚拟博主”人设,调整反 AI 检测提示,允许使用高质量词汇和专业光效 - 更新所有调用 `get_sd_prompt_guide` 的地方(如文案生成函数),传入 `persona` 参数 ♻️ refactor(sd): 重构文生图服务以支持人设视觉增强 - 修改 `SDService.txt2img` 函数签名,新增 `persona` 参数 - 在生成最终提示词时,注入人设特定的增强词(`prompt_boost`)和风格词(`prompt_style`) - 在生成最终负面提示词时,追加人设特定的额外负面词(`negative_extra`) - 增加人设视觉增强已注入的日志信息 🔧 chore(config): 更新默认人设配置 - 将 `config_manager.py` 中的默认 `persona` 从“身材管理健身美女”更新为“性感福利主播” 🔧 chore(main): 更新 UI 函数签名以传递人设参数 - 更新 `generate_images` 函数签名,新增 `persona_text` 参数,并在内部解析为人设对象 - 更新 `auto_publish_once` 和 `generate_to_queue` 函数中调用 `sd_svc.txt2img` 的地方,传入 `persona` 参数 - 更新 Gradio 界面中 `btn_gen_img` 的点击事件,将 `persona` 输入传递给 `generate_images` 函数
This commit is contained in:
parent
5ee26cb782
commit
1ea8bfb554
@ -87,7 +87,14 @@ class AnalyticsService:
|
|||||||
raw = mcp_client.get_user_profile(user_id, xsec_token)
|
raw = mcp_client.get_user_profile(user_id, xsec_token)
|
||||||
text = ""
|
text = ""
|
||||||
if isinstance(raw, dict):
|
if isinstance(raw, dict):
|
||||||
content_list = raw.get("content", [])
|
# _call_tool 返回 {"success": True, "text": "...", "raw": <mcp原始响应>}
|
||||||
|
# 优先从 raw["raw"]["content"] 提取,兼容直接 content
|
||||||
|
inner_raw = raw.get("raw", {})
|
||||||
|
content_list = []
|
||||||
|
if isinstance(inner_raw, dict):
|
||||||
|
content_list = inner_raw.get("content", [])
|
||||||
|
if not content_list:
|
||||||
|
content_list = raw.get("content", [])
|
||||||
for item in content_list:
|
for item in content_list:
|
||||||
if isinstance(item, dict) and item.get("type") == "text":
|
if isinstance(item, dict) and item.get("type") == "text":
|
||||||
text = item.get("text", "")
|
text = item.get("text", "")
|
||||||
@ -122,8 +129,10 @@ class AnalyticsService:
|
|||||||
|
|
||||||
for f in feeds:
|
for f in feeds:
|
||||||
nc = f.get("noteCard") or {}
|
nc = f.get("noteCard") or {}
|
||||||
note_id = nc.get("noteId") or f.get("noteId", "")
|
# MCP 用户主页 feeds 中,笔记 ID 在 f["id"] 而非 nc["noteId"]
|
||||||
|
note_id = nc.get("noteId") or f.get("id", "") or f.get("noteId", "")
|
||||||
if not note_id:
|
if not note_id:
|
||||||
|
logger.warning("跳过无 ID 的笔记条目: keys=%s", list(f.keys()))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
interact = nc.get("interactInfo") or {}
|
interact = nc.get("interactInfo") or {}
|
||||||
@ -170,10 +179,19 @@ class AnalyticsService:
|
|||||||
result = mcp_client.get_feed_detail(note_id, xsec_token, load_all_comments=False)
|
result = mcp_client.get_feed_detail(note_id, xsec_token, load_all_comments=False)
|
||||||
text = ""
|
text = ""
|
||||||
if isinstance(result, dict):
|
if isinstance(result, dict):
|
||||||
for item in result.get("content", []):
|
# 兼容 _call_tool 包装格式
|
||||||
|
inner_raw = result.get("raw", {})
|
||||||
|
content_list = []
|
||||||
|
if isinstance(inner_raw, dict):
|
||||||
|
content_list = inner_raw.get("content", [])
|
||||||
|
if not content_list:
|
||||||
|
content_list = result.get("content", [])
|
||||||
|
for item in content_list:
|
||||||
if isinstance(item, dict) and item.get("type") == "text":
|
if isinstance(item, dict) and item.get("type") == "text":
|
||||||
text = item.get("text", "")
|
text = item.get("text", "")
|
||||||
break
|
break
|
||||||
|
if not text:
|
||||||
|
text = result.get("text", "")
|
||||||
if text:
|
if text:
|
||||||
data = None
|
data = None
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -17,7 +17,7 @@ DEFAULT_CONFIG = {
|
|||||||
"sd_url": "http://127.0.0.1:7860",
|
"sd_url": "http://127.0.0.1:7860",
|
||||||
"mcp_url": "http://localhost:18060/mcp",
|
"mcp_url": "http://localhost:18060/mcp",
|
||||||
"model": "gpt-3.5-turbo",
|
"model": "gpt-3.5-turbo",
|
||||||
"persona": "身材管理健身美女,热爱分享好身材秘诀和穿搭显身材技巧",
|
"persona": "性感福利主播,身材火辣衣着大胆,专注分享穿衣显身材和私房写真风穿搭",
|
||||||
"auto_reply_enabled": False,
|
"auto_reply_enabled": False,
|
||||||
"schedule_enabled": False,
|
"schedule_enabled": False,
|
||||||
"my_user_id": "",
|
"my_user_id": "",
|
||||||
|
|||||||
@ -289,9 +289,9 @@ class LLMService:
|
|||||||
self.model = model
|
self.model = model
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_sd_prompt_guide(sd_model_name: str = None) -> str:
|
def get_sd_prompt_guide(sd_model_name: str = None, persona: str = None) -> str:
|
||||||
"""根据当前 SD 模型生成 LLM 使用的绘图 Prompt 指南(含反 AI 检测指导)"""
|
"""根据当前 SD 模型 + 人设 生成 LLM 使用的绘图 Prompt 指南(含反 AI 检测指导 + 人设视觉风格)"""
|
||||||
from sd_service import SD_MODEL_PROFILES, detect_model_profile
|
from sd_service import SD_MODEL_PROFILES, detect_model_profile, get_persona_sd_profile
|
||||||
|
|
||||||
key = detect_model_profile(sd_model_name) if sd_model_name else "juggernautXL"
|
key = detect_model_profile(sd_model_name) if sd_model_name else "juggernautXL"
|
||||||
profile = SD_MODEL_PROFILES.get(key, SD_MODEL_PROFILES["juggernautXL"])
|
profile = SD_MODEL_PROFILES.get(key, SD_MODEL_PROFILES["juggernautXL"])
|
||||||
@ -309,8 +309,22 @@ class LLMService:
|
|||||||
"- 模拟手机拍照特征:phone camera, slightly overexposed, casual angle, not centered\n"
|
"- 模拟手机拍照特征:phone camera, slightly overexposed, casual angle, not centered\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 人设视觉风格指导 (追加于末尾)
|
||||||
|
persona_guide = ""
|
||||||
|
persona_sd = get_persona_sd_profile(persona) if persona else None
|
||||||
|
if persona_sd and persona_sd.get("llm_guide"):
|
||||||
|
persona_guide = persona_sd["llm_guide"]
|
||||||
|
# 赛博AI虚拟博主不需要反AI检测(大方承认是AI)
|
||||||
|
if "赛博AI虚拟博主" in (persona or ""):
|
||||||
|
anti_detect_tips = (
|
||||||
|
"\n\n【注意 - 本人设无需反AI检测】该博主大方承认是AI生成,不需要伪装真实感。\n"
|
||||||
|
"- 尽情使用 perfect, flawless, ultra detailed 等高质量词\n"
|
||||||
|
"- 追求视觉震撼和极致精致,不需要模拟手机拍照\n"
|
||||||
|
"- 可以使用 studio lighting, dramatic lighting, cinematic 等专业光效词\n"
|
||||||
|
)
|
||||||
|
|
||||||
if key == "majicmixRealistic":
|
if key == "majicmixRealistic":
|
||||||
return (
|
base = (
|
||||||
f"生成 Stable Diffusion 英文提示词,当前使用模型: {display} ({desc})\n"
|
f"生成 Stable Diffusion 英文提示词,当前使用模型: {display} ({desc})\n"
|
||||||
"该模型擅长东亚网红/朋友圈自拍风格,请按以下规则生成 sd_prompt:\n"
|
"该模型擅长东亚网红/朋友圈自拍风格,请按以下规则生成 sd_prompt:\n"
|
||||||
"- 人物要求(最重要!):必须是东亚面孔中国人\n"
|
"- 人物要求(最重要!):必须是东亚面孔中国人\n"
|
||||||
@ -320,10 +334,9 @@ class LLMService:
|
|||||||
"- 非常适合:自拍、穿搭展示、美妆效果、生活日常、闺蜜合照风格\n"
|
"- 非常适合:自拍、穿搭展示、美妆效果、生活日常、闺蜜合照风格\n"
|
||||||
"- 画面要有「朋友圈精选照片」的感觉,自然不做作\n"
|
"- 画面要有「朋友圈精选照片」的感觉,自然不做作\n"
|
||||||
"- 用英文逗号分隔"
|
"- 用英文逗号分隔"
|
||||||
+ anti_detect_tips
|
|
||||||
)
|
)
|
||||||
elif key == "realisticVision":
|
elif key == "realisticVision":
|
||||||
return (
|
base = (
|
||||||
f"生成 Stable Diffusion 英文提示词,当前使用模型: {display} ({desc})\n"
|
f"生成 Stable Diffusion 英文提示词,当前使用模型: {display} ({desc})\n"
|
||||||
"该模型擅长写实纪实摄影风格,请按以下规则生成 sd_prompt:\n"
|
"该模型擅长写实纪实摄影风格,请按以下规则生成 sd_prompt:\n"
|
||||||
"- 人物要求(最重要!):必须是东亚面孔中国人\n"
|
"- 人物要求(最重要!):必须是东亚面孔中国人\n"
|
||||||
@ -334,10 +347,9 @@ class LLMService:
|
|||||||
"- 非常适合:街拍、纪实风、旅行照、真实场景、有故事感的画面\n"
|
"- 非常适合:街拍、纪实风、旅行照、真实场景、有故事感的画面\n"
|
||||||
"- 画面要有「专业摄影师抓拍」的质感,保留真实皮肤纹理\n"
|
"- 画面要有「专业摄影师抓拍」的质感,保留真实皮肤纹理\n"
|
||||||
"- 用英文逗号分隔"
|
"- 用英文逗号分隔"
|
||||||
+ anti_detect_tips
|
|
||||||
)
|
)
|
||||||
else: # juggernautXL (SDXL)
|
else: # juggernautXL (SDXL)
|
||||||
return (
|
base = (
|
||||||
f"生成 Stable Diffusion 英文提示词,当前使用模型: {display} ({desc})\n"
|
f"生成 Stable Diffusion 英文提示词,当前使用模型: {display} ({desc})\n"
|
||||||
"该模型为 SDXL 架构,擅长电影级大片质感,请按以下规则生成 sd_prompt:\n"
|
"该模型为 SDXL 架构,擅长电影级大片质感,请按以下规则生成 sd_prompt:\n"
|
||||||
"- 人物要求(最重要!):必须是东亚面孔中国人,绝对禁止西方人特征\n"
|
"- 人物要求(最重要!):必须是东亚面孔中国人,绝对禁止西方人特征\n"
|
||||||
@ -348,9 +360,10 @@ class LLMService:
|
|||||||
"- 非常适合:商业摄影、时尚大片、复杂光影场景、杂志封面风格\n"
|
"- 非常适合:商业摄影、时尚大片、复杂光影场景、杂志封面风格\n"
|
||||||
"- 画面要有「电影画面/杂志大片」的高级感\n"
|
"- 画面要有「电影画面/杂志大片」的高级感\n"
|
||||||
"- 用英文逗号分隔"
|
"- 用英文逗号分隔"
|
||||||
+ anti_detect_tips
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return base + anti_detect_tips + persona_guide
|
||||||
|
|
||||||
def _chat(self, system_prompt: str, user_message: str,
|
def _chat(self, system_prompt: str, user_message: str,
|
||||||
json_mode: bool = True, temperature: float = 0.8) -> str:
|
json_mode: bool = True, temperature: float = 0.8) -> str:
|
||||||
"""底层聊天接口(含空返回检测、json_mode 回退、模型降级)"""
|
"""底层聊天接口(含空返回检测、json_mode 回退、模型降级)"""
|
||||||
@ -526,7 +539,7 @@ class LLMService:
|
|||||||
|
|
||||||
def generate_copy(self, topic: str, style: str, sd_model_name: str = None, persona: str = None) -> dict:
|
def generate_copy(self, topic: str, style: str, sd_model_name: str = None, persona: str = None) -> dict:
|
||||||
"""生成小红书文案(含重试逻辑,自动适配SD模型,支持人设)"""
|
"""生成小红书文案(含重试逻辑,自动适配SD模型,支持人设)"""
|
||||||
sd_guide = self.get_sd_prompt_guide(sd_model_name)
|
sd_guide = self.get_sd_prompt_guide(sd_model_name, persona=persona)
|
||||||
system_prompt = PROMPT_COPYWRITING.format(sd_prompt_guide=sd_guide)
|
system_prompt = PROMPT_COPYWRITING.format(sd_prompt_guide=sd_guide)
|
||||||
user_msg = f"主题:{topic}\n风格:{style}"
|
user_msg = f"主题:{topic}\n风格:{style}"
|
||||||
if persona:
|
if persona:
|
||||||
@ -569,7 +582,7 @@ class LLMService:
|
|||||||
def generate_copy_with_reference(self, topic: str, style: str,
|
def generate_copy_with_reference(self, topic: str, style: str,
|
||||||
reference_notes: str, sd_model_name: str = None, persona: str = None) -> dict:
|
reference_notes: str, sd_model_name: str = None, persona: str = None) -> dict:
|
||||||
"""参考热门笔记生成文案(含重试逻辑,自动适配SD模型,支持人设)"""
|
"""参考热门笔记生成文案(含重试逻辑,自动适配SD模型,支持人设)"""
|
||||||
sd_guide = self.get_sd_prompt_guide(sd_model_name)
|
sd_guide = self.get_sd_prompt_guide(sd_model_name, persona=persona)
|
||||||
prompt = PROMPT_COPY_WITH_REFERENCE.format(
|
prompt = PROMPT_COPY_WITH_REFERENCE.format(
|
||||||
reference_notes=reference_notes, topic=topic, style=style,
|
reference_notes=reference_notes, topic=topic, style=style,
|
||||||
sd_prompt_guide=sd_guide,
|
sd_prompt_guide=sd_guide,
|
||||||
@ -858,7 +871,7 @@ class LLMService:
|
|||||||
weight_insights: str, title_advice: str,
|
weight_insights: str, title_advice: str,
|
||||||
hot_tags: str, sd_model_name: str = None, persona: str = None) -> dict:
|
hot_tags: str, sd_model_name: str = None, persona: str = None) -> dict:
|
||||||
"""基于权重学习生成高互动潜力的文案(自动适配SD模型,支持人设)"""
|
"""基于权重学习生成高互动潜力的文案(自动适配SD模型,支持人设)"""
|
||||||
sd_guide = self.get_sd_prompt_guide(sd_model_name)
|
sd_guide = self.get_sd_prompt_guide(sd_model_name, persona=persona)
|
||||||
prompt = PROMPT_WEIGHTED_COPYWRITING.format(
|
prompt = PROMPT_WEIGHTED_COPYWRITING.format(
|
||||||
weight_insights=weight_insights,
|
weight_insights=weight_insights,
|
||||||
title_advice=title_advice,
|
title_advice=title_advice,
|
||||||
|
|||||||
44
main.py
44
main.py
@ -331,8 +331,8 @@ def generate_copy(model, topic, style, sd_model_name, persona_text):
|
|||||||
return "", "", "", "", f"❌ 生成失败: {e}"
|
return "", "", "", "", f"❌ 生成失败: {e}"
|
||||||
|
|
||||||
|
|
||||||
def generate_images(sd_url, prompt, neg_prompt, model, steps, cfg_scale, face_swap_on, face_img, quality_mode):
|
def generate_images(sd_url, prompt, neg_prompt, model, steps, cfg_scale, face_swap_on, face_img, quality_mode, persona_text=None):
|
||||||
"""生成图片(可选 ReActor 换脸,支持质量模式预设)"""
|
"""生成图片(可选 ReActor 换脸,支持质量模式预设,支持人设视觉优化)"""
|
||||||
if not model:
|
if not model:
|
||||||
return None, [], "❌ 未选择 SD 模型"
|
return None, [], "❌ 未选择 SD 模型"
|
||||||
try:
|
try:
|
||||||
@ -363,6 +363,7 @@ def generate_images(sd_url, prompt, neg_prompt, model, steps, cfg_scale, face_sw
|
|||||||
else:
|
else:
|
||||||
logger.warning("换脸已启用但未找到有效头像")
|
logger.warning("换脸已启用但未找到有效头像")
|
||||||
|
|
||||||
|
persona = _resolve_persona(persona_text) if persona_text else None
|
||||||
images = svc.txt2img(
|
images = svc.txt2img(
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
negative_prompt=neg_prompt,
|
negative_prompt=neg_prompt,
|
||||||
@ -371,6 +372,7 @@ def generate_images(sd_url, prompt, neg_prompt, model, steps, cfg_scale, face_sw
|
|||||||
cfg_scale=float(cfg_scale),
|
cfg_scale=float(cfg_scale),
|
||||||
face_image=face_image,
|
face_image=face_image,
|
||||||
quality_mode=quality_mode,
|
quality_mode=quality_mode,
|
||||||
|
persona=persona,
|
||||||
)
|
)
|
||||||
preset = get_sd_preset(quality_mode)
|
preset = get_sd_preset(quality_mode)
|
||||||
swap_hint = " (已换脸)" if face_image else ""
|
swap_hint = " (已换脸)" if face_image else ""
|
||||||
@ -1080,6 +1082,8 @@ def _get_stats_summary() -> str:
|
|||||||
|
|
||||||
# ================= 人设池 =================
|
# ================= 人设池 =================
|
||||||
DEFAULT_PERSONAS = [
|
DEFAULT_PERSONAS = [
|
||||||
|
"赛博AI虚拟博主,住在2077年的数码女孩,用AI生成高颜值写真和全球场景打卡,与粉丝超高频互动",
|
||||||
|
"性感福利主播,身材火辣衣着大胆,专注分享穿衣显身材和私房写真风穿搭",
|
||||||
"身材管理健身美女,热爱分享好身材秘诀和穿搭显身材技巧",
|
"身材管理健身美女,热爱分享好身材秘诀和穿搭显身材技巧",
|
||||||
"温柔知性的时尚博主,喜欢分享日常穿搭和生活美学",
|
"温柔知性的时尚博主,喜欢分享日常穿搭和生活美学",
|
||||||
"元气满满的大学生,热爱探店和平价好物分享",
|
"元气满满的大学生,热爱探店和平价好物分享",
|
||||||
@ -1114,6 +1118,20 @@ RANDOM_PERSONA_LABEL = "🎲 随机人设(每次自动切换)"
|
|||||||
# 每个人设对应一组相符的评论关键词和主题,切换人设时自动同步
|
# 每个人设对应一组相符的评论关键词和主题,切换人设时自动同步
|
||||||
|
|
||||||
PERSONA_POOL_MAP = {
|
PERSONA_POOL_MAP = {
|
||||||
|
# ---- 性感福利类 ----
|
||||||
|
"性感福利主播": {
|
||||||
|
"topics": [
|
||||||
|
"辣妹穿搭", "内衣测评", "比基尼穿搭", "私房写真风穿搭", "吊带裙穿搭",
|
||||||
|
"低胸穿搭", "紧身连衣裙", "蕾丝穿搭", "泳衣测评", "居家睡衣穿搭",
|
||||||
|
"露背装穿搭", "热裤穿搭", "性感御姐穿搭", "渔网袜穿搭", "包臀裙穿搭",
|
||||||
|
"锁骨链饰品", "身材展示", "好身材日常", "氛围感私房照", "海边度假穿搭",
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"辣妹", "性感穿搭", "内衣", "比基尼", "吊带", "低胸",
|
||||||
|
"紧身", "蕾丝", "泳衣", "睡衣", "露背", "热裤",
|
||||||
|
"御姐", "好身材", "包臀裙", "身材展示", "私房", "氛围感",
|
||||||
|
],
|
||||||
|
},
|
||||||
# ---- 身材管理类 ----
|
# ---- 身材管理类 ----
|
||||||
"身材管理健身美女": {
|
"身材管理健身美女": {
|
||||||
"topics": [
|
"topics": [
|
||||||
@ -1404,6 +1422,20 @@ PERSONA_POOL_MAP = {
|
|||||||
"装修", "灯光", "绿植", "北欧", "ins风", "布置",
|
"装修", "灯光", "绿植", "北欧", "ins风", "布置",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
# ---- 赛博/AI 虚拟博主类 ----
|
||||||
|
"赛博AI虚拟博主": {
|
||||||
|
"topics": [
|
||||||
|
"AI女孩日常", "虚拟人物写真", "AI生成美女", "赛博朴克穿搭", "未来风穿搭",
|
||||||
|
"全球场景打卡", "巴黎打卡写真", "东京街头拍照", "外太空写真", "古风仙侠写真",
|
||||||
|
"AI换装挑战", "粉丝许愿穿搭", "二次元风格写真", "女仆装写真", "护士制服写真",
|
||||||
|
"校园制服写真", "婚纱写真", "水下写真", "AI绘画教程", "虚拟人物背后故事",
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"AI女孩", "AI美女", "虚拟人物", "赛博朴克", "AI绘画", "AI写真",
|
||||||
|
"数码女孩", "2077", "未来风", "场景切换", "换装挑战", "粉丝许愿",
|
||||||
|
"高颜值", "特写", "全球打卡", "制服写真", "AI创作", "互动",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# 为"随机人设"使用的全量池(兼容旧逻辑)
|
# 为"随机人设"使用的全量池(兼容旧逻辑)
|
||||||
@ -2077,7 +2109,8 @@ def auto_publish_once(topics_str, mcp_url, sd_url_val, sd_model_name, model, per
|
|||||||
_auto_log_append("⚠️ 换脸已启用但未找到头像,跳过换脸")
|
_auto_log_append("⚠️ 换脸已启用但未找到头像,跳过换脸")
|
||||||
images = sd_svc.txt2img(prompt=sd_prompt, model=sd_model_name,
|
images = sd_svc.txt2img(prompt=sd_prompt, model=sd_model_name,
|
||||||
face_image=face_image,
|
face_image=face_image,
|
||||||
quality_mode=quality_mode_val or "快速 (约30秒)")
|
quality_mode=quality_mode_val or "快速 (约30秒)",
|
||||||
|
persona=persona)
|
||||||
if not images:
|
if not images:
|
||||||
_record_error()
|
_record_error()
|
||||||
return "❌ 图片生成失败:没有返回图片"
|
return "❌ 图片生成失败:没有返回图片"
|
||||||
@ -2210,7 +2243,8 @@ def generate_to_queue(topics_str, sd_url_val, sd_model_name, model, persona_text
|
|||||||
face_image = SDService.load_face_image()
|
face_image = SDService.load_face_image()
|
||||||
images = sd_svc.txt2img(prompt=sd_prompt, model=sd_model_name,
|
images = sd_svc.txt2img(prompt=sd_prompt, model=sd_model_name,
|
||||||
face_image=face_image,
|
face_image=face_image,
|
||||||
quality_mode=quality_mode_val or "快速 (约30秒)")
|
quality_mode=quality_mode_val or "快速 (约30秒)",
|
||||||
|
persona=persona)
|
||||||
if not images:
|
if not images:
|
||||||
_auto_log_append(f"⚠️ 第 {i+1} 篇图片生成失败,跳过")
|
_auto_log_append(f"⚠️ 第 {i+1} 篇图片生成失败,跳过")
|
||||||
continue
|
continue
|
||||||
@ -3873,7 +3907,7 @@ with gr.Blocks(
|
|||||||
btn_gen_img.click(
|
btn_gen_img.click(
|
||||||
fn=generate_images,
|
fn=generate_images,
|
||||||
inputs=[sd_url, res_prompt, neg_prompt, sd_model, steps, cfg_scale,
|
inputs=[sd_url, res_prompt, neg_prompt, sd_model, steps, cfg_scale,
|
||||||
face_swap_toggle, face_image_preview, quality_mode],
|
face_swap_toggle, face_image_preview, quality_mode, persona],
|
||||||
outputs=[gallery, state_images, status_bar],
|
outputs=[gallery, state_images, status_bar],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
249
sd_service.py
249
sd_service.py
@ -202,6 +202,236 @@ SD_MODEL_PROFILES = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ==================== 人设 SD 视觉配置 ====================
|
||||||
|
# 每个人设对应一组 SD prompt 增强词 + 视觉风格 + LLM 绘图指导
|
||||||
|
# key 匹配规则: persona 文本中包含 key 即命中
|
||||||
|
|
||||||
|
PERSONA_SD_PROFILES = {
|
||||||
|
# ---- 赛博AI虚拟博主 ----
|
||||||
|
"赛博AI虚拟博主": {
|
||||||
|
# 追加到 SD prompt 前面的人设特有增强词
|
||||||
|
"prompt_boost": (
|
||||||
|
"(perfect face:1.3), (extremely detailed face:1.3), (beautiful detailed eyes:1.3), "
|
||||||
|
"(flawless skin:1.2), (ultra high resolution face:1.2), (glossy lips:1.1), "
|
||||||
|
),
|
||||||
|
# 追加到 SD prompt 后面的风格词
|
||||||
|
"prompt_style": (
|
||||||
|
", cinematic portrait, fashion editorial, vibrant colors, "
|
||||||
|
"dramatic lighting, creative background, fantasy environment, "
|
||||||
|
"instagram influencer, trending on artstation"
|
||||||
|
),
|
||||||
|
# 额外追加到负面提示词
|
||||||
|
"negative_extra": "",
|
||||||
|
# LLM 绘图指导 (追加到 sd_prompt_guide)
|
||||||
|
"llm_guide": (
|
||||||
|
"\n\n【人设视觉风格 - 赛博AI虚拟博主】\n"
|
||||||
|
"你在为一个大方承认自己是AI的虚拟博主生成图片,主打高颜值+场景奇观:\n"
|
||||||
|
"- 五官极致精致:重点描述 detailed eyes, long eyelashes, glossy lips, perfect skin\n"
|
||||||
|
"- 场景大胆奇幻:巴黎埃菲尔铁塔前、东京霓虹街头、赛博朋克城市、外太空空间站、水下宫殿、樱花隧道等\n"
|
||||||
|
"- 光效华丽:neon lights, cyberpunk glow, holographic, lens flare, volumetric fog\n"
|
||||||
|
"- 穿搭多变:可大胆使用 futuristic outfit, holographic dress, cyberpunk jacket, maid outfit, school uniform, wedding dress 等\n"
|
||||||
|
"- 构图要有视觉冲击力:close-up portrait, dynamic angle, full body shot, looking at viewer\n"
|
||||||
|
"- 整体风格:超现实+高颜值,不需要追求真实感,要追求视觉震撼\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
# ---- 性感福利主播 ----
|
||||||
|
"性感福利主播": {
|
||||||
|
"prompt_boost": (
|
||||||
|
"(beautiful detailed face:1.3), (seductive eyes:1.2), (glossy lips:1.2), "
|
||||||
|
"(perfect body proportions:1.2), (slim waist:1.2), (long legs:1.2), "
|
||||||
|
"(glamour photography:1.3), "
|
||||||
|
),
|
||||||
|
"prompt_style": (
|
||||||
|
", soft glamour lighting, beauty retouching, "
|
||||||
|
"intimate atmosphere, warm golden tones, shallow depth of field, "
|
||||||
|
"boudoir style, sensual but tasteful, fashion model pose"
|
||||||
|
),
|
||||||
|
"negative_extra": "",
|
||||||
|
"llm_guide": (
|
||||||
|
"\n\n【人设视觉风格 - 性感福利主播】\n"
|
||||||
|
"为一个身材曼妙的时尚博主生成图片,主打身材美感+氛围感:\n"
|
||||||
|
"- 身材描述:slim waist, long legs, perfect figure, hourglass body, graceful pose\n"
|
||||||
|
"- 穿搭关键:lingerie, bikini, bodycon dress, off-shoulder, backless dress, lace, sheer fabric\n"
|
||||||
|
"- 光线氛围:soft warm lighting, golden hour, window light, candle light, intimate mood\n"
|
||||||
|
"- 场景选择:bedroom, luxury hotel, swimming pool, beach sunset, mirror selfie\n"
|
||||||
|
"- 构图要点:full body or three-quarter shot, emphasize curves, elegant pose, looking at viewer\n"
|
||||||
|
"- 整体风格:glamour photography,魅力但有品位,不要低俗\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
# ---- 身材管理健身美女 ----
|
||||||
|
"身材管理健身美女": {
|
||||||
|
"prompt_boost": (
|
||||||
|
"(fit body:1.3), (athletic physique:1.2), (toned muscles:1.2), "
|
||||||
|
"(healthy glow:1.2), (confident pose:1.2), "
|
||||||
|
),
|
||||||
|
"prompt_style": (
|
||||||
|
", fitness photography, gym environment, athletic wear, "
|
||||||
|
"dynamic pose, energetic mood, healthy lifestyle, "
|
||||||
|
"motivational, natural sweat, workout aesthetic"
|
||||||
|
),
|
||||||
|
"negative_extra": "",
|
||||||
|
"llm_guide": (
|
||||||
|
"\n\n【人设视觉风格 - 身材管理健身美女】\n"
|
||||||
|
"为一个健身达人生成图片,主打健康美+运动感:\n"
|
||||||
|
"- 身材描述:fit body, toned abs, lean muscles, athletic build, healthy skin\n"
|
||||||
|
"- 穿搭关键:sports bra, yoga pants, running outfit, gym wear, crop top\n"
|
||||||
|
"- 场景选择:gym, yoga studio, outdoor running, home workout, mirror selfie at gym\n"
|
||||||
|
"- 动作姿势:workout pose, stretching, running, yoga pose, flexing, plank\n"
|
||||||
|
"- 光线:gym lighting, natural daylight, morning sun, energetic bright tones\n"
|
||||||
|
"- 整体风格:充满活力的运动健身风,展现自律和力量美\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
# ---- 温柔知性的时尚博主 ----
|
||||||
|
"温柔知性时尚博主": {
|
||||||
|
"prompt_boost": (
|
||||||
|
"(elegant:1.2), (gentle expression:1.2), (sophisticated:1.2), "
|
||||||
|
"(fashion forward:1.2), (graceful:1.1), "
|
||||||
|
),
|
||||||
|
"prompt_style": (
|
||||||
|
", fashion editorial, street style photography, "
|
||||||
|
"chic outfit, elegant pose, soft natural tones, "
|
||||||
|
"magazine cover quality, lifestyle photography"
|
||||||
|
),
|
||||||
|
"negative_extra": "",
|
||||||
|
"llm_guide": (
|
||||||
|
"\n\n【人设视觉风格 - 温柔知性时尚博主】\n"
|
||||||
|
"为一个知性优雅的时尚博主生成图片,主打高级感穿搭:\n"
|
||||||
|
"- 气质描述:elegant, gentle smile, sophisticated, poised, graceful\n"
|
||||||
|
"- 穿搭关键:french style, minimalist outfit, trench coat, silk blouse, midi skirt, neutral colors\n"
|
||||||
|
"- 场景选择:cafe, art gallery, tree-lined street, bookstore, european architecture\n"
|
||||||
|
"- 构图:three-quarter shot, walking pose, looking away candidly, holding coffee\n"
|
||||||
|
"- 色调:warm neutral tones, soft creamy, muted colors, film aesthetic\n"
|
||||||
|
"- 整体风格:法式优雅+知性温柔,像时尚杂志的生活方式大片\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
# ---- 文艺青年摄影师 ----
|
||||||
|
"文艺青年摄影师": {
|
||||||
|
"prompt_boost": (
|
||||||
|
"(artistic:1.2), (moody atmosphere:1.2), (film grain:1.2), "
|
||||||
|
"(vintage tones:1.1), "
|
||||||
|
),
|
||||||
|
"prompt_style": (
|
||||||
|
", film photography, 35mm film, kodak portra 400, "
|
||||||
|
"vintage color grading, indie aesthetic, dreamy atmosphere, "
|
||||||
|
"golden hour, nostalgic mood"
|
||||||
|
),
|
||||||
|
"negative_extra": "",
|
||||||
|
"llm_guide": (
|
||||||
|
"\n\n【人设视觉风格 - 文艺青年摄影师】\n"
|
||||||
|
"为文艺摄影师生成图片,主打胶片感+故事性:\n"
|
||||||
|
"- 风格关键:film grain, vintage tones, kodak portra, dreamy, nostalgic\n"
|
||||||
|
"- 场景选择:old streets, abandoned places, cafe corner, train station, seaside, flower field\n"
|
||||||
|
"- 光线:golden hour, window light, overcast soft light, dappled sunlight\n"
|
||||||
|
"- 构图:off-center composition, back view, silhouette, reflection\n"
|
||||||
|
"- 色调:warm vintage, faded colors, low contrast, film color grading\n"
|
||||||
|
"- 整体风格:独立电影画面感,有故事性的文艺氛围\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
# ---- 二次元coser ----
|
||||||
|
"二次元coser": {
|
||||||
|
"prompt_boost": (
|
||||||
|
"(cosplay:1.3), (detailed costume:1.2), (colorful:1.2), "
|
||||||
|
"(anime inspired:1.1), (vibrant:1.2), "
|
||||||
|
),
|
||||||
|
"prompt_style": (
|
||||||
|
", cosplay photography, anime convention, colorful costume, "
|
||||||
|
"dynamic pose, vibrant colors, fantasy setting, "
|
||||||
|
"dramatic lighting, character portrayal"
|
||||||
|
),
|
||||||
|
"negative_extra": "",
|
||||||
|
"llm_guide": (
|
||||||
|
"\n\n【人设视觉风格 - 二次元coser】\n"
|
||||||
|
"为cos博主生成图片,主打二次元还原+视觉冲击:\n"
|
||||||
|
"- 风格关键:cosplay, detailed costume, colorful wig, contact lenses, anime style\n"
|
||||||
|
"- 场景选择:anime convention, fantasy landscape, school rooftop, cherry blossoms, studio backdrop\n"
|
||||||
|
"- 动作姿势:character pose, dynamic action, cute pose, peace sign, holding prop\n"
|
||||||
|
"- 光效:colored lighting, rim light, sparkle effects, dramatic shadows\n"
|
||||||
|
"- 色调:vibrant saturated, anime color palette, high contrast\n"
|
||||||
|
"- 整体风格:真人cos感,兼具二次元的鲜艳感和真实摄影的质感\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
# ---- 汉服爱好者 ----
|
||||||
|
"汉服爱好者": {
|
||||||
|
"prompt_boost": (
|
||||||
|
"(traditional chinese dress:1.3), (hanfu:1.3), (chinese aesthetic:1.2), "
|
||||||
|
"(elegant traditional:1.2), (delicate accessories:1.1), "
|
||||||
|
),
|
||||||
|
"prompt_style": (
|
||||||
|
", traditional chinese photography, han dynasty style, "
|
||||||
|
"ink painting aesthetic, bamboo forest, ancient architecture, "
|
||||||
|
"flowing silk fabric, classical beauty, ethereal atmosphere"
|
||||||
|
),
|
||||||
|
"negative_extra": "",
|
||||||
|
"llm_guide": (
|
||||||
|
"\n\n【人设视觉风格 - 汉服爱好者】\n"
|
||||||
|
"为国风汉服博主生成图片,主打古典美+中国风:\n"
|
||||||
|
"- 服饰描述:hanfu, flowing silk robes, wide sleeves, hair accessories, jade earrings, fan\n"
|
||||||
|
"- 场景选择:bamboo forest, ancient temple, moon gate, lotus pond, plum blossom, mountain mist\n"
|
||||||
|
"- 光线:soft diffused light, misty atmosphere, morning fog, moonlight, lantern glow\n"
|
||||||
|
"- 构图:full body flowing fabric, profile view, looking down gently, holding umbrella\n"
|
||||||
|
"- 色调:muted earth tones, ink wash style, red and white contrast, jade green\n"
|
||||||
|
"- 整体风格:仙气飘飘的古风摄影,有水墨画的意境\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
# ---- 独居女孩 ----
|
||||||
|
"独居女孩": {
|
||||||
|
"prompt_boost": (
|
||||||
|
"(cozy atmosphere:1.3), (warm lighting:1.2), (homey:1.2), "
|
||||||
|
"(casual style:1.1), (relaxed:1.1), "
|
||||||
|
),
|
||||||
|
"prompt_style": (
|
||||||
|
", cozy home photography, warm ambient light, "
|
||||||
|
"casual indoor style, hygge aesthetic, "
|
||||||
|
"soft blanket, candle light, peaceful morning"
|
||||||
|
),
|
||||||
|
"negative_extra": "",
|
||||||
|
"llm_guide": (
|
||||||
|
"\n\n【人设视觉风格 - 独居女孩】\n"
|
||||||
|
"为独居生活博主生成图片,主打温馨氛围感+仪式感:\n"
|
||||||
|
"- 氛围关键:cozy, warm, hygge, peaceful, intimate, ambient candlelight\n"
|
||||||
|
"- 场景选择:small apartment, kitchen cooking, bathtub, reading by window, balcony garden\n"
|
||||||
|
"- 穿搭关键:oversized sweater, pajamas, casual homewear, messy bun\n"
|
||||||
|
"- 光线:warm lamp light, candle glow, morning window light, fairy lights\n"
|
||||||
|
"- 道具:coffee mug, book, cat, houseplant, scented candle, blanket\n"
|
||||||
|
"- 整体风格:温暖治愈的独居日常,有仪式感的精致生活\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
# ---- 资深美妆博主 ----
|
||||||
|
"资深美妆博主": {
|
||||||
|
"prompt_boost": (
|
||||||
|
"(flawless makeup:1.3), (detailed eye makeup:1.3), (beauty close-up:1.2), "
|
||||||
|
"(perfect skin:1.2), (beauty lighting:1.2), "
|
||||||
|
),
|
||||||
|
"prompt_style": (
|
||||||
|
", beauty photography, ring light, macro lens, "
|
||||||
|
"studio beauty lighting, makeup tutorial style, "
|
||||||
|
"dewy skin, perfect complexion, vibrant lip color"
|
||||||
|
),
|
||||||
|
"negative_extra": "",
|
||||||
|
"llm_guide": (
|
||||||
|
"\n\n【人设视觉风格 - 资深美妆博主】\n"
|
||||||
|
"为美妆博主生成图片,主打妆容特写+产品展示:\n"
|
||||||
|
"- 妆容描述:detailed eye shadow, winged eyeliner, glossy lips, dewy foundation, blush\n"
|
||||||
|
"- 构图要点:face close-up, half face, eye close-up, lip close-up, before and after\n"
|
||||||
|
"- 场景选择:vanity desk, bathroom mirror, ring light studio, flat lay of products\n"
|
||||||
|
"- 光线:ring light, beauty dish, soft diffused studio light, bright even lighting\n"
|
||||||
|
"- 色调:clean bright, pink tones, neutral with pops of color\n"
|
||||||
|
"- 整体风格:专业美妆教程感,妆容细节清晰可见\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_persona_sd_profile(persona_text: str) -> dict | None:
|
||||||
|
"""根据人设文本匹配 SD 视觉配置,返回 profile dict 或 None"""
|
||||||
|
if not persona_text:
|
||||||
|
return None
|
||||||
|
for key, profile in PERSONA_SD_PROFILES.items():
|
||||||
|
if key in persona_text:
|
||||||
|
return profile
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# 默认配置 profile key
|
# 默认配置 profile key
|
||||||
DEFAULT_MODEL_PROFILE = "juggernautXL"
|
DEFAULT_MODEL_PROFILE = "juggernautXL"
|
||||||
|
|
||||||
@ -514,13 +744,15 @@ class SDService:
|
|||||||
scheduler: str = None,
|
scheduler: str = None,
|
||||||
face_image: Image.Image = None,
|
face_image: Image.Image = None,
|
||||||
quality_mode: str = None,
|
quality_mode: str = None,
|
||||||
|
persona: str = None,
|
||||||
) -> list[Image.Image]:
|
) -> list[Image.Image]:
|
||||||
"""文生图(自动适配当前 SD 模型的最优参数)
|
"""文生图(自动适配当前 SD 模型 + 人设的最优参数)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
model: SD 模型名,自动识别并应用对应配置
|
model: SD 模型名,自动识别并应用对应配置
|
||||||
face_image: 头像 PIL Image,传入后自动启用 ReActor 换脸
|
face_image: 头像 PIL Image,传入后自动启用 ReActor 换脸
|
||||||
quality_mode: 预设模式名
|
quality_mode: 预设模式名
|
||||||
|
persona: 博主人设文本,自动注入人设视觉增强词
|
||||||
"""
|
"""
|
||||||
if model:
|
if model:
|
||||||
self.switch_model(model)
|
self.switch_model(model)
|
||||||
@ -534,11 +766,20 @@ class SDService:
|
|||||||
# 加载模型专属预设参数
|
# 加载模型专属预设参数
|
||||||
preset = get_sd_preset(quality_mode, model) if quality_mode else get_sd_preset("标准 (约1分钟)", model)
|
preset = get_sd_preset(quality_mode, model) if quality_mode else get_sd_preset("标准 (约1分钟)", model)
|
||||||
|
|
||||||
# 自动增强 prompt: 前缀 + 原始 prompt + 后缀
|
# 自动增强 prompt: 人设增强 + 模型前缀 + 原始 prompt + 模型后缀 + 人设风格
|
||||||
enhanced_prompt = profile.get("prompt_prefix", "") + prompt + profile.get("prompt_suffix", "")
|
persona_sd = get_persona_sd_profile(persona)
|
||||||
|
persona_boost = persona_sd.get("prompt_boost", "") if persona_sd else ""
|
||||||
|
persona_style = persona_sd.get("prompt_style", "") if persona_sd else ""
|
||||||
|
enhanced_prompt = persona_boost + profile.get("prompt_prefix", "") + prompt + profile.get("prompt_suffix", "") + persona_style
|
||||||
|
|
||||||
# 使用模型专属反向提示词
|
if persona_sd:
|
||||||
|
logger.info("🎭 人设视觉增强已注入: +%d boost词 +%d style词",
|
||||||
|
len(persona_boost), len(persona_style))
|
||||||
|
|
||||||
|
# 使用模型专属反向提示词 + 人设额外负面词
|
||||||
final_negative = negative_prompt if negative_prompt is not None else profile.get("negative_prompt", DEFAULT_NEGATIVE)
|
final_negative = negative_prompt if negative_prompt is not None else profile.get("negative_prompt", DEFAULT_NEGATIVE)
|
||||||
|
if persona_sd and persona_sd.get("negative_extra"):
|
||||||
|
final_negative = final_negative + ", " + persona_sd["negative_extra"]
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"prompt": enhanced_prompt,
|
"prompt": enhanced_prompt,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user