- 新增智能选题引擎 `TopicEngine`,整合热点数据与历史权重,提供多维度评分和创作角度建议 - 新增内容模板系统 `ContentTemplate`,支持从 JSON 文件加载模板并应用于文案生成 - 新增批量创作功能 `batch_generate_copy`,支持串行生成多篇文案并自动入草稿队列 - 升级文案质量流水线:实现 Prompt 分层架构(基础层 + 风格层 + 人设层)、LLM 自检与改写机制、深度去 AI 化后处理 - 优化图文协同:新增封面图策略选择、SD prompt 与文案语义联动、图文匹配度评估 - 集成数据闭环:在文案生成中自动注入 `AnalyticsService` 权重数据,实现发布 → 数据回收 → 优化创作的完整循环 - 更新 UI 组件:新增选题推荐展示区、批量创作折叠面板、封面图策略选择器和图文匹配度评分展示 ♻️ refactor(llm): 重构 Prompt 架构并增强去 AI 化处理 - 将 `PROMPT_COPYWRITING` 拆分为分层架构(基础层 + 风格层 + 人设层),提高维护性和灵活性 - 增强 `_humanize_content` 方法:新增语气词注入、标点不规范化、段落节奏打散和 emoji 密度控制 - 新增 `_self_check` 和 `_self_check_rewrite` 方法,实现文案 AI 痕迹自检与自动改写 - 新增 `evaluate_image_text_match` 方法,支持文案与 SD prompt 的语义匹配度评估(可选,失败不阻塞) - 新增封面图策略配置 `COVER_STRATEGIES` 和情感基调映射 `EMOTION_SD_MAP` 📝 docs(openspec): 归档内容创作优化提案和详细规格 - 新增 `openspec/changes/archive/2026-02-28-optimize-content-creation/` 目录,包含设计文档、提案、规格说明和任务清单 - 新增 `openspec/specs/` 下的批量创作、文案质量流水线、图文协同、服务内容和智能选题引擎规格文档 - 更新 `openspec/specs/services-content/spec.md`,反映新增的批量创作和智能选题入口函数 🔧 chore(config): 更新服务配置和 UI 集成 - 在 `services/content.py` 中集成权重数据自动注入逻辑,实现数据驱动创作 - 在 `ui/app.py` 中新增选题推荐、批量生成和图文匹配度评估的回调函数 - 在 `ui/tab_create.py` 中新增智能选题推荐区、批量创作面板和图文匹配度评估组件 - 修复 `services/sd_service.py` 中的头像文件路径问题,确保目录存在
133 lines
4.9 KiB
Python
133 lines
4.9 KiB
Python
"""
|
|
services/content_template.py
|
|
内容模板系统 — 管理和应用创作模板
|
|
"""
|
|
import json
|
|
import os
|
|
import logging
|
|
|
|
logger = logging.getLogger("autobot")
|
|
|
|
TEMPLATES_FILE = "templates.json"
|
|
|
|
# 内置默认模板 (templates.json 不存在时使用)
|
|
DEFAULT_TEMPLATES = [
|
|
{
|
|
"name": "好物种草",
|
|
"description": "适合分享好用的产品和购物推荐",
|
|
"topic_pattern": "",
|
|
"style": "好物种草",
|
|
"prompt_override": "请以真实使用者的口吻分享产品体验,突出个人感受和使用前后对比,避免像广告文案。",
|
|
"tags_preset": ["好物推荐", "真实测评", "分享好物"],
|
|
},
|
|
{
|
|
"name": "日常分享",
|
|
"description": "记录日常生活点滴、感悟和心情",
|
|
"topic_pattern": "",
|
|
"style": "日常分享",
|
|
"prompt_override": "请以轻松随意的语气记录生活日常,像发朋友圈那样自然,多用短句和口语化表达。",
|
|
"tags_preset": ["日常", "生活记录", "碎碎念"],
|
|
},
|
|
{
|
|
"name": "攻略教程",
|
|
"description": "分享经验技巧、教程和攻略指南",
|
|
"topic_pattern": "",
|
|
"style": "攻略教程",
|
|
"prompt_override": "请以过来人的身份分享干货经验,用分步骤的方式让读者易懂,加入踩坑经历增加可信度。",
|
|
"tags_preset": ["干货分享", "经验", "保姆级教程"],
|
|
},
|
|
]
|
|
|
|
|
|
class ContentTemplate:
|
|
"""
|
|
内容模板管理器
|
|
|
|
从 xhs_workspace/templates.json 加载模板,
|
|
文件不存在时使用内置默认模板。
|
|
"""
|
|
|
|
def __init__(self, workspace_dir: str = "xhs_workspace"):
|
|
self.workspace_dir = workspace_dir
|
|
self.templates_path = os.path.join(workspace_dir, TEMPLATES_FILE)
|
|
self._templates: list[dict] = self._load_templates()
|
|
|
|
def _load_templates(self) -> list[dict]:
|
|
"""加载模板列表"""
|
|
if os.path.exists(self.templates_path):
|
|
try:
|
|
with open(self.templates_path, "r", encoding="utf-8") as f:
|
|
templates = json.load(f)
|
|
if isinstance(templates, list) and templates:
|
|
logger.info("已从 %s 加载 %d 个模板", self.templates_path, len(templates))
|
|
return templates
|
|
except (json.JSONDecodeError, IOError) as e:
|
|
logger.warning("模板文件加载失败,使用默认模板: %s", e)
|
|
|
|
logger.info("使用内置默认模板 (%d 个)", len(DEFAULT_TEMPLATES))
|
|
return list(DEFAULT_TEMPLATES)
|
|
|
|
def save_templates(self):
|
|
"""将当前模板保存到文件"""
|
|
try:
|
|
os.makedirs(self.workspace_dir, exist_ok=True)
|
|
with open(self.templates_path, "w", encoding="utf-8") as f:
|
|
json.dump(self._templates, f, ensure_ascii=False, indent=2)
|
|
logger.info("模板已保存到 %s", self.templates_path)
|
|
except IOError as e:
|
|
logger.error("模板保存失败: %s", e)
|
|
|
|
@property
|
|
def templates(self) -> list[dict]:
|
|
"""获取所有模板"""
|
|
return self._templates
|
|
|
|
def get_template_names(self) -> list[str]:
|
|
"""获取模板名称列表"""
|
|
return [t.get("name", "未命名") for t in self._templates]
|
|
|
|
def get_template(self, name: str) -> dict | None:
|
|
"""按名称获取模板"""
|
|
for t in self._templates:
|
|
if t.get("name") == name:
|
|
return t
|
|
return None
|
|
|
|
def apply_template(self, template_name: str) -> dict:
|
|
"""
|
|
应用模板,返回用于文案生成的参数覆盖
|
|
|
|
Returns:
|
|
dict with keys:
|
|
- style: str
|
|
- prompt_override: str (附加到 LLM prompt 的额外指令)
|
|
- tags_preset: list[str] (标签默认值)
|
|
"""
|
|
template = self.get_template(template_name)
|
|
if not template:
|
|
logger.warning("模板 '%s' 不存在,返回空覆盖", template_name)
|
|
return {"style": "", "prompt_override": "", "tags_preset": []}
|
|
|
|
return {
|
|
"style": template.get("style", ""),
|
|
"prompt_override": template.get("prompt_override", ""),
|
|
"tags_preset": template.get("tags_preset", []),
|
|
}
|
|
|
|
def add_template(self, template: dict):
|
|
"""添加新模板"""
|
|
required_fields = {"name", "description", "style"}
|
|
if not required_fields.issubset(template.keys()):
|
|
raise ValueError(f"模板缺少必要字段: {required_fields - template.keys()}")
|
|
self._templates.append(template)
|
|
self.save_templates()
|
|
|
|
def remove_template(self, name: str) -> bool:
|
|
"""删除模板"""
|
|
before = len(self._templates)
|
|
self._templates = [t for t in self._templates if t.get("name") != name]
|
|
if len(self._templates) < before:
|
|
self.save_templates()
|
|
return True
|
|
return False
|