front/xhs-agent-desktop/engine/xhs_bridge/skill_catalog.py
zzh 5a26ae0048 feat: 小红书模块迁移、登录重构与 xhs-agent-desktop 桌面 Agent
- 新增小红书前端模块(启动服务、内容分析、爆款创作、发布记录、技能卡片、拟人设置、AI 助手)

- 新增 xhs-agent-desktop 独立桌面 Agent,对接 OMS 任务调度

- 登录页重构,整合微信登录与 OMS JWT 认证

- 订单、渠道、用户中心等页面与 API 增强

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 16:13:13 +08:00

525 lines
19 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
# 爆款公式四要素(对齐超级内容引擎:情绪30% + 实用30% + 视觉20% + 社交20%)
FORMULA_FACTORS: list[tuple[str, str, str, str]] = [
("emotion", "情绪共鸣", "让读者产生情感连接", "例:「说实话,我之前也是一团糟…」"),
("utility", "实用价值", "可收藏、可照做的具体方法", "例:3步清单 + 收藏起来慢慢实践"),
("visual", "视觉冲击", "标题与封面第一眼抓停", "例:大标题封面 + 步骤卡片配图"),
("social", "社交货币", "值得转发、讨论、晒圈", "例:转发给朋友也有用 / 表达我的观点"),
]
# 标题技法(多选搭配)
TITLE_TYPES: list[tuple[str, str, str, str]] = [
("number", "数字型", "量化结果,降低阅读门槛", "5个让效率翻倍的神器推荐!"),
("question", "疑问型", "激发好奇,促点击", "为什么你的笔记总是乱?"),
("compare", "对比型", "制造反差与选择", "别再用 Word 记笔记了"),
("story", "故事型", "人物弧线,增强信任", "我从一团糟到每天节省2小时"),
]
# 正文四维 — 选项与超级内容引擎 ContentDomain / ContentType / Emotion / SocialCurrency 对齐
BODY_FIELDS: list[tuple[str, str, list[str], dict[str, str]]] = [
(
"domain",
"内容领域",
[
"效率工具",
"职场发展",
"情感共鸣",
"美妆护肤",
"生活方式",
"理财副业",
"学习技能",
"美食探店",
"旅行攻略",
"家居装修",
"自定义",
],
{
"效率工具": "关键词:笔记工具、时间管理 → PRODUCTIVITY",
"职场发展": "关键词:升职、面试、人际关系 → CAREER",
"情感共鸣": "关键词:情绪、关系、治愈 → EMOTIONAL",
"美妆护肤": "关键词:护肤、妆容、好物 → BEAUTY",
"理财副业": "关键词:副业、搞钱、理财 → SIDE_HUSTLE",
"学习技能": "关键词:学习方法、考证 → LEARNING",
},
),
(
"content_type",
"内容类型",
[
"干货教程",
"经验分享",
"资源推荐",
"避坑指南",
"热点追蹭",
"槽点讨论",
"种草推荐",
"测评体验",
"问答互动",
"自定义",
],
{
"干货教程": "新号建信任首选 · TUTORIAL",
"经验分享": "职场/成长类常用 · EXPERIENCE",
"资源推荐": "工具清单型 · RESOURCE",
"避坑指南": "存在感确认 + 观点表达 · PITFALL",
"热点追蹭": "成长阶段拉曝光 · TREND",
"槽点讨论": "激活评论 · DISCUSSION",
"种草推荐": "成熟阶段变现 · RECOMMEND",
},
),
(
"emotion",
"情绪触发",
[
"焦虑共鸣",
"生活场景",
"转变故事",
"理想生活",
"吐槽宣泄",
"错失恐惧",
"梦想向往",
"自定义",
],
{
"焦虑共鸣": "「你也这样吗?」效率/职场爆款组合",
"生活场景": "「这也太真实了」情感共鸣首选",
"转变故事": "「从XX到XX」配合故事型标题",
"理想生活": "「这就是我想要的」职场/生活方式",
"吐槽宣泄": "职场 PUA、不合理现象",
},
),
(
"social",
"社交货币",
[
"价值传递",
"身份标签",
"观点表达",
"知识炫耀",
"情感支持",
"娱乐价值",
"自定义",
],
{
"价值传递": "收藏起来慢慢看 · 转给朋友有用",
"身份标签": "「我是这样的人」职场/生活方式",
"观点表达": "转发表达我的态度",
"情感支持": "共鸣内容,点赞支持",
},
),
]
# 图片呈现
IMAGE_MODES: list[tuple[str, str, str, str]] = [
("cover_impact", "强对比封面", "大标题 + 高对比色块", "封面 emoji + 15字内大标题"),
("step_cards", "步骤卡片", "Markdown 渲染多图卡片", "cover.png + card_1/2/3…"),
("before_after", "前后对比", "效果对比拼图", "使用前 vs 使用后"),
("info_chart", "信息图表", "结构图 / 数据清单", "清单体 + 图标排版"),
("lifestyle", "氛围实拍", "场景感生活图", "探店 / 家居 / Vlog 截图"),
]
# 视频呈现
VIDEO_MODES: list[tuple[str, str, str, str]] = [
("talking_head", "口播人设", "真人出镜讲解", "开头3秒钩子 + 人设口头禅"),
("screen_record", "屏幕录制", "实操演示录屏", "笔记工具 / AI 工具实操"),
("vlog_clip", "实拍 Vlog", "生活场景切片", "探店 / 日常 vlog"),
("slideshow", "图文轮播", "卡片配乐滑动", "渲染图转视频发布"),
("mixed_cut", "混剪卡点", "快剪 + BGM", "热点 / 挑战类"),
]
# 官方示例模板(来自超级内容引擎爆款组合表 + 运营阶段建议)
PRESET_TEMPLATES: list[tuple[str, str, dict[str, Any]]] = [
(
"efficiency_tutorial",
"效率工具 · 干货教程(新号信任)",
{
"formula": ["emotion", "utility", "visual", "social"],
"title_types": ["number", "question"],
"body": {
"domain": "效率工具",
"content_type": "干货教程",
"emotion": "焦虑共鸣",
"social": "价值传递",
},
"image_modes": ["cover_impact", "step_cards"],
"video_modes": [],
"notes": "话题示例:笔记工具提升效率。钩子:说实话,我之前笔记也是一团糟… CTA:收藏起来,慢慢实践",
},
),
(
"career_experience",
"职场发展 · 经验分享",
{
"formula": ["emotion", "utility", "social"],
"title_types": ["story", "compare"],
"body": {
"domain": "职场发展",
"content_type": "经验分享",
"emotion": "理想生活",
"social": "身份标签",
},
"image_modes": ["cover_impact", "lifestyle"],
"video_modes": [],
"notes": "话题示例:职场人际关系。角度:从打工人到… 社交:证明我是上进的人",
},
),
(
"emotional_relatable",
"情感共鸣 · 真实场景",
{
"formula": ["emotion", "social"],
"title_types": ["story", "question"],
"body": {
"domain": "情感共鸣",
"content_type": "经验分享",
"emotion": "生活场景",
"social": "情感支持",
},
"image_modes": ["lifestyle"],
"video_modes": ["vlog_clip"],
"notes": "让读者觉得「这也太真实了」。结尾互动:你也有过吗?",
},
),
(
"pitfall_opinion",
"避坑指南 · 观点表达",
{
"formula": ["emotion", "utility", "social"],
"title_types": ["compare", "number"],
"body": {
"domain": "职场发展",
"content_type": "避坑指南",
"emotion": "吐槽宣泄",
"social": "观点表达",
},
"image_modes": ["before_after", "info_chart"],
"video_modes": [],
"notes": "话题示例:避坑指南。情绪:存在感确认。适合引发评论讨论",
},
),
(
"trend_discussion",
"热点追蹭 · 槽点讨论(成长曝光)",
{
"formula": ["emotion", "visual", "social"],
"title_types": ["question", "compare"],
"body": {
"domain": "生活方式",
"content_type": "热点追蹭",
"emotion": "吐槽宣泄",
"social": "观点表达",
},
"image_modes": ["cover_impact"],
"video_modes": ["mixed_cut"],
"notes": "成长阶段:TREND + DISCUSSION 拉曝光。注意热点时效",
},
),
(
"recommend_review",
"种草测评 · 成熟变现",
{
"formula": ["utility", "visual", "social"],
"title_types": ["number", "story"],
"body": {
"domain": "美妆护肤",
"content_type": "种草推荐",
"emotion": "转变故事",
"social": "知识炫耀",
},
"image_modes": ["before_after", "lifestyle"],
"video_modes": ["talking_head", "screen_record"],
"notes": "成熟阶段:RECOMMEND / REVIEW。真实测评 + 优缺点",
},
),
]
@dataclass
class SkillRecipe:
"""可自主搭配的爆款技能配方。"""
formula: list[str] = field(default_factory=list)
title_types: list[str] = field(default_factory=list)
body: dict[str, str] = field(default_factory=dict)
image_modes: list[str] = field(default_factory=list)
video_modes: list[str] = field(default_factory=list)
notes: str = ""
@classmethod
def empty(cls) -> SkillRecipe:
return cls(
formula=["emotion", "utility", "visual", "social"],
title_types=[],
body={},
image_modes=[],
video_modes=[],
)
def to_dict(self) -> dict[str, Any]:
return {
"formula": list(self.formula),
"title_types": list(self.title_types),
"body": dict(self.body),
"image_modes": list(self.image_modes),
"video_modes": list(self.video_modes),
"notes": self.notes,
}
@classmethod
def from_dict(cls, raw: dict[str, Any] | None) -> SkillRecipe:
if not raw:
return cls.empty()
return cls(
formula=[str(x) for x in raw.get("formula") or []],
title_types=[str(x) for x in raw.get("title_types") or []],
body={str(k): str(v) for k, v in (raw.get("body") or {}).items()},
image_modes=[str(x) for x in raw.get("image_modes") or []],
video_modes=[str(x) for x in raw.get("video_modes") or []],
notes=str(raw.get("notes") or ""),
)
def _label_map(items: list[tuple]) -> dict[str, str]:
return {row[0]: row[1] for row in items}
def _example_map(items: list[tuple]) -> dict[str, str]:
out: dict[str, str] = {}
for row in items:
if len(row) >= 4:
out[row[0]] = row[3]
return out
def body_field_hints() -> dict[str, dict[str, str]]:
return {key: hints for key, _label, _opts, hints in BODY_FIELDS}
def get_body_hint(field_key: str, value: str) -> str:
hints = body_field_hints().get(field_key, {})
return hints.get(value, "")
def preset_recipe(preset_id: str) -> SkillRecipe | None:
for pid, _name, raw in PRESET_TEMPLATES:
if pid == preset_id:
return SkillRecipe.from_dict(raw)
return None
def list_preset_templates() -> list[tuple[str, str]]:
return [(pid, name) for pid, name, _ in PRESET_TEMPLATES]
def compose_title_example(recipe: SkillRecipe) -> str:
"""根据已选标题类型合成示例标题。"""
examples = _example_map(TITLE_TYPES)
parts = [examples[k] for k in recipe.title_types if k in examples]
if not parts:
return ""
if len(parts) == 1:
return parts[0]
return f"{parts[0]} / {parts[1]}" if len(parts) > 1 else parts[0]
def compose_hook_example(recipe: SkillRecipe) -> str:
"""根据正文情绪合成钩子示例。"""
emotion = recipe.body.get("emotion", "")
hooks = {
"焦虑共鸣": "说实话,我之前也是一团糟…",
"生活场景": "昨天发生一件事,太真实了…",
"转变故事": "半年前我还…现在终于…",
"理想生活": "这就是我想要的效率状态",
"吐槽宣泄": "真的忍不住了,必须说说…",
"错失恐惧": "别人都在用,就你还不知道?",
}
if emotion in hooks:
return hooks[emotion]
domain = recipe.body.get("domain", "")
if domain == "效率工具":
return "从0到1的笔记工具路径,今天不说虚的"
return ""
def compose_cta_example(recipe: SkillRecipe) -> str:
social = recipe.body.get("social", "")
ctas = {
"价值传递": "收藏起来,慢慢实践",
"身份标签": "认同的姐妹/兄弟点个赞",
"观点表达": "你觉得呢?评论区聊聊",
"情感支持": "有过同感的抱抱",
"知识炫耀": "转发给需要的朋友",
}
return ctas.get(social, "点赞收藏,下次用得上")
def compose_workflow(recipe: SkillRecipe) -> str:
"""将配方合成为卡片/预览用的流程文案。"""
lines: list[str] = []
f_labels = _label_map(FORMULA_FACTORS)
if recipe.formula:
parts = [f_labels.get(k, k) for k in recipe.formula]
lines.append("【爆款公式】" + " × ".join(parts))
t_labels = _label_map(TITLE_TYPES)
if recipe.title_types:
lines.append("【标题】" + " + ".join(t_labels.get(k, k) for k in recipe.title_types))
title_ex = compose_title_example(recipe)
if title_ex:
lines.append(f" 示例:{title_ex}")
if recipe.body:
field_labels = {row[0]: row[1] for row in BODY_FIELDS}
body_parts = []
for key, label, _opts, _hints in BODY_FIELDS:
val = recipe.body.get(key, "").strip()
if val:
body_parts.append(f"{field_labels.get(key, key)}:{val}")
if body_parts:
lines.append("【正文】" + " | ".join(body_parts))
hook = compose_hook_example(recipe)
cta = compose_cta_example(recipe)
if hook:
lines.append(f" 钩子:{hook}")
if cta:
lines.append(f" CTA:{cta}")
i_labels = _label_map(IMAGE_MODES)
if recipe.image_modes:
lines.append("【图片】" + " + ".join(i_labels.get(k, k) for k in recipe.image_modes))
ex = _example_map(IMAGE_MODES)
img_ex = " · ".join(ex[k] for k in recipe.image_modes[:2] if k in ex)
if img_ex:
lines.append(f" 示例:{img_ex}")
v_labels = _label_map(VIDEO_MODES)
if recipe.video_modes:
lines.append("【视频】" + " + ".join(v_labels.get(k, k) for k in recipe.video_modes))
if recipe.notes.strip():
lines.append("【备注】" + recipe.notes.strip())
return "\n".join(lines) if lines else "(请搭配各维度选项)"
def compose_card_preview_text(recipe: SkillRecipe) -> str:
"""卡片正文:多行搭配预览,在固定高度内尽可能展示更多信息。"""
f_labels = _label_map(FORMULA_FACTORS)
t_labels = _label_map(TITLE_TYPES)
i_labels = _label_map(IMAGE_MODES)
v_labels = _label_map(VIDEO_MODES)
lines: list[str] = []
if recipe.formula:
lines.append("公式 " + "×".join(f_labels.get(k, k) for k in recipe.formula))
if recipe.title_types:
title_part = "+".join(t_labels.get(k, k) for k in recipe.title_types)
title_ex = compose_title_example(recipe)
if title_ex:
title_part += f" →「{title_ex}」"
lines.append("标题 " + title_part)
body_bits = [
recipe.body.get(k, "").strip()
for k in ("domain", "content_type", "emotion", "social")
if recipe.body.get(k, "").strip()
]
if body_bits:
lines.append("正文 " + " · ".join(body_bits))
hook = compose_hook_example(recipe)
if hook:
lines.append("钩子 " + hook)
if recipe.image_modes:
lines.append("图片 " + "+".join(i_labels.get(k, k) for k in recipe.image_modes))
if recipe.video_modes:
lines.append("视频 " + "+".join(v_labels.get(k, k) for k in recipe.video_modes))
cta = compose_cta_example(recipe)
if cta:
lines.append("CTA " + cta)
if not lines:
return "(请编辑搭配各维度)"
return "\n".join(lines)
def compose_card_preview_line(recipe: SkillRecipe) -> str:
"""兼容:单行摘要。"""
return compose_card_preview_text(recipe).replace("\n", " · ")
def recipe_tag_groups(recipe: SkillRecipe) -> tuple[list[str], list[str]]:
"""底部标签:标题方向 | 正文(领域/类型/情绪/社交)。"""
t_labels = _label_map(TITLE_TYPES)
title_tags = [t_labels.get(k, k) for k in recipe.title_types if k in t_labels]
body_tags: list[str] = []
for key in ("domain", "content_type", "emotion", "social"):
val = recipe.body.get(key, "").strip()
if val:
body_tags.append(val)
return title_tags, body_tags
def compose_card_summary(recipe: SkillRecipe) -> str:
"""卡片上显示的精简摘要(2~3 行)。"""
lines: list[str] = []
f_labels = _label_map(FORMULA_FACTORS)
if recipe.formula:
lines.append(" × ".join(f_labels.get(k, k) for k in recipe.formula[:4]))
domain = recipe.body.get("domain", "")
ctype = recipe.body.get("content_type", "")
if domain or ctype:
lines.append(f"{domain} · {ctype}".strip(" · "))
title_ex = compose_title_example(recipe)
if title_ex:
lines.append(f"「{title_ex[:28]}{'…' if len(title_ex) > 28 else ''}」")
return "\n".join(lines) if lines else "点击编辑搭配技能"
def compose_prompt_brief(recipe: SkillRecipe) -> str:
"""生成给 AI / 超级内容引擎使用的创作指令。"""
wf = compose_workflow(recipe)
topic_hint = recipe.body.get("domain", "你的主题")
parts = [
"请按以下爆款技能配方创作笔记内容(参考超级内容引擎策略):",
"",
wf,
"",
"【创作要求】",
"- 标题与正文风格一致,口语化、有温度",
"- 正文 200~800 字,段落清晰,适量 emoji",
"- 结尾 5~10 个 SEO 标签",
f"- 话题方向:{topic_hint}",
]
hook = compose_hook_example(recipe)
cta = compose_cta_example(recipe)
if hook:
parts.append(f"- 开头钩子参考:{hook}")
if cta:
parts.append(f"- 行动召唤:{cta}")
if recipe.image_modes:
parts.append("- 配图:封面抓眼球 + 正文分步卡片(可 Markdown 渲染)")
parts.append("")
parts.append("输出:标题 + 正文 + 标签建议")
return "\n".join(parts)
def suggest_skill_title(recipe: SkillRecipe) -> str:
parts: list[str] = []
t_labels = _label_map(TITLE_TYPES)
if recipe.title_types:
parts.append("/".join(t_labels.get(k, k) for k in recipe.title_types[:2]))
domain = recipe.body.get("domain", "").strip()
if domain and domain != "自定义":
parts.append(domain)
if recipe.image_modes and not recipe.video_modes:
parts.append("图文")
elif recipe.video_modes:
parts.append("视频")
if not parts:
return "我的爆款技能"
return " · ".join(parts[:3])