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])