From 1ea8bfb554f430022eb3833b29a63175bce2358d Mon Sep 17 00:00:00 2001 From: zhoujie <929834232@qq.com> Date: Tue, 10 Feb 2026 22:29:55 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(analytics):=20=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=20MCP=20=E6=95=B0=E6=8D=AE=E8=A7=A3=E6=9E=90=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化用户资料和笔记详情的数据提取逻辑,优先从 `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` 函数 --- analytics_service.py | 24 ++++- config_manager.py | 2 +- llm_service.py | 37 ++++--- main.py | 44 +++++++- sd_service.py | 249 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 331 insertions(+), 25 deletions(-) diff --git a/analytics_service.py b/analytics_service.py index 982e169..2d0e5a3 100644 --- a/analytics_service.py +++ b/analytics_service.py @@ -87,7 +87,14 @@ class AnalyticsService: raw = mcp_client.get_user_profile(user_id, xsec_token) text = "" if isinstance(raw, dict): - content_list = raw.get("content", []) + # _call_tool 返回 {"success": True, "text": "...", "raw": } + # 优先从 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: if isinstance(item, dict) and item.get("type") == "text": text = item.get("text", "") @@ -122,8 +129,10 @@ class AnalyticsService: for f in feeds: 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: + logger.warning("跳过无 ID 的笔记条目: keys=%s", list(f.keys())) continue 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) text = "" 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": text = item.get("text", "") break + if not text: + text = result.get("text", "") if text: data = None try: diff --git a/config_manager.py b/config_manager.py index 5c42977..e70cc6e 100644 --- a/config_manager.py +++ b/config_manager.py @@ -17,7 +17,7 @@ DEFAULT_CONFIG = { "sd_url": "http://127.0.0.1:7860", "mcp_url": "http://localhost:18060/mcp", "model": "gpt-3.5-turbo", - "persona": "身材管理健身美女,热爱分享好身材秘诀和穿搭显身材技巧", + "persona": "性感福利主播,身材火辣衣着大胆,专注分享穿衣显身材和私房写真风穿搭", "auto_reply_enabled": False, "schedule_enabled": False, "my_user_id": "", diff --git a/llm_service.py b/llm_service.py index 0fdb343..fd718ff 100644 --- a/llm_service.py +++ b/llm_service.py @@ -289,9 +289,9 @@ class LLMService: self.model = model @staticmethod - def get_sd_prompt_guide(sd_model_name: str = None) -> str: - """根据当前 SD 模型生成 LLM 使用的绘图 Prompt 指南(含反 AI 检测指导)""" - from sd_service import SD_MODEL_PROFILES, detect_model_profile + def get_sd_prompt_guide(sd_model_name: str = None, persona: str = None) -> str: + """根据当前 SD 模型 + 人设 生成 LLM 使用的绘图 Prompt 指南(含反 AI 检测指导 + 人设视觉风格)""" + 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" 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" ) + # 人设视觉风格指导 (追加于末尾) + 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": - return ( + base = ( f"生成 Stable Diffusion 英文提示词,当前使用模型: {display} ({desc})\n" "该模型擅长东亚网红/朋友圈自拍风格,请按以下规则生成 sd_prompt:\n" "- 人物要求(最重要!):必须是东亚面孔中国人\n" @@ -320,10 +334,9 @@ class LLMService: "- 非常适合:自拍、穿搭展示、美妆效果、生活日常、闺蜜合照风格\n" "- 画面要有「朋友圈精选照片」的感觉,自然不做作\n" "- 用英文逗号分隔" - + anti_detect_tips ) elif key == "realisticVision": - return ( + base = ( f"生成 Stable Diffusion 英文提示词,当前使用模型: {display} ({desc})\n" "该模型擅长写实纪实摄影风格,请按以下规则生成 sd_prompt:\n" "- 人物要求(最重要!):必须是东亚面孔中国人\n" @@ -334,10 +347,9 @@ class LLMService: "- 非常适合:街拍、纪实风、旅行照、真实场景、有故事感的画面\n" "- 画面要有「专业摄影师抓拍」的质感,保留真实皮肤纹理\n" "- 用英文逗号分隔" - + anti_detect_tips ) else: # juggernautXL (SDXL) - return ( + base = ( f"生成 Stable Diffusion 英文提示词,当前使用模型: {display} ({desc})\n" "该模型为 SDXL 架构,擅长电影级大片质感,请按以下规则生成 sd_prompt:\n" "- 人物要求(最重要!):必须是东亚面孔中国人,绝对禁止西方人特征\n" @@ -348,9 +360,10 @@ class LLMService: "- 非常适合:商业摄影、时尚大片、复杂光影场景、杂志封面风格\n" "- 画面要有「电影画面/杂志大片」的高级感\n" "- 用英文逗号分隔" - + anti_detect_tips ) + return base + anti_detect_tips + persona_guide + def _chat(self, system_prompt: str, user_message: str, json_mode: bool = True, temperature: float = 0.8) -> str: """底层聊天接口(含空返回检测、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: """生成小红书文案(含重试逻辑,自动适配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) user_msg = f"主题:{topic}\n风格:{style}" if persona: @@ -569,7 +582,7 @@ class LLMService: def generate_copy_with_reference(self, topic: str, style: str, reference_notes: str, sd_model_name: str = None, persona: str = None) -> dict: """参考热门笔记生成文案(含重试逻辑,自动适配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( reference_notes=reference_notes, topic=topic, style=style, sd_prompt_guide=sd_guide, @@ -858,7 +871,7 @@ class LLMService: weight_insights: str, title_advice: str, hot_tags: str, sd_model_name: str = None, persona: str = None) -> dict: """基于权重学习生成高互动潜力的文案(自动适配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( weight_insights=weight_insights, title_advice=title_advice, diff --git a/main.py b/main.py index 373e4d9..c787b36 100644 --- a/main.py +++ b/main.py @@ -331,8 +331,8 @@ def generate_copy(model, topic, style, sd_model_name, persona_text): return "", "", "", "", f"❌ 生成失败: {e}" -def generate_images(sd_url, prompt, neg_prompt, model, steps, cfg_scale, face_swap_on, face_img, quality_mode): - """生成图片(可选 ReActor 换脸,支持质量模式预设)""" +def generate_images(sd_url, prompt, neg_prompt, model, steps, cfg_scale, face_swap_on, face_img, quality_mode, persona_text=None): + """生成图片(可选 ReActor 换脸,支持质量模式预设,支持人设视觉优化)""" if not model: return None, [], "❌ 未选择 SD 模型" try: @@ -363,6 +363,7 @@ def generate_images(sd_url, prompt, neg_prompt, model, steps, cfg_scale, face_sw else: logger.warning("换脸已启用但未找到有效头像") + persona = _resolve_persona(persona_text) if persona_text else None images = svc.txt2img( prompt=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), face_image=face_image, quality_mode=quality_mode, + persona=persona, ) preset = get_sd_preset(quality_mode) swap_hint = " (已换脸)" if face_image else "" @@ -1080,6 +1082,8 @@ def _get_stats_summary() -> str: # ================= 人设池 ================= DEFAULT_PERSONAS = [ + "赛博AI虚拟博主,住在2077年的数码女孩,用AI生成高颜值写真和全球场景打卡,与粉丝超高频互动", + "性感福利主播,身材火辣衣着大胆,专注分享穿衣显身材和私房写真风穿搭", "身材管理健身美女,热爱分享好身材秘诀和穿搭显身材技巧", "温柔知性的时尚博主,喜欢分享日常穿搭和生活美学", "元气满满的大学生,热爱探店和平价好物分享", @@ -1114,6 +1118,20 @@ RANDOM_PERSONA_LABEL = "🎲 随机人设(每次自动切换)" # 每个人设对应一组相符的评论关键词和主题,切换人设时自动同步 PERSONA_POOL_MAP = { + # ---- 性感福利类 ---- + "性感福利主播": { + "topics": [ + "辣妹穿搭", "内衣测评", "比基尼穿搭", "私房写真风穿搭", "吊带裙穿搭", + "低胸穿搭", "紧身连衣裙", "蕾丝穿搭", "泳衣测评", "居家睡衣穿搭", + "露背装穿搭", "热裤穿搭", "性感御姐穿搭", "渔网袜穿搭", "包臀裙穿搭", + "锁骨链饰品", "身材展示", "好身材日常", "氛围感私房照", "海边度假穿搭", + ], + "keywords": [ + "辣妹", "性感穿搭", "内衣", "比基尼", "吊带", "低胸", + "紧身", "蕾丝", "泳衣", "睡衣", "露背", "热裤", + "御姐", "好身材", "包臀裙", "身材展示", "私房", "氛围感", + ], + }, # ---- 身材管理类 ---- "身材管理健身美女": { "topics": [ @@ -1404,6 +1422,20 @@ PERSONA_POOL_MAP = { "装修", "灯光", "绿植", "北欧", "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("⚠️ 换脸已启用但未找到头像,跳过换脸") images = sd_svc.txt2img(prompt=sd_prompt, model=sd_model_name, face_image=face_image, - quality_mode=quality_mode_val or "快速 (约30秒)") + quality_mode=quality_mode_val or "快速 (约30秒)", + persona=persona) if not images: _record_error() 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() images = sd_svc.txt2img(prompt=sd_prompt, model=sd_model_name, face_image=face_image, - quality_mode=quality_mode_val or "快速 (约30秒)") + quality_mode=quality_mode_val or "快速 (约30秒)", + persona=persona) if not images: _auto_log_append(f"⚠️ 第 {i+1} 篇图片生成失败,跳过") continue @@ -3873,7 +3907,7 @@ with gr.Blocks( btn_gen_img.click( fn=generate_images, 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], ) diff --git a/sd_service.py b/sd_service.py index 7bb8a03..c516390 100644 --- a/sd_service.py +++ b/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 DEFAULT_MODEL_PROFILE = "juggernautXL" @@ -514,13 +744,15 @@ class SDService: scheduler: str = None, face_image: Image.Image = None, quality_mode: str = None, + persona: str = None, ) -> list[Image.Image]: - """文生图(自动适配当前 SD 模型的最优参数) + """文生图(自动适配当前 SD 模型 + 人设的最优参数) Args: model: SD 模型名,自动识别并应用对应配置 face_image: 头像 PIL Image,传入后自动启用 ReActor 换脸 quality_mode: 预设模式名 + persona: 博主人设文本,自动注入人设视觉增强词 """ if 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) - # 自动增强 prompt: 前缀 + 原始 prompt + 后缀 - enhanced_prompt = profile.get("prompt_prefix", "") + prompt + profile.get("prompt_suffix", "") + # 自动增强 prompt: 人设增强 + 模型前缀 + 原始 prompt + 模型后缀 + 人设风格 + 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) + if persona_sd and persona_sd.get("negative_extra"): + final_negative = final_negative + ", " + persona_sd["negative_extra"] payload = { "prompt": enhanced_prompt,