- 新增热点自动采集后台线程,支持定时搜索关键词并执行 AI 分析,结果缓存至结构化状态 - 新增热点分析状态管理接口,提供线程安全的 `get_last_analysis` 和 `set_last_analysis` 方法 - 新增热点数据桥接函数 `feed_hotspot_to_engine`,将分析结果注入 TopicEngine 实现热点加权推荐 - 新增热点选题下拉组件,分析完成后自动填充推荐选题,选中后自动写入选题输入框 - 优化 `generate_from_hotspot` 函数,自动获取结构化分析摘要并增强生成上下文 - 新增热点自动采集配置节点,支持通过 `config.json` 管理关键词和采集间隔 ♻️ refactor(queue): 实现智能排期引擎并统一发布路径 - 新增智能排期引擎,基于 `AnalyticsService` 的 `time_weights` 自动计算最优发布时段 - 新增 `PublishQueue.suggest_schedule_time` 和 `auto_schedule_item` 方法,支持时段冲突检测和内容分布控制 - 修改 `generate_to_queue` 函数,新增 `auto_schedule` 和 `auto_approve` 参数,支持自动排期和自动审核 - 重构 `_scheduler_loop` 的自动发布分支,改为调用 `generate_to_queue` 通过队列发布,统一发布路径 - 重构 `auto_publish_once` 函数,移除直接发布逻辑,改为生成内容入队并返回队列信息 - 新增队列时段使用情况查询方法 `get_slot_usage`,支持 UI 热力图展示 📝 docs(openspec): 新增内容排期优化和热点探测优化规范文档 - 新增 `smart-schedule-engine` 规范,定义智能排期引擎的功能需求和场景 - 新增 `unified-publish-path` 规范,定义统一发布路径的改造方案 - 新增 `hotspot-analysis-state` 规范,定义热点分析状态存储的线程安全接口 - 新增 `hotspot-auto-collector` 规范,定义定时热点自动采集的任务流程 - 新增 `hotspot-engine-bridge` 规范,定义热点数据注入 TopicEngine 的桥接机制 - 新增 `hotspot-topic-selector` 规范,定义热点选题下拉组件的交互行为 - 更新 `services-queue`、`services-scheduler` 和 `services-hotspot` 规范,反映功能修改和新增参数 🔧 chore(config): 新增热点自动采集默认配置 - 在 `DEFAULT_CONFIG` 中新增 `hotspot_auto_collect` 配置节点,包含 `enabled`、`keywords` 和 `interval_hours` 字段 - 提供默认关键词列表 `["穿搭", "美妆", "好物"]` 和默认采集间隔 4 小时 🐛 fix(llm): 增强 JSON 解析容错能力 - 新增 `_try_fix_truncated_json` 方法,尝试修复被 token 限制截断的 JSON 输出 - 支持多种截断场景的自动补全,包括字符串值、数组和嵌套对象的截断修复 - 提高 LLM 分析热点等返回 JSON 的函数的稳定性 💄 style(ui): 优化队列管理和热点探测界面 - 在队列生成区域新增自动排期复选框,勾选后隐藏手动排期输入框 - 在日历视图旁新增推荐时段 Markdown 面板,展示各时段权重和建议热力图 - 在热点探测 Tab 新增推荐选题下拉组件,分析完成后动态填充选项 - 在热点探测 Tab 新增热点自动采集控制区域,支持启动、停止和配置采集参数
100 lines
5.4 KiB
Markdown
100 lines
5.4 KiB
Markdown
## Context
|
||
|
||
当前 `services/hotspot.py` 提供三个纯函数式的热点功能:`search_hotspots`(搜索)、`analyze_and_suggest`(LLM 分析)、`generate_from_hotspot`(基于热点生成文案)。分析结果在函数内部被渲染成 Markdown 后直接返回 UI,结构化数据(`hot_topics`、`suggestions` 等)即刻丢失。
|
||
|
||
`services/scheduler.py` 已有一套成熟的定时调度模式(`_scheduler_loop` + `threading.Event` + daemon thread),可以复用此模式实现热点自动采集。
|
||
|
||
`services/topic_engine.py` 的 `TopicEngine.recommend_topics(hotspot_data=...)` 已支持接收热点数据但从未被实际传入。
|
||
|
||
`services/config_manager.py` 使用单例 + JSON 持久化,新增配置节点零改动即可被 `cfg.get()` 读取。
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
|
||
- 让分析结果在内存中以结构化形式持续可用,供生成和选题引擎消费
|
||
- 让用户在热点 UI 中直接从分析出的建议列表选择选题,而非手动输入
|
||
- 提供后台自动热点采集,无需人工触发即可获得最新热点数据
|
||
- 将热点分析数据桥接到 TopicEngine,使智能选题获得热点维度加权
|
||
|
||
**Non-Goals:**
|
||
|
||
- 不做热点数据持久化到磁盘(进程内缓存即可,重启后重新采集)
|
||
- 不修改 LLM prompt 或分析逻辑(`LLMService.analyze_hotspots` 保持不变)
|
||
- 不改造现有调度器架构(复用已有 `threading.Event` + loop 模式)
|
||
- 不增加新的外部依赖
|
||
|
||
## Decisions
|
||
|
||
### D1: 模块级状态 + RLock 存储分析结果
|
||
|
||
**选择**:在 `services/hotspot.py` 中新增 `_last_analysis: dict | None` 和对应的 `get_last_analysis()` / `set_last_analysis()` 线程安全接口,复用已有的 `_cache_lock`(RLock)。
|
||
|
||
**替代方案**:
|
||
- 单独的 `HotspotState` 类 — 过度封装,模块内部状态无需类化
|
||
- 写入文件持久化 — 增加 IO 复杂度,热点数据本身时效性短,内存缓存足够
|
||
|
||
**理由**:与已有的 `_cached_proactive_entries` 模式一致,最小改动,线程安全由已有的 `_cache_lock` 覆盖。
|
||
|
||
### D2: `analyze_and_suggest` 副作用写入状态
|
||
|
||
**选择**:在 `analyze_and_suggest` 函数中,调用 `svc.analyze_hotspots()` 获得 `analysis` dict 后,先调用 `set_last_analysis(analysis)` 缓存,再渲染 Markdown 返回 UI。
|
||
|
||
**理由**:无需改变函数签名和返回值,对现有 UI 绑定零破坏。
|
||
|
||
### D3: 热点选题下拉组件
|
||
|
||
**选择**:在热点探测 UI(`ui/app.py`)中新增 `gr.Dropdown`(选题下拉),当 `analyze_and_suggest` 完成后动态更新下拉选项为 `suggestions` 的 `topic` 列表。选中后写入 `topic_from_hot` Textbox。
|
||
|
||
**替代方案**:
|
||
- 用 Radio 按钮 — 选项数量不固定,Dropdown 更适合
|
||
- 保持现有 Textbox 手动输入 — 无法利用分析出的建议
|
||
|
||
**理由**:Gradio `gr.Dropdown` 支持 `gr.update(choices=...)` 动态更新,与已有的笔记列表下拉模式一致。
|
||
|
||
### D4: `generate_from_hotspot` 增强上下文
|
||
|
||
**选择**:新增可选参数 `analysis_summary: str = None`。若提供,将其拼入 `reference_notes` 前部(结构化摘要 + 原始片段),总长度仍限制在 3000 字符以内。函数内部同时尝试从 `get_last_analysis()` 自动获取摘要。
|
||
|
||
**理由**:向后兼容,现有调用方无需修改。
|
||
|
||
### D5: TopicEngine 桥接
|
||
|
||
**选择**:新增独立函数 `feed_hotspot_to_engine(topic_engine: TopicEngine)` 在 `services/hotspot.py` 中。该函数读取 `get_last_analysis()`,调用 `topic_engine.recommend_topics(hotspot_data=data)`。
|
||
|
||
**替代方案**:
|
||
- 在 `TopicEngine` 中直接引用 `hotspot.get_last_analysis()` — 导致循环依赖风险
|
||
- 在 UI 层手动传递 — 增加 UI 复杂度
|
||
|
||
**理由**:单向依赖(hotspot → topic_engine),职责清晰。
|
||
|
||
### D6: 自动采集任务集成到调度器
|
||
|
||
**选择**:在 `services/scheduler.py` 中新增独立的 `_hotspot_collector_loop` + `start_hotspot_collector` / `stop_hotspot_collector`,复用现有的 `threading.Event` + daemon thread 模式。与已有 `_learn_scheduler_loop` 模式完全对齐。
|
||
|
||
**配置节点**:`config.json` 新增 `hotspot_auto_collect` 对象:
|
||
```json
|
||
{
|
||
"hotspot_auto_collect": {
|
||
"enabled": false,
|
||
"keywords": ["穿搭", "美妆", "好物"],
|
||
"interval_hours": 4
|
||
}
|
||
}
|
||
```
|
||
|
||
**采集流程**:每个 interval → 遍历 keywords → 调用 `search_hotspots` + `analyze_and_suggest`(复用现有函数) → 结果自动写入 `_last_analysis` 状态 → 休眠至下个周期。
|
||
|
||
**替代方案**:
|
||
- 合并到现有 `_scheduler_loop` — 该循环参数已经过多,耦合度太高
|
||
- 使用 APScheduler 等库 — 引入新依赖,不符合项目风格
|
||
|
||
**理由**:独立线程与已有调度器互不干扰,可独立启停。
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- **内存状态丢失** → 进程重启后 `_last_analysis` 为空,自动采集线程会在首个 interval 后重新填充,可接受
|
||
- **多关键词分析结果覆盖** → 遍历多个 keywords 时后者覆盖前者的分析结果 → 采用合并策略:新分析的 `hot_topics` 和 `suggestions` 追加到已有列表并去重
|
||
- **LLM 调用频率** → 自动采集每个关键词都需调用一次 LLM → 通过 `interval_hours`(默认 4 小时)+ keywords 数量(默认 3 个)控制成本
|
||
- **线程安全竞态** → 手动分析和自动采集可能同时写入 `_last_analysis` → `_cache_lock`(RLock)已覆盖
|