配置全面迁移数据库:PromptConfig、TaskConfig动态调度、敏感词/清洗规则/趋势映射/平台标签/痛点模板全部可编辑

- 新增 PromptConfig 模型 + API,支持提示词在线编辑(16条默认)
- 调度器动态读取 TaskConfig.schedule,admin 可调执行时间
- 新增 KeywordDomainMap、SensitiveWord、ContentCleanRule、TrendFieldMapping 表
- DOMAINS、TREND_DOMAIN_MAP、PLATFORM_TAGS、china_pains、RSS关键词、priority_weights 全部迁移到 DB
- tasks.html 重构:卡片网格+配置/产出/历史/提示词四个Tab,折叠显示
- 清理冗余代码:DEFAULT_PROMPTS死代码、collector.py unreachable代码、compliance_checker bug
- strip_thinking_html 改用 DB 规则优先
This commit is contained in:
Yuzhiran Dev
2026-05-22 11:18:23 +08:00
parent a8e0a76e07
commit 1855f190f5
31 changed files with 2927 additions and 1127 deletions
+44 -100
View File
@@ -12,6 +12,8 @@ sys.path.insert(0, str(PROJECT_ROOT))
sys.path.insert(0, str(PROJECT_ROOT / 'platform' / 'backend'))
from db_helper import get_topic_by_id, update_topic_status, save_article
from content_cleaner import strip_thinking, strip_ai_preface, clean_markdown_content, clean_html_content
from prompt_loader import get_prompt, get_prompt_params
try:
from app.core.nvidia_client import call_llm
HAVE_LLM = True
@@ -116,21 +118,7 @@ class Writer:
@staticmethod
def _clean_markdown(text: str) -> str:
lines = text.split('\n')
cleaned = []
in_code_fence = False
for line in lines:
if line.strip().startswith('```'):
in_code_fence = not in_code_fence
continue
if in_code_fence:
continue
line = re.sub(r'^#{1,6}\s+', '', line)
line = re.sub(r'^[\-\*\+]\s+', '', line)
line = re.sub(r'^\d+[\.\)]\s+', '', line)
line = re.sub(r'\*{1,3}([^*]+)\*{1,3}', r'\1', line)
cleaned.append(line)
return '\n'.join(cleaned).strip()
return clean_markdown_content(text)
@staticmethod
def _is_outline_noise(line: str) -> bool:
@@ -160,31 +148,15 @@ class Writer:
# 大纲要点格式(>40% 行以 -/*/** 开头)应始终由 LLM 展开为连贯段落
if HAVE_LLM and self._is_bullet_only(content):
logger.info(f"使用 LLM 扩写章节(要点→段落): {section['title']}")
prompt = f"""你是一个资深作者,正在写一篇关于「{self.topic['title']}」的文章。请写「{section['title']}」这一节。
今天日期:{datetime.datetime.now().strftime('%Y年%m月%d')}
笔记要点:
{content}
【输出要求】
输出3-6段纯粹、流畅的段落文字,每节内容根据平台需求控制在200-800字之间。
格式:
- 禁止任何标题/列表/格式标记(#、-、*、1.、**等)
- 每段3-5句,段间空行分隔
- 用「你」或「我们」视角,自然口语化
内容要求(让文章在各平台能被推荐):
- 开头直接切入痛点或反常识观点,抓住注意力
- 每个观点配具体案例或数据(用「据统计」「调研显示」等),不要空泛说理
- 有独特判断和立场,避免正确废话
- 回答「所以呢」——读者看完能带走什么
- 结尾有情绪感召力,让人想点赞/收藏/转发
直接输出段落正文,不要任何附加说明。"""
prompt = get_prompt("section_expansion",
topic_title=self.topic['title'],
section_title=section['title'],
date=datetime.datetime.now().strftime('%Y年%m月%d'),
content=content,
)
try:
expanded = call_llm(prompt, temperature=0.6)
params = get_prompt_params("section_expansion")
expanded = call_llm(prompt, temperature=params.get("temperature", 0.75), max_tokens=params.get("max_tokens", 3000))
if expanded:
cleaned = self._clean_markdown(expanded.strip())
if cleaned:
@@ -284,15 +256,17 @@ class Writer:
core = self.topic.get('core_concept', '')
tag_prompts = {
"zhihu": f"为以下文章生成知乎标签(3-5个)。标题:{title} 领域:{field} 核心观点:{core} 每个2-4字。直接输出标签,空格分隔。不要输出思考过程。",
"wechat": f"为以下文章生成公众号标签(3-5个)。标题:{title} 领域:{field} 核心观点:{core} 每个2-4字。直接输出标签,空格分隔。不要输出思考过程。",
"xiaohongshu": f"为以下文章生成小红书标签(3-5个)。标题:{title} 领域:{field} 核心观点:{core} 每个2-4字。直接输出标签,空格分隔。不要输出思考过程。",
"zhihu": get_prompt("tags_generation", platform="知乎", title=title, field=field, core=core),
"wechat": get_prompt("tags_generation", platform="公众号", title=title, field=field, core=core),
"xiaohongshu": get_prompt("tags_generation", platform="小红书", title=title, field=field, core=core),
}
if HAVE_LLM:
prompt = tag_prompts.get(platform, f"根据文章信息生成适合{platform}的标签。标题:{title} 领域:{field} 核心观点:{core} 直接输出标签,空格分隔。")
prompt = tag_prompts.get(platform, get_prompt("tags_generation", platform=platform, title=title, field=field, core=core))
try:
tags_text = call_llm(prompt, temperature=0.2)
params = get_prompt_params("tags_generation")
tags_text = call_llm(prompt, temperature=params.get("temperature", 0.3), max_tokens=params.get("max_tokens", 500))
tags_text = strip_thinking(tags_text)
if tags_text:
tags = [t.strip('#') for t in tags_text.strip().split() if t.strip('#')]
if tags:
@@ -331,64 +305,33 @@ class Writer:
if not HAVE_LLM:
return original
title_templates = {
"zhihu": f"""你是一个知乎用户,在给自己的深度回答起高点击率标题。
if platform == "zhihu":
prompt = get_prompt("title_optimize_zhihu",
title=original,
core=self.topic.get('core_concept', ''),
pain=self.topic.get('audience_pain', ''),
field=self.topic.get('field', ''),
)
elif platform == "wechat":
prompt = get_prompt("title_optimize_wechat",
title=original,
core=self.topic.get('core_concept', ''),
)
elif platform == "xiaohongshu":
prompt = get_prompt("title_optimize_xhs",
title=original,
core=self.topic.get('core_concept', ''),
)
else:
prompt = f"给以下文章改个吸引人的{platform}标题:{original}"
原文标题:{original}
领域:{self.topic.get('field', '')}
要求:
- 有信息量:一看就知道能解决什么问题
- 含知乎搜索关键词(SEO),利用知乎搜索联想热词
- 带数字或对比最好(「3个方法」「从…到…」)
- 20字以内
- 参考知乎真实高赞标题风格,不要套路句式
- 避免「如何…」废句式、「XXX指南/手册/全攻略」
- 有观点、有态度,不是中性描述
- 直击目标读者痛点或好奇心
- 直接输出3个标题选项,每行一个,不要输出思考过程
生成 3 个选项,每行一个。""",
"wechat": f"""你是一个公众号作者,在给可能10万+的文章起标题。
原文标题:{original}
领域:{self.topic.get('field', '')}
要求:
- 制造好奇心和点击欲,让人觉得不点开会错过
- 包含微信搜索关键词(微信SEO),利用搜一搜热门词
- 口语化,不要书面腔
- 不要感叹号堆砌,不要「重磅/震惊/紧急」
- 字数15-25字最佳
- 有情绪感召力:共鸣/好奇/焦虑/期待
- 参考近期10万+标题的语气节奏
- 直接输出3个标题选项,每行一个,不要输出思考过程
生成 3 个选项,每行一个。""",
"xiaohongshu": f"""你是一个小红书用户,在给笔记起能上热门推荐的标题。
原文标题:{original}
领域:{self.topic.get('field', '')}
要求:
- 20字以内
- 采用爆款模式:数字+结果/痛点+方案/反常识观点/对比式
- 包含小红书搜索关键词(SEO),利用搜索下拉热词
- 带1个精准emoji点缀,不要三个起堆
- 有场景感/结果感/获得感
- 不要「必看/收藏/码住」
- 像真实用户写的,不是运营写的
- 参考小红书搜索热榜标题风格
- 直接输出3个标题选项,每行一个,不要输出思考过程
生成 3 个选项,每行一个。""",
}
prompt = title_templates.get(platform, f"给以下文章改个吸引人的{platform}标题:{original}")
try:
resp = call_llm(prompt, temperature=0.7)
if platform in ("zhihu", "wechat", "xiaohongshu"):
params = get_prompt_params(f"title_optimize_{platform}")
resp = call_llm(prompt, temperature=params.get("temperature", 0.8), max_tokens=params.get("max_tokens", 1500))
else:
resp = call_llm(prompt, temperature=0.7)
resp = strip_thinking(resp)
titles = []
for line in resp.strip().split('\n'):
line = line.strip()
@@ -414,6 +357,7 @@ class Writer:
else:
template = "<!DOCTYPE html><html><head><meta charset='UTF-8'><title>{{TITLE}}</title><meta name='viewport' content='width=device-width'><style>body{max-width:800px;margin:0 auto;padding:20px;font-family:-apple-system,sans-serif;line-height:1.8}</style></head><body><h1>{{TITLE}}</h1><!-- CONTENT --></body></html>"
adapted = strip_ai_preface(adapted)
html = template.replace("{{TITLE}}", title).replace("{{DATE}}", TODAY).replace("{{GEN_TIME}}", GEN_TIME)
html_content = _md_parser(adapted)