feat: 内容数据迁移至数据库,合规审查全链路打通
- 文章 HTML 存储从文件系统迁移至 articles 表,删除 releases 目录 - 合规审查从 DB 读取 HTML,审查结果写回 DB,通过后自动推进至待发布 - 新增 todayCount 筛选按钮,与系统概览统计数据一致 - 全屏预览修复:提升 z-index 超过侧边栏,添加退出全屏/关闭按钮 - 统一 '优化' → '审查' 命名,消除前后端术语不一致 - 调度器创作完成后自动触发审查(生成 → 审查 → 待发布) - 清理旧备份/调试文件、过期大纲和研究笔记
This commit is contained in:
+57
-36
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
大纲阶段:基于研究笔记生成文章大纲(数据库版)
|
||||
大纲阶段:基于选题和研究笔记,用 LLM 动态生成结构化大纲
|
||||
"""
|
||||
|
||||
import json, datetime, logging, sys
|
||||
@@ -9,9 +9,14 @@ from typing import Dict
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
sys.path.insert(0, str(PROJECT_ROOT / "platform" / "backend"))
|
||||
|
||||
# 导入数据库辅助模块
|
||||
from db_helper import get_topic_by_id
|
||||
try:
|
||||
from app.core.nvidia_client import call_llm
|
||||
HAVE_LLM = True
|
||||
except ImportError:
|
||||
HAVE_LLM = False
|
||||
|
||||
DATA_DIR = PROJECT_ROOT / "automation" / "data"
|
||||
RESEARCH_DIR = DATA_DIR / "research"
|
||||
@@ -28,34 +33,64 @@ class Outliner:
|
||||
self.topic_id = topic_id
|
||||
self.topic = self._load_topic()
|
||||
research_file = RESEARCH_DIR / TODAY / f"{topic_id}_research.md"
|
||||
if not research_file.exists():
|
||||
raise FileNotFoundError(f"Research notes not found: {research_file}")
|
||||
self.research_notes = research_file.read_text(encoding='utf-8')
|
||||
self.research_notes = research_file.read_text(encoding='utf-8') if research_file.exists() else ""
|
||||
self.output_dir = OUTPUT_DIR / TODAY
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _load_topic(self) -> Dict:
|
||||
topic = get_topic_by_id(self.topic_id)
|
||||
if not topic:
|
||||
raise ValueError(f"Topic {self.topic_id} not found")
|
||||
if not topic: raise ValueError(f"Topic {self.topic_id} not found")
|
||||
return topic
|
||||
|
||||
def generate_outline(self) -> str:
|
||||
"""生成文章大纲 Markdown(基于模板)"""
|
||||
title = self.topic['title']
|
||||
field = self.topic.get('field', '')
|
||||
core = self.topic.get('core_concept', '')
|
||||
pain = self.topic.get('audience_pain', '')
|
||||
angle = self.topic.get('unique_angle', '')
|
||||
cases_summary = self.research_notes[:2000] if self.research_notes else "暂无研究笔记"
|
||||
|
||||
# 解析研究笔记中的案例数量
|
||||
case_count = self.research_notes.count('### 案例')
|
||||
if HAVE_LLM:
|
||||
prompt = f"""你是一个真人编辑,在为一篇文章列大纲。不要套模板,根据具体内容灵活设计结构。
|
||||
|
||||
outline = f"""# 文章大纲:{title}
|
||||
## 选题信息
|
||||
标题:{title}
|
||||
领域:{field}
|
||||
核心观点:{core}
|
||||
受众痛点:{pain}
|
||||
独特视角:{angle}
|
||||
|
||||
## 研究笔记
|
||||
{cases_summary}
|
||||
|
||||
## 要求
|
||||
- 章节数灵活,5-8章都行,不硬凑
|
||||
- 每章给2-4个要点即可,不需要每章都标字数
|
||||
- 结构要有递进,但不一定非得是痛点-分析-方案这种套路
|
||||
- 不要用「引言」「总结」这类通用标题,要贴合具体内容
|
||||
- 尽量把独特视角和受众痛点融入各章,而不是单独列出来
|
||||
- 每个要点一句话点出核心,不用展开写
|
||||
|
||||
直接输出大纲。"""
|
||||
try:
|
||||
outline = call_llm(prompt, temperature=0.6, max_tokens=2000, system_prompt="你是一个有经验的内容编辑,擅长为不同选题设计差异化的文章结构。")
|
||||
logger.info(f"LLM 大纲生成成功,长度:{len(outline)}")
|
||||
return f"# 文章大纲:{title}\n\n{outline}\n\n---\n*大纲生成时间:{TODAY}*"
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM 大纲生成失败: {e},使用模板")
|
||||
|
||||
return self._template_outline(title, field, core, pain, angle)
|
||||
|
||||
def _template_outline(self, title, field, core, pain, angle) -> str:
|
||||
case_count = self.research_notes.count('### 案例') if self.research_notes else 0
|
||||
case_section = f"""## 四、全球/行业趋势与案例
|
||||
- 引用研究笔记中的 {case_count} 个案例,精选 2-3 个详述
|
||||
- 数据支撑:提取研究笔记中的关键数据
|
||||
- 趋势分析""" if case_count > 0 else ""
|
||||
return f"""# 文章大纲:{title}
|
||||
|
||||
## 一、引言
|
||||
- 开场场景/痛点引入
|
||||
- 提出核心问题:{title}
|
||||
- 场景切入:{title}
|
||||
- 点明文章价值
|
||||
|
||||
## 二、核心观点
|
||||
@@ -63,36 +98,23 @@ class Outliner:
|
||||
|
||||
## 三、受众痛点分析
|
||||
{pain}
|
||||
{case_section}
|
||||
|
||||
## 四、全球/行业趋势与案例
|
||||
- 引用研究笔记中的 {case_count} 个案例,精选 2-3 个详述
|
||||
- 数据支撑:提取研究笔记中的关键数据
|
||||
- 趋势分析
|
||||
|
||||
## 五、本土落地建议
|
||||
## {"五" if case_section else "四"}、本土落地建议
|
||||
- 结合{field}领域特点
|
||||
- 提供可执行的步骤
|
||||
- 注意事项
|
||||
|
||||
## 六、独特视角:{angle}
|
||||
## {"六" if case_section else "五"}、独特视角:{angle}
|
||||
|
||||
## 七、行动指南(MVP)
|
||||
1. 理解现状
|
||||
2. 小范围试验
|
||||
3. 评估效果
|
||||
4. 形成习惯
|
||||
## {"七" if case_section else "六"}、行动指南
|
||||
1. 了解现状 2. 制定方案 3. 小范围验证 4. 持续优化
|
||||
|
||||
## 八、总结与鼓励
|
||||
- 回顾要点
|
||||
- 呼吁行动
|
||||
|
||||
## 九、参考文献
|
||||
- 从研究笔记中提取来源链接
|
||||
## {"八" if case_section else "七"}、总结与鼓励
|
||||
|
||||
---
|
||||
*大纲生成时间:{TODAY}*
|
||||
"""
|
||||
return outline
|
||||
|
||||
*大纲生成时间:{TODAY}*"""
|
||||
|
||||
def save(self):
|
||||
outline_text = self.generate_outline()
|
||||
@@ -104,9 +126,8 @@ class Outliner:
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--topic-id', required=True, help='选题ID')
|
||||
parser.add_argument('--topic-id', required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
o = Outliner(args.topic_id)
|
||||
o.save()
|
||||
print(f"SUCCESS: Outline created for {args.topic_id}")
|
||||
|
||||
Reference in New Issue
Block a user