diff --git a/.cursor/plans/refactor-geo-tool-py-structure_357e8386.plan.md b/.cursor/plans/refactor-geo-tool-py-structure_357e8386.plan.md
deleted file mode 100644
index d4dcfd0..0000000
--- a/.cursor/plans/refactor-geo-tool-py-structure_357e8386.plan.md
+++ /dev/null
@@ -1,166 +0,0 @@
----
-name: refactor-geo-tool-py-structure
-overview: 将当前约 8000 行的 `geo_tool.py` 拆分为按 Tab/功能模块组织的多文件结构,在不明显改变现有功能的前提下,大幅提升可维护性和扩展性。
-todos:
- - id: identify-tabs
- content: 梳理 `geo_tool.py` 中的所有 Tab/功能分区,并确定对应的模块命名方案。
- status: completed
- - id: extract-tab-modules
- content: 为各个 Tab 创建 `modules/ui/tab_*.py` 模块,并将对应 UI+逻辑从 `geo_tool.py` 迁移过去。
- status: in_progress
- - id: create-ui-components
- content: 在 `modules/ui/components.py` 中抽取公共 UI 组件,并在各 Tab 模块中复用。
- status: pending
- - id: setup-services-layer
- content: 在 `modules/services/` 下创建服务层模块,封装对已有业务模块的常用工作流。
- status: pending
- - id: centralize-state
- content: 在 `modules/ui/state.py` 中统一初始化和管理 `st.session_state`。
- status: completed
- - id: extract-theme
- content: 在 `modules/ui/theme.py` 中抽取并注入全局 CSS/主题配置。
- status: completed
- - id: update-docs
- content: 更新文档,描述新的前端结构及新增 Tab 的开发流程。
- status: pending
-isProject: false
----
-
-## 目标
-
-- **核心目标**: 保持现有产品功能与 UI 行为基本不变的前提下,将臃肿的 `geo_tool.py` 拆分为清晰的多模块结构,降低单文件复杂度,方便后续开发与调试。
-- **次要目标**: 建立可复用的 UI 组件与工具函数库,为后续功能拓展预留架构空间。
-
-## 当前情况快速判断
-
-- `geo_tool.py` 是 **Streamlit 单入口文件**,负责:
- - 全局样式(CSS 注入)、页面配置、会话状态管理。
- - 多个 Tab/功能区的 UI 与业务逻辑(Schema 生成、技术配置、内容优化、ROI 分析等)。
- - 大量直接使用的 `modules/*` 业务类(如 `SchemaGenerator`, `TechnicalConfigGenerator`, `MultimodalPromptGenerator` 等)。
-- 文件超过 7800 行,已经严重影响:
- - 阅读理解成本(很难快速定位某个 Tab 的逻辑)。
- - 测试与重构(任意修改都有牵一发动全身的风险)。
-
-## 拆分总体思路
-
-- **保持入口不变**: 继续以 `[geo_tool.py](geo_tool.py)` 作为 Streamlit 启动入口(`streamlit run geo_tool.py` 不变)。
-- **按 Tab/大功能模块拆分**: 每个 Tab/大功能区域抽象为独立的“页面模块”函数,放到 `modules/ui/` 目录下,由 `geo_tool.py` 统一路由与调用。
-- **抽取公共基础层**:
- - UI 级别:常见的卡片布局、标题区域、Metric 区、表格封装等。
- - 逻辑级别:表单输入校验、异常展示、下载按钮封装、会话状态初始化等。
-- **不改变底层业务类接口**: 继续使用现有的 `modules/*.py`(如 `SchemaGenerator` 等),优先只改调用位置与组织形式。
-
-用一个简单架构图表示目标结构:
-
-```mermaid
-flowchart TD
- appMain[geo_tool.py_main] --> layout[layout & routing]
- layout --> tabSchema[tab_schema_module]
- layout --> tabTechConfig[tab_tech_config_module]
- layout --> tabContent[tab_content_module]
- layout --> tabROI[tab_roi_module]
-
- tabSchema --> services[domain_services]
- tabTechConfig --> services
- tabContent --> services
- tabROI --> services
-
- services --> modulesData[modules.data_storage]
- services --> modulesSchema[modules.schema_generator]
- services --> modulesOther[other_existing_modules]
-```
-
-
-
-## 具体实施步骤
-
-### 第 1 阶段:识别与规划 Tab 结构(只读分析)
-
-- **任务**:
- - 在 `[geo_tool.py](geo_tool.py)` 中定位所有 `st.tabs([...])` / 顶层功能区块(如“Schema 生成”、“技术配置”、“内容评分”等)。
- - 为每个 Tab/功能区命名一个清晰的模块名(例如 `tab_schema`, `tab_tech_config`, `tab_multimodal`, `tab_roi`)。
-- **产出**:
- - 一份 Tab → 模块名的映射清单(可写入 `docs/analysis/TABS_TO_MODULES.md`)。
-
-### 第 2 阶段:抽取 Tab 级模块骨架
-
-- **任务**:
- - 在 `modules/ui/`(如不存在则创建)下,为每个 Tab 新建一个文件,例如:
- - `[modules/ui/tab_schema.py](modules/ui/tab_schema.py)`
- - `[modules/ui/tab_tech_config.py](modules/ui/tab_tech_config.py)`
- - `[modules/ui/tab_multimodal.py](modules/ui/tab_multimodal.py)`
- - `[modules/ui/tab_roi.py](modules/ui/tab_roi.py)`
- - 每个文件提供一个统一签名的入口函数,例如:
- - `def render_tab_schema(state: st.session_state) -> None:`
- - 在 `[geo_tool.py](geo_tool.py)` 中:
- - 保留顶部导入和页面整体布局(标题、侧边栏等)。
- - 将各个 Tab 内部原有代码整体“剪切”到对应模块的 `render_*` 函数中。
- - Tab 切换处仅保留类似逻辑:
- - `if selected_tab == "Schema 工具": tab_schema.render_tab_schema(st.session_state)`。
-- **注意点**:
- - 暂时避免重命名变量;尽可能原样移动,少量调整缩进与局部变量作用域以保证运行。
-
-### 第 3 阶段:抽取通用 UI 组件与工具函数
-
-- **任务**:
- - 分析不同 Tab 中重复使用的 UI 片段,例如:
- - 标题区 + 说明文字 + 表单布局。
- - 结果展示 + 下载按钮 + 使用说明。
- - 在 `modules/ui/components.py` 中创建可复用组件,例如:
- - `def render_section_header(title: str, subtitle: str) -> None`。
- - `def render_download_block(label: str, content: str, filename: str, mime: str, key: str) -> None`。
- - 在各个 `tab_*.py` 中用这些组件替换重复的代码片段。
-- **收益**:
- - 减少重复代码,使 `tab_*.py` 更聚焦于业务逻辑。
-
-### 第 4 阶段:整理业务服务层(可选但推荐)
-
-- **任务**:
- - 对 `modules/*.py` 中的业务类/函数按领域拆分轻量“服务层”,例如在 `modules/services/` 下:
- - `schema_service.py`:封装 `SchemaGenerator` 的常用工作流(输入参数 → JSON-LD/HTML 输出)。
- - `tech_config_service.py`:封装 `TechnicalConfigGenerator` 的组合逻辑。
- - `multimodal_service.py`:封装 `MultimodalPromptGenerator` 的常见调用路径。
- - 在 `tab_*.py` 中只调用“服务函数”,不直接操作底层复杂对象。
-- **收益**:
- - Tab 代码更偏向“用例/流程”,服务层集中处理细节,方便后续替换模型或 API。
-
-### 第 5 阶段:会话状态与常量集中管理
-
-- **任务**:
- - 在 `modules/ui/state.py` 中:
- - 定义统一的 `init_session_state()`,负责初始化所有在不同 Tab 使用的 `st.session_state` key。
- - 定义相关的 key 名常量,避免魔法字符串散落在各个模块。
- - 在 `geo_tool.py` 的入口处调用 `init_session_state()`。
-- **收益**:
- - 避免 session_state key 拼写错误导致的运行时 bug。
- - 未来扩展/重构 session 结构更简单。
-
-### 第 6 阶段:CSS 与布局抽象
-
-- **任务**:
- - 将 `geo_tool.py` 顶部大段 CSS 抽出到单独模块(例如 `[modules/ui/theme.py](modules/ui/theme.py)`):
- - 暴露 `inject_global_theme()` 函数,内含 `st.markdown("""""", unsafe_allow_html=True)`。
- - 入口文件 `geo_tool.py` 在最开始调用 `inject_global_theme()`,保持 UI 现状。
-- **收益**:
- - 清爽入口文件,CSS 调整有单一来源。
-
-### 第 7 阶段:清理与文档更新
-
-- **任务**:
- - 删除 `geo_tool.py` 中已迁移的死代码/注释块。
- - 在 `[DOCS.md](DOCS.md)` 或现有 `docs/analysis/*.md` 中补充:
- - 新的文件结构说明(入口 → Tab → 服务层 → 业务模块)。
- - 如何新增一个新 Tab:
- - 创建 `modules/ui/tab_newfeature.py`,实现 `render_tab_newfeature()`。
- - 在 `geo_tool.py` 中注册 Tab,并调用该函数。
-
-## Todo 列表(后续实施用)
-
-- **identify-tabs**: 在 `geo_tool.py` 中梳理所有 Tab/功能分区,并形成 Tab → 模块名映射清单。
-- **extract-tab-modules**: 在 `modules/ui/` 下为每个 Tab 创建 `tab_*.py`,并从 `geo_tool.py` 中迁移对应 UI 与逻辑。
-- **create-ui-components**: 在 `modules/ui/components.py` 中抽取通用 UI 组件,并在各个 `tab_*.py` 中替换重复片段。
-- **setup-services-layer**: 在 `modules/services/` 下封装对现有业务模块的常见调用路径。
-- **centralize-state**: 在 `modules/ui/state.py` 中集中管理 `st.session_state` 初始化与常量。
-- **extract-theme**: 在 `modules/ui/theme.py` 中集中管理 CSS/主题注入,并在入口调用。
-- **update-docs**: 更新 `DOCS.md` 或相关文档,说明新结构和新增功能的规范流程。
-
diff --git a/.cursorrules b/.cursorrules
deleted file mode 100644
index 75178c2..0000000
--- a/.cursorrules
+++ /dev/null
@@ -1,80 +0,0 @@
-# Cursor Rules for GEO Tool Project
-
-## 📁 目录结构规范
-
-### 根目录文件规则
-
-**允许在根目录的文件**:
-- `README.md` - 项目主文档(必须)
-- `DOCS.md` - 文档索引(必须)
-- `geo_tool.py` - 主程序(必须)
-- `requirements.txt` - 依赖文件(必须)
-- `.gitignore` - Git配置(必须)
-- `.streamlit/` - Streamlit配置目录(必须)
-
-**禁止在根目录创建**:
-- ❌ 任何新的 `.md` 文档文件(除了 README.md 和 DOCS.md)
-- ❌ 任何功能模块 `.py` 文件(应放在 `modules/` 目录)
-- ❌ 任何工具脚本 `.py` 文件(应放在 `scripts/` 目录)
-
-### 文档文件位置规则
-
-**所有新文档必须创建在 `docs/` 的相应子目录中**:
-
-- **功能文档** → `docs/features/`
- - 格式:`*_FEATURE.md`
- - 示例:`docs/features/CONFIG_OPTIMIZER_FEATURE.md`
-
-- **分析报告** → `docs/analysis/`
- - 格式:`*_ANALYSIS.md` 或 `*_REPORT.md`
- - 示例:`docs/analysis/FEATURE_ANALYSIS.md`
-
-- **使用指南** → `docs/guides/`
- - 格式:`*_GUIDE.md` 或相关文档
- - 示例:`docs/guides/QUICK_START_GUIDE.md`
-
-- **实现文档** → `docs/implementation/`
- - 格式:实现相关的文档
- - 示例:`docs/implementation/IMPLEMENTATION_SUMMARY.md`
-
-### Python 文件位置规则
-
-- **功能模块** → `modules/` 目录
- - 所有功能模块文件应放在 `modules/` 目录
- - 导入方式:`from modules.xxx import Xxx`
-
-- **工具脚本** → `scripts/` 目录
- - 所有工具脚本应放在 `scripts/` 目录
- - 使用方式:`python scripts/script_name.py`
-
-- **主程序** → 根目录
- - `geo_tool.py` 保留在根目录
-
-### 平台同步模块
-
-- **平台同步相关** → `platform_sync/` 目录
- - 保持现有结构不变
-
-## 📝 创建新文件时的检查清单
-
-创建新文件前,请确认:
-
-- [ ] 如果是 `.md` 文档,是否放在了正确的 `docs/` 子目录?
-- [ ] 如果是功能模块 `.py` 文件,是否放在了 `modules/` 目录?
-- [ ] 如果是工具脚本 `.py` 文件,是否放在了 `scripts/` 目录?
-- [ ] 是否更新了 `DOCS.md` 中的文档索引(如果是新文档)?
-- [ ] 是否更新了相关的导入路径(如果是新模块)?
-
-## 🚫 禁止操作
-
-- ❌ 禁止在根目录创建新的 `.md` 文档(除了 README.md 和 DOCS.md)
-- ❌ 禁止在根目录创建功能模块 `.py` 文件
-- ❌ 禁止在根目录创建工具脚本 `.py` 文件
-- ❌ 禁止在根目录创建重复的文档文件
-
-## ✅ 推荐做法
-
-- ✅ 创建新文档时,先确定文档类型,然后放在对应的 `docs/` 子目录
-- ✅ 创建新模块时,放在 `modules/` 目录,并更新 `modules/__init__.py`
-- ✅ 创建新脚本时,放在 `scripts/` 目录
-- ✅ 更新 `DOCS.md` 添加新文档的索引链接
diff --git a/.gitignore b/.gitignore
index 9de60a4..34217da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,11 @@ env/
venv/
ENV/
.venv
+*.egg-info/
+dist/
+build/
+.mypy_cache/
+.pytest_cache/
# Streamlit
.streamlit/secrets.toml
@@ -23,6 +28,13 @@ ENV/
*.swp
*.swo
*~
+.cursor/
+.cursorrules
+
+# Environment
+.env
+.env.local
+.env.*.local
# OS
.DS_Store
@@ -31,5 +43,8 @@ Thumbs.db
# 数据目录(如果使用JSON方式)
data/
+# 知识库数据
+knowledge_base/
+
# 本地敏感配置(不要提交到仓库)
config.json
diff --git a/.streamlit/config.toml b/.streamlit/config.toml
index b1b4ec2..860525e 100644
--- a/.streamlit/config.toml
+++ b/.streamlit/config.toml
@@ -4,7 +4,10 @@ primaryColor="#2563EB"
backgroundColor="#FFFFFF"
secondaryBackgroundColor="#F7FAFC"
textColor="#1A202C"
-borderColor="#E2E8F0"
-baseRadius="10"
-buttonRadius="10"
-showSidebarBorder=true
+
+[server]
+headless = true
+maxUploadSize = 50
+
+[browser]
+gatherUsageStats = false
diff --git a/.streamlit/secrets.toml.example b/.streamlit/secrets.toml.example
new file mode 100644
index 0000000..9d0732c
--- /dev/null
+++ b/.streamlit/secrets.toml.example
@@ -0,0 +1,41 @@
+# Streamlit Secrets 配置文件
+# 复制此文件为 secrets.toml 并填入真实的 API Key
+# 注意:secrets.toml 已在 .gitignore 中,不会被提交到仓库
+
+[api_keys]
+# DeepSeek API Key(推荐,性价比高)
+deepseek = ""
+
+# OpenAI API Key(可选)
+# openai = ""
+
+# 通义千问 API Key(可选)
+# tongyi = ""
+
+# Groq API Key(可选)
+# groq = ""
+
+# Moonshot (Kimi) API Key(可选)
+# moonshot = ""
+
+# 豆包 API Key(可选,格式:access_key:secret_key:endpoint_id)
+# doubao = ""
+
+# 文心一言 API Key(可选,格式:app_key:app_secret)
+# wenxin = ""
+
+# 通义万相 API Key(图片生成,可选)
+# tongyi_wanxiang = ""
+
+[app_config]
+# 品牌名称
+brand = ""
+
+# 品牌优势
+advantages = ""
+
+# 竞品列表(换行分隔)
+competitors = ""
+
+# LLM 温度参数
+temperature = 0.7
diff --git a/DOCS.md b/DOCS.md
index 006e085..79bfe29 100644
--- a/DOCS.md
+++ b/DOCS.md
@@ -11,6 +11,8 @@
所有功能模块的详细说明:
+### 核心功能
+
- [配置优化器](docs/features/CONFIG_OPTIMIZER_FEATURE.md)
- [内容质量指标](docs/features/CONTENT_METRICS_FEATURE.md)
- [内容质量评分](docs/features/CONTENT_SCORER_FEATURE.md)
@@ -28,15 +30,21 @@
- [话题集群](docs/features/TOPIC_CLUSTER_FEATURE.md)
- [工作流自动化](docs/features/WORKFLOW_AUTOMATION_FEATURE.md)
+### GEO 增强功能(新增)
+
+- [品牌知识库(RAG)](docs/features/KNOWLEDGE_BASE_FEATURE.md) - 基于 RAG 的知识库管理
+- [AI 搜索验证](docs/features/AI_SEARCH_VERIFIER_FEATURE.md) - 使用真实搜索引擎验证品牌提及
+- [内容独特性检测](docs/features/CONTENT_UNIQUENESS_FEATURE.md) - 批量内容相似度检测
+- [关键词数据增强](docs/features/KEYWORD_DATA_ENHANCER_FEATURE.md) - 基于历史数据优化关键词策略
+- [LLM 工厂模块](docs/features/LLM_FACTORY_FEATURE.md) - 统一的 LLM 客户端构建接口
+
## 📊 分析报告
+- [Tab 与模块映射](docs/analysis/TABS_TO_MODULES_ANALYSIS.md) - 主导航 Tab 与 `modules/ui/tab_*.py` 对应关系
+- [Tab 拆分模式](docs/analysis/TAB_SPLIT_PATTERN.md) - Tab 拆分模式说明
- [分析准确性报告](docs/analysis/ANALYSIS_ACCURACY_REPORT.md)
-- [代码文档分析](docs/analysis/CODE_DOCUMENTATION_ANALYSIS.md)
- [文档反向验证](docs/analysis/DOCUMENTATION_REVERSE_VERIFICATION.md)
-- [功能重要性分析](docs/analysis/FEATURE_ANALYSIS.md)
-- [功能优先级分析](docs/analysis/FEATURE_PRIORITY_ANALYSIS.md)
- [功能验证报告](docs/analysis/FUNCTION_VERIFICATION_REPORT.md)
-- [GEO 合规性分析](docs/analysis/GEO_COMPLIANCE_ANALYSIS.md)
## 📘 指南文档
@@ -45,6 +53,7 @@
- [平台设置指南](docs/guides/PLATFORM_SETUP.md)
- [快速开始指南](docs/guides/QUICK_START_GUIDE.md)
- [数据存储指南](docs/guides/STORAGE_GUIDE.md)
+- [根目录文件管理规则](docs/guides/ROOT_DIRECTORY_RULES.md)
## 🔧 实现文档
@@ -56,18 +65,25 @@
- [平台同步实现](docs/implementation/PLATFORM_SYNC_IMPLEMENTATION.md)
- [平台同步测试](docs/implementation/PLATFORM_SYNC_TEST.md)
+## 🖥️ 前端结构与开发流程(拆分后)
+
+- **入口**:`geo_tool.py` 为 Streamlit 单入口,负责侧边栏配置、全局状态、主导航 Tabs 与对各 Tab 模块的调用。
+- **Tab 模块**:`modules/ui/tab_*.py`,每个文件提供 `render_tab_*(...)`,由主入口在对应 `with tabN:` 内调用。
+- **公共 UI**:`modules/ui/components.py` 提供 `sanitize_filename`、`render_section_header`、`render_download_button`、`render_tab_top_with_clear` 等,供各 Tab 复用。
+- **状态与主题**:`modules/ui/state.py` 统一初始化 `st.session_state`;`modules/ui/theme.py` 注入全局 CSS。
+- **服务层**:`modules/services/` 封装对业务模块的常用工作流(如 `schema_service`、`tech_config_service`),Tab 可按需调用。
+
+**新增一个 Tab 的流程**:
+
+1. 在 `modules/ui/` 下新建 `tab_新功能.py`,实现 `def render_tab_新功能(...)`,接收主入口传入的 `storage`、`ss_init`、`brand` 等依赖。
+2. 在 `geo_tool.py` 的 `st.tabs([...])` 中增加新 Tab 标签,并增加 `with tabN:` 块内调用 `tab_新功能.render_tab_新功能(...)`。
+3. 在 `modules/ui/__init__.py` 的 `from . import (...)` 中增加 `tab_新功能`。
+
+详见:[Tab 与模块映射](docs/analysis/TABS_TO_MODULES_ANALYSIS.md)、[Tab 拆分模式](docs/analysis/TAB_SPLIT_PATTERN.md)。
+
## 📁 项目结构
-- [项目结构分析](docs/guides/PROJECT_STRUCTURE_ANALYSIS.md) - 目录结构分析报告
-- [目录结构优化方案](docs/DIRECTORY_STRUCTURE_OPTIMIZATION.md) - 优化方案详情
-- [快速重组指南](docs/guides/QUICK_REORGANIZE.md) - 文件重组执行步骤
-- [重组总结](docs/guides/REORGANIZATION_SUMMARY.md) - 重组工作总结
-- [文档清理指南](docs/guides/DOCUMENTATION_CLEANUP_GUIDE.md) - 文档清理说明
-- [最终优化指南](docs/guides/FINAL_OPTIMIZATION_GUIDE.md) - 深度优化执行指南
-- [优化状态报告](docs/guides/OPTIMIZATION_STATUS.md) - 优化进度和状态
-- [手动清理指南](docs/guides/MANUAL_CLEANUP_GUIDE.md) - 手动清理操作说明
-- [高级优化方案](docs/guides/ADVANCED_OPTIMIZATION_PLAN.md) - 详细优化方案
-- [引用路径更新总结](docs/guides/REFERENCE_UPDATE_SUMMARY.md) - 引用路径更新总结
+项目采用模块化架构,详见 [README.md](README.md) 中的项目结构说明。
## 🔍 查找文档
@@ -84,17 +100,9 @@
- **功能配置** → [平台设置指南](docs/guides/PLATFORM_SETUP.md)
- **数据存储** → [数据存储指南](docs/guides/STORAGE_GUIDE.md)
- **平台同步** → [平台同步实现](docs/implementation/PLATFORM_SYNC_IMPLEMENTATION.md)
+- **知识库** → [品牌知识库功能](docs/features/KNOWLEDGE_BASE_FEATURE.md)
+- **AI 搜索验证** → [AI 搜索验证功能](docs/features/AI_SEARCH_VERIFIER_FEATURE.md)
---
-💡 **提示**:如果找不到需要的文档,请查看 [README.md](README.md) 或使用搜索功能。
-
----
-
-## 📌 重要规则
-
-**根目录文档管理规则**:
-- ✅ 根目录只保留 `README.md` 和 `DOCS.md`
-- ✅ 所有新文档应创建在 `docs/` 的相应子目录中
-- 📖 详细规则请查看:[根目录文件管理规则](docs/guides/ROOT_DIRECTORY_RULES.md)
-- 📋 清理清单请查看:[根目录文档清理清单](ROOT_DOCS_CLEANUP.md)
+💡 **提示**:如果找不到需要的文档,请查看 [README.md](README.md) 或使用搜索功能。
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d5b4c45
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 GEO Tool
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 8d0e1bf..fff25fa 100644
--- a/README.md
+++ b/README.md
@@ -1,290 +1,307 @@
-# 运行命令
+# GEO 智能内容优化平台
+
+
+
+
+
+
+
+
+**让您的品牌在 AI 搜索中被优先引用**
+
+[快速开始](#-快速开始) · [功能特性](#-功能特性) · [使用指南](#-使用指南) · [文档](#-文档)
+
+
+
-`streamlit run geo_tool.py`
---
-# 功能迭代计划
+## 📖 项目简介
-## ✅ 已完成功能
+**GEO(Generative Engine Optimization)智能内容优化平台** 是一款 AI 驱动的品牌内容策略工具,帮助企业在 AI 搜索引擎(如 ChatGPT、Perplexity、Google SGE)的回答中被优先、准确、可信地提及。
-- [x] **数据持久化(SQLite)** - 已完成
- - 关键词、文章、优化记录、验证结果自动保存
- - 历史记录查看功能(Tab5)
- - 详见 `docs/implementation/INTEGRATION_NOTES.md`
+### 解决什么问题?
-- [x] **AI 蒸馏词 - 托词工具** - 已完成
- - 支持三种生成模式:AI生成、托词工具、混合模式
- - 词库管理(编辑、导入、导出)
- - 组合算法(支持10种组合模式)
- - LLM 润色功能(混合模式)
- - 自动去重和相似度过滤
+当用户向 AI 提问"最好的外贸 ERP 软件是什么?"时,AI 会从互联网内容中检索信息并生成回答。**GEO 工具帮助您的品牌出现在这个回答中**。
-- [x] **收录平台扩展** - 已完成 ✅
- - 支持 DeepSeek、OpenAI、通义千问、Groq、Moonshot、豆包(字节跳动)、文心一言(百度)
- - API Key 格式提示和验证
- - 详见 `docs/guides/PLATFORM_SETUP.md`
+### 核心价值
-- [x] **自媒体账号平台扩展** - 已完成 ✅
- - 支持 **20个内容生成平台**:
- 1. 知乎(专业问答)
- 2. 小红书(生活种草)
- 3. CSDN(技术博客)
- 4. B站(视频脚本)
- 5. 头条号(资讯软文)
- 6. GitHub(README/文档)
- 7. 微信公众号(长文)
- 8. 抖音图文(短内容)
- 9. 百家号(资讯)
- 10. 网易号(资讯)
- 11. 企鹅号(资讯)
- 12. 简书(文艺)
- 13. 新浪博客(博客)
- 14. 新浪新闻(资讯)
- 15. 搜狐号(资讯)
- 16. QQ空间(社交)
- 17. 邦阅网(外贸)
- 18. 一点号(资讯)
- 19. 东方财富(财经)
- 20. 原创力文档(文档)
- - 每个平台都有专门的 Prompt 模板
- - 支持 Markdown 格式输出
-- [x] **AI 数据报表** - 已完成 ✅
- - 自动验证任务(使用历史关键词)
- - 提及率趋势图(按日期展示)
- - 平台贡献度分析(文章平台分布)
- - 关键词效果排名(Top 20)
- - 竞品对比分析(多维度对比)
- - 数据导出功能(CSV 格式)
+| 价值 | 说明 |
+| ----------- | ------------------ |
+| 🎯 **品牌曝光** | 在 AI 回答中被优先提及 |
+| 📈 **搜索排名** | 内容被 AI 引用,间接提升 SEO |
+| 🏆 **权威建立** | 多平台、多角度内容建立专业形象 |
+| 📊 **数据驱动** | 基于验证数据持续优化策略 |
-- [x] **高级功能** - 已完成 ✅
- - E-E-A-T 评估与强化(`docs/features/docs/features/EEAT_FEATURE.md`)
- - 话题集群生成(`docs/features/docs/features/TOPIC_CLUSTER_FEATURE.md`)
- - JSON-LD Schema.org 生成(`docs/features/docs/features/JSON_LD_SCHEMA_FEATURE.md`)
- - 内容质量评分(`docs/features/docs/features/CONTENT_SCORER_FEATURE.md`)
- - 事实密度增强(`docs/features/docs/features/FACT_DENSITY_FEATURE.md`)
- - 语义扩展(`docs/features/docs/features/SEMANTIC_EXPANSION_FEATURE.md`)
- - 关键词挖掘(`docs/features/docs/features/KEYWORD_MINING_FEATURE.md`)
- - 工作流自动化(`docs/features/docs/features/WORKFLOW_AUTOMATION_FEATURE.md`)
- - 内容质量指标分析(`docs/features/docs/features/CONTENT_METRICS_FEATURE.md`)
- - ROI 分析(`docs/features/docs/features/ROI_ANALYSIS_FEATURE.md`)
- - 负面监控(`docs/features/docs/features/NEGATIVE_MONITOR_FEATURE.md`)
- - 多模态提示生成(`docs/features/docs/features/MULTIMODAL_FEATURE.md`)
- - 技术配置生成(`docs/features/docs/features/TECHNICAL_CONFIG_FEATURE.md`)
- - 资源推荐(`docs/features/docs/features/RESOURCE_RECOMMENDER_FEATURE.md`)
-
-- [x] **平台同步功能** - 已完成 ✅
- - GitHub API 发布
- - 12个平台一键复制功能
- - 发布记录跟踪
- - 详见 `docs/implementation/IMPLEMENTATION_SUMMARY.md`
---
-## 📋 待实现功能(按优先级排序)
+## ✨ 功能特性
-> **优先级说明**:优先级基于对 GEO 效果的直接影响、实现成本和用户价值综合评估。
-> **调整原则**:优先实现能直接提升 GEO 效果的功能(平台扩展、内容渠道),延后实现辅助性功能(图库)。
+### 🔍 关键词策略
-### 🔥 高优先级(核心功能增强)
+- **智能关键词生成**:AI 生成 + 词库组合 + 混合模式
+- **搜索意图覆盖**:对比、评测、使用、购买、问题、推荐
+- **数据增强**:基于历史验证数据反哺关键词策略
+- **语义扩展**:自动扩展相关关键词
-#### 1. 更多平台 API 发布
+### ✍️ 内容生成
-**当前支持:** GitHub API 发布
+- **20+ 平台模板**:知乎、小红书、CSDN、B站、微信公众号等
+- **E-E-A-T 内嵌**:专业性、经验性、权威性、可信度
+- **品牌植入策略**:自然提及 2-4 次,避免过度营销
+- **结构化输出**:结论摘要、清单、FAQ 格式
-**待添加平台:**
-- 微信公众号 API 发布
-- B站 API 发布
-- 知乎 API 发布
-- CSDN API 发布
-- 百家号 API 发布
-- 企鹅号 API 发布
-- 网易号 API 发布
+### 📚 知识库(RAG)
-**重要性分析:**
-- ✅ **提升发布效率**:API 发布比手动复制更高效
-- ✅ **自动化流程**:支持批量发布和定时发布
-- ⚠️ **实现成本高**:需要各平台 API 接入,技术难度较高
+- **文档管理**:上传品牌文档、产品手册、案例库
+- **智能检索**:生成内容时自动检索相关信息
+- **来源保障**:基于真实品牌信息生成内容
-**实现建议:**
-- 参考 GitHub 发布器实现模式
-- 逐步接入各平台 API
-- 详见 `docs/implementation/PLATFORM_SYNC_IMPLEMENTATION.md`
+### 🔧 内容优化
+
+- **E-E-A-T 强化**:四维评估与自动优化
+- **事实密度增强**:添加数据支撑和来源引用
+- **结构化数据**:自动生成 JSON-LD Schema(FAQ、HowTo、Article 等)
+- **内容评分**:量化评估内容质量
+
+### ✅ 验证监控
+
+- **多模型验证**:DeepSeek、OpenAI、通义千问等多模型交叉验证
+- **AI 搜索验证**:接入 Perplexity 真实搜索引擎验证
+- **竞品对比**:同时验证自有品牌和竞品
+- **负面防护**:主动生成负面查询并监控
+
+### 📊 数据分析
+
+- **提及率趋势**:按日期跟踪品牌提及率变化
+- **平台贡献度**:分析各平台的内容贡献
+- **ROI 分析**:API 调用成本统计与优化建议
+- **内容独特性**:检测批量内容的相似度
+
+### 🔄 平台同步
+
+- **GitHub API**:一键发布到 GitHub
+- **12 平台复制**:知乎、小红书、CSDN 等平台一键复制
+- **发布记录**:跟踪发布状态
---
-#### 2. 批量发布功能
+## 🚀 快速开始
-**功能描述:**
-- 批量发布多篇文章到多个平台
-- 发布队列管理
-- 定时发布功能
+### 环境要求
-**状态:** ⏳ **部分实现**
-- ✅ 有发布记录功能
-- ❌ 无批量发布UI
-- ❌ 无发布队列管理
-- ❌ 无定时发布
+- Python 3.10+
+- pip 或 conda
+
+### 安装步骤
+
+```bash
+# 1. 克隆仓库
+git clone https://github.com/your-username/geo_tool.git
+cd geo_tool
+
+# 2. 安装依赖
+pip install -r requirements.txt
+
+# 3. 运行应用
+streamlit run geo_tool.py
+```
+
+### 配置 API Key
+
+首次运行后,在侧边栏配置 LLM API Key:
+
+
+| 提供商 | 说明 |
+| -------- | ----------- |
+| DeepSeek | 推荐,性价比高 |
+| OpenAI | GPT-4o-mini |
+| 通义千问 | 阿里云 |
+| Groq | 推理加速 |
+| Moonshot | Kimi |
+| 豆包 | 字节跳动 |
+| 文心一言 | 百度 |
-**实现建议:**
-- 扩展平台同步模块
-- 添加批量发布UI
-- 实现发布队列和定时任务
---
-### 🟡 中优先级(功能扩展)
+## 📋 使用指南
-#### 4. AI 数据报表
+### 基本工作流程
-**功能描述:**
-- 系统自动模拟用户提问
-- 收录结果实时同步至 AI 数据报表
-- 清晰展示哪些词已被引用、哪些平台贡献了曝光
+```
+1. 关键词蒸馏 → 生成 GEO 优化关键词
+ ↓
+2. 品牌知识库 → 上传品牌文档和案例
+ ↓
+3. 自动创作 → 基于知识库生成内容
+ ↓
+4. 内容优化 → E-E-A-T 强化 + Schema 生成
+ ↓
+5. 多模型验证 → 验证品牌是否被提及
+ ↓
+6. 平台同步 → 发布到各平台
+ ↓
+7. 数据报表 → 监控效果并优化
+```
-**重要性分析:**
-- ✅ **监控 GEO 效果**:自动化监控,数据可视化
-- ✅ **指导优化方向**:通过数据反馈优化内容策略
-- ⚠️ **实现成本较高**:需要定时任务、数据可视化等
+### Tab 功能说明
-**评估与优化建议:**
-- ⚠️ **需要优化**:
- 1. **模拟提问的策略**:
- - 定期自动验证(如每天/每周)
- - 支持自定义验证频率
- - 记录历史趋势(提及率变化)
- 2. **数据存储**:
- - 使用数据库(SQLite)存储历史数据
- - 支持数据导出和分析
- 3. **报表功能**:
- - 提及率趋势图
- - 平台贡献度分析
- - 关键词效果排名
- - 竞品对比分析
- 4. **实时同步**:
- - 后台任务 + 实时更新 UI
-**实现建议:**
-- 新增模块:AI 数据报表(可放在 Tab5 或独立 Tab)
-- 自动验证任务(定时/手动触发)
-- 数据可视化(趋势图、对比图、热力图)
-- 数据导出功能
+| Tab | 功能 | 说明 |
+| ---------- | ------- | -------------- |
+| 🎯 关键词蒸馏 | 关键词生成 | AI/词库/混合三种模式 |
+| ✍️ 自动创作 | 内容生成 | 20+ 平台模板 |
+| 🔧 文章优化 | 内容优化 | E-E-A-T 强化 |
+| ✅ 多模型验证 | 效果验证 | 多模型交叉验证 |
+| 📚 历史记录 | 数据查看 | 历史数据回顾 |
+| 📊 AI 数据报表 | 数据分析 | 趋势图、ROI 分析 |
+| ⚙️ 工作流自动化 | 自动化 | 一键完成全流程 |
+| 📦 GEO 资源库 | 资源管理 | 优化资源推荐 |
+| 🔄 平台同步 | 内容分发 | GitHub + 12 平台 |
+| 🛠️ 配置优化助手 | 配置管理 | 智能配置建议 |
+| 📚 品牌知识库 | RAG 知识库 | 文档管理与检索 |
+
---
-### 🟢 低优先级(高级功能 / 可选功能)
+## 🏗️ 项目结构
-#### 5. 企业知识库 - 企业图库
-
-**功能描述:**
-- 分类上传产品图、场景图、资质证书等
-- 这些素材会在后续内容生成中自动嵌入,确保品牌一致性
-
-**重要性分析:**
-- ⚠️ **对 GEO 直接贡献有限**:GEO 核心是文本内容,大模型主要从文本中提取信息
-- ⚠️ **适用场景有限**:主要适用于小红书、抖音等图文平台,对知乎、CSDN 等文字平台作用不大
-- ⚠️ **实现成本较高**:需要图片存储、管理、智能匹配等功能
-- ✅ **替代方案**:可手动配图,或让 LLM 生成图片描述/建议
-
-**评估与优化建议:**
-- ✅ **优点**:提升品牌一致性,素材复用
-- ⚠️ **需要优化**:
- 1. **图片存储与管理**:
- - 使用本地文件系统或云存储(OSS/S3)
- - 支持图片分类、标签、搜索
- 2. **图片在内容中的嵌入方式**:
- - 文本内容:生成图片描述,提示"可配图:xxx"
- - Markdown:自动插入图片链接
- - 小红书/抖音:生成图片使用建议
- 3. **图片与内容的智能匹配**:
- - 使用 LLM 分析内容主题,自动推荐匹配图片
- 4. **版权与合规**:
- - 增加图片版权信息记录
-
-**实现建议:**
-- 新增模块:企业图库管理(可放在侧边栏或独立 Tab)
-- 图片上传(支持批量)
-- 图片分类(产品图、场景图、资质证书等)
-- 图片标签系统
-- 内容生成时自动匹配图片
-
-**建议:** 可延后实现,或先实现简化版(仅图片上传和描述生成)
+```
+geo_tool/
+├── .streamlit/ # Streamlit 配置
+│ └── config.toml # 主题配置
+├── docs/ # 项目文档
+│ ├── features/ # 功能文档
+│ ├── guides/ # 使用指南
+│ ├── implementation/ # 实现文档
+│ └── analysis/ # 分析报告
+├── modules/ # 核心模块
+│ ├── ui/ # UI 模块(Tab)
+│ │ ├── tab_keywords.py # 关键词 Tab
+│ │ ├── tab_autowrite.py # 自动创作 Tab
+│ │ ├── tab_optimize.py # 文章优化 Tab
+│ │ ├── tab_validation.py # 验证 Tab
+│ │ ├── tab_reports.py # 报表 Tab
+│ │ ├── tab_workflow.py # 工作流 Tab
+│ │ ├── tab_resources.py # 资源库 Tab
+│ │ ├── tab_platform_sync.py # 平台同步 Tab
+│ │ ├── tab_config_optimizer.py # 配置优化 Tab
+│ │ ├── tab_knowledge.py # 知识库 Tab
+│ │ ├── components.py # 公共组件
+│ │ ├── state.py # 状态管理
+│ │ └── theme.py # 主题样式
+│ ├── knowledge_base.py # RAG 知识库
+│ ├── ai_search_verifier.py # AI 搜索验证
+│ ├── content_uniqueness.py # 内容独特性检测
+│ ├── keyword_data_enhancer.py # 关键词数据增强
+│ ├── llm_factory.py # LLM 工厂
+│ ├── data_storage.py # 数据存储
+│ ├── content_scorer.py # 内容评分
+│ ├── eeat_enhancer.py # E-E-A-T 强化
+│ ├── schema_generator.py # Schema 生成
+│ └── ... # 其他模块
+├── platform_sync/ # 平台同步模块
+│ ├── base_publisher.py # 发布器基类
+│ ├── github_publisher.py # GitHub 发布器
+│ └── copy_manager.py # 复制管理器
+├── geo_tool.py # 主程序入口
+├── requirements.txt # 依赖声明
+├── README.md # 项目说明
+└── DOCS.md # 文档索引
+```
---
-#### 6. 数据报表高级分析
+## 📊 技术栈
-- 更详细的统计分析
-- 预测性分析
-- 竞品深度对比
-#### 7. 自动发布功能
+| 技术 | 用途 |
+| ------------------------------------ | --------- |
+| [Streamlit](https://streamlit.io/) | Web UI 框架 |
+| [LangChain](https://langchain.com/) | LLM 编排框架 |
+| [SQLite](https://sqlite.org/) | 数据存储 |
+| [Plotly](https://plotly.com/) | 数据可视化 |
+| [Pandas](https://pandas.pydata.org/) | 数据处理 |
+
+
+### 支持的 LLM
+
+
+| 提供商 | 模型 |
+| -------- | -------------------- |
+| DeepSeek | deepseek-chat |
+| OpenAI | gpt-4o-mini, gpt-4 |
+| 通义千问 | qwen-max, qwen-turbo |
+| Groq | llama3-70b-8192 |
+| Moonshot | moonshot-v1-128k |
+| 豆包 | 自定义 |
+| 文心一言 | ernie-bot-turbo |
-- 接入各平台 API
-- 自动发布生成的内容
-- 发布状态跟踪
---
-## 📊 整体架构建议
+## 📚 文档
-### 优先级排序
-1. **高优先级**(核心功能增强)
- - 收录平台扩展(豆包、文心一言等)⭐ ✅ 已完成
- - 自媒体平台扩展(微信公众号、抖音等)⭐ ✅ 已完成
- - 稿件记录 ✅ 已完成
+| 文档 | 说明 |
+| ------------------------------------------ | ---------- |
+| [DOCS.md](DOCS.md) | 完整文档索引 |
+| [快速开始指南](docs/guides/QUICK_START_GUIDE.md) | 新用户入门 |
+| [平台设置指南](docs/guides/PLATFORM_SETUP.md) | API Key 配置 |
+| [数据存储指南](docs/guides/STORAGE_GUIDE.md) | 存储方案说明 |
+| [产品规格文档](docs/PRODUCT_SPEC.md) | 产品定位与规划 |
-2. **中优先级**(功能扩展)
- - AI 数据报表(基础版)✅ 已完成
-3. **低优先级**(高级功能 / 可选功能)
- - 企业图库(对 GEO 直接贡献有限,可延后)
- - 数据报表高级分析
- - 自动发布功能
+### 功能文档
-### 技术架构建议
-
-1. **数据持久化** ✅ 已完成
- - 使用 SQLite(轻量)
- - 存储:关键词、内容、优化记录、验证结果、图片元数据
-
-2. **模块化重构**
- - 将功能拆分为独立模块
- - 便于维护和扩展
-
-3. **配置管理**
- - 使用配置文件管理平台模板、词库等
- - 支持用户自定义
-
-4. **性能优化**
- - 批量操作使用并发/异步
- - 缓存常用数据
+- [品牌知识库(RAG)](docs/features/KNOWLEDGE_BASE_FEATURE.md)
+- [AI 搜索验证](docs/features/AI_SEARCH_VERIFIER_FEATURE.md)
+- [内容独特性检测](docs/features/CONTENT_UNIQUENESS_FEATURE.md)
+- [关键词数据增强](docs/features/KEYWORD_DATA_ENHANCER_FEATURE.md)
+- [E-E-A-T 评估与强化](docs/features/EEAT_FEATURE.md)
+- [JSON-LD Schema 生成](docs/features/JSON_LD_SCHEMA_FEATURE.md)
---
-## 📝 实现记录
+## 🤝 贡献
-### 已完成
-- [x] SQLite 数据持久化(2024)
-- [x] 历史记录查看功能
-- [x] 托词工具(AI 蒸馏词)- 2024
-- [x] 收录平台扩展(豆包、文心一言)- 2024
-- [x] 自媒体平台扩展(微信公众号、抖音等)- 2024
-- [x] AI 数据报表 - 2024
+欢迎贡献代码、报告问题或提出建议!
-### 待开始(按优先级)
-- [ ] 企业图库 - 低优先级(可延后)
+### 贡献方式
+
+1. Fork 本仓库
+2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
+3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
+4. 推送到分支 (`git push origin feature/AmazingFeature`)
+5. 创建 Pull Request
+
+### 开发规范
+
+- 遵循 PEP 8 代码规范
+- 添加必要的注释和文档
+- 确保所有测试通过
---
-## 🔗 相关文档
+## 📄 许可证
-📚 **完整文档索引**:查看 [DOCS.md](DOCS.md) 获取所有文档的快速导航
+本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件
+
+---
+
+## 🙏 致谢
+
+- [Streamlit](https://streamlit.io/) - 优秀的 Web UI 框架
+- [LangChain](https://langchain.com/) - 强大的 LLM 编排框架
+- 所有贡献者和用户
+
+---
+
+
+
+**如果这个项目对您有帮助,请给一个 ⭐ Star 支持一下!**
-**主要文档**:
-- `docs/implementation/INTEGRATION_NOTES.md` - SQLite 持久化集成说明
-- `docs/guides/STORAGE_GUIDE.md` - 数据持久化方案对比
-- `docs/guides/PLATFORM_SETUP.md` - 平台扩展安装说明(豆包、文心一言)
-- `modules/data_storage.py` - 数据存储模块实现
\ No newline at end of file
diff --git a/docs/DIRECTORY_STRUCTURE_OPTIMIZATION.md b/docs/DIRECTORY_STRUCTURE_OPTIMIZATION.md
deleted file mode 100644
index c7cd0fe..0000000
--- a/docs/DIRECTORY_STRUCTURE_OPTIMIZATION.md
+++ /dev/null
@@ -1,305 +0,0 @@
-# 项目目录结构优化方案
-
-## 📋 当前问题分析
-
-### 当前目录结构问题
-1. **根目录文件过多**:50+ 个文件混在一起
- - 15个功能模块(.py文件)
- - 15个功能文档(*_FEATURE.md)
- - 7个分析报告(*_ANALYSIS.md, *_REPORT.md)
- - 5个指南文档(*_GUIDE.md)
- - 7个实现文档
- - 主程序(geo_tool.py)
- - 配置文件
-
-2. **文件引用关系**
- - `modules/geo_tool.py` 从根目录导入所有模块
- - 文档之间相互引用,路径混乱
- - 没有清晰的模块化结构
-
-## 🎯 优化目标
-
-1. **清晰的目录结构**:按功能分类组织文件
-2. **易于维护**:相关文件集中管理
-3. **便于扩展**:新功能易于添加
-4. **保持兼容**:更新所有引用路径
-
-## 📁 优化后的目录结构
-
-```
-geo_tool/
-├── README.md # 项目主文档(保留在根目录)
-├── requirements.txt # 依赖文件(保留在根目录)
-├── .gitignore # Git配置(保留在根目录)
-├── .streamlit/ # Streamlit配置(保留)
-│ └── config.toml
-├── geo_tool.py # 主程序(保留在根目录)
-│
-├── modules/ # 功能模块目录
-│ ├── __init__.py # 包初始化文件
-│ ├── modules/data_storage.py
-│ ├── modules/keyword_tool.py
-│ ├── modules/content_scorer.py
-│ ├── modules/eeat_enhancer.py
-│ ├── modules/semantic_expander.py
-│ ├── modules/fact_density_enhancer.py
-│ ├── modules/schema_generator.py
-│ ├── modules/topic_cluster.py
-│ ├── modules/multimodal_prompt.py
-│ ├── modules/roi_analyzer.py
-│ ├── modules/workflow_automation.py
-│ ├── modules/keyword_mining.py
-│ ├── modules/optimization_techniques.py
-│ ├── modules/content_metrics.py
-│ ├── modules/technical_config_generator.py
-│ ├── modules/negative_monitor.py
-│ ├── modules/resource_recommender.py
-│ ├── modules/config_optimizer.py
-│ └── storage_example.py
-│
-├── platform_sync/ # 平台同步模块(已存在)
-│ ├── __init__.py
-│ ├── base_publisher.py
-│ ├── github_publisher.py
-│ └── copy_manager.py
-│
-└── docs/ # 文档目录
- ├── features/ # 功能文档
- │ ├── docs/features/CONFIG_OPTIMIZER_FEATURE.md
- │ ├── docs/features/CONTENT_METRICS_FEATURE.md
- │ ├── docs/features/CONTENT_SCORER_FEATURE.md
- │ ├── docs/features/EEAT_FEATURE.md
- │ ├── docs/features/FACT_DENSITY_FEATURE.md
- │ ├── docs/features/JSON_LD_SCHEMA_FEATURE.md
- │ ├── docs/features/KEYWORD_MINING_FEATURE.md
- │ ├── docs/features/MULTIMODAL_FEATURE.md
- │ ├── docs/features/NEGATIVE_MONITOR_FEATURE.md
- │ ├── docs/features/OPTIMIZATION_TECHNIQUES_FEATURE.md
- │ ├── docs/features/RESOURCE_RECOMMENDER_FEATURE.md
- │ ├── docs/features/ROI_ANALYSIS_FEATURE.md
- │ ├── docs/features/SEMANTIC_EXPANSION_FEATURE.md
- │ ├── docs/features/TECHNICAL_CONFIG_FEATURE.md
- │ ├── docs/features/TOPIC_CLUSTER_FEATURE.md
- │ └── docs/features/WORKFLOW_AUTOMATION_FEATURE.md
- │
- ├── analysis/ # 分析报告
- │ ├── ANALYSIS_ACCURACY_REPORT.md
- │ ├── CODE_DOCUMENTATION_ANALYSIS.md
- │ ├── DOCUMENTATION_REVERSE_VERIFICATION.md
- │ ├── FEATURE_ANALYSIS.md
- │ ├── FEATURE_PRIORITY_ANALYSIS.md
- │ ├── FUNCTION_VERIFICATION_REPORT.md
- │ └── GEO_COMPLIANCE_ANALYSIS.md
- │
- ├── guides/ # 指南文档
- │ ├── QUICK_START_GUIDE.md
- │ ├── STORAGE_GUIDE.md
- │ ├── PLATFORM_SETUP.md
- │ ├── LAYOUT_UPGRADE_GUIDE.md
- │ └── DECISION_GUIDE.md
- │
- └── implementation/ # 实现文档
- ├── IMPLEMENTATION_SUMMARY.md
- ├── PLATFORM_SYNC_ANALYSIS.md
- ├── PLATFORM_SYNC_IMPLEMENTATION.md
- ├── PLATFORM_SYNC_TEST.md
- ├── INTEGRATION_NOTES.md
- ├── FEATURES_COMPLETE_LIST.md
- └── ADVANCED_FEATURES.md
-```
-
-## 🔧 实施步骤
-
-### 步骤1:创建目录结构
-
-```powershell
-# 创建目录
-New-Item -ItemType Directory -Force -Path modules
-New-Item -ItemType Directory -Force -Path docs\features
-New-Item -ItemType Directory -Force -Path docs\analysis
-New-Item -ItemType Directory -Force -Path docs\guides
-New-Item -ItemType Directory -Force -Path docs\implementation
-```
-
-### 步骤2:移动文件
-
-**注意**:如果文件被IDE或其他程序占用,需要先关闭这些程序。
-
-#### 2.1 移动功能模块到 modules/
-```powershell
-# 需要移动的文件列表
-$modules = @(
- "modules/data_storage.py",
- "modules/keyword_tool.py",
- "modules/content_scorer.py",
- "modules/eeat_enhancer.py",
- "modules/semantic_expander.py",
- "modules/fact_density_enhancer.py",
- "modules/schema_generator.py",
- "modules/topic_cluster.py",
- "modules/multimodal_prompt.py",
- "modules/roi_analyzer.py",
- "modules/workflow_automation.py",
- "modules/keyword_mining.py",
- "modules/optimization_techniques.py",
- "modules/content_metrics.py",
- "modules/technical_config_generator.py",
- "modules/negative_monitor.py",
- "modules/resource_recommender.py",
- "modules/config_optimizer.py",
- "storage_example.py"
-)
-
-foreach ($file in $modules) {
- if (Test-Path $file) {
- Move-Item $file -Destination "modules\" -Force
- Write-Host "Moved: $file"
- }
-}
-```
-
-#### 2.2 移动功能文档到 docs/features/
-```powershell
-Get-ChildItem -Filter "*_FEATURE.md" | ForEach-Object {
- Move-Item $_.FullName -Destination "docs\features\" -Force
- Write-Host "Moved: $($_.Name)"
-}
-```
-
-#### 2.3 移动分析报告到 docs/analysis/
-```powershell
-$analysis = @(
- "ANALYSIS_ACCURACY_REPORT.md",
- "CODE_DOCUMENTATION_ANALYSIS.md",
- "DOCUMENTATION_REVERSE_VERIFICATION.md",
- "FEATURE_ANALYSIS.md",
- "FEATURE_PRIORITY_ANALYSIS.md",
- "FUNCTION_VERIFICATION_REPORT.md",
- "GEO_COMPLIANCE_ANALYSIS.md"
-)
-
-foreach ($file in $analysis) {
- if (Test-Path $file) {
- Move-Item $file -Destination "docs\analysis\" -Force
- Write-Host "Moved: $file"
- }
-}
-```
-
-#### 2.4 移动指南文档到 docs/guides/
-```powershell
-$guides = @(
- "QUICK_START_GUIDE.md",
- "STORAGE_GUIDE.md",
- "PLATFORM_SETUP.md",
- "LAYOUT_UPGRADE_GUIDE.md",
- "DECISION_GUIDE.md"
-)
-
-foreach ($file in $guides) {
- if (Test-Path $file) {
- Move-Item $file -Destination "docs\guides\" -Force
- Write-Host "Moved: $file"
- }
-}
-```
-
-#### 2.5 移动实现文档到 docs/implementation/
-```powershell
-$implementation = @(
- "IMPLEMENTATION_SUMMARY.md",
- "PLATFORM_SYNC_ANALYSIS.md",
- "PLATFORM_SYNC_IMPLEMENTATION.md",
- "PLATFORM_SYNC_TEST.md",
- "INTEGRATION_NOTES.md",
- "FEATURES_COMPLETE_LIST.md",
- "ADVANCED_FEATURES.md"
-)
-
-foreach ($file in $implementation) {
- if (Test-Path $file) {
- Move-Item $file -Destination "docs\implementation\" -Force
- Write-Host "Moved: $file"
- }
-}
-```
-
-### 步骤3:创建 modules/__init__.py
-
-创建 `modules/__init__.py` 文件,方便导入:
-
-```python
-"""
-GEO Tool 功能模块包
-"""
-```
-
-### 步骤4:更新导入路径
-
-运行 `modules/update_imports.py` 脚本自动更新所有文件中的导入路径。
-
-### 步骤5:更新文档引用
-
-运行 `modules/update_doc_references.py` 脚本自动更新所有文档中的路径引用。
-
-## 📝 需要更新的文件
-
-### Python 文件导入更新
-
-**geo_tool.py** 中的导入需要从:
-```python
-from data_storage import DataStorage
-from keyword_tool import KeywordTool
-# ...
-```
-
-更新为:
-```python
-from modules.data_storage import DataStorage
-from modules.keyword_tool import KeywordTool
-# ...
-```
-
-**storage_example.py** 中的导入需要从:
-```python
-from data_storage import DataStorage
-```
-
-更新为:
-```python
-from modules.data_storage import DataStorage
-```
-
-### 文档路径引用更新
-
-所有 `.md` 文件中的路径引用需要更新:
-
-- `xxx_FEATURE.md` → `docs/features/xxx_FEATURE.md`
-- `xxx_ANALYSIS.md` → `docs/analysis/xxx_ANALYSIS.md`
-- `xxx_GUIDE.md` → `docs/guides/xxx_GUIDE.md`
-- `xxx.md` (implementation) → `docs/implementation/xxx.md`
-
-## ✅ 验证清单
-
-- [ ] 所有模块文件已移动到 `modules/`
-- [ ] 所有文档文件已分类移动到 `docs/` 子目录
-- [ ] `modules/__init__.py` 已创建
-- [ ] `modules/geo_tool.py` 中的导入路径已更新
-- [ ] 所有文档中的路径引用已更新
-- [ ] 运行 `python geo_tool.py` 测试导入是否正常
-- [ ] 检查所有文档链接是否正常
-
-## 🚀 优化后的优势
-
-1. **清晰的目录结构**:文件按功能分类,易于查找
-2. **模块化组织**:功能模块集中管理
-3. **文档分类清晰**:功能文档、分析报告、指南文档分开管理
-4. **便于维护**:新功能添加时,只需在对应目录添加文件
-5. **专业规范**:符合Python项目最佳实践
-
-## 📌 注意事项
-
-1. **文件占用**:移动文件前,确保文件未被IDE或其他程序打开
-2. **路径更新**:移动文件后,必须更新所有引用路径
-3. **测试验证**:完成移动后,务必测试程序是否正常运行
-4. **Git提交**:建议分步骤提交,便于回滚
diff --git a/docs/PRODUCT_SPEC.md b/docs/PRODUCT_SPEC.md
new file mode 100644
index 0000000..8c919db
--- /dev/null
+++ b/docs/PRODUCT_SPEC.md
@@ -0,0 +1,172 @@
+# GEO 智能内容优化平台 - 产品规格文档
+
+## 📦 产品名称 & 一句话描述
+
+**GEO 智能内容优化平台** — AI 自动生成 20+ 平台差异化内容并跟踪 AI 曝光效果,让品牌在 ChatGPT 等 AI 助手中获得优先推荐。
+
+---
+
+## 🎯 目标客户/使用场景
+
+**谁用**:SaaS 公司、AI 工具厂商、B2B 企业的内容营销/增长团队
+
+**解决什么痛点**:
+
+- AI 不认识品牌 → 系统化投放 20+ 平台,让 AI 学习
+- 内容效率低(人工 2-4 小时/篇)→ AI 生成 5 分钟完成
+- 多平台适配难 → 一键生成知乎、小红书、CSDN 等差异化内容
+- 效果无法追踪 → 自动验证 AI 提及率 + 数据报表
+
+---
+
+## 💰 核心价值/商业影响
+
+**预期收益**:
+
+- **增收**:AI 引荐转化,预计年增收 600 万(假设月均 200 次咨询,5% 转化率,客单价 5000 元)
+- **降本**:内容生产效率提升 12-24 倍,年节省人力成本 9.4 万
+- **提效**:从 2-4 小时/篇降至 5-10 分钟/篇
+
+**量化目标**:
+
+
+| 指标 | 3 个月 | 6 个月 | 12 个月 |
+| ------ | ---- | ---- | ----- |
+| AI 提及率 | 30% | 60% | 85% |
+| 内容效率 | 10x | 15x | 20x |
+| ROI | 3:1 | 5:1 | 8:1 |
+
+
+---
+
+## 🚀 独特卖点/差异化
+
+**竞品对比**:
+
+
+| 维度 | 传统 SEO 工具 | AI 写作工具 | **我们** |
+| ---- | --------- | --------- | ------------- |
+| 核心目标 | Google 排名 | 加速写作 | **AI 对话优化** |
+| 平台适配 | 单一 SEO | 通用模板 | **20+ 平台差异化** |
+| 效果追踪 | ❌ | ❌ | **✅ AI 提及率** |
+| 成本 | $99-499/月 | $49-125/月 | **$39-79/月** |
+
+
+**我们靠什么胜出**:
+
+1. **精准定位 AI 时代新流量入口**(ChatGPT、Perplexity 等,竞品忽视)
+2. **多平台差异化内容**(20 个平台专属 Prompt,自动适配风格)
+3. **效果可量化**(AI 提及率追踪 + 平台贡献度分析)
+4. **全流程闭环**(关键词挖掘 → 内容生成 → 发布 → 追踪,一站式)
+
+---
+
+## 🤖 AI 技术亮点
+
+**技术架构**:用户 UI(Streamlit)→ 业务逻辑层 → AI 模型层(7 个模型)→ 数据存储(SQLite)
+
+**核心技术**:
+
+1. **多模型智能调度**:DeepSeek、OpenAI GPT-4、通义千问等 7 个模型,场景化选择 + 成本优化
+2. **RAG + 自定义 Prompt**:20 个平台专属 Prompt 模板 + 企业知识库增强
+3. **E-E-A-T 质量评分**:经验、专业性、权威性、可信度四维评分
+4. **事实密度增强**:自动检测空洞内容 + 智能补充数据支撑
+5. **语义扩展**:关键词变体生成(同义词、问句、场景) + 话题集群
+6. **AI 提及率验证**:自动向 7 个 AI 平台提问 + 数据可视化
+
+---
+
+## 💼 资源需求估算
+
+**人力需求**(约 4.8 人全职等效):
+
+- 前端 + 后端工程师:2 人(全职)
+- 算法工程师:0.5 人(Prompt 工程、模型调优)
+- 产品 + 设计 + 测试:1.3 人
+- 运营/市场:1 人
+
+**预算需求**(年):
+
+
+| 类型 | 成本 |
+| --------------------------------- | --------- |
+| 人力 | 136 万 |
+| 技术(AI 模型 30 万 + 云服务 7 万 + 其他 5 万) | 42 万 |
+| 市场推广 + 其他 | 15 万 |
+| **总计** | **193 万** |
+
+
+**MVP 阶段可压缩至 50-80 万**(小团队 + 低成本模型)
+
+**依赖资源**:
+
+- 内部:企业知识库权限、客户案例、销售配合
+- 外部:平台 API 接入(微信、知乎、B站等)、AI 平台账号
+
+---
+
+## 🎯 优先级排序理由(RICE 评分)
+
+
+| 功能 | RICE 得分 | 优先级 | 状态 | 理由 |
+| ------- | ------- | ----- | --- | ---------- |
+| 核心内容生成 | 1000 | 🔥 P0 | ✅ | 产品基础 |
+| 多平台适配 | 1200 | 🔥 P0 | ✅ | 核心竞争力 |
+| AI 验证追踪 | 400 | 🔥 P0 | ✅ | 效果可量化 |
+| 质量评分系统 | 240 | 🟡 P1 | ✅ | 减少人工审核 |
+| 竞品监控 | 140 | 🟡 P1 | ✅ | 市场洞察 |
+| 平台自动发布 | 96 | 🟢 P2 | ⏳ | 一键复制已够用 |
+| 企业图库 | 25 | 🟢 P3 | ❌ | 对 GEO 贡献有限 |
+
+
+**RICE 公式**:`(影响范围 × 影响力度 × 信心) / 工作量`
+
+---
+
+## 📊 成功指标/KPI
+
+
+| 时间 | 核心指标 | 目标值 |
+| --------- | ------------------ | --------------- |
+| **3 个月** | 内测用户 NPS 评分 AI 提及率 | 50 人 40+ 30% |
+| **6 个月** | 付费用户 ARR 续费率 | 100 人 50 万 70% |
+| **12 个月** | 付费用户 ARR AI 提及率 | 500 人 250 万 85% |
+
+
+**关键里程碑**:
+
+- 3 个月:第一个付费用户
+- 6 个月:PMF 验证 + 标杆案例
+- 12 个月:行业前 3 + 发布 GEO 白皮书
+
+---
+
+## 🎁 可选加分项
+
+**潜在扩展**:
+
+- 2.0 版本:多品牌管理 + 团队协作 + 企业版(SSO、私有部署)
+- 3.0 版本:垂直行业包(SaaS、外贸、金融专属术语库 + 合规检查)
+- 4.0 版本:AI Agent 自主运营(自动发布、监控、优化)
+
+**能力复用**:多模型调度引擎、Prompt 模板库、质量评分系统可复用到 AI 写作助手、客服系统、营销工具等产品
+
+---
+
+## 📈 商业模式
+
+**定价策略**:
+
+
+| 套餐 | 价格 | 核心限制 | 目标客户 |
+| --- | ------ | ----------- | ----- |
+| 免费版 | ¥0/月 | 10 篇/月 | 试用用户 |
+| 专业版 | ¥399/月 | 100 篇/月 | 小型企业 |
+| 企业版 | ¥999/月 | 无限生成 + 团队协作 | 中大型企业 |
+
+
+**收入预测**:6 个月 ARR 90 万,12 个月 ARR 240 万
+
+---
+
+**文档版本**:v1.0 | **创建日期**:2026-02-06
\ No newline at end of file
diff --git a/docs/analysis/ANALYSIS_ACCURACY_REPORT.md b/docs/analysis/ANALYSIS_ACCURACY_REPORT.md
index 8700f45..e9c0d3e 100644
--- a/docs/analysis/ANALYSIS_ACCURACY_REPORT.md
+++ b/docs/analysis/ANALYSIS_ACCURACY_REPORT.md
@@ -9,18 +9,21 @@
## ✅ 分析准确的部分
### 1. 工具整体定位和架构分析
+
- ✅ **准确**:单页面 Streamlit 应用
- ✅ **准确**:基于 LangChain + LLM 的闭环设计
- ✅ **准确**:核心逻辑"配置 → 关键词生成 → 内容创作 → 验证/对比"
- ✅ **准确**:模块化设计,代码结构清晰
### 2. 当前功能总结
+
- ✅ **准确**:侧边栏全局配置
- ✅ **准确**:关键词蒸馏、内容创作、文章优化、多模型验证
- ✅ **准确**:GitHub 模板、多模型支持
- ✅ **准确**:闭环完整、用户友好、可扩展
### 3. 局限分析
+
- ✅ **准确**:验证是模拟 LLM 输出,非真实 RAG/搜索引擎收录
- ✅ **准确**:缺少高级 GEO 指标(但实际已部分实现,见下文)
- ⚠️ **部分准确**:无持久化 → **实际已有 SQLite 持久化**(`modules/data_storage.py`)
@@ -33,6 +36,7 @@
### 文档说"缺失",但实际已实现的功能
#### 1. E-E-A-T 扫描 + 强化 ❌→✅
+
- **文档说**:缺失,需要添加
- **实际情况**:✅ **已完全实现**
- 模块:`modules/eeat_enhancer.py`
@@ -41,6 +45,7 @@
- **结论**:文档分析错误,功能已完整实现
#### 2. 话题集群生成(Semantic Topic Clusters)❌→✅
+
- **文档说**:缺失,高优先级
- **实际情况**:✅ **已完全实现**
- 模块:`modules/topic_cluster.py`
@@ -49,6 +54,7 @@
- **结论**:文档分析错误,功能已完整实现
#### 3. JSON-LD Schema.org 生成 ❌→✅
+
- **文档说**:缺失,高优先级
- **实际情况**:✅ **已完全实现**
- 模块:`modules/schema_generator.py`
@@ -58,6 +64,7 @@
- **结论**:文档分析错误,功能已完整实现
#### 4. GEO 指标仪表盘扩展 ❌→✅
+
- **文档说**:缺失,需要添加 Citation Share、Trust Density 等
- **实际情况**:✅ **已完全实现**
- 模块:`modules/content_metrics.py`
@@ -72,6 +79,7 @@
- **结论**:文档分析错误,功能已完整实现
#### 5. 负面/风险监控 ❌→✅
+
- **文档说**:缺失,需要添加
- **实际情况**:✅ **已完全实现**
- 模块:`modules/negative_monitor.py`
@@ -80,6 +88,7 @@
- **结论**:文档分析错误,功能已完整实现
#### 6. 多模态提示生成 ❌→✅
+
- **文档说**:缺失,需要添加
- **实际情况**:✅ **已完全实现**
- 模块:`modules/multimodal_prompt.py`
@@ -88,6 +97,7 @@
- **结论**:文档分析错误,功能已完整实现
#### 7. 优化技巧选择器 ❌→✅
+
- **文档说**:需要优化,添加"技巧选择器"
- **实际情况**:✅ **已完全实现**
- 模块:`modules/optimization_techniques.py`
@@ -96,6 +106,7 @@
- **结论**:文档分析错误,功能已完整实现
#### 8. 资源推荐 ❌→✅
+
- **文档说**:需要添加"资源推荐" expander
- **实际情况**:✅ **已完全实现**
- 模块:`modules/resource_recommender.py`
@@ -104,6 +115,7 @@
- **结论**:文档分析错误,功能已完整实现
#### 9. 数据持久化 ❌→✅
+
- **文档说**:无持久化,session_state 刷新丢失
- **实际情况**:✅ **已完全实现**
- 模块:`modules/data_storage.py`(SQLite)
@@ -114,6 +126,7 @@
- **结论**:文档分析错误,功能已完整实现
#### 10. 关键词挖掘 ❌→✅
+
- **文档说**:未提及
- **实际情况**:✅ **已完全实现**
- 模块:`modules/keyword_mining.py`
@@ -122,6 +135,7 @@
- **结论**:文档遗漏,功能已完整实现
#### 11. 工作流自动化 ❌→✅
+
- **文档说**:未提及
- **实际情况**:✅ **已完全实现**
- 模块:`modules/workflow_automation.py`
@@ -130,6 +144,7 @@
- **结论**:文档遗漏,功能已完整实现
#### 12. 平台同步 ❌→✅
+
- **文档说**:无自动化发布
- **实际情况**:✅ **已部分实现**
- 模块:`platform_sync/`(GitHub发布器、一键复制)
@@ -147,26 +162,32 @@
### 1. 功能优化建议(部分准确)
#### 关键词蒸馏优化
+
- **文档说**:可加"意图标签"输出
- **实际情况**:✅ **已实现**(语义扩展功能包含意图分析)
#### 内容创作优化
+
- **文档说**:可加"技巧选择器"
- **实际情况**:✅ **已实现**(`modules/optimization_techniques.py`)
#### 文章优化优化
+
- **文档说**:可加"长度控制滑块"和"平台强度调节"
- **实际情况**:⚠️ **部分实现**(有平台选择,但长度控制需要确认)
#### 验证模块优化
+
- **文档说**:可加"真实 RAG 模拟"
- **实际情况**:❌ **未实现**(当前确实是模拟 LLM 输出)
#### 界面/体验优化
+
- **文档说**:可加进度条、主题切换
- **实际情况**:⚠️ **部分实现**(已有 spinner,但主题切换未实现)
#### 整体优化
+
- **文档说**:加"保存/加载配置"
- **实际情况**:✅ **已实现**(SQLite 持久化,但配置保存需要确认)
@@ -205,30 +226,30 @@
### 真正缺失的功能(基于实际代码)
#### 高优先级
-1. **真实 RAG 模拟**(验证模块)
- - 当前:模拟 LLM 输出
- - 建议:集成 web_search 工具,补充外部来源
- - 实现难度:中-高
+1. **真实 RAG 模拟**(验证模块)
+ - 当前:模拟 LLM 输出
+ - 建议:集成 web_search 工具,补充外部来源
+ - 实现难度:中-高
2. **更多平台 API 发布**
- - 当前:仅 GitHub API,其他平台一键复制
- - 建议:微信公众号、B站、知乎等 API 发布
- - 实现难度:高(需要各平台 API)
+ - 当前:仅 GitHub API,其他平台一键复制
+ - 建议:微信公众号、B站、知乎等 API 发布
+ - 实现难度:高(需要各平台 API)
#### 中优先级
+
1. **主题切换**(light/dark)
- - 当前:仅 light 主题
- - 实现难度:低
-
+ - 当前:仅 light 主题
+ - 实现难度:低
2. **配置导入/导出**
- - 当前:SQLite 持久化,但无配置文件导入/导出
- - 实现难度:低
-
+ - 当前:SQLite 持久化,但无配置文件导入/导出
+ - 实现难度:低
3. **批量发布队列**
- - 当前:单篇发布
- - 实现难度:中
+ - 当前:单篇发布
+ - 实现难度:中
#### 低优先级
+
1. **长度控制滑块**(文章优化)
2. **平台强度调节**(文章优化)
3. **进度条优化**(已有 spinner,可增强)
@@ -240,16 +261,19 @@
### 文档准确性评分:⭐⭐☆☆☆(40%)
**主要问题**:
+
1. ❌ **严重低估了功能完整度**:文档说"缺失"的6个核心功能,实际已全部实现
2. ❌ **遗漏了多个重要功能**:关键词挖掘、工作流自动化、平台同步等
3. ⚠️ **部分分析准确**:工具定位、架构分析、局限分析(部分)准确
**修正建议**:
+
1. ✅ 工具功能完整度远超文档描述
2. ✅ 核心 GEO 功能(E-E-A-T、话题集群、JSON-LD、指标仪表盘)已全部实现
3. ✅ 真正缺失的是:真实 RAG 模拟、更多平台 API 发布、主题切换等辅助功能
**实际评估**:
+
- **功能完整度**:约 **90%**(核心功能已完整)
- **与商业工具对比**:核心能力已接近 Conductor、Profound 等商业工具
- **主要差距**:真实搜索引擎收录验证、更多平台 API 集成
@@ -257,4 +281,4 @@
---
**报告生成日期**:2025-01-26
-**分析基于**:实际代码审查(`modules/geo_tool.py` + 所有模块文件)
+**分析基于**:实际代码审查(`modules/geo_tool.py` + 所有模块文件)
\ No newline at end of file
diff --git a/docs/analysis/CODE_DOCUMENTATION_ANALYSIS.md b/docs/analysis/CODE_DOCUMENTATION_ANALYSIS.md
deleted file mode 100644
index 8badd9e..0000000
--- a/docs/analysis/CODE_DOCUMENTATION_ANALYSIS.md
+++ /dev/null
@@ -1,529 +0,0 @@
-# 代码与文档对比分析报告
-
-## 📋 分析说明
-
-本报告系统对比了代码实现与文档描述,找出:
-1. 文档中描述但代码中缺失的功能
-2. 代码中实现但文档中未记录的功能
-3. 代码和文档不一致的地方
-4. 需要补充或更新的内容
-
-**分析日期**:2025-01-27
-**分析范围**:所有代码文件(.py)和文档文件(.md)
-
----
-
-## 📊 总体概览
-
-### 代码文件统计
-- **主文件**:`modules/geo_tool.py` (5953行)
-- **功能模块**:23个独立模块
-- **平台同步模块**:4个文件(platform_sync/)
-- **总计**:27个Python文件
-
-### 文档文件统计
-- **功能文档**:14个 *FEATURE.md 文件
-- **分析报告**:4个分析文档
-- **指南文档**:3个指南文档
-- **总计**:21个文档文件
-
-### 功能完整度评估
-- **核心功能**:✅ 95% 完整
-- **高级功能**:✅ 90% 完整
-- **文档覆盖**:✅ 85% 覆盖(主要功能都有文档,基础功能无需独立文档)
-
----
-
-## ✅ 代码已实现但文档缺失的功能
-
-### 1. 技术配置生成器(TechnicalConfigGenerator)
-
-**代码位置**:
-- `modules/technical_config_generator.py`
-- `modules/geo_tool.py` Tab2(自动创作)
-
-**功能描述**:
-- 生成 robots.txt
-- 生成 sitemap.xml
-- 生成技术SEO配置
-
-**文档状态**:✅ **已有文档** (`docs/features/docs/features/TECHNICAL_CONFIG_FEATURE.md`)
-
----
-
-### 2. ROI分析器(ROIAnalyzer)
-
-**代码位置**:
-- `modules/roi_analyzer.py`
-- `modules/geo_tool.py` Tab6(AI 数据报表)
-
-**功能描述**:
-- API成本统计
-- ROI计算
-- 成本优化建议
-
-**文档状态**:⚠️ **有文档但可能不完整**
-
-**建议**:检查 `docs/features/docs/features/ROI_ANALYSIS_FEATURE.md` 是否完整
-
----
-
-### 3. 事实密度增强器(FactDensityEnhancer)
-
-**代码位置**:
-- `modules/fact_density_enhancer.py`
-- `modules/geo_tool.py` Tab3(文章优化)
-
-**功能描述**:
-- 事实密度分析
-- 自动添加数据点
-- 提升内容可信度
-
-**文档状态**:✅ **有文档** (`docs/features/docs/features/FACT_DENSITY_FEATURE.md`)
-
----
-
-### 4. 语义扩展器(SemanticExpander)
-
-**代码位置**:
-- `modules/semantic_expander.py`
-- `modules/geo_tool.py` Tab1(关键词蒸馏)
-
-**功能描述**:
-- 关键词语义扩展
-- 长尾词生成
-- 关联词推荐
-
-**文档状态**:✅ **有文档** (`docs/features/docs/features/SEMANTIC_EXPANSION_FEATURE.md`)
-
----
-
-### 5. 内容评分器(ContentScorer)
-
-**代码位置**:
-- `modules/content_scorer.py`
-- `modules/geo_tool.py` Tab2(自动创作)
-
-**功能描述**:
-- 内容质量评分
-- 多维度评估
-- 改进建议
-
-**文档状态**:✅ **已有文档** (`docs/features/docs/features/CONTENT_SCORER_FEATURE.md`)
-
----
-
-## ⚠️ 文档中描述但代码可能缺失的功能
-
-### 1. 批量发布功能
-
-**文档位置**:
-- `docs/implementation/PLATFORM_SYNC_IMPLEMENTATION.md` 第142行
-- `docs/implementation/IMPLEMENTATION_SUMMARY.md` 第142行
-
-**文档描述**:
-- 批量发布功能
-- 发布队列管理
-- 定时发布
-
-**代码状态**:⚠️ **部分实现**
-- ✅ 有发布记录功能
-- ❌ 无批量发布UI
-- ❌ 无发布队列管理
-- ❌ 无定时发布
-
-**建议**:实现批量发布功能或更新文档说明当前状态
-
----
-
-### 2. 更多平台API发布
-
-**文档位置**:
-- `docs/implementation/IMPLEMENTATION_SUMMARY.md` 第132-139行
-- `docs/implementation/PLATFORM_SYNC_IMPLEMENTATION.md`
-
-**文档描述**:
-- 微信公众号API发布
-- B站API发布
-- 知乎API发布
-- CSDN API发布
-- 百家号API发布
-- 企鹅号API发布
-- 网易号API发布
-
-**代码状态**:❌ **未实现**
-- ✅ 有GitHub发布器
-- ✅ 有一键复制功能(12个平台)
-- ❌ 无其他API发布器
-
-**建议**:更新文档明确说明当前只支持GitHub API发布
-
----
-
-### 3. 工作流定时任务
-
-**文档位置**:
-- `docs/features/docs/features/WORKFLOW_AUTOMATION_FEATURE.md` 第179行
-
-**文档描述**:
-- 定时任务支持(使用 APScheduler)
-
-**代码状态**:❌ **未实现**
-- ✅ 有工作流执行功能
-- ❌ 无定时任务功能
-
-**建议**:更新文档说明当前不支持定时任务,或实现该功能
-
----
-
-## 📝 代码和文档不一致的地方
-
-### 1. README.md 中的待实现功能
-
-**问题**:`README.md` 中列出了一些"待实现功能",但实际代码中已经实现
-
-**具体问题**:
-
-#### 1.1 收录平台扩展
-- **README说**:待添加豆包、文心一言
-- **实际情况**:✅ 已实现(代码中已支持)
-
-**建议**:更新 README.md,将已完成功能标记为 ✅
-
-#### 1.2 自媒体平台扩展
-- **README说**:待添加微信公众号、抖音等
-- **实际情况**:✅ 已实现(代码中已支持20个平台)
-
-**建议**:更新 README.md
-
----
-
-### 2. 功能文档中的实现状态
-
-**问题**:部分功能文档描述为"需要添加",但代码中已实现
-
-**具体问题**:
-
-#### 2.1 E-E-A-T 功能
-- **FEATURE_ANALYSIS.md说**:需要添加
-- **实际情况**:✅ 已完全实现(`modules/eeat_enhancer.py`)
-
-#### 2.2 话题集群功能
-- **FEATURE_ANALYSIS.md说**:需要添加
-- **实际情况**:✅ 已完全实现(`modules/topic_cluster.py`)
-
-#### 2.3 JSON-LD Schema功能
-- **FEATURE_ANALYSIS.md说**:需要添加
-- **实际情况**:✅ 已完全实现(`modules/schema_generator.py`)
-
-**建议**:更新 `docs/analysis/FEATURE_ANALYSIS.md`,标记已实现功能
-
----
-
-### 3. 平台数量不一致
-
-**问题**:不同文档中提到的平台数量不一致
-
-**具体问题**:
-
-#### 3.1 内容生成平台
-- **README.md说**:新增8个平台
-- **IMPLEMENTATION_SUMMARY.md说**:20个平台(原有12个+新增8个)
-- **代码实际情况**:✅ **已确认20个平台**
- - 代码第2060-2081行明确列出20个平台:
- 1. 知乎(专业问答)
- 2. 小红书(生活种草)
- 3. CSDN(技术博客)
- 4. B站(视频脚本)
- 5. 头条号(资讯软文)
- 6. GitHub(README/文档)
- 7. 微信公众号(长文)
- 8. 抖音图文(短内容)
- 9. 百家号(资讯)
- 10. 网易号(资讯)
- 11. 企鹅号(资讯)
- 12. 简书(文艺)
- 13. 新浪博客(博客)
- 14. 新浪新闻(资讯)
- 15. 搜狐号(资讯)
- 16. QQ空间(社交)
- 17. 邦阅网(外贸)
- 18. 一点号(资讯)
- 19. 东方财富(财经)
- 20. 原创力文档(文档)
-
-**建议**:更新README.md,明确列出所有20个平台
-
-#### 3.2 一键复制平台
-- **IMPLEMENTATION_SUMMARY.md说**:12个一键复制平台
-- **代码实际情况**:✅ **已确认12个平台**(`modules/copy_manager.py` 第15-81行)
- - 1. 头条号(资讯软文)
- - 2. 小红书(生活种草)
- - 3. 抖音图文(短内容)
- - 4. 简书(文艺)
- - 5. QQ空间(社交)
- - 6. 新浪博客(博客)
- - 7. 新浪新闻(资讯)
- - 8. 搜狐号(资讯)
- - 9. 一点号(资讯)
- - 10. 东方财富(财经)
- - 11. 邦阅网(外贸)
- - 12. 原创力文档(文档)
-
-**建议**:在文档中明确列出所有支持的平台
-
-#### 3.3 API发布平台
-- **代码实际情况**:✅ **仅1个平台**(GitHub)
- - 代码第5760行:`api_platforms = ["GitHub"]`
-
-**建议**:更新文档,明确说明当前仅支持GitHub API发布
-
----
-
-## 🔍 需要补充的文档
-
-### 1. 缺少的功能文档
-
-#### 1.1 ContentScorer 功能文档
-- **文件**:`modules/content_scorer.py`
-- **状态**:✅ **已创建** `docs/features/docs/features/CONTENT_SCORER_FEATURE.md`
-
-#### 1.2 TechnicalConfigGenerator 功能文档
-- **文件**:`modules/technical_config_generator.py`
-- **状态**:✅ **已存在** `docs/features/docs/features/TECHNICAL_CONFIG_FEATURE.md`
-
-#### 1.3 DataStorage 使用文档
-- **文件**:`modules/data_storage.py`
-- **建议**:创建 `DATA_STORAGE_GUIDE.md` 或更新 `docs/guides/STORAGE_GUIDE.md`
-
----
-
-### 2. 需要更新的文档
-
-#### 2.1 README.md
-- **问题**:待实现功能列表过时
-- **状态**:✅ **已更新** - 标记已完成功能,更新平台列表
-
-#### 2.2 FEATURE_ANALYSIS.md
-- **问题**:功能状态不准确
-- **状态**:✅ **已更新** - 添加状态说明,标记已实现功能
-
-#### 2.3 IMPLEMENTATION_SUMMARY.md
-- **问题**:平台数量描述不清晰
-- **状态**:✅ **已更新** - 明确列出所有支持的平台
-
----
-
-### 3. 需要创建的文档
-
-#### 3.1 完整功能列表文档
-- **状态**:✅ **已创建** `docs/implementation/FEATURES_COMPLETE_LIST.md`
-- **内容**:列出所有43个已实现功能,每个功能的简要说明和位置
-
-#### 3.2 模块架构文档
-- **建议**:创建 `ARCHITECTURE.md`
-- **内容**:代码架构、模块关系、数据流
-
-#### 3.3 API文档
-- **建议**:创建 `API_DOCUMENTATION.md`
-- **内容**:各模块的API接口说明(如果适用)
-
----
-
-## 📋 代码中需要确认的功能
-
-### 1. 平台支持情况
-
-**状态**:✅ **已确认**
-- ✅ 内容生成平台:20个(已确认)
-- ✅ 一键复制平台:12个(已确认)
-- ✅ API发布平台:1个(GitHub,已确认)
-- ✅ 验证平台:7个(DeepSeek、OpenAI、通义千问、Groq、Moonshot、豆包、文心一言)
-
----
-
-### 2. 数据库表结构
-
-**需要确认**:
-- [ ] `modules/data_storage.py` 中定义的所有表
-- [ ] 表结构是否与文档描述一致
-- [ ] 是否有未文档化的表
-
-**建议**:检查 `modules/data_storage.py` 的 `_init_sqlite` 方法
-
----
-
-### 3. Tab功能完整性
-
-**需要确认**:
-- [ ] Tab1-Tab9 的所有功能是否都有文档
-- [ ] 是否有隐藏功能未文档化
-- [ ] UI功能是否与文档描述一致
-
-**建议**:逐一检查每个Tab的实现
-
----
-
-## 🎯 优化建议
-
-### 1. 文档同步机制
-
-**建议**:
-1. 每次代码更新时,同步更新相关文档
-2. 建立文档检查清单
-3. 定期进行代码-文档对比检查
-
----
-
-### 2. 文档结构优化
-
-**建议**:
-1. 统一文档格式和命名规范
-2. 建立文档索引(如 `DOCUMENTATION_INDEX.md`)
-3. 添加文档更新日期和版本号
-
----
-
-### 3. 代码注释完善
-
-**建议**:
-1. 为每个模块添加详细的模块级文档字符串
-2. 为关键函数添加详细的函数文档字符串
-3. 添加类型提示(Type Hints)
-
----
-
-### 4. 功能测试文档
-
-**建议**:
-1. 为每个功能创建测试用例文档
-2. 添加功能演示截图或视频链接
-3. 添加常见问题解答(FAQ)
-
----
-
-## 📊 优先级建议
-
-### 🔥 高优先级(立即处理)
-
-1. **更新 README.md**
- - 标记已完成功能
- - 更新功能列表
- - 添加当前功能概览
-
-2. **创建缺失的功能文档**
- - `docs/features/docs/features/CONTENT_SCORER_FEATURE.md`
- - `docs/features/docs/features/TECHNICAL_CONFIG_FEATURE.md`(如不存在)
-
-3. **统一平台数量描述**
- - 明确列出所有支持平台
- - 统一各文档中的平台数量
-
----
-
-### 🟡 中优先级(近期处理)
-
-1. **更新 FEATURE_ANALYSIS.md**
- - 标记已实现功能
- - 更新功能状态
-
-2. **完善 IMPLEMENTATION_SUMMARY.md**
- - 明确平台列表
- - 更新实现状态
-
-3. **创建功能列表文档**
- - `docs/implementation/FEATURES_COMPLETE_LIST.md`
-
----
-
-### 🟢 低优先级(长期优化)
-
-1. **创建架构文档**
- - `ARCHITECTURE.md`
-
-2. **完善代码注释**
- - 添加模块级文档字符串
- - 添加类型提示
-
-3. **建立文档同步机制**
- - 文档检查清单
- - 定期对比检查
-
----
-
-## 📝 总结
-
-### 主要发现
-
-1. **代码功能完整度很高**(约90-95%)
- - 核心功能已全部实现
- - 高级功能大部分已实现
-
-2. **文档覆盖度中等**(约70%)
- - 大部分功能有文档
- - 部分功能缺少详细文档
- - 部分文档状态过时
-
-3. **主要问题**
- - README.md 功能状态过时
- - 部分功能文档缺失
- - 平台数量描述不一致
- - 部分文档描述与实际不符
-
-### 建议行动
-
-1. **立即行动**:
- - 更新 README.md
- - 创建缺失的功能文档
- - 统一平台数量描述
-
-2. **近期行动**:
- - 更新过时的文档
- - 创建功能列表文档
- - 完善文档结构
-
-3. **长期优化**:
- - 建立文档同步机制
- - 完善代码注释
- - 创建架构文档
-
----
-
-**报告生成日期**:2025-01-27
-**最后更新**:2025-01-27(已完成高优先级任务)
-**下次检查建议**:代码更新后立即进行
-
----
-
-## ✅ 已完成的优化任务
-
-### 高优先级任务(已完成)
-
-1. ✅ **更新 README.md**
- - 标记已完成功能
- - 更新功能列表
- - 添加当前功能概览
- - 明确列出所有20个内容生成平台
-
-2. ✅ **创建缺失的功能文档**
- - 创建 `docs/features/docs/features/CONTENT_SCORER_FEATURE.md`
- - 确认 `docs/features/docs/features/TECHNICAL_CONFIG_FEATURE.md` 已存在
-
-3. ✅ **统一平台数量描述**
- - 更新 `README.md` 明确列出所有平台
- - 更新 `docs/implementation/IMPLEMENTATION_SUMMARY.md` 明确平台列表
- - 更新 `docs/analysis/FEATURE_ANALYSIS.md` 标记已实现功能
-
-### 中优先级任务(已完成)
-
-1. ✅ **更新 FEATURE_ANALYSIS.md**
- - 添加状态说明
- - 标记已实现功能
-
-2. ✅ **完善 IMPLEMENTATION_SUMMARY.md**
- - 明确列出所有20个内容生成平台
- - 明确列出所有12个一键复制平台
-
-3. ✅ **创建功能列表文档**
- - 创建 `docs/implementation/FEATURES_COMPLETE_LIST.md`
- - 列出所有43个已实现功能
diff --git a/docs/analysis/DOCUMENTATION_REVERSE_VERIFICATION.md b/docs/analysis/DOCUMENTATION_REVERSE_VERIFICATION.md
index 9db3d1f..957f73b 100644
--- a/docs/analysis/DOCUMENTATION_REVERSE_VERIFICATION.md
+++ b/docs/analysis/DOCUMENTATION_REVERSE_VERIFICATION.md
@@ -3,6 +3,7 @@
## 📋 验证说明
本报告根据功能文档反向验证代码实现,确保:
+
1. 文档中描述的功能是否在代码中实现
2. 功能位置是否与文档一致
3. 功能参数、返回值是否与文档一致
@@ -16,11 +17,13 @@
## 📊 验证概览
### 验证范围
+
- **功能文档**:15个 *FEATURE.md 文件
- **代码文件**:27个Python文件
- **验证功能**:43个主要功能模块
### 验证结果统计
+
- ✅ **完全一致**:40个功能(95%)
- ⚠️ **部分一致**:2个功能(5%)
- 工作流定时任务(文档标记为"未来增强")
@@ -36,6 +39,7 @@
**文档位置**:`docs/features/docs/features/SEMANTIC_EXPANSION_FEATURE.md`
**文档描述**:
+
- 位置:Tab1(关键词蒸馏)
- 功能:基于现有关键词进行语义扩展
- 扩展数量:10-100个
@@ -43,6 +47,7 @@
- 扩展类型:同义、场景、问题、功能、长尾
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第1061-1142行,Tab1中
- ✅ **功能实现**:`modules/semantic_expander.py` - `SemanticExpander.expand_keywords()`
- ✅ **扩展数量**:代码第1069-1076行,slider范围10-100,默认30
@@ -58,12 +63,14 @@
**文档位置**:`docs/features/docs/features/TOPIC_CLUSTER_FEATURE.md`
**文档描述**:
+
- 位置:Tab1(关键词蒸馏)
- 功能:生成话题集群
- 集群数量:3-10个
- 功能:语义聚类、话题命名、话题关联、内容规划建议
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` Tab1中
- ✅ **功能实现**:`modules/topic_cluster.py` - `TopicCluster.generate_clusters()`
- ✅ **集群数量**:代码中支持3-10个集群设置
@@ -78,11 +85,13 @@
**文档位置**:`docs/features/docs/features/KEYWORD_MINING_FEATURE.md`
**文档描述**:
+
- 位置:Tab1(关键词蒸馏)
- 功能:行业热点挖掘、竞争度分析、趋势预测、价值矩阵分析
- 子标签页:4个(行业热点挖掘、竞争度分析、趋势预测、价值矩阵)
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第1452-1463行,Tab1中
- ✅ **功能实现**:`modules/keyword_mining.py` - `KeywordMining`类
- ✅ **子标签页**:代码第1458-1463行,4个子标签页完全一致
@@ -99,12 +108,14 @@
**文档位置**:`docs/features/docs/features/EEAT_FEATURE.md`
**文档描述**:
+
- 位置:Tab2(自动创作)和Tab3(文章优化)
- 功能:评估E-E-A-T水平(0-100分,四个维度各25分)
- 功能:强化E-E-A-T,添加来源占位
- 来源占位类型:数据来源(至少2处)、案例来源(至少1处)、标准来源(至少1处)、专家观点(可选1处)
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第2800-2899行(Tab2),第3316-3396行(Tab3)
- ✅ **功能实现**:`modules/eeat_enhancer.py` - `EEATEnhancer.assess_eeat()` 和 `enhance_eeat()`
- ✅ **评估按钮**:代码第2804行 "📊 评估 E-E-A-T"
@@ -122,12 +133,14 @@
**文档位置**:`docs/features/docs/features/FACT_DENSITY_FEATURE.md`
**文档描述**:
+
- 位置:Tab2(自动创作)和Tab3(文章优化)
- 功能:评估事实密度和结构化块(总分100分:事实密度50分+结构化50分)
- 功能:强化事实密度,添加事实信息和结构化块
- 事实信息类型:数据信息、案例信息、标准信息、对比信息、时间信息、来源信息
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第2900-3050行(Tab2),第3438-3550行(Tab3)
- ✅ **功能实现**:`modules/fact_density_enhancer.py` - `FactDensityEnhancer.assess_fact_density()` 和 `enhance_fact_density()`
- ✅ **评估按钮**:代码第2906行 "📊 评估事实密度"
@@ -144,12 +157,14 @@
**文档位置**:`docs/features/docs/features/JSON_LD_SCHEMA_FEATURE.md`
**文档描述**:
+
- 位置:Tab2(自动创作)- 独立生成模块
- 功能:生成5种Schema类型(Organization、SoftwareApplication、Product、Service、组合)
- 功能:在生成GitHub README时自动生成对应的JSON-LD Schema
- 输出:JSON-LD代码、HTML Script标签
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第1740-1853行(独立生成),第2539-2553行(自动生成)
- ✅ **功能实现**:`modules/schema_generator.py` - `SchemaGenerator`类
- ✅ **Schema类型**:代码第1758-1807行,支持5种类型
@@ -165,12 +180,14 @@
**文档位置**:`docs/features/docs/features/MULTIMODAL_FEATURE.md`
**文档描述**:
+
- 位置:Tab2(自动创作)- 内容生成后
- 功能:生成配图描述和视频脚本描述
- 功能:自动检测配图占位符(【配图:xxx】)
- 功能:为B站等视频平台生成视频脚本描述
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第2642-2779行,Tab2中
- ✅ **功能实现**:`modules/multimodal_prompt.py` - `MultimodalPromptGenerator`类
- ✅ **生成按钮**:代码第2654行 "🎨 生成配图/视频描述"
@@ -187,12 +204,14 @@
**文档位置**:`docs/features/docs/features/CONTENT_SCORER_FEATURE.md`
**文档描述**:
+
- 位置:Tab2(自动创作)- 内容生成后自动评分
- 功能:多维度评分(结构化、品牌提及、权威性、可引用性,每个25分,总分100分)
- 功能:显示评分等级(优秀/良好/中等/需改进)
- 功能:提供改进建议
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第2555-2568行(自动评分),第2597-2620行(显示评分)
- ✅ **功能实现**:`modules/content_scorer.py` - `ContentScorer.score_content()`
- ✅ **自动评分**:代码第2556-2568行,内容生成后自动调用
@@ -208,12 +227,14 @@
**文档位置**:`docs/features/docs/features/TECHNICAL_CONFIG_FEATURE.md`
**文档描述**:
+
- 位置:Tab2(自动创作)- 技术配置生成模块
- 功能:生成robots.txt
- 功能:生成sitemap.xml(基于关键词或历史文章)
- 功能:支持配置允许/禁止路径、更新频率、优先级
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第1859-2050行,Tab2中
- ✅ **功能实现**:`modules/technical_config_generator.py` - `TechnicalConfigGenerator`类
- ✅ **robots.txt**:代码第1859-1890行,生成robots.txt
@@ -228,11 +249,13 @@
**文档位置**:`docs/features/docs/features/OPTIMIZATION_TECHNIQUES_FEATURE.md`
**文档描述**:
+
- 位置:Tab2(自动创作)和Tab3(文章优化)
- 功能:8种优化技巧(证据驱动、对话式、故事化、对比式、步骤式、数据丰富、案例研究、FAQ聚焦)
- 功能:可多选,动态调整生成策略
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第2097-2110行(Tab2),第3198-3210行(Tab3)
- ✅ **功能实现**:`modules/optimization_techniques.py` - `OptimizationTechniqueManager`类
- ✅ **技巧选择**:代码第2104-2108行,支持多选
@@ -250,6 +273,7 @@
**文档位置**:无独立文档(基础功能)
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` Tab3
- ✅ **功能实现**:代码第3164-3600行,文章优化功能完整
@@ -264,6 +288,7 @@
**文档位置**:无独立文档(基础功能)
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` Tab4
- ✅ **支持平台**:代码中支持7个验证平台(DeepSeek、OpenAI、通义千问、Groq、Moonshot、豆包、文心一言)
- ✅ **功能实现**:代码第3613-3890行,多模型验证功能完整
@@ -277,6 +302,7 @@
**文档位置**:`docs/features/docs/features/NEGATIVE_MONITOR_FEATURE.md`
**文档描述**:
+
- 位置:Tab4(多模型验证)- 负面防护监控模块
- 功能:负面查询生成(3-10个)
- 功能:负面情感检测
@@ -284,6 +310,7 @@
- 功能:澄清模板生成
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第3625-3890行,Tab4中
- ✅ **功能实现**:`modules/negative_monitor.py` - `NegativeMonitor`类
- ✅ **负面监控开关**:代码第3625-3631行,启用负面监控复选框
@@ -302,11 +329,13 @@
**文档位置**:`docs/implementation/INTEGRATION_NOTES.md`、`docs/guides/STORAGE_GUIDE.md`
**文档描述**:
+
- 位置:Tab5(历史记录)
- 功能:查看关键词、文章、优化记录、验证结果
- 数据源:SQLite数据库
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` Tab5
- ✅ **功能实现**:`modules/data_storage.py` - `DataStorage`类
- ✅ **数据源**:代码中使用SQLite数据库
@@ -322,6 +351,7 @@
**文档位置**:`docs/features/docs/features/ROI_ANALYSIS_FEATURE.md`
**文档描述**:
+
- 位置:Tab6(AI 数据报表)- ROI分析与成本优化模块
- 功能:成本概览(总成本、总Token数、API调用次数)
- 功能:成本趋势图
@@ -331,6 +361,7 @@
- 功能:未来成本预测
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第4377-4580行,Tab6中
- ✅ **功能实现**:`modules/roi_analyzer.py` - `ROIAnalyzer`类
- ✅ **成本概览**:代码第4392-4405行,显示总成本、总Token数、API调用次数
@@ -349,6 +380,7 @@
**文档位置**:`docs/features/docs/features/CONTENT_METRICS_FEATURE.md`
**文档描述**:
+
- 位置:Tab6(AI 数据报表)- 内容质量指标分析模块
- 功能:Trust Density(信任密度)
- 功能:Citation Share(引用比例)
@@ -357,6 +389,7 @@
- 功能:指标可视化和Top排名
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` Tab6中
- ✅ **功能实现**:`modules/content_metrics.py` - `ContentMetricsAnalyzer`类
- ✅ **指标计算**:代码中实现所有4个指标
@@ -371,12 +404,14 @@
**文档位置**:`docs/features/docs/features/TOPIC_CLUSTER_FEATURE.md`
**文档描述**:
+
- 位置:Tab6(AI 数据报表)- 话题集群分析
- 功能:基于历史关键词生成话题集群分析
- 功能:话题分布图、覆盖情况分析、内容规划建议
- 功能:话题集群统计、话题关联关系
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第4207-4375行,Tab6中
- ✅ **功能实现**:`modules/topic_cluster.py` - `TopicCluster`类
- ✅ **生成按钮**:代码第4232行 "🚀 生成话题集群分析"
@@ -396,10 +431,12 @@
**文档位置**:`README.md`(基础功能)
**文档描述**:
+
- 位置:Tab6(AI 数据报表)
- 功能:使用历史关键词自动进行多模型验证
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第4021-4124行,Tab6中
- ✅ **功能实现**:代码中实现自动验证功能
- ✅ **验证按钮**:代码第4025行 "开始自动验证"
@@ -414,10 +451,12 @@
**文档位置**:`README.md`(基础功能)
**文档描述**:
+
- 位置:Tab6(AI 数据报表)
- 功能:按日期展示提及率变化趋势
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第4156-4178行,Tab6中
- ✅ **功能实现**:代码中实现提及率趋势图
- ✅ **图表类型**:代码第4168-4178行,使用plotly折线图
@@ -431,10 +470,12 @@
**文档位置**:`README.md`(基础功能)
**文档描述**:
+
- 位置:Tab6(AI 数据报表)
- 功能:分析各平台的文章分布和贡献度
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第4180-4205行,Tab6中
- ✅ **功能实现**:代码中实现平台贡献度分析
- ✅ **图表类型**:代码第4194-4203行,使用plotly柱状图
@@ -448,10 +489,12 @@
**文档位置**:`README.md`(基础功能)
**文档描述**:
+
- 位置:Tab6(AI 数据报表)
- 功能:Top 20 关键词效果排名
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第4781-4811行,Tab6中
- ✅ **功能实现**:代码中实现关键词效果排名
- ✅ **排名数量**:代码中显示Top 20关键词
@@ -465,10 +508,12 @@
**文档位置**:`README.md`(基础功能)
**文档描述**:
+
- 位置:Tab6(AI 数据报表)
- 功能:多维度竞品对比
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第4814-4852行,Tab6中
- ✅ **功能实现**:代码中实现竞品对比分析
@@ -481,10 +526,12 @@
**文档位置**:`README.md`(基础功能)
**文档描述**:
+
- 位置:Tab6(AI 数据报表)
- 功能:导出CSV格式数据
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` Tab6中
- ✅ **功能实现**:代码中实现数据导出功能
@@ -499,12 +546,14 @@
**文档位置**:`docs/features/docs/features/WORKFLOW_AUTOMATION_FEATURE.md`
**文档描述**:
+
- 位置:Tab7(工作流自动化)
- 功能:工作流列表、创建工作流、执行历史
- 功能:自定义工作流、工作流模板
- 功能:工作流执行、执行历史查看
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第4996-5450行,Tab7中
- ✅ **功能实现**:`modules/workflow_automation.py` - `WorkflowManager`类
- ✅ **子标签页**:代码第5007行,3个子标签页(工作流列表、创建工作流、执行历史)
@@ -521,9 +570,11 @@
**文档位置**:`docs/features/docs/features/WORKFLOW_AUTOMATION_FEATURE.md` 第179行
**文档描述**:
+
- 功能:定时任务支持(使用 APScheduler)
**代码验证**:
+
- ❌ **未实现**:代码中无定时任务功能
- ✅ **工作流执行**:代码中实现工作流执行功能
@@ -538,11 +589,13 @@
**文档位置**:`docs/features/docs/features/RESOURCE_RECOMMENDER_FEATURE.md`
**文档描述**:
+
- 位置:Tab8(GEO 资源库)
- 功能:GEO代理列表、工具推荐、论文/指南链接、社区资源
- 功能:资源搜索、资源统计
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第5417-5650行,Tab8中
- ✅ **功能实现**:`modules/resource_recommender.py` - `ResourceRecommender`类
- ✅ **子标签页**:代码第5458行,4个子标签页(GEO代理、工具推荐、论文/指南、社区资源)
@@ -560,11 +613,13 @@
**文档位置**:`docs/implementation/IMPLEMENTATION_SUMMARY.md`、`docs/implementation/PLATFORM_SYNC_IMPLEMENTATION.md`
**文档描述**:
+
- 位置:Tab9(平台同步)
- 功能:GitHub API发布
- 功能:账号验证、发布状态跟踪
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` 第5660-5953行,Tab9中
- ✅ **功能实现**:`platform_sync/github_publisher.py` - `GitHubPublisher`类
- ✅ **账号配置**:代码第5673-5790行,GitHub账号配置界面
@@ -579,11 +634,13 @@
**文档位置**:`docs/implementation/IMPLEMENTATION_SUMMARY.md`
**文档描述**:
+
- 位置:Tab9(平台同步)
- 功能:12个平台一键复制
- 功能:内容格式化、自动复制到剪贴板、发布指南显示
**代码验证**:
+
- ✅ **位置一致**:`modules/geo_tool.py` Tab9中
- ✅ **功能实现**:`platform_sync/copy_manager.py` - `CopyManager`类
- ✅ **平台数量**:代码第5761-5765行,12个一键复制平台
@@ -599,15 +656,18 @@
### 1. 批量发布功能 ⚠️ **部分实现**
**文档位置**:
+
- `docs/implementation/PLATFORM_SYNC_IMPLEMENTATION.md` 第142行
- `docs/implementation/IMPLEMENTATION_SUMMARY.md` 第142行
**文档描述**:
+
- 批量发布功能
- 发布队列管理
- 定时发布
**代码验证**:
+
- ✅ **发布记录**:代码中有发布记录功能
- ❌ **批量发布UI**:代码中无批量发布UI
- ❌ **发布队列管理**:代码中无发布队列管理
@@ -620,10 +680,12 @@
### 2. 更多平台API发布 ❌ **未实现**
**文档位置**:
+
- `docs/implementation/IMPLEMENTATION_SUMMARY.md` 第132-139行
- `docs/implementation/PLATFORM_SYNC_IMPLEMENTATION.md`
**文档描述**:
+
- 微信公众号API发布
- B站API发布
- 知乎API发布
@@ -633,6 +695,7 @@
- 网易号API发布
**代码验证**:
+
- ✅ **GitHub发布器**:代码中实现GitHub发布器
- ❌ **其他API发布器**:代码中无其他7个平台的API发布器
@@ -644,28 +707,32 @@
### 按Tab统计
-| Tab | 功能数 | 完全一致 | 部分一致 | 不一致 |
-|-----|--------|---------|---------|--------|
-| Tab1 | 6 | 6 | 0 | 0 |
-| Tab2 | 8 | 8 | 0 | 0 |
-| Tab3 | 5 | 5 | 0 | 0 |
-| Tab4 | 3 | 3 | 0 | 0 |
-| Tab5 | 1 | 1 | 0 | 0 |
-| Tab6 | 9 | 9 | 0 | 0 | ✅ 全部验证 |
-| Tab7 | 3 | 2 | 1 | 0 | ⚠️ 定时任务未实现 |
-| Tab8 | 4 | 4 | 0 | 0 | ✅ 全部验证 |
-| Tab9 | 3 | 2 | 1 | 0 | ⚠️ 批量发布部分实现 |
-| **总计** | **42** | **40** | **2** | **0** | **95%一致** |
+
+| Tab | 功能数 | 完全一致 | 部分一致 | 不一致 |
+| ------ | ------ | ------ | ----- | ----- |
+| Tab1 | 6 | 6 | 0 | 0 |
+| Tab2 | 8 | 8 | 0 | 0 |
+| Tab3 | 5 | 5 | 0 | 0 |
+| Tab4 | 3 | 3 | 0 | 0 |
+| Tab5 | 1 | 1 | 0 | 0 |
+| Tab6 | 9 | 9 | 0 | 0 |
+| Tab7 | 3 | 2 | 1 | 0 |
+| Tab8 | 4 | 4 | 0 | 0 |
+| Tab9 | 3 | 2 | 1 | 0 |
+| **总计** | **42** | **40** | **2** | **0** |
+
### 按功能类型统计
-| 功能类型 | 功能数 | 完全一致 | 部分一致 | 不一致 |
-|---------|--------|---------|---------|--------|
-| 核心功能 | 20 | 20 | 0 | 0 |
-| 高级功能 | 15 | 13 | 2 | 0 |
-| 辅助功能 | 7 | 7 | 0 | 0 |
+
+| 功能类型 | 功能数 | 完全一致 | 部分一致 | 不一致 |
+| ------ | ------ | ------ | ----- | ----- |
+| 核心功能 | 20 | 20 | 0 | 0 |
+| 高级功能 | 15 | 13 | 2 | 0 |
+| 辅助功能 | 7 | 7 | 0 | 0 |
| **总计** | **42** | **40** | **2** | **0** |
+
---
## 📝 验证结论
@@ -673,37 +740,32 @@
### 主要发现
1. **文档与代码高度一致**(95%)
- - 40个功能完全一致(95%)
- - 2个功能部分一致(5%)
- - 工作流定时任务:文档中标记为"未来增强",代码中未实现
- - 批量发布功能:基础功能已实现,但高级功能(批量发布UI、队列管理、定时发布)未实现
- - 0个功能不一致
-
+ - 40个功能完全一致(95%)
+ - 2个功能部分一致(5%)
+ - 工作流定时任务:文档中标记为"未来增强",代码中未实现
+ - 批量发布功能:基础功能已实现,但高级功能(批量发布UI、队列管理、定时发布)未实现
+ - 0个功能不一致
2. **功能位置准确**
- - 所有功能的位置描述与代码实现一致
- - Tab位置、子标签页位置都准确
-
+ - 所有功能的位置描述与代码实现一致
+ - Tab位置、子标签页位置都准确
3. **功能参数一致**
- - 功能参数、返回值与文档描述一致
- - 工作流程与文档描述一致
-
+ - 功能参数、返回值与文档描述一致
+ - 工作流程与文档描述一致
4. **部分功能未完全实现**
- - 工作流定时任务:文档中标记为"未来增强",代码中未实现
- - 批量发布功能:基础功能已实现,但高级功能(批量发布UI、队列管理、定时发布)未实现
+ - 工作流定时任务:文档中标记为"未来增强",代码中未实现
+ - 批量发布功能:基础功能已实现,但高级功能(批量发布UI、队列管理、定时发布)未实现
### 建议
1. **更新文档说明**
- - 在`docs/features/docs/features/WORKFLOW_AUTOMATION_FEATURE.md`中明确说明定时任务为"未来增强"功能
- - 在`docs/implementation/IMPLEMENTATION_SUMMARY.md`中明确说明批量发布功能的状态
-
+ - 在`docs/features/docs/features/WORKFLOW_AUTOMATION_FEATURE.md`中明确说明定时任务为"未来增强"功能
+ - 在`docs/implementation/IMPLEMENTATION_SUMMARY.md`中明确说明批量发布功能的状态
2. **实现缺失功能**(可选)
- - 实现工作流定时任务(使用APScheduler)
- - 实现批量发布UI和队列管理
-
+ - 实现工作流定时任务(使用APScheduler)
+ - 实现批量发布UI和队列管理
3. **保持文档同步**
- - 代码更新时同步更新文档
- - 定期进行文档-代码对比检查
+ - 代码更新时同步更新文档
+ - 定期进行文档-代码对比检查
---
@@ -736,63 +798,72 @@
## 📋 详细验证清单
### Tab1功能验证清单
-- [x] 语义扩展功能 - ✅ 完全一致
-- [x] 话题集群生成 - ✅ 完全一致
-- [x] 关键词挖掘(4个子功能) - ✅ 完全一致
-- [x] AI关键词生成 - ✅ 完全一致
-- [x] 托词工具 - ✅ 完全一致
-- [x] 混合模式 - ✅ 完全一致
+
+- 语义扩展功能 - ✅ 完全一致
+- 话题集群生成 - ✅ 完全一致
+- 关键词挖掘(4个子功能) - ✅ 完全一致
+- AI关键词生成 - ✅ 完全一致
+- 托词工具 - ✅ 完全一致
+- 混合模式 - ✅ 完全一致
### Tab2功能验证清单
-- [x] E-E-A-T评估与强化 - ✅ 完全一致
-- [x] 事实密度增强 - ✅ 完全一致
-- [x] JSON-LD Schema生成 - ✅ 完全一致
-- [x] 多模态提示生成 - ✅ 完全一致
-- [x] 内容质量评分 - ✅ 完全一致
-- [x] 技术配置生成 - ✅ 完全一致
-- [x] 优化技巧选择器 - ✅ 完全一致
-- [x] 内容生成(20个平台) - ✅ 完全一致
+
+- E-E-A-T评估与强化 - ✅ 完全一致
+- 事实密度增强 - ✅ 完全一致
+- JSON-LD Schema生成 - ✅ 完全一致
+- 多模态提示生成 - ✅ 完全一致
+- 内容质量评分 - ✅ 完全一致
+- 技术配置生成 - ✅ 完全一致
+- 优化技巧选择器 - ✅ 完全一致
+- 内容生成(20个平台) - ✅ 完全一致
### Tab3功能验证清单
-- [x] 文章优化 - ✅ 完全一致
-- [x] E-E-A-T强化 - ✅ 完全一致
-- [x] 事实密度增强 - ✅ 完全一致
-- [x] 结构化块优化 - ✅ 完全一致
-- [x] 优化技巧应用 - ✅ 完全一致
+
+- 文章优化 - ✅ 完全一致
+- E-E-A-T强化 - ✅ 完全一致
+- 事实密度增强 - ✅ 完全一致
+- 结构化块优化 - ✅ 完全一致
+- 优化技巧应用 - ✅ 完全一致
### Tab4功能验证清单
-- [x] 多模型验证 - ✅ 完全一致
-- [x] 竞品对比分析 - ✅ 完全一致
-- [x] 负面监控 - ✅ 完全一致
+
+- 多模型验证 - ✅ 完全一致
+- 竞品对比分析 - ✅ 完全一致
+- 负面监控 - ✅ 完全一致
### Tab5功能验证清单
-- [x] 历史记录查看 - ✅ 完全一致
+
+- 历史记录查看 - ✅ 完全一致
### Tab6功能验证清单
-- [x] 自动验证任务 - ✅ 完全一致
-- [x] 提及率趋势图 - ✅ 完全一致
-- [x] 平台贡献度分析 - ✅ 完全一致
-- [x] 话题集群分析 - ✅ 完全一致
-- [x] ROI分析与成本优化 - ✅ 完全一致
-- [x] 内容质量指标分析 - ✅ 完全一致
-- [x] 关键词效果排名 - ✅ 完全一致
-- [x] 竞品对比分析 - ✅ 完全一致
-- [x] 数据导出 - ✅ 完全一致
+
+- 自动验证任务 - ✅ 完全一致
+- 提及率趋势图 - ✅ 完全一致
+- 平台贡献度分析 - ✅ 完全一致
+- 话题集群分析 - ✅ 完全一致
+- ROI分析与成本优化 - ✅ 完全一致
+- 内容质量指标分析 - ✅ 完全一致
+- 关键词效果排名 - ✅ 完全一致
+- 竞品对比分析 - ✅ 完全一致
+- 数据导出 - ✅ 完全一致
### Tab7功能验证清单
-- [x] 工作流管理 - ✅ 完全一致
-- [x] 工作流创建 - ✅ 完全一致
-- [x] 工作流执行历史 - ✅ 完全一致
-- [ ] 工作流定时任务 - ⚠️ 未实现(文档标记为"未来增强")
+
+- 工作流管理 - ✅ 完全一致
+- 工作流创建 - ✅ 完全一致
+- 工作流执行历史 - ✅ 完全一致
+- 工作流定时任务 - ⚠️ 未实现(文档标记为"未来增强")
### Tab8功能验证清单
-- [x] GEO代理推荐 - ✅ 完全一致
-- [x] 工具推荐 - ✅ 完全一致
-- [x] 论文/指南链接 - ✅ 完全一致
-- [x] 社区资源 - ✅ 完全一致
+
+- GEO代理推荐 - ✅ 完全一致
+- 工具推荐 - ✅ 完全一致
+- 论文/指南链接 - ✅ 完全一致
+- 社区资源 - ✅ 完全一致
### Tab9功能验证清单
-- [x] GitHub API发布 - ✅ 完全一致
-- [x] 一键复制功能 - ✅ 完全一致
-- [x] 发布记录查看 - ✅ 完全一致
-- [ ] 批量发布功能 - ⚠️ 部分实现(基础功能已实现,高级功能未实现)
+
+- GitHub API发布 - ✅ 完全一致
+- 一键复制功能 - ✅ 完全一致
+- 发布记录查看 - ✅ 完全一致
+- 批量发布功能 - ⚠️ 部分实现(基础功能已实现,高级功能未实现)
\ No newline at end of file
diff --git a/docs/analysis/FEATURE_ANALYSIS.md b/docs/analysis/FEATURE_ANALYSIS.md
deleted file mode 100644
index cad4671..0000000
--- a/docs/analysis/FEATURE_ANALYSIS.md
+++ /dev/null
@@ -1,303 +0,0 @@
-# 功能重要性分析报告
-
-> **⚠️ 状态说明**:本报告最初用于分析功能重要性,但以下功能**已全部实现**:
-> - ✅ JSON-LD Schema.org 结构化数据生成(已实现,详见 `docs/features/docs/features/JSON_LD_SCHEMA_FEATURE.md`)
-> - ✅ 语义足迹扩展/话题集群生成(已实现,详见 `docs/features/docs/features/TOPIC_CLUSTER_FEATURE.md`)
-> - ✅ 多模态提示生成(已实现,详见 `docs/features/docs/features/MULTIMODAL_FEATURE.md`)
-> - ✅ 负面防护监控(已实现,详见 `docs/features/docs/features/NEGATIVE_MONITOR_FEATURE.md`)
->
-> 本报告保留作为历史参考,实际功能状态请参考各功能文档。
-
-## 📋 分析背景
-
-基于 GEO 工具的核心目标(提升品牌在 AI 模型中的提及率和权威性),对以下四个功能进行重要性分析:
-
-1. JSON-LD Schema.org 结构化数据生成 ✅ **已实现**
-2. 语义足迹扩展(话题集群生成)✅ **已实现**
-3. 多模态提示生成(配图/视频描述)✅ **已实现**
-4. 负面防护监控 ✅ **已实现**
-
----
-
-## 1. JSON-LD Schema.org 结构化数据生成
-
-### 📊 重要性评估:⭐⭐⭐⭐⭐(极高)
-
-### ✅ 为什么重要?
-
-1. **直接提升实体识别**
- - 2026 年 AI 模型越来越依赖结构化数据识别实体
- - Schema.org 是 Google、百度等搜索引擎的标准
- - AI 模型(如 ChatGPT、Claude)会优先解析结构化数据
-
-2. **立竿见影的效果**
- - 用户可直接将生成的 JSON-LD 代码贴到官网/GitHub
- - 无需等待内容被爬取和索引
- - 效果可立即验证
-
-3. **权威性提升**
- - 结构化数据明确标识品牌信息(名称、描述、类型、属性)
- - 提升品牌在知识图谱中的权威性
- - 帮助 AI 模型准确理解品牌定位
-
-4. **与项目高度契合**
- - 项目已有 GitHub README 生成功能
- - 可以无缝集成到内容生成流程
- - 符合 GEO 的"多渠道、多格式"策略
-
-### 🎯 实现建议
-
-**集成位置:**
-- Tab2(内容生成):在生成 GitHub README 时,自动生成对应的 JSON-LD
-- 新增独立模块:专门生成 JSON-LD Schema.org 代码
-- 支持多种 Schema 类型:Organization、SoftwareApplication、Product、Service
-
-**实现难度:** 低-中
-- 主要是 JSON 模板生成
-- 需要了解 Schema.org 规范
-- 可以基于品牌信息自动填充
-
-**预期 ROI:** 极高
-- 实现成本低
-- 效果立竿见影
-- 用户价值高
-
----
-
-## 2. 语义足迹扩展(话题集群生成)
-
-### 📊 重要性评估:⭐⭐⭐⭐(高,但需先有基础)
-
-### ✅ 为什么重要?
-
-1. **从"点"到"面"的占领**
- - 当前功能:单篇内容优化(点)
- - 话题集群:系统化覆盖整个话题领域(面)
- - 长期 ROI 最高
-
-2. **与现有功能互补**
- - 已有:语义足迹扩展(关键词扩展)
- - 话题集群:更高级的功能,基于关键词生成话题集群
- - 可以基于历史关键词自动生成话题集群
-
-3. **提升内容策略**
- - 帮助用户发现内容盲区
- - 系统化规划内容投放
- - 建立完整的内容矩阵
-
-### ⚠️ 注意事项
-
-1. **需要先有关键词基础**
- - 依赖现有的关键词生成功能
- - 需要积累一定的关键词数据
- - 适合在工具成熟后添加
-
-2. **实现复杂度较高**
- - 需要话题聚类算法
- - 需要内容关联分析
- - 需要可视化展示
-
-### 🎯 实现建议
-
-**集成位置:**
-- Tab1(关键词蒸馏):在语义扩展后,提供"生成话题集群"按钮
-- Tab6(AI 数据报表):新增"话题集群分析"模块
-
-**实现难度:** 中-高
-- 需要话题聚类算法
-- 需要内容关联分析
-- 需要可视化展示
-
-**预期 ROI:** 高(长期)
-- 短期:帮助用户发现内容盲区
-- 长期:系统化提升品牌覆盖面
-
----
-
-## 3. 多模态提示生成(配图/视频描述)
-
-### 📊 重要性评估:⭐⭐⭐(中等,未来趋势)
-
-### ✅ 为什么重要?
-
-1. **未来趋势**
- - AI 模型多模态能力越来越强
- - 图文/视频内容更容易被引用
- - 符合内容营销趋势
-
-2. **平台适配**
- - 小红书、抖音等平台以图文/视频为主
- - 已有平台模板支持配图建议
- - 可以增强内容吸引力
-
-### ⚠️ 注意事项
-
-1. **当前 ROI 较低**
- - 国内模型多模态能力参差不齐
- - 短期效果不如文本内容
- - 需要额外成本(图片生成/获取)
-
-2. **实现复杂度**
- - 需要图片生成或匹配
- - 需要视频脚本生成
- - 需要多模态内容评估
-
-### 🎯 实现建议
-
-**集成位置:**
-- Tab2(内容生成):在生成小红书/抖音内容时,提供配图建议
-- 新增模块:多模态内容生成(可选)
-
-**实现难度:** 中-高
-- 需要图片生成 API 或图库匹配
-- 需要多模态内容评估
-- 需要平台适配
-
-**预期 ROI:** 中(未来)
-- 短期:辅助功能,提升内容吸引力
-- 长期:随着多模态能力提升,价值增加
-
-**建议:** 适合在工具有稳定用户后添加
-
----
-
-## 4. 负面防护监控
-
-### 📊 重要性评估:⭐⭐⭐(中等,防护性功能)
-
-### ✅ 为什么重要?
-
-1. **风险防护**
- - 监控品牌负面提及
- - 及时发现和处理负面信息
- - 保护品牌声誉
-
-2. **竞品对比**
- - 已有竞品对比功能
- - 可以扩展为负面监控
- - 帮助用户了解市场动态
-
-### ⚠️ 注意事项
-
-1. **实现复杂度高**
- - 需要负面情感分析
- - 需要多平台监控
- - 需要预警机制
-
-2. **ROI 不确定**
- - 负面情况可能不常见
- - 需要持续监控成本
- - 用户需求可能不强烈
-
-### 🎯 实现建议
-
-**集成位置:**
-- Tab4(多模型验证):扩展为"负面监控"模块
-- Tab6(AI 数据报表):新增"负面监控报告"
-
-**实现难度:** 高
-- 需要情感分析
-- 需要多平台监控
-- 需要预警机制
-
-**预期 ROI:** 中-低
-- 防护性功能,价值取决于负面情况频率
-- 适合作为高级功能
-
----
-
-## 📊 综合优先级排序
-
-### 🔥 第一优先级(立即实现)
-
-#### 1. JSON-LD Schema.org 结构化数据生成 ⭐⭐⭐⭐⭐
-- **重要性**:极高
-- **实现难度**:低-中
-- **ROI**:极高
-- **理由**:
- - 直接提升实体识别和权威性
- - 效果立竿见影
- - 实现成本低
- - 与项目高度契合
-
-### 🟡 第二优先级(工具成熟后)
-
-#### 2. 语义足迹扩展(话题集群生成)⭐⭐⭐⭐
-- **重要性**:高
-- **实现难度**:中-高
-- **ROI**:高(长期)
-- **理由**:
- - 从"点"到"面"的占领
- - 长期 ROI 最高
- - 但需要先有关键词基础
- - 适合在工具成熟后添加
-
-### 🟢 第三优先级(有稳定用户后)
-
-#### 3. 多模态提示生成 ⭐⭐⭐
-- **重要性**:中等
-- **实现难度**:中-高
-- **ROI**:中(未来)
-- **理由**:
- - 未来趋势强
- - 但当前国内模型多模态能力参差不齐
- - 短期 ROI 低于前两项
- - 适合在工具有稳定用户后添加
-
-#### 4. 负面防护监控 ⭐⭐⭐
-- **重要性**:中等
-- **实现难度**:高
-- **ROI**:中-低
-- **理由**:
- - 防护性功能
- - 实现复杂度高
- - ROI 取决于负面情况频率
- - 适合作为高级功能
-
----
-
-## 💡 实施建议
-
-### 立即实施
-
-**JSON-LD Schema.org 结构化数据生成**
-- 实现成本低,效果立竿见影
-- 可以快速提升用户价值
-- 与现有功能高度契合
-
-### 分阶段实施
-
-1. **第一阶段**(当前):完善核心功能
- - ✅ E-E-A-T 强化
- - ✅ 语义足迹扩展(关键词扩展)
- - ✅ 事实密度 + 结构化块
-
-2. **第二阶段**(工具成熟后):
- - JSON-LD Schema.org 生成
- - 话题集群生成
-
-3. **第三阶段**(有稳定用户后):
- - 多模态提示生成
- - 负面防护监控
-
----
-
-## 🎯 结论
-
-### 最值得立即实现的功能
-
-**JSON-LD Schema.org 结构化数据生成**
-- 重要性:⭐⭐⭐⭐⭐
-- 实现难度:低-中
-- ROI:极高
-- 与项目契合度:极高
-
-### 其他功能建议
-
-- **话题集群生成**:工具成熟后添加,长期 ROI 高
-- **多模态提示生成**:有稳定用户后添加,未来趋势
-- **负面防护监控**:作为高级功能,防护性价值
-
----
-
-**分析日期**:2025-01-26
diff --git a/docs/analysis/FEATURE_PRIORITY_ANALYSIS.md b/docs/analysis/FEATURE_PRIORITY_ANALYSIS.md
index 0b1d5ed..6b6528a 100644
--- a/docs/analysis/FEATURE_PRIORITY_ANALYSIS.md
+++ b/docs/analysis/FEATURE_PRIORITY_ANALYSIS.md
@@ -5,6 +5,7 @@
### ✅ 已实现功能
#### 1. E-E-A-T 扫描与强化
+
- **状态**:✅ **已完全实现**
- **位置**:`modules/eeat_enhancer.py`、Tab2、Tab3
- **功能**:
@@ -18,6 +19,7 @@
### ⚠️ 部分实现功能
#### 2. 指标仪表盘扩展
+
- **状态**:✅ **已完全实现**(2025-01-26 完成)
- **已实现**:
- ROI 分析(成本、收益、ROI比率)
@@ -36,6 +38,7 @@
- **结论**:功能已完整实现,无需重复开发
#### 3. 技术配置生成
+
- **状态**:✅ **已完全实现**(2025-01-26 完成)
- **已实现**:
- ✅ JSON-LD Schema.org 生成(`modules/schema_generator.py`)
@@ -53,6 +56,7 @@
### ❌ 未实现功能
#### 4. 高级优化技巧选择器
+
- **状态**:✅ **已完全实现**
- **位置**:`modules/optimization_techniques.py`、Tab2(内容生成)、Tab3(文章优化)
- **功能**:
@@ -64,6 +68,7 @@
- **结论**:功能已完整实现,无需重复开发
#### 5. 负面防护监控
+
- **状态**:✅ **已完全实现**(2025-01-26 完成)
- **位置**:`modules/negative_monitor.py`、Tab4(多模型验证)、Tab6(AI 数据报表)
- **功能**:
@@ -78,6 +83,7 @@
- **结论**:功能已完整实现,无需重复开发
#### 6. 资源推荐模块
+
- **状态**:✅ **已完全实现**(2025-01-26 完成)
- **位置**:`modules/resource_recommender.py`、Tab8(GEO 资源库)
- **功能**:
@@ -98,17 +104,20 @@
### 🔥 第一优先级(高价值 + 中等难度)
#### 1. 高级优化技巧选择器 ⭐⭐⭐⭐
+
**优先级:最高** ✅ **已完成**
**状态**:已完全实现(`modules/optimization_techniques.py`、Tab2、Tab3)
**价值分析**:
+
- ✅ **直接提升内容质量**:让用户选择最适合的优化技巧
- ✅ **提升内容可见性**:社区验证显示可提升 35%
- ✅ **与现有功能互补**:扩展 E-E-A-T 和事实密度强化
- ✅ **用户需求明确**:社区反馈 ROI 高
**已实现功能**:
+
- 8 种优化技巧(证据驱动、对话式设计、故事化叙述、对比式结构、步骤式指南、数据丰富、案例研究、FAQ 聚焦)
- 多选技巧,动态增强 Prompt
- 在 Tab2(内容生成)和 Tab3(文章优化)中已集成
@@ -119,17 +128,20 @@
---
#### 2. 指标仪表盘扩展 ⭐⭐⭐⭐
+
**优先级:高** ✅ **已完成**(2025-01-26)
**状态**:已完全实现(`modules/content_metrics.py`、Tab6)
**价值分析**:
+
- ✅ **量化 GEO 效果**:社区痛点,能指导迭代
- ✅ **数据驱动优化**:报告显示能提升整体 ROI 2-3x
- ✅ **已有基础**:ROI 分析器已实现,只需扩展
- ✅ **用户价值高**:帮助用户理解效果和优化方向
**已实现功能**:
+
- ✅ **Trust Density**:每100字信任信号数(来源占位、数据、案例等)
- ✅ **Citation Share**:品牌引用比例(品牌提及次数 / 总提及次数)
- ✅ **Authority Score**:权威性得分(基于来源占位数量,0-100)
@@ -146,17 +158,20 @@
### 🟡 第二优先级(中等价值 + 低-中难度)
#### 3. 技术配置生成扩展 ⭐⭐⭐
+
**优先级:中** ✅ **已完成**(2025-01-26)
**状态**:已完全实现(`modules/technical_config_generator.py`、Tab2)
**价值分析**:
+
- ✅ **加速内容收录**:社区测试显示提升 20-30%
- ✅ **实现简单**:主要是模板生成
- ✅ **用户需求明确**:尤其对网站/GitHub 内容
- ⚠️ **价值有限**:辅助性功能,不是核心
**已实现功能**:
+
- ✅ robots.txt 生成(支持允许/禁止路径配置)
- ✅ sitemap.xml 生成(支持基于关键词或历史文章生成)
- ✅ 自动生成 sitemap URL
@@ -169,17 +184,20 @@
---
#### 4. 负面防护监控 ⭐⭐⭐
+
**优先级:中** ✅ **已完成**(2025-01-26)
**状态**:已完全实现(`modules/negative_monitor.py`、Tab4、Tab6)
**价值分析**:
+
- ✅ **风险防护**:社区讨论显示负面风险上升
- ✅ **减少损失**:防护能减少损失 40%
- ⚠️ **ROI 不确定**:取决于负面情况频率
- ⚠️ **实现复杂度**:需要情感分析、预警机制
**已实现功能**:
+
- ✅ 自动生成负面查询(15种负面查询模板)
- ✅ 负面情感检测和风险等级评估(高/中/低)
- ✅ 验证负面查询的提及情况
@@ -195,17 +213,20 @@
### 🟢 第三优先级(低-中价值 + 低难度)
#### 5. 资源推荐模块 ⭐⭐
+
**优先级:低** ✅ **已完成**(2025-01-26)
**状态**:已完全实现(`modules/resource_recommender.py`、Tab8)
**价值分析**:
+
- ✅ **增强工具生态**:提供额外价值
- ✅ **实现简单**:主要是静态列表 + 搜索功能
- ⚠️ **价值有限**:辅助性功能,不直接影响 GEO 效果
- ⚠️ **用户需求不明确**:可能使用率不高
**已实现功能**:
+
- ✅ GEO 代理列表(KrillinAI、AutoGPT、AgentGPT 等)
- ✅ 工具推荐(Google Search Console、Bing Webmaster Tools、Schema.org Validator 等)
- ✅ 论文/指南链接(E-E-A-T 指南、Schema.org 文档、GEO 策略指南等)
@@ -221,13 +242,15 @@
## 📊 优先级对比表
-| 功能 | 价值 | 难度 | ROI | 优先级 | 建议实现顺序 |
-|------|------|------|-----|--------|--------------|
-| 高级优化技巧选择器 | ⭐⭐⭐⭐ | 中 | 高 | 🔥 最高 | 1 |
-| 指标仪表盘扩展 | ⭐⭐⭐⭐ | 低-中 | 高 | 🔥 高 | 2 |
-| 技术配置生成扩展 | ⭐⭐⭐ | 低 | 中 | 🟡 中 | 3 |
-| 负面防护监控 | ⭐⭐⭐ | 中-高 | 中-低 | 🟡 中 | 4 |
-| 资源推荐模块 | ⭐⭐ | 低 | 低-中 | 🟢 低 | 5 |
+
+| 功能 | 价值 | 难度 | ROI | 优先级 | 建议实现顺序 |
+| --------- | ---- | --- | --- | ----- | ------ |
+| 高级优化技巧选择器 | ⭐⭐⭐⭐ | 中 | 高 | 🔥 最高 | 1 |
+| 指标仪表盘扩展 | ⭐⭐⭐⭐ | 低-中 | 高 | 🔥 高 | 2 |
+| 技术配置生成扩展 | ⭐⭐⭐ | 低 | 中 | 🟡 中 | 3 |
+| 负面防护监控 | ⭐⭐⭐ | 中-高 | 中-低 | 🟡 中 | 4 |
+| 资源推荐模块 | ⭐⭐ | 低 | 低-中 | 🟢 低 | 5 |
+
---
@@ -236,51 +259,54 @@
### 立即实施(第一优先级)
1. **高级优化技巧选择器**
- - 价值最高,直接提升内容质量
- - 与现有功能高度契合
- - 实现难度适中
-
+ - 价值最高,直接提升内容质量
+ - 与现有功能高度契合
+ - 实现难度适中
2. **指标仪表盘扩展**
- - 已有基础,扩展成本低
- - 数据驱动,提升整体 ROI
- - 用户价值高
+ - 已有基础,扩展成本低
+ - 数据驱动,提升整体 ROI
+ - 用户价值高
### 后续实施(第二优先级)
-3. **技术配置生成扩展**
- - 实现简单,快速完成
- - 提升收录效果
-
-4. **负面防护监控**
- - 防护性功能,根据用户需求决定
- - 实现复杂度较高
+1. **技术配置生成扩展**
+ - 实现简单,快速完成
+ - 提升收录效果
+2. **负面防护监控**
+ - 防护性功能,根据用户需求决定
+ - 实现复杂度较高
### 可选实施(第三优先级)
-5. **资源推荐模块**
- - 辅助性功能,价值有限
- - 可在工具成熟后添加
+1. **资源推荐模块**
+ - 辅助性功能,价值有限
+ - 可在工具成熟后添加
---
## 🎯 推荐实施顺序
### 第一阶段(当前)
+
1. ✅ 智能工作流自动化(已完成)
2. ✅ 智能关键词挖掘与趋势分析(已完成)
### 第二阶段(已完成)
-3. ✅ **高级优化技巧选择器**(已完成)
-4. ✅ **指标仪表盘扩展**(已完成,2025-01-26)
+
+1. ✅ **高级优化技巧选择器**(已完成)
+2. ✅ **指标仪表盘扩展**(已完成,2025-01-26)
### 第三阶段(已完成)
-5. ✅ **技术配置生成扩展**(已完成,2025-01-26)
+
+1. ✅ **技术配置生成扩展**(已完成,2025-01-26)
### 第四阶段(已完成)
-6. ✅ **负面防护监控**(已完成,2025-01-26)
+
+1. ✅ **负面防护监控**(已完成,2025-01-26)
### 第五阶段(已完成)
-7. ✅ **资源推荐模块**(已完成,2025-01-26)
+
+1. ✅ **资源推荐模块**(已完成,2025-01-26)
---
@@ -289,45 +315,44 @@
**已完成的重要功能**:
1. ✅ **高级优化技巧选择器** ⭐⭐⭐⭐
- - 价值:高(提升内容质量 35%)
- - 难度:中
- - ROI:高
- - 与现有功能契合度:极高
- - **状态**:已完全实现
-
+ - 价值:高(提升内容质量 35%)
+ - 难度:中
+ - ROI:高
+ - 与现有功能契合度:极高
+ - **状态**:已完全实现
2. ✅ **指标仪表盘扩展** ⭐⭐⭐⭐
- - 价值:高(提升整体 ROI 2-3x)
- - 难度:低-中
- - ROI:高
- - 已有基础:是
- - **状态**:已完全实现(2025-01-26)
+ - 价值:高(提升整体 ROI 2-3x)
+ - 难度:低-中
+ - ROI:高
+ - 已有基础:是
+ - **状态**:已完全实现(2025-01-26)
**已完成的功能**:
-3. ✅ **技术配置生成扩展** ⭐⭐⭐
- - 价值:中(提升收录 20-30%)
- - 难度:低
- - ROI:中
- - 实现简单:主要是模板生成
- - **状态**:已完全实现(2025-01-26)
+1. ✅ **技术配置生成扩展** ⭐⭐⭐
+ - 价值:中(提升收录 20-30%)
+ - 难度:低
+ - ROI:中
+ - 实现简单:主要是模板生成
+ - **状态**:已完全实现(2025-01-26)
**已完成的功能**:
-4. ✅ **负面防护监控** ⭐⭐⭐
- - 价值:中(风险防护,减少损失 40%)
- - 难度:中-高
- - ROI:中-低(防护性功能)
- - 实现复杂度:需要情感分析、预警机制
- - **状态**:已完全实现(2025-01-26)
+1. ✅ **负面防护监控** ⭐⭐⭐
+ - 价值:中(风险防护,减少损失 40%)
+ - 难度:中-高
+ - ROI:中-低(防护性功能)
+ - 实现复杂度:需要情感分析、预警机制
+ - **状态**:已完全实现(2025-01-26)
**已完成的功能**:
-5. ✅ **资源推荐模块** ⭐⭐
- - 价值:低-中(增强工具生态,间接价值)
- - 难度:低
- - ROI:低-中
- - 实现简单:主要是静态列表 + 搜索功能
- - **状态**:已完全实现(2025-01-26)
+1. ✅ **资源推荐模块** ⭐⭐
+ - 价值:低-中(增强工具生态,间接价值)
+ - 难度:低
+ - ROI:低-中
+ - 实现简单:主要是静态列表 + 搜索功能
+ - **状态**:已完全实现(2025-01-26)
---
@@ -342,6 +367,7 @@
5. ✅ **资源推荐模块** ⭐⭐
**下一步建议**:
+
- 根据用户反馈优化现有功能
- 持续收集使用数据,优化算法
- 考虑添加新功能或扩展现有功能
@@ -349,4 +375,4 @@
---
**分析日期**:2025-01-26
-**版本**:1.0.0
+**版本**:1.0.0
\ No newline at end of file
diff --git a/docs/analysis/FUNCTION_VERIFICATION_REPORT.md b/docs/analysis/FUNCTION_VERIFICATION_REPORT.md
index c45aaed..60b0e0b 100644
--- a/docs/analysis/FUNCTION_VERIFICATION_REPORT.md
+++ b/docs/analysis/FUNCTION_VERIFICATION_REPORT.md
@@ -13,10 +13,12 @@
**验证结果**:✅ **已实现**
**实现位置**:
+
- Tab2(自动创作):第2800-2899行
- Tab3(文章优化):第3397-3436行
**功能详情**:
+
- ✅ **评估按钮**:`📊 评估 E-E-A-T` - 评估内容E-E-A-T水平(0-100分,四个维度各25分)
- ✅ **强化按钮**:`✨ 强化 E-E-A-T` - 自动优化内容以提升E-E-A-T
- ✅ **详细评估**:显示专业性、经验性、权威性、可信度四个维度的得分和详情
@@ -34,10 +36,12 @@
**验证结果**:✅ **已实现**
**实现位置**:
+
- **内容生成 Prompt**:第2153-2191行(所有平台模板都包含来源占位要求)
- **E-E-A-T 强化**:第2850行(自动添加来源占位)
**功能详情**:
+
- ✅ **内容生成时强制要求**:
- 第2156行:`- 权威性:添加来源占位(如"根据XX行业报告"、"参考XX标准"),至少2处数据来源占位`
- 第2191行:`- 权威性:引用技术标准或文档占位(如"参考XX技术规范"、"按照XX框架标准"),至少1处标准来源占位`
@@ -57,10 +61,12 @@
**验证结果**:✅ **已实现**
**实现位置**:
+
- Tab1(关键词蒸馏):第1198-1300行
- Tab6(AI 数据报表):第4236-4260行
**功能详情**:
+
- ✅ **话题集群生成**:`🚀 生成话题集群` 按钮
- ✅ **集群数量设置**:3-10个话题集群(可调节)
- ✅ **语义聚类**:基于LLM进行语义相似性聚类
@@ -73,6 +79,7 @@
**模块**:`modules/topic_cluster.py`
**额外功能**:
+
- ✅ **语义扩展**:`modules/semantic_expander.py` - 从单一关键词扩展到相关长尾词(8-15个关联词)
**结论**:✅ **功能已完整实现,超出文档描述**(不仅有简单扩展,还有完整的话题集群系统)
@@ -84,9 +91,11 @@
**验证结果**:✅ **已实现**
**实现位置**:
+
- Tab2(自动创作):第1740-1853行
**功能详情**:
+
- ✅ **独立生成模块**:`📋 JSON-LD Schema.org 结构化数据生成`
- ✅ **Schema 类型选择**:
- Organization(组织/公司)
@@ -111,9 +120,11 @@
**验证结果**:✅ **已实现**
**实现位置**:
+
- Tab4(多模型验证):第3620-3680行
**功能详情**:
+
- ✅ **负面监控开关**:`启用负面监控` 复选框
- ✅ **负面查询生成**:自动生成负面查询(3-10个,可调节)
- ✅ **批量验证**:将负面查询添加到验证查询中进行批量验证
@@ -131,9 +142,11 @@
**验证结果**:✅ **已实现**
**实现位置**:
+
- Tab6(AI 数据报表):第4636-4750行
**功能详情**:
+
- ✅ **Citation Share 计算**:`平均 Citation Share` 指标显示
- ✅ **详细数据表格**:显示每个关键词/平台的 Citation Share(%)
- ✅ **Top 5 排名**:`Top 5 Citation Share` 排名显示
@@ -141,6 +154,7 @@
- ✅ **可视化**:指标热力图、相关性分析
**计算公式**:
+
- Citation Share = 品牌提及次数 / 总提及次数 × 100%
**结论**:✅ **功能已完整实现,超出文档描述**(不仅有简单计算,还有详细分析和可视化)
@@ -152,9 +166,11 @@
**验证结果**:✅ **已实现**
**实现位置**:
+
- Tab2(自动创作):第2642-2779行
**功能详情**:
+
- ✅ **配图描述生成**:`🎨 生成配图/视频描述` 按钮
- ✅ **自动检测**:自动检测内容中的配图占位符(【配图:xxx】)
- ✅ **配图描述**:为每个配图占位符生成详细的配图描述
@@ -165,6 +181,7 @@
**模块**:`modules/multimodal_prompt.py`
**额外功能**:
+
- ✅ **内容生成时自动包含配图建议**:第2230-2390行(所有平台模板都包含配图建议要求)
**结论**:✅ **功能已完整实现,超出文档描述**(不仅有生成功能,还在内容生成时自动包含配图建议)
@@ -175,15 +192,17 @@
### 功能完整度:100% ✅
-| 序号 | 功能点名称 | 文档建议 | 实际实现 | 状态 |
-|------|-----------|---------|---------|------|
-| 1 | E-E-A-T 检查与提示 | 需要添加 | ✅ 已实现(评估+强化) | ✅ 超出预期 |
-| 2 | 来源引用占位强制生成 | 需要添加 | ✅ 已实现(生成+强化) | ✅ 超出预期 |
-| 3 | 话题/关键词集群扩展 | 需要添加 | ✅ 已实现(完整系统) | ✅ 超出预期 |
-| 4 | JSON-LD Schema.org 生成 | 需要添加 | ✅ 已实现(5种类型) | ✅ 超出预期 |
-| 5 | 负面提及快速检查 | 需要添加 | ✅ 已实现(监控+澄清) | ✅ 超出预期 |
-| 6 | Citation Share 计算 | 需要添加 | ✅ 已实现(分析+可视化) | ✅ 超出预期 |
-| 7 | 配图/视频描述提示 | 需要添加 | ✅ 已实现(生成+自动建议) | ✅ 超出预期 |
+
+| 序号 | 功能点名称 | 文档建议 | 实际实现 | 状态 |
+| --- | --------------------- | ---- | -------------- | ------ |
+| 1 | E-E-A-T 检查与提示 | 需要添加 | ✅ 已实现(评估+强化) | ✅ 超出预期 |
+| 2 | 来源引用占位强制生成 | 需要添加 | ✅ 已实现(生成+强化) | ✅ 超出预期 |
+| 3 | 话题/关键词集群扩展 | 需要添加 | ✅ 已实现(完整系统) | ✅ 超出预期 |
+| 4 | JSON-LD Schema.org 生成 | 需要添加 | ✅ 已实现(5种类型) | ✅ 超出预期 |
+| 5 | 负面提及快速检查 | 需要添加 | ✅ 已实现(监控+澄清) | ✅ 超出预期 |
+| 6 | Citation Share 计算 | 需要添加 | ✅ 已实现(分析+可视化) | ✅ 超出预期 |
+| 7 | 配图/视频描述提示 | 需要添加 | ✅ 已实现(生成+自动建议) | ✅ 超出预期 |
+
### 实现质量评估
@@ -206,6 +225,7 @@
**验证结果**:提供的7个功能点**全部已实现**,且实现质量**超出文档建议**。
**建议**:
+
- ✅ 无需重复开发这些功能
- ✅ 可以专注于其他优化(如真实RAG模拟、更多平台API发布等)
- ✅ 可以优化现有功能的用户体验(如UI改进、性能优化等)
@@ -213,4 +233,4 @@
---
**验证日期**:2025-01-26
-**验证方法**:代码审查 + 功能定位 + 模块检查
+**验证方法**:代码审查 + 功能定位 + 模块检查
\ No newline at end of file
diff --git a/docs/analysis/IMAGE_GENERATION_DEBUG.md b/docs/analysis/IMAGE_GENERATION_DEBUG.md
deleted file mode 100644
index dc2d9ab..0000000
--- a/docs/analysis/IMAGE_GENERATION_DEBUG.md
+++ /dev/null
@@ -1,221 +0,0 @@
-# 图片生成问题调试指南
-
-> 问题:点击生成图片后直接提示"未成功生成任何图片"
-> 创建日期:2026-01-28
-
----
-
-## 🔍 问题分析
-
-### 可能的原因
-
-1. **API Key 未配置或配置错误**
- - 通义万相 API Key 未在侧边栏配置
- - API Key 格式错误
- - API Key 已过期或无效
-
-2. **API 调用失败**
- - 网络连接问题
- - API 服务不可用
- - 免费额度已用完
- - API 限流
-
-3. **Prompt 生成失败**
- - LLM 调用失败
- - Prompt 为空或格式错误
- - 内容不合规
-
-4. **API 响应解析错误**
- - 响应格式不符合预期
- - `response.output.results` 为空
- - `image_url` 为空
-
-5. **异常被静默捕获**
- - 异常被捕获但没有正确显示
- - 错误信息丢失
-
----
-
-## ✅ 已添加的调试功能
-
-### 1. 详细的错误信息显示
-
-在图片生成失败时,现在会显示:
-- **错误类型**:API调用失败、Prompt为空、响应解析错误等
-- **错误详情**:具体的错误消息
-- **调试信息**:
- - Prompt 内容(前200字符)
- - API Key 配置状态
- - 异常堆栈跟踪
-
-### 2. 输入验证
-
-- ✅ 验证 Prompt 不为空
-- ✅ 验证 API 响应不为空
-- ✅ 验证 `image_url` 存在
-- ✅ 验证 `response.output.results` 不为空
-
-### 3. 异常处理增强
-
-- ✅ 捕获所有异常并显示详细信息
-- ✅ 显示异常堆栈跟踪
-- ✅ 区分不同类型的错误
-
----
-
-## 🔧 调试步骤
-
-### 步骤1:检查 API Key 配置
-
-1. 打开侧边栏 **⚙️ 全局配置**
-2. 找到 **🖼️ 通义万相(图片生成)** 部分
-3. 确认 API Key 已正确配置
-4. 点击 **应用配置**
-
-### 步骤2:查看错误信息
-
-如果生成失败,点击 **查看详细错误信息** 或 **查看异常详情**,查看:
-- 错误消息
-- Prompt 内容
-- API Key 配置状态
-- 异常堆栈跟踪
-
-### 步骤3:检查常见问题
-
-#### 问题1:API Key 未配置
-**症状**:错误信息显示"API Key 配置: 未配置"
-**解决**:在侧边栏配置通义万相 API Key
-
-#### 问题2:API 调用失败
-**症状**:错误信息显示"API调用失败,状态码:XXX"
-**可能原因**:
-- 网络连接问题
-- API 服务不可用
-- 免费额度已用完
-- API 限流
-
-**解决**:
-- 检查网络连接
-- 检查 API 服务状态
-- 检查免费额度
-- 等待后重试
-
-#### 问题3:Prompt 为空
-**症状**:错误信息显示"图片生成 Prompt 为空"
-**可能原因**:
-- LLM 调用失败
-- 内容为空
-- Prompt 生成异常
-
-**解决**:
-- 检查内容是否为空
-- 检查 LLM 配置
-- 重试生成
-
-#### 问题4:响应解析错误
-**症状**:错误信息显示"生成成功但未返回图片URL"
-**可能原因**:
-- API 响应格式变化
-- `response.output.results` 为空
-- `image_url` 为空
-
-**解决**:
-- 查看详细错误信息中的响应内容
-- 检查 API 文档是否有变化
-- 联系技术支持
-
----
-
-## 📋 代码修改说明
-
-### 修改1:增强错误处理(geo_tool.py)
-
-**位置**:第3670-3700行(智能生成模式)
-
-**修改内容**:
-- 添加 Prompt 验证
-- 添加 result 验证
-- 添加 image_url 验证
-- 添加详细的错误信息显示
-- 添加异常堆栈跟踪
-
-### 修改2:增强错误处理(geo_tool.py)
-
-**位置**:第3792-3820行(基于描述生成模式)
-
-**修改内容**:
-- 与智能生成模式相同的错误处理增强
-
-### 修改3:增强 API 响应处理(multimodal_prompt.py)
-
-**位置**:第706-740行
-
-**修改内容**:
-- 验证 `response.output.results` 不为空且长度 > 0
-- 验证 `image_url` 不为空
-- 添加详细的错误信息
-- 包含响应状态码、消息、错误码、请求ID等
-
----
-
-## 🧪 测试建议
-
-### 测试1:正常流程
-1. 配置正确的 API Key
-2. 生成内容
-3. 点击"生成图片"
-4. 应该成功生成图片
-
-### 测试2:API Key 未配置
-1. 不配置 API Key
-2. 点击"生成图片"
-3. 应该显示"请在侧边栏配置中设置通义万相 API Key"
-
-### 测试3:API 调用失败
-1. 配置错误的 API Key
-2. 点击"生成图片"
-3. 应该显示详细的错误信息
-
-### 测试4:Prompt 为空
-1. 使用空内容
-2. 点击"生成图片"
-3. 应该显示"图片生成 Prompt 为空"错误
-
----
-
-## 📝 常见错误信息对照表
-
-| 错误信息 | 可能原因 | 解决方法 |
-|---------|---------|---------|
-| "图片生成 Prompt 为空" | LLM 调用失败或内容为空 | 检查内容、检查 LLM 配置 |
-| "图片生成 API 返回空结果" | API 调用异常 | 检查网络、检查 API Key |
-| "API调用失败,状态码:XXX" | API 服务错误 | 检查 API 服务状态、检查免费额度 |
-| "生成成功但未返回图片URL" | API 响应格式错误 | 查看详细错误信息、检查 API 文档 |
-| "图片生成成功但图片URL为空" | API 响应中缺少 URL | 查看详细错误信息、联系技术支持 |
-| "未安装 dashscope 库" | 缺少依赖库 | 运行:pip install dashscope |
-
----
-
-## 🔍 进一步调试
-
-如果问题仍然存在,请:
-
-1. **查看详细错误信息**
- - 点击"查看详细错误信息"或"查看异常详情"
- - 复制错误信息
-
-2. **检查日志**
- - 查看浏览器控制台
- - 查看 Streamlit 日志
-
-3. **验证 API Key**
- - 在阿里云 DashScope 控制台验证 API Key
- - 检查免费额度
-
-4. **测试 API 调用**
- - 使用 curl 或 Postman 直接测试 API
- - 验证 API Key 是否有效
-
----
-
-*文档创建时间:2026-01-28*
diff --git a/docs/analysis/IMAGE_GENERATION_FIXES.md b/docs/analysis/IMAGE_GENERATION_FIXES.md
deleted file mode 100644
index dd677c7..0000000
--- a/docs/analysis/IMAGE_GENERATION_FIXES.md
+++ /dev/null
@@ -1,179 +0,0 @@
-# 图片生成问题修复报告
-
-> 修复日期:2026-01-28
-> 修复了三个严重问题
-
----
-
-## 🔴 问题1:点击【基于配图描述生成】会刷新Tab
-
-### 问题描述
-点击【基于配图描述生成】按钮后,页面会刷新,导致Tab状态丢失,用户需要重新点击【增强工具】Tab。
-
-### 根本原因
-代码中使用了 `st.rerun()`,导致整个页面刷新,Streamlit的Tab状态在rerun后会重置。
-
-### 修复方案
-移除了所有不必要的 `st.rerun()` 调用:
-- 配图描述生成后不再自动刷新(第4002行、第4025行)
-- 图文版本替换后不再自动刷新(第3784行、第3948行、第3968行)
-
-### 修复位置
-- `geo_tool.py` 第3784行:智能生成模式 - 图文版本替换
-- `geo_tool.py` 第3948行:基于描述生成模式 - 图文版本替换
-- `geo_tool.py` 第4002行:生成配图描述(第一个位置)
-- `geo_tool.py` 第4025行:生成配图描述(第二个位置)
-
-### 修复效果
-✅ 现在点击按钮后不会刷新页面,Tab状态保持不变
-
----
-
-## 🔴 问题2:点击【生成配图描述】按钮没有反应
-
-### 问题描述
-点击【生成配图描述】按钮后,没有任何反应,没有错误提示,也没有生成结果。
-
-### 可能原因
-1. 内容中没有配图占位符(【配图:xxx】格式)
-2. `generate_batch_image_descriptions` 返回空结果
-3. 异常被静默捕获
-
-### 修复方案
-1. **添加配图占位符检查**:在生成前检查内容中是否有配图占位符
-2. **添加结果验证**:验证生成结果不为空
-3. **添加详细错误信息**:显示完整的异常堆栈跟踪
-4. **添加用户提示**:如果没有配图占位符,提示用户
-
-### 修复位置
-- `geo_tool.py` 第3995-4007行:生成配图描述(第一个位置)
-- `geo_tool.py` 第4018-4030行:生成配图描述(第二个位置)
-
-### 修复代码
-```python
-# 检查内容中是否有配图占位符
-placeholders = multimodal_gen.extract_image_placeholders(content)
-if not placeholders:
- st.warning("⚠️ 内容中没有找到配图占位符(【配图:xxx】格式)。系统将基于内容自动生成配图描述。")
-
-# 验证生成结果
-if not image_descriptions or image_descriptions.get("total_images", 0) == 0:
- st.warning("⚠️ 未生成任何配图描述。可能原因:内容中没有配图占位符,或生成失败。")
- st.info("💡 提示:如果内容中没有【配图:xxx】格式的占位符,系统可能无法生成配图描述。")
-```
-
-### 修复效果
-✅ 现在会显示明确的提示信息,告知用户为什么没有生成配图描述
-
----
-
-## 🔴 问题3:点击【基于配图描述生成】导致页面空白
-
-### 问题描述
-配图描述生成之后,点击【基于配图描述生成】按钮,整个工具项目就会变成一片空白,错误:`Cannot set a node at a delta path`
-
-### 根本原因
-在循环中使用了 `st.status()`,这是Streamlit的一个已知问题。`st.status()` 在循环中使用会导致状态管理冲突,引发 "Cannot set a node at a delta path" 错误。
-
-### 修复方案
-**将 `st.status()` 替换为 `st.progress()` + `st.empty()`**:
-- 使用 `st.progress()` 显示进度条
-- 使用 `st.empty()` 显示状态文字
-- 在循环外创建进度条和状态文字
-- 在循环内更新进度和状态
-
-### 修复位置
-- `geo_tool.py` 第3636-3755行:智能生成模式
-- `geo_tool.py` 第3840-3938行:基于描述生成模式
-
-### 修复代码
-```python
-# 修复前(错误):
-for idx in range(num_images):
- with st.status(f"正在生成第 {idx + 1}/{num_images} 张图片..."):
- # ... 生成逻辑
-
-# 修复后(正确):
-progress_bar_img = st.progress(0)
-status_text_img = st.empty()
-
-for idx in range(num_images):
- progress = (idx + 1) / num_images
- progress_bar_img.progress(progress)
- status_text_img.text(f"正在生成第 {idx + 1}/{num_images} 张图片...")
- # ... 生成逻辑
-
-# 清理进度显示
-progress_bar_img.empty()
-status_text_img.empty()
-```
-
-### 修复效果
-✅ 现在不会再出现页面空白的问题,进度显示正常
-
----
-
-## ✅ 额外修复
-
-### 1. 初始化 multimodal_descriptions
-**问题**:`st.session_state has no attribute "multimodal_descriptions"`
-
-**修复**:
-- 在初始化部分添加 `ss_init("multimodal_descriptions", {})`
-- 在所有使用前添加安全检查
-
-### 2. 增强错误处理
-- 添加详细的错误信息显示
-- 添加异常堆栈跟踪
-- 添加输入验证
-
-### 3. 改进用户体验
-- 移除自动刷新,避免Tab状态丢失
-- 添加配图占位符检查提示
-- 添加生成结果验证提示
-
----
-
-## 📋 修复验证清单
-
-- [x] 修复Tab刷新问题(移除st.rerun)
-- [x] 修复生成配图描述按钮无反应(添加检查和提示)
-- [x] 修复页面空白问题(替换st.status为st.progress)
-- [x] 初始化multimodal_descriptions
-- [x] 增强错误处理和调试信息
-- [x] 修复缩进错误
-
----
-
-## 🧪 测试建议
-
-### 测试1:生成配图描述
-1. 生成一篇包含【配图:xxx】占位符的内容
-2. 点击【生成配图描述】按钮
-3. 应该显示生成结果或明确的提示信息
-
-### 测试2:基于配图描述生成图片
-1. 先生成配图描述
-2. 点击【基于配图描述生成】按钮
-3. 应该正常生成图片,不会刷新Tab
-4. 不会出现页面空白
-
-### 测试3:智能生成图片
-1. 点击【智能生成(推荐)】
-2. 选择生成数量
-3. 点击【生成图片】按钮
-4. 应该正常生成图片,不会出现页面空白
-
----
-
-## 📝 注意事项
-
-1. **配图占位符**:如果内容中没有【配图:xxx】格式的占位符,系统可能无法生成配图描述。建议在生成内容时添加配图占位符。
-
-2. **Tab状态**:现在不会自动刷新,如果需要查看最新结果,可以手动刷新页面或重新生成。
-
-3. **进度显示**:现在使用进度条和状态文字,而不是st.status,避免在循环中使用st.status导致的问题。
-
----
-
-*修复完成时间:2026-01-28*
diff --git a/docs/analysis/TAB1_CODE_REVIEW.md b/docs/analysis/TAB1_CODE_REVIEW.md
deleted file mode 100644
index 332bf75..0000000
--- a/docs/analysis/TAB1_CODE_REVIEW.md
+++ /dev/null
@@ -1,528 +0,0 @@
-# Tab 1:关键词蒸馏 - 代码逻辑与 AI 提示词全面检查报告
-
-> **检查日期**:2026-01-28
-> **检查范围**:Tab 1 完整代码逻辑、AI 提示词、错误处理、边界条件
-> **检查维度**:逻辑正确性、提示词质量、错误处理、性能、用户体验
-
----
-
-## 📋 目录
-
-1. [代码逻辑检查](#代码逻辑检查)
-2. [AI 提示词检查](#ai-提示词检查)
-3. [错误处理检查](#错误处理检查)
-4. [边界条件检查](#边界条件检查)
-5. [性能问题检查](#性能问题检查)
-6. [用户体验问题检查](#用户体验问题检查)
-7. [修复建议清单](#修复建议清单)
-
----
-
-## 代码逻辑检查
-
-### ✅ 正常逻辑
-
-#### 1. 模式选择逻辑
-- **位置**:第 932-940 行
-- **状态**:✅ 正常
-- **说明**:正确使用 `st.radio` 和 `st.session_state` 管理模式状态
-
-#### 2. 词库初始化逻辑
-- **位置**:第 944-952 行
-- **状态**:✅ 正常
-- **说明**:正确检查 `wordbanks` 是否为 None 并初始化
-
-#### 3. 组合模式选择逻辑
-- **位置**:第 954-981 行
-- **状态**:✅ 正常
-- **说明**:正确处理多选和默认值
-
-#### 4. 词库编辑逻辑
-- **位置**:第 993-1043 行
-- **状态**:✅ 正常
-- **说明**:横向展示、单列更新、批量更新逻辑正确
-
-### ⚠️ 潜在问题
-
-#### 1. 词库编辑 - 状态同步问题
-- **位置**:第 1000-1026 行
-- **问题**:`edited_wordbanks` 在循环中收集,但如果在循环中点击"更新"按钮,可能导致状态不一致
-- **影响**:中等
-- **建议**:在循环外统一处理更新逻辑
-
-#### 2. 生成控制 - 禁用条件判断
-- **位置**:第 1098-1105 行
-- **问题**:托词工具模式的禁用条件检查了 `selected_patterns`,但如果用户刚切换模式,可能还未初始化
-- **影响**:低
-- **建议**:确保在检查前已初始化
-
-#### 3. 混合模式 - 空关键词处理
-- **位置**:第 1270-1286 行
-- **问题**:如果 `raw_keywords` 为空,直接赋值给 `keywords`,但后续清理逻辑可能有问题
-- **影响**:低
-- **建议**:添加空值检查
-
-#### 4. 语义扩展 - 状态管理
-- **位置**:第 1452-1496 行
-- **问题**:扩展后直接修改 `st.session_state.keywords`,但未检查扩展前是否有扩展关键词
-- **影响**:中等
-- **建议**:在扩展前保存原始关键词列表
-
-#### 5. 话题集群 - 重复实例化
-- **位置**:第 1586 行
-- **问题**:每次点击都创建新的 `TopicCluster()` 实例,可能浪费资源
-- **影响**:低
-- **建议**:考虑缓存实例
-
-#### 6. 智能挖掘 - 重复实例化
-- **位置**:第 1852 行
-- **问题**:每次展开都创建新的 `KeywordMining()` 实例
-- **影响**:低
-- **建议**:考虑缓存实例
-
----
-
-## AI 提示词检查
-
-### 1. AI 生成模式提示词
-
-**位置**:第 1152-1169 行
-
-**当前提示词**:
-```
-你是AI领域GEO专家,目标是提升品牌在大模型自然回答中的提及率。
-
-【输入】
-- 品牌:{brand}
-- 核心优势:{advantages}
-- 数量:{num_keywords}
-
-【要求(GEO本质)】
-1) 覆盖AI用户真实搜索意图:模型对比、推理性能、多模态、实时知识、开源生态、部署成本、行业应用、评测基准
-2) 品牌词占比约30%(护城河),70%泛词(新增流量)
-3) 口语化、自然、12–28字
-4) 去重、均衡意图
-5) 输出严格JSON数组:["问题1","问题2",...]
-
-【开始输出JSON数组】
-```
-
-**问题分析**:
-
-1. ❌ **硬编码行业示例**
- - 问题:要求中硬编码了"模型对比、推理性能、多模态"等 AI 领域示例
- - 影响:如果用户是其他行业(如外贸ERP),这些示例不适用
- - 建议:根据 `advantages` 动态生成示例,或使用更通用的描述
-
-2. ⚠️ **输出格式不够明确**
- - 问题:只说"严格JSON数组",但未说明如何处理解析失败
- - 影响:代码中已有 fallback(`extract_json_array`),但提示词未说明
- - 建议:明确说明输出格式,并说明如果格式错误会如何处理
-
-3. ✅ **其他要求清晰**
- - 品牌词占比、长度、口语化等要求明确
-
-**改进建议**:
-```python
-keyword_prompt = PromptTemplate.from_template(
- """
-你是GEO(Generative Engine Optimization)专家,目标是提升品牌在大模型自然回答中的提及率。
-
-【输入】
-- 品牌:{brand}
-- 核心优势:{advantages}
-- 数量:{num_keywords}
-
-【GEO核心要求】
-1) 覆盖用户真实搜索意图:
- - 根据品牌和优势,识别用户可能的搜索场景(对比、评测、使用、购买、问题等)
- - 关键词应反映用户真实需求,而非营销术语
-
-2) 品牌词占比策略:
- - 约30%包含品牌词(建立护城河,提升品牌提及率)
- - 约70%为泛词(扩大覆盖面,获取新流量)
- - 品牌词应自然融入,避免生硬拼接
-
-3) 表达要求:
- - 口语化、自然、符合用户搜索习惯
- - 长度控制在 12-28 字
- - 避免过于正式或营销化
-
-4) 多样性要求:
- - 去重:避免生成相同或过于相似的关键词
- - 均衡意图:覆盖不同搜索意图(对比、评测、使用、购买、问题等)
- - 多样化表达:使用不同的表达方式
-
-【输出格式】
-请严格按照以下 JSON 数组格式输出,不要添加任何其他内容:
-["关键词1", "关键词2", "关键词3", ...]
-
-如果无法生成 JSON 格式,请每行输出一个关键词(纯文本格式)。
-
-【开始生成】
-"""
-)
-```
-
----
-
-### 2. 语义扩展提示词
-
-**位置**:`modules/semantic_expander.py` 第 18-95 行
-
-**当前提示词**:✅ 质量较高
-
-**优点**:
-- 结构清晰,分层次说明
-- 包含具体示例
-- 输出格式明确(JSON)
-- 扩展策略详细
-
-**潜在问题**:
-
-1. ⚠️ **扩展数量控制**
- - 问题:提示词中要求扩展 `{expansion_count}` 个,但实际可能生成更多或更少
- - 影响:需要代码层面控制数量
- - 建议:在提示词中明确说明"至少生成 N 个,可以生成更多"
-
-2. ✅ **其他方面良好**
-
----
-
-### 3. 话题集群提示词
-
-**位置**:`modules/topic_cluster.py` 第 20-89 行
-
-**当前提示词**:✅ 质量较高
-
-**优点**:
-- 聚类要求明确
-- 输出格式详细
-- 包含统计信息要求
-
-**潜在问题**:
-
-1. ⚠️ **聚类数量控制**
- - 问题:要求生成 `{cluster_count}` 个集群,但实际可能生成不同数量
- - 影响:需要代码层面验证
- - 建议:在提示词中说明"尽量生成 N 个,如果关键词不足以支撑 N 个集群,可以生成更少"
-
----
-
-### 4. 关键词挖掘提示词
-
-**位置**:`modules/keyword_mining.py` 第 51-89 行
-
-**当前提示词**:✅ 质量较高
-
-**优点**:
-- 任务说明清晰
-- 输出格式明确
-- 包含示例
-
-**潜在问题**:
-
-1. ⚠️ **行业通用性**
- - 问题:提示词中使用了 `{industry}` 变量,但示例中硬编码了"软件"
- - 影响:对于非软件行业可能不适用
- - 建议:使用更通用的示例或动态生成
-
----
-
-### 5. 混合模式润色提示词
-
-**位置**:`modules/keyword_tool.py` 第 159-173 行
-
-**当前提示词**:
-```
-你是关键词优化专家。请将以下关键词润色为更自然、更符合用户搜索习惯的表达。
-
-{"品牌:" + brand if brand else ""}
-
-原始关键词列表:
-{json.dumps(keywords_to_polish, ensure_ascii=False, indent=2)}
-
-要求:
-1) 保持原意,但表达更自然、口语化
-2) 长度控制在 12-28 字
-3) 去除生硬拼接感
-4) 输出 JSON 数组格式:["润色后的关键词1", "润色后的关键词2", ...]
-
-只输出 JSON 数组,不要其他内容。
-```
-
-**问题分析**:
-
-1. ⚠️ **品牌信息格式不一致**
- - 问题:使用 `{"品牌:" + brand if brand else ""}` 这种 Python 表达式,但实际在模板中可能无法正确解析
- - 影响:品牌信息可能无法正确传递
- - 建议:使用模板变量 `{brand}` 并在调用时处理
-
-2. ✅ **其他要求清晰**
-
-**改进建议**:
-```python
-polish_prompt = f"""你是关键词优化专家。请将以下关键词润色为更自然、更符合用户搜索习惯的表达。
-
-{f'品牌:{brand}' if brand else ''}
-
-原始关键词列表:
-{json.dumps(keywords_to_polish, ensure_ascii=False, indent=2)}
-
-要求:
-1) 保持原意,但表达更自然、口语化
-2) 长度控制在 12-28 字
-3) 去除生硬拼接感
-4) 输出 JSON 数组格式:["润色后的关键词1", "润色后的关键词2", ...]
-
-只输出 JSON 数组,不要其他内容。
-"""
-```
-
----
-
-## 错误处理检查
-
-### ✅ 已处理的错误
-
-1. **AI 生成模式 - JSON 解析失败**
- - **位置**:第 1185-1196 行
- - **处理**:使用 `try-except` 捕获,fallback 到文本解析
- - **状态**:✅ 良好
-
-2. **词库导入 - JSON 解析失败**
- - **位置**:第 1066-1074 行
- - **处理**:使用 `try-except` 捕获并显示错误
- - **状态**:✅ 良好
-
-3. **数据库保存失败**
- - **位置**:第 1325-1328 行
- - **处理**:使用 `try-except` 捕获,显示警告但不阻止流程
- - **状态**:✅ 良好
-
-### ⚠️ 缺失的错误处理
-
-1. **词库编辑 - 空词库检查**
- - **位置**:第 1008 行
- - **问题**:如果 `wordbanks.get(bank_type, [])` 返回空列表,text_area 会显示空,但未提示用户
- - **建议**:添加空词库提示
-
-2. **语义扩展 - LLM 调用失败**
- - **位置**:第 1458-1496 行
- - **问题**:虽然有 `try-except`,但错误信息不够详细
- - **建议**:区分不同类型的错误(网络错误、API 错误、解析错误等)
-
-3. **话题集群 - 可视化失败**
- - **位置**:第 1664-1748 行
- - **问题**:虽然有 `try-except`,但错误信息不够详细
- - **建议**:提供更详细的错误信息
-
-4. **智能挖掘 - 空结果处理**
- - **位置**:第 1878-1896 行
- - **问题**:如果 `mined_keywords` 为空,只显示警告,但未说明原因
- - **建议**:提供更详细的失败原因
-
----
-
-## 边界条件检查
-
-### ✅ 已处理的边界条件
-
-1. **空关键词列表**
- - **位置**:第 1414 行
- - **处理**:使用 `if st.session_state.keywords:` 检查
- - **状态**:✅ 良好
-
-2. **空词库检查**
- - **位置**:第 1216-1222 行、第 1252-1258 行
- - **处理**:在生成前检查空词库
- - **状态**:✅ 良好
-
-3. **空组合模式检查**
- - **位置**:第 1100-1103 行、第 1363-1371 行
- - **处理**:检查 `selected_patterns` 是否为空
- - **状态**:✅ 良好
-
-### ⚠️ 未处理的边界条件
-
-1. **关键词数量为 0**
- - **位置**:第 1310 行
- - **问题**:如果 `cleaned` 为空列表,会进入错误处理,但未检查 `st.session_state.kw_last_num` 是否为 0
- - **建议**:添加数量为 0 的检查
-
-2. **扩展数量超过关键词数量**
- - **位置**:第 1423-1430 行
- - **问题**:如果 `expansion_count` 大于现有关键词数量,可能导致扩展效果不佳
- - **建议**:添加合理性检查
-
-3. **话题集群数量大于关键词数量**
- - **位置**:第 1567-1574 行
- - **问题**:如果 `cluster_count` 大于关键词数量,可能导致聚类失败
- - **建议**:添加合理性检查
-
-4. **搜索词为空字符串**
- - **位置**:第 1797-1798 行
- - **问题**:如果 `search_term` 为空字符串(非 None),会匹配所有关键词
- - **建议**:添加空字符串检查
-
----
-
-## 性能问题检查
-
-### ⚠️ 潜在性能问题
-
-1. **词库编辑 - 每次渲染都处理**
- - **位置**:第 1002-1018 行
- - **问题**:在循环中每次都处理 `edited_wordbanks`,即使未点击更新
- - **影响**:低(但可以优化)
- - **建议**:只在点击更新时处理
-
-2. **话题集群 - 重复创建实例**
- - **位置**:第 1586 行
- - **问题**:每次点击都创建新实例
- - **影响**:低
- - **建议**:考虑缓存实例
-
-3. **智能挖掘 - 重复创建实例**
- - **位置**:第 1852 行
- - **问题**:每次展开都创建新实例
- - **影响**:低
- - **建议**:考虑缓存实例
-
-4. **语义扩展 - 重复创建实例**
- - **位置**:第 1459 行、第 1529 行
- - **问题**:每次调用都创建新实例
- - **影响**:低
- - **建议**:考虑缓存实例
-
-5. **关键词列表 - 分页计算**
- - **位置**:第 1804-1815 行
- - **问题**:每次渲染都计算分页,但可以优化
- - **影响**:极低
- - **建议**:可以接受
-
----
-
-## 用户体验问题检查
-
-### ✅ 良好的用户体验
-
-1. **进度条和状态提示**
- - **位置**:第 1176-1202 行、第 1206-1238 行、第 1242-1293 行
- - **状态**:✅ 良好
-
-2. **错误提示详细**
- - **位置**:第 1331-1412 行
- - **状态**:✅ 良好
-
-3. **搜索和筛选功能**
- - **位置**:第 1788-1801 行
- - **状态**:✅ 良好
-
-### ⚠️ 可改进的用户体验
-
-1. **词库编辑 - 缺少保存提示**
- - **位置**:第 1021-1026 行
- - **问题**:点击"更新"后只显示成功提示,但未提示用户需要重新生成关键词
- - **建议**:添加提示"词库已更新,建议重新生成关键词以应用新词库"
-
-2. **语义扩展 - 缺少撤销功能**
- - **位置**:第 1476-1492 行
- - **问题**:扩展后直接修改关键词列表,无法撤销
- - **建议**:添加撤销功能或确认对话框
-
-3. **话题集群 - 缺少重新生成选项**
- - **位置**:第 1585-1624 行
- - **问题**:如果用户不满意结果,需要手动清空后重新生成
- - **建议**:添加"重新生成"按钮
-
-4. **智能挖掘 - 缺少批量添加**
- - **位置**:第 1912-1917 行
- - **问题**:只能逐个添加关键词,效率低
- - **建议**:添加"全选"和"批量添加"功能
-
----
-
-## 修复建议清单
-
-### 🔴 P0 - 必须修复(逻辑错误/严重问题)
-
-1. **AI 生成提示词 - 硬编码行业示例**
- - **位置**:第 1162 行
- - **修复**:移除硬编码示例,使用通用描述或动态生成
- - **优先级**:高
-
-2. **混合模式润色提示词 - 品牌信息格式**
- - **位置**:`modules/keyword_tool.py` 第 161 行
- - **修复**:使用正确的模板变量格式
- - **优先级**:高
-
-3. **搜索词空字符串检查**
- - **位置**:第 1797-1798 行
- - **修复**:添加空字符串检查
- - **优先级**:中
-
-### 🟡 P1 - 建议修复(体验优化)
-
-4. **词库编辑 - 状态同步优化**
- - **位置**:第 1000-1026 行
- - **修复**:优化更新逻辑,避免状态不一致
- - **优先级**:中
-
-5. **语义扩展 - 保存原始关键词**
- - **位置**:第 1458-1496 行
- - **修复**:在扩展前保存原始关键词列表
- - **优先级**:中
-
-6. **错误处理 - 更详细的错误信息**
- - **位置**:多处
- - **修复**:区分不同类型的错误,提供更详细的错误信息
- - **优先级**:中
-
-7. **边界条件 - 数量合理性检查**
- - **位置**:第 1423-1430 行、第 1567-1574 行
- - **修复**:添加扩展数量和集群数量的合理性检查
- - **优先级**:低
-
-### 🟢 P2 - 可选优化(性能/体验)
-
-8. **实例缓存优化**
- - **位置**:第 1459 行、第 1529 行、第 1586 行、第 1852 行
- - **修复**:考虑缓存实例,避免重复创建
- - **优先级**:低
-
-9. **用户体验优化**
- - **位置**:多处
- - **修复**:添加撤销功能、批量操作、重新生成选项等
- - **优先级**:低
-
----
-
-## 总结
-
-### 整体评价
-
-**代码质量**:⭐⭐⭐⭐☆(4/5)
-- 逻辑清晰,结构合理
-- 错误处理基本完善
-- 用户体验良好
-
-**提示词质量**:⭐⭐⭐⭐☆(4/5)
-- 大部分提示词质量较高
-- 部分提示词需要优化(硬编码、格式问题)
-
-**需要优先修复的问题**:
-1. AI 生成提示词的硬编码行业示例
-2. 混合模式润色提示词的品牌信息格式
-3. 搜索词空字符串检查
-
-**建议优化方向**:
-1. 提升错误处理的详细程度
-2. 添加更多边界条件检查
-3. 优化用户体验(撤销、批量操作等)
-
----
-
-**报告版本**:v1.0
-**最后更新**:2026-01-28
-**下一步**:根据优先级修复 P0 和 P1 问题
diff --git a/docs/analysis/TAB1_OPTIMIZATION_ANALYSIS.md b/docs/analysis/TAB1_OPTIMIZATION_ANALYSIS.md
deleted file mode 100644
index f54e7ca..0000000
--- a/docs/analysis/TAB1_OPTIMIZATION_ANALYSIS.md
+++ /dev/null
@@ -1,1189 +0,0 @@
-# Tab 1:关键词蒸馏 - 优化分析报告
-
-> **分析维度**:逻辑 / UI 布局 / 用户体验
-> **分析日期**:2026-01-28
-> **分析范围**:Tab 1 完整功能模块
-
----
-
-## 📋 目录
-
-1. [全局问题:序号使用规范](#全局问题序号使用规范)
-2. [Tab 1 重排后的结构草图](#tab-1-重排后的结构草图)
-3. [问题清单(含严重级别)](#问题清单含严重级别)
-4. [改动方案(含具体交互/文案/状态设计)](#改动方案含具体交互文案状态设计)
-5. [测试用例清单](#测试用例清单)
-6. [最小改动 MVP 优化清单](#最小改动-mvp-优化清单)
-
----
-
-## 全局问题:序号使用规范
-
-### 判断标准:何时保留、何时删除序号
-
-#### ✅ **保留序号的情况**
-
-1. **流程型 Tab(有明确顺序依赖)**
- - 示例:Tab 1 → Tab 2 → Tab 3(关键词 → 创作 → 优化)
- - 理由:序号帮助用户理解工作流顺序
-
-2. **教程/引导型内容**
- - 示例:"步骤 1:配置 → 步骤 2:生成 → 步骤 3:验证"
- - 理由:序号是内容的一部分,不可省略
-
-3. **Tab 数量 > 5 个**
- - 理由:序号帮助快速定位("第 7 个 Tab")
-
-#### ❌ **删除序号的情况**
-
-1. **功能型 Tab(无顺序依赖)**
- - 当前情况:Tab 1-10 虽然有一定流程,但用户可能跳转使用
- - 理由:序号造成视觉噪音,Tab 名称本身已足够清晰
-
-2. **Tab 数量 ≤ 5 个**
- - 理由:无需序号即可快速识别
-
-3. **Tab 内模块标题**
- - 当前问题:Tab 1 内无序号,但其他 Tab 可能有
- - 理由:通过视觉层级(h2/h3/emoji)已能区分
-
-### 推荐方案:默认去掉 Tab 名称中的序号
-
-**理由**:
-- Tab 名称已足够描述性("关键词蒸馏" vs "1 关键词蒸馏")
-- 减少视觉噪音,提升现代感
-- 用户通过 Tab 位置和名称即可识别,无需序号
-
-**替代方案**:
-- 使用 **emoji 图标** 增强识别度(已部分实现)
-- 使用 **视觉分组**(通过间距、分隔线)
-- 使用 **颜色/高亮** 区分重要 Tab
-
-### 如何通过标题层级/分组/间距替代"序号引导"
-
-#### 方案 A:视觉层级(推荐)
-
-```python
-# Tab 名称(无序号)
-st.tabs([
- "🎯 关键词蒸馏", # 核心功能,emoji 突出
- "✍️ 自动创作", # 核心功能
- "🔧 文章优化", # 核心功能
- "✅ 多模型验证", # 验证功能
- "📊 AI 数据报表", # 分析功能
- # ...
-])
-```
-
-#### 方案 B:分组 + 分隔符
-
-```python
-# 主要流程组
-st.tabs([
- "关键词蒸馏",
- "自动创作",
- "文章优化",
- "多模型验证",
- "---", # 分隔符(视觉分组)
- "历史记录",
- "AI 数据报表",
- # ...
-])
-```
-
-#### 方案 C:保留序号但优化样式
-
-```python
-# 序号作为副标题(更轻量)
-st.tabs([
- "关键词蒸馏 · 1",
- "自动创作 · 2",
- # ...
-])
-```
-
-**最终推荐**:**方案 A**(去掉序号,保留 emoji,通过视觉层级区分)
-
----
-
-## Tab 1 重排后的结构草图
-
-### 当前结构问题
-
-1. **信息分散**:功能模块之间缺乏明确分组
-2. **条件显示混乱**:部分功能仅在有关键词时显示,但布局不清晰
-3. **重复输入**:品牌/优势信息在侧边栏,但某些功能可能需要重复输入
-4. **视觉层级不清晰**:所有模块使用相同的 `st.markdown("#### ...")` 层级
-
-### 重排后的布局结构
-
-```
-┌─────────────────────────────────────────────────────────┐
-│ Tab 1: 关键词蒸馏 │
-├─────────────────────────────────────────────────────────┤
-│ │
-│ 【区域 1:模式选择】(始终显示,顶部固定) │
-│ ┌────────────────────────────────────────────────────┐ │
-│ │ 生成模式: [AI生成] [托词工具] [混合模式] │ │
-│ └────────────────────────────────────────────────────┘ │
-│ │
-│ 【区域 2:配置区】(条件显示:托词工具/混合模式) │
-│ ┌────────────────────────────────────────────────────┐ │
-│ │ 📐 组合模式选择 │ │
-│ │ - 多选组合模式 │ │
-│ │ - [展开:组合模式说明] │ │
-│ └────────────────────────────────────────────────────┘ │
-│ ┌────────────────────────────────────────────────────┐ │
-│ │ 📚 词库管理 │ │
-│ │ [编辑词库 Tab] [导入/导出 Tab] │ │
-│ └────────────────────────────────────────────────────┘ │
-│ │
-│ 【区域 3:生成控制】(始终显示) │
-│ ┌────────────────────────────────────────────────────┐ │
-│ │ 生成数量: [滑块 10-100] │ │
-│ │ [生成关键词] [清空本模块结果] │ │
-│ └────────────────────────────────────────────────────┘ │
-│ │
-│ 【区域 4:扩展功能】(条件显示:有关键词时) │
-│ ┌────────────────────────────────────────────────────┐ │
-│ │ 🌐 语义足迹扩展 │ │
-│ │ - 扩展数量: [滑块] │ │
-│ │ - [开始语义扩展] [合并策略: 追加/替换/交替] │ │
-│ │ - [统计信息] [扩展详情] [覆盖面分析] │ │
-│ └────────────────────────────────────────────────────┘ │
-│ ┌────────────────────────────────────────────────────┐ │
-│ │ 🎯 话题集群生成 │ │
-│ │ - 话题集群数量: [滑块] │ │
-│ │ - [生成话题集群] │ │
-│ │ - [统计] [集群列表] [关联关系] [网络图] [规划建议] │ │
-│ └────────────────────────────────────────────────────┘ │
-│ │
-│ 【区域 5:关键词列表】(条件显示:有关键词时) │
-│ ┌────────────────────────────────────────────────────┐ │
-│ │ 📋 关键词列表 │ │
-│ │ [DataFrame 表格] │ │
-│ │ [下载关键词CSV] │ │
-│ └────────────────────────────────────────────────────┘ │
-│ │
-│ 【区域 6:智能挖掘】(条件显示:有关键词时,折叠默认) │
-│ ┌────────────────────────────────────────────────────┐ │
-│ │ 🔍 智能关键词挖掘与趋势分析 │ │
-│ │ [🌐 行业热点挖掘] [📊 竞争度分析] [📈 趋势预测] │ │
-│ │ [💎 价值矩阵] │ │
-│ └────────────────────────────────────────────────────┘ │
-│ │
-└─────────────────────────────────────────────────────────┘
-```
-
-### 组件顺序与交互流
-
-#### 交互流程 1:AI 生成模式(最简单)
-
-```
-1. 选择"AI生成"模式
-2. 设置生成数量(默认 10)
-3. 点击"生成关键词"
-4. 查看关键词列表
-5. (可选)使用扩展功能
-```
-
-#### 交互流程 2:托词工具模式
-
-```
-1. 选择"托词工具"模式
-2. 配置组合模式(多选)
-3. 管理词库(编辑/导入)
-4. 设置生成数量
-5. 点击"生成关键词"
-6. 查看关键词列表
-```
-
-#### 交互流程 3:混合模式
-
-```
-1. 选择"混合模式"
-2. 配置组合模式
-3. 管理词库
-4. 设置生成数量
-5. 点击"生成关键词"(先托词生成,再 LLM 润色)
-6. 查看关键词列表
-7. (可选)使用扩展功能
-```
-
-### 紧凑布局规则
-
-#### 间距规则
-
-```python
-# 区域间距
-区域之间:st.markdown("---") # 1.5rem 间距(CSS 已定义)
-
-# 容器内间距
-with st.container(border=True):
- # 容器内 padding: 1.5rem(CSS 已定义)
- # 容器内元素间距:0.75rem
- st.markdown("#### 标题") # margin-top: 0, margin-bottom: 0.75rem
- # 控件间距:0.5rem
-```
-
-#### 分组规则
-
-```python
-# 主要功能区域:使用 container(border=True)
-# 次要信息:使用 expander(默认折叠)
-# 统计信息:使用 columns + metric(紧凑显示)
-```
-
-#### 折叠规则
-
-```python
-# 默认展开
-- 模式选择
-- 生成控制
-- 关键词列表(如果有关键词)
-
-# 默认折叠
-- 组合模式说明(expander)
-- 扩展详情(expander)
-- 覆盖面分析(expander)
-- 智能挖掘(整个区域,如果功能复杂)
-```
-
----
-
-## 问题清单(含严重级别)
-
-### 🔴 P0 - 必改(逻辑漏洞/严重体验问题)
-
-#### 1. 空值检查不完整
-- **位置**:第 1064-1192 行(生成关键词逻辑)
-- **问题**:
- - `generation_mode == "AI生成"` 时,未检查 `brand` 和 `advantages` 是否为空
- - `generation_mode == "托词工具"` 时,检查了空词库,但错误提示在生成后,应该在生成前阻止
-- **影响**:可能导致 API 调用失败或生成无效结果
-- **修复**:在 `run_kw` 点击时,先验证所有必需参数
-
-#### 2. 状态同步问题
-- **位置**:第 1194-1867 行(扩展功能区域)
-- **问题**:
- - `st.session_state.expanded_keywords` 在扩展后未清空,可能导致重复合并
- - `st.session_state.topic_clusters` 在生成新关键词后未清空,可能显示旧数据
-- **影响**:数据混乱,用户困惑
-- **修复**:在生成新关键词时,清空所有扩展/集群相关状态
-
-#### 3. 并发点击防护缺失
-- **位置**:所有按钮点击处理
-- **问题**:
- - 用户快速点击"生成关键词"可能触发多次 API 调用
- - 无 loading 状态保护
-- **影响**:重复请求、资源浪费、可能的数据覆盖
-- **修复**:使用 `st.session_state` 标记处理中状态,禁用按钮
-
-#### 4. 错误提示不友好
-- **位置**:第 1180-1192 行(生成失败处理)
-- **问题**:
- - 错误信息过于技术化("生成失败,可能的原因:...")
- - 未提供具体解决建议
-- **影响**:用户不知道如何修复问题
-- **修复**:提供分场景的错误提示和操作建议
-
-#### 5. 缓存问题
-- **位置**:第 1107-1120 行(托词工具生成)
-- **问题**:
- - `wordbanks` 每次从 `st.session_state` 读取,但更新词库后可能未同步
- - `selected_patterns` 可能过期
-- **影响**:使用旧配置生成,结果不符合预期
-- **修复**:在生成前重新加载最新配置
-
-### 🟡 P1 - 建议(体验优化)
-
-#### 6. 布局分散
-- **位置**:整个 Tab 1
-- **问题**:
- - 功能模块之间缺乏视觉分组
- - 条件显示的功能(扩展/集群/挖掘)与主要功能混在一起
-- **影响**:信息过载,用户难以聚焦
-- **修复**:使用容器分组,明确区域划分
-
-#### 7. 重复输入
-- **位置**:第 1604 行(行业热点挖掘)
-- **问题**:
- - `industry` 输入框默认值 "外贸ERP",但用户可能已在侧边栏配置了品牌信息
- - 未复用侧边栏的 `brand` 信息
-- **影响**:用户需要重复输入
-- **修复**:默认使用 `brand`,允许覆盖
-
-#### 8. 加载态不明确
-- **位置**:所有异步操作
-- **问题**:
- - `st.spinner` 文本不够具体("AI生成中..." vs "正在生成 20 个关键词...")
- - 无进度提示(对于大量关键词生成)
-- **影响**:用户不知道需要等待多久
-- **修复**:提供更详细的加载信息和进度估算
-
-#### 9. 默认值不合理
-- **位置**:第 1042 行(生成数量滑块)
-- **问题**:
- - 默认值使用 `st.session_state.kw_last_num`,但首次使用时可能未初始化
- - 范围 10-100,但用户可能需要更少或更多
-- **影响**:首次使用体验不佳
-- **修复**:确保默认值始终有效,考虑扩大范围或添加手动输入
-
-#### 10. 结果展示不清晰
-- **位置**:第 1566 行(关键词列表)
-- **问题**:
- - DataFrame 显示所有关键词,但列表可能很长(100+)
- - 未区分原始关键词和扩展关键词(虽然有 caption,但不够明显)
-- **影响**:难以快速查看和筛选
-- **修复**:添加分页、搜索、筛选功能
-
-### 🟢 P2 - 可做(锦上添花)
-
-#### 11. 撤销功能缺失
-- **位置**:第 1060 行(清空按钮)
-- **问题**:
- - "清空本模块结果" 不可撤销
- - 用户误操作后无法恢复
-- **影响**:用户需要重新生成
-- **修复**:添加撤销功能或确认对话框
-
-#### 12. 批量操作缺失
-- **位置**:关键词列表
-- **问题**:
- - 无法批量删除/导出/标记关键词
- - 智能挖掘结果需要逐个添加
-- **影响**:效率低下
-- **修复**:添加多选和批量操作
-
-#### 13. 历史记录集成
-- **位置**:整个 Tab 1
-- **问题**:
- - 生成的关键词未与 Tab 5(历史记录)深度集成
- - 无法快速查看/恢复历史关键词
-- **影响**:无法复用历史数据
-- **修复**:在 Tab 1 添加"从历史记录加载"功能
-
-#### 14. 导出格式单一
-- **位置**:第 1574 行(下载 CSV)
-- **问题**:
- - 仅支持 CSV 格式
- - 未支持 JSON、TXT、Excel 等格式
-- **影响**:用户需要手动转换格式
-- **修复**:添加多格式导出选项
-
-#### 15. 快捷键支持
-- **位置**:整个 Tab 1
-- **问题**:
- - 无键盘快捷键(如 Ctrl+Enter 生成)
- - 无快速导航
-- **影响**:效率较低
-- **修复**:添加常用快捷键(Streamlit 限制,可能需 JS 扩展)
-
----
-
-## 改动方案(含具体交互/文案/状态设计)
-
-### 改动 1:去掉 Tab 名称中的序号
-
-**位置**:第 913 行
-
-**改动前**:
-```python
-tab1, tab2, tab3, ... = st.tabs([
- "1 关键词蒸馏",
- "2 自动创作",
- ...
-])
-```
-
-**改动后**:
-```python
-tab1, tab2, tab3, ... = st.tabs([
- "🎯 关键词蒸馏",
- "✍️ 自动创作",
- "🔧 文章优化",
- "✅ 多模型验证",
- "📚 历史记录",
- "📊 AI 数据报表",
- "⚙️ 工作流自动化",
- "📦 GEO 资源库",
- "🔄 平台同步",
- "🛠️ 配置优化助手"
-])
-```
-
-**理由**:去掉序号,保留 emoji,提升现代感和识别度
-
----
-
-### 改动 2:优化布局结构(区域分组)
-
-**位置**:第 918-1868 行(整个 Tab 1)
-
-**改动前**:功能模块平铺,缺乏分组
-
-**改动后**:
-
-```python
-with tab1:
- # ========== 区域 1:模式选择 ==========
- st.markdown("### 🎯 生成模式")
- generation_mode = st.radio(
- "选择生成模式",
- ["AI生成", "托词工具", "混合模式"],
- index=["AI生成", "托词工具", "混合模式"].index(st.session_state.kw_generation_mode),
- horizontal=True,
- key="kw_mode_radio"
- )
- st.session_state.kw_generation_mode = generation_mode
- st.markdown("---")
-
- # ========== 区域 2:配置区(条件显示) ==========
- if generation_mode in ["托词工具", "混合模式"]:
- # 组合模式选择
- with st.container(border=True):
- st.markdown("#### 📐 组合模式选择")
- # ... 现有代码 ...
-
- # 词库管理
- with st.container(border=True):
- st.markdown("#### 📚 词库管理")
- # ... 现有代码 ...
-
- st.markdown("---")
-
- # ========== 区域 3:生成控制 ==========
- with st.container(border=True):
- st.markdown("#### ⚙️ 生成控制")
- c1, c2, c3 = st.columns([2, 1, 1])
- with c1:
- st.session_state.kw_last_num = st.slider(
- "生成数量", 10, 100, st.session_state.kw_last_num, key="kw_num"
- )
- with c2:
- run_kw_disabled = (
- (generation_mode != "托词工具" and (not st.session_state.cfg_valid or gen_llm is None))
- or (generation_mode in ["托词工具", "混合模式"] and not st.session_state.get("selected_patterns"))
- )
- run_kw = st.button(
- "🚀 生成关键词",
- type="primary",
- use_container_width=True,
- disabled=run_kw_disabled,
- key="kw_run",
- )
- with c3:
- if st.button("🗑️ 清空结果", use_container_width=True, key="kw_clear"):
- # 清空所有相关状态
- st.session_state.keywords = []
- st.session_state.expanded_keywords = []
- st.session_state.topic_clusters = []
- st.session_state.mined_keywords = []
- st.toast("已清空所有关键词和相关数据")
- st.rerun()
-
- # 生成逻辑(改进错误处理和状态管理)
- if run_kw:
- # 参数验证
- if generation_mode == "AI生成":
- if not brand or not advantages:
- st.error("❌ 请先在侧边栏配置品牌名称和核心优势")
- st.stop()
-
- # 防止并发点击
- if st.session_state.get("kw_generating", False):
- st.warning("⏳ 正在生成中,请勿重复点击")
- st.stop()
-
- st.session_state.kw_generating = True
-
- # ... 生成逻辑(保持现有代码) ...
-
- st.session_state.kw_generating = False
-
- # ========== 区域 4:扩展功能(条件显示) ==========
- if st.session_state.keywords:
- st.markdown("---")
-
- # 语义足迹扩展
- with st.container(border=True):
- st.markdown("#### 🌐 语义足迹扩展")
- # ... 现有代码(改进状态管理) ...
-
- # 话题集群生成
- with st.container(border=True):
- st.markdown("#### 🎯 话题集群生成")
- # ... 现有代码(改进状态管理) ...
-
- # ========== 区域 5:关键词列表(条件显示) ==========
- if st.session_state.keywords:
- st.markdown("---")
- st.markdown("#### 📋 关键词列表")
-
- # 添加搜索和筛选
- search_col, filter_col = st.columns([3, 1])
- with search_col:
- search_term = st.text_input("🔍 搜索关键词", key="kw_search", placeholder="输入关键词搜索...")
- with filter_col:
- show_original = st.checkbox("仅显示原始关键词", key="kw_filter_original", value=False)
-
- # 过滤关键词
- display_keywords = st.session_state.keywords
- if search_term:
- display_keywords = [kw for kw in display_keywords if search_term.lower() in kw.lower()]
- if show_original and st.session_state.expanded_keywords:
- original_count = len(st.session_state.keywords) - len(st.session_state.expanded_keywords)
- display_keywords = display_keywords[:original_count]
-
- # 显示列表(分页)
- if display_keywords:
- page_size = 20
- total_pages = (len(display_keywords) - 1) // page_size + 1
- page = st.session_state.get("kw_page", 1)
-
- page_col1, page_col2, page_col3 = st.columns([1, 2, 1])
- with page_col2:
- page = st.selectbox("页码", range(1, total_pages + 1), index=page - 1, key="kw_page_select")
-
- start_idx = (page - 1) * page_size
- end_idx = start_idx + page_size
- page_keywords = display_keywords[start_idx:end_idx]
-
- df = pd.DataFrame(page_keywords, columns=["长尾关键词/问题"])
- st.dataframe(df, use_container_width=True, hide_index=True)
-
- st.caption(f"显示第 {start_idx + 1}-{min(end_idx, len(display_keywords))} 条,共 {len(display_keywords)} 条关键词")
-
- # 区分原始和扩展关键词
- if st.session_state.expanded_keywords:
- original_count = len(st.session_state.keywords) - len(st.session_state.expanded_keywords)
- st.info(f"📌 原始关键词:{original_count} 个 | 🆕 扩展关键词:{len(st.session_state.expanded_keywords)} 个")
- else:
- st.info("未找到匹配的关键词")
-
- # 下载按钮
- st.download_button(
- "📥 下载关键词 CSV",
- pd.DataFrame(st.session_state.keywords, columns=["长尾关键词/问题"]).to_csv(index=False, encoding="utf-8-sig"),
- f"{sanitize_filename(brand,40)}_keywords.csv",
- mime="text/csv",
- use_container_width=True,
- key="kw_dl_csv",
- )
-
- # ========== 区域 6:智能挖掘(条件显示,默认折叠) ==========
- if st.session_state.keywords:
- st.markdown("---")
- with st.expander("🔍 智能关键词挖掘与趋势分析", expanded=False):
- st.caption("发现高价值关键词,分析竞争度,预测趋势,优化关键词策略")
- # ... 现有代码(保持 tabs 结构) ...
- else:
- st.info("💡 提示:在左侧完成配置后,点击"生成关键词"开始使用。")
-```
-
-**改动点**:
-1. 明确区域划分(使用注释和分隔线)
-2. 改进状态管理(清空时清空所有相关状态)
-3. 添加参数验证和并发防护
-4. 添加搜索、筛选、分页功能
-5. 智能挖掘默认折叠(减少信息过载)
-
----
-
-### 改动 3:改进错误处理和用户提示
-
-**位置**:第 1064-1192 行(生成逻辑)
-
-**改动前**:
-```python
-if cleaned:
- st.session_state.keywords = cleaned
- st.success(f"生成完成({len(cleaned)} 条)")
-else:
- error_msg = "生成失败,可能的原因:\n"
- # ... 通用错误信息
- st.error(error_msg)
-```
-
-**改动后**:
-```python
-if cleaned:
- st.session_state.keywords = cleaned
- # 清空扩展和集群相关状态
- st.session_state.expanded_keywords = []
- st.session_state.topic_clusters = []
- st.session_state.mined_keywords = []
-
- st.success(f"✅ 生成完成!共生成 {len(cleaned)} 个关键词")
- st.balloons() # 成功动画(可选)
-else:
- # 分场景错误提示
- if generation_mode == "AI生成":
- st.error("""
- ❌ **AI 生成失败**
-
- **可能原因:**
- - API Key 配置错误或余额不足
- - 网络连接问题
- - 品牌名称或核心优势为空
-
- **解决建议:**
- 1. 检查侧边栏的 API Key 配置
- 2. 确认品牌名称和核心优势已填写
- 3. 稍后重试或联系技术支持
- """)
- elif generation_mode == "托词工具":
- wordbanks = st.session_state.wordbanks or st.session_state.keyword_tool.load_wordbanks()
- empty_banks = [k for k, v in wordbanks.items() if not v]
- if empty_banks:
- st.error(f"""
- ❌ **词库为空**
-
- 以下词库为空,请先添加词汇:
- - {', '.join(empty_banks)}
-
- **操作步骤:**
- 1. 点击"词库管理"
- 2. 选择空的词库类型
- 3. 添加至少 3-5 个词汇
- 4. 点击"更新词库"
- 5. 重新生成关键词
- """)
- elif not st.session_state.get("selected_patterns"):
- st.error("""
- ❌ **未选择组合模式**
-
- 请至少选择一个组合模式:
- 1. 在"组合模式选择"区域
- 2. 勾选至少一个模式
- 3. 重新生成关键词
- """)
- elif generation_mode == "混合模式":
- # 类似托词工具的错误处理
- # ...
-```
-
-**改动点**:
-1. 分场景错误提示(更具体)
-2. 提供解决建议(可操作)
-3. 使用 markdown 格式化(更易读)
-4. 清空相关状态(避免数据混乱)
-
----
-
-### 改动 4:优化加载状态和进度提示
-
-**位置**:所有异步操作
-
-**改动前**:
-```python
-with st.spinner("AI生成中..."):
- # ...
-```
-
-**改动后**:
-```python
-# 使用进度条和详细状态
-progress_bar = st.progress(0)
-status_text = st.empty()
-
-with st.spinner(""):
- status_text.text("🔄 正在生成关键词...")
- progress_bar.progress(10)
-
- # 模拟进度(实际应根据 API 响应调整)
- if generation_mode == "AI生成":
- status_text.text("🤖 调用 AI 模型生成关键词...")
- progress_bar.progress(30)
- # ... 实际生成逻辑
- progress_bar.progress(80)
- status_text.text("✨ 处理生成结果...")
- progress_bar.progress(100)
- elif generation_mode == "托词工具":
- status_text.text("🔧 加载词库和组合模式...")
- progress_bar.progress(20)
- status_text.text("🔄 生成关键词组合...")
- progress_bar.progress(60)
- status_text.text("✨ 去重和筛选...")
- progress_bar.progress(100)
- # ...
-
-status_text.text(f"✅ 完成!生成 {len(cleaned)} 个关键词")
-progress_bar.empty()
-```
-
-**改动点**:
-1. 使用进度条(视觉反馈)
-2. 详细状态文本(用户知道在做什么)
-3. 分阶段进度(更真实)
-
----
-
-### 改动 5:改进默认值和初始化
-
-**位置**:第 1042 行(生成数量滑块)
-
-**改动前**:
-```python
-st.session_state.kw_last_num = st.slider(
- "生成数量", 10, 100, st.session_state.kw_last_num, key="kw_num"
-)
-```
-
-**改动后**:
-```python
-# 确保默认值有效
-ss_init("kw_last_num", 20) # 默认 20 个
-
-# 滑块 + 手动输入
-col1, col2 = st.columns([3, 1])
-with col1:
- st.session_state.kw_last_num = st.slider(
- "生成数量",
- 5, 200, # 扩大范围
- st.session_state.kw_last_num,
- key="kw_num",
- help="建议范围:10-50 个关键词"
- )
-with col2:
- manual_num = st.number_input(
- "手动输入",
- min_value=5,
- max_value=200,
- value=st.session_state.kw_last_num,
- key="kw_num_manual"
- )
- if manual_num != st.session_state.kw_last_num:
- st.session_state.kw_last_num = manual_num
- st.rerun()
-```
-
-**改动点**:
-1. 确保默认值初始化
-2. 扩大范围(5-200)
-3. 添加手动输入选项(精确控制)
-4. 提供建议范围(帮助用户)
-
----
-
-## 测试用例清单
-
-### 测试用例 1:AI 生成模式 - 正常流程
-
-**前置条件**:
-- 侧边栏已配置 API Key、品牌名称、核心优势
-- Tab 1 处于默认状态
-
-**测试步骤**:
-1. 选择"AI生成"模式(默认)
-2. 设置生成数量为 20
-3. 点击"生成关键词"按钮
-4. 等待生成完成
-
-**预期结果**:
-- ✅ 显示加载状态(进度条 + 状态文本)
-- ✅ 生成 20 个关键词(或接近 20 个,取决于去重)
-- ✅ 显示成功提示:"✅ 生成完成!共生成 X 个关键词"
-- ✅ 关键词列表区域显示 DataFrame
-- ✅ 可以下载 CSV 文件
-
----
-
-### 测试用例 2:AI 生成模式 - 参数缺失
-
-**前置条件**:
-- 侧边栏未配置品牌名称或核心优势
-- Tab 1 处于默认状态
-
-**测试步骤**:
-1. 选择"AI生成"模式
-2. 设置生成数量为 20
-3. 点击"生成关键词"按钮
-
-**预期结果**:
-- ✅ 显示错误提示:"❌ 请先在侧边栏配置品牌名称和核心优势"
-- ✅ 不执行 API 调用
-- ✅ 按钮保持可用状态(允许修复后重试)
-
----
-
-### 测试用例 3:托词工具模式 - 正常流程
-
-**前置条件**:
-- 侧边栏已配置品牌名称、核心优势
-- 词库已初始化(有词汇)
-- 至少选择一个组合模式
-
-**测试步骤**:
-1. 选择"托词工具"模式
-2. 在"组合模式选择"中勾选至少一个模式
-3. 设置生成数量为 30
-4. 点击"生成关键词"按钮
-5. 等待生成完成
-
-**预期结果**:
-- ✅ 显示"组合模式选择"和"词库管理"区域
-- ✅ 生成 30 个关键词(或接近,取决于组合结果)
-- ✅ 显示成功提示
-- ✅ 关键词列表显示结果
-
----
-
-### 测试用例 4:托词工具模式 - 词库为空
-
-**前置条件**:
-- 侧边栏已配置
-- 词库为空(所有词库类型都没有词汇)
-
-**测试步骤**:
-1. 选择"托词工具"模式
-2. 选择一个组合模式
-3. 点击"生成关键词"按钮
-
-**预期结果**:
-- ✅ 显示错误提示:"❌ 词库为空",列出空的词库类型
-- ✅ 提供解决建议(添加词汇的步骤)
-- ✅ 不执行生成逻辑
-
----
-
-### 测试用例 5:托词工具模式 - 未选择组合模式
-
-**前置条件**:
-- 侧边栏已配置
-- 词库有词汇
-- 未选择任何组合模式
-
-**测试步骤**:
-1. 选择"托词工具"模式
-2. 不选择任何组合模式(取消所有勾选)
-3. 点击"生成关键词"按钮
-
-**预期结果**:
-- ✅ "生成关键词"按钮被禁用(或点击后显示错误)
-- ✅ 显示错误提示:"❌ 未选择组合模式"
-- ✅ 提供解决建议
-
----
-
-### 测试用例 6:混合模式 - 正常流程
-
-**前置条件**:
-- 侧边栏已配置 API Key、品牌名称、核心优势
-- 词库已初始化
-- 至少选择一个组合模式
-
-**测试步骤**:
-1. 选择"混合模式"
-2. 配置组合模式和词库
-3. 设置生成数量为 25
-4. 点击"生成关键词"按钮
-5. 观察两个阶段的加载状态
-
-**预期结果**:
-- ✅ 显示"托词生成中..."状态
-- ✅ 显示"LLM 润色中..."状态
-- ✅ 生成 25 个关键词(经过润色)
-- ✅ 显示成功提示
-
----
-
-### 测试用例 7:并发点击防护
-
-**前置条件**:
-- 侧边栏已配置
-- Tab 1 处于可生成状态
-
-**测试步骤**:
-1. 点击"生成关键词"按钮
-2. 在加载过程中,快速再次点击按钮(模拟并发)
-
-**预期结果**:
-- ✅ 第一次点击后,按钮被禁用或显示"正在生成中"
-- ✅ 第二次点击被忽略或显示警告:"⏳ 正在生成中,请勿重复点击"
-- ✅ 只执行一次 API 调用
-- ✅ 生成完成后,按钮恢复可用
-
----
-
-### 测试用例 8:语义扩展功能
-
-**前置条件**:
-- 已生成关键词(至少 10 个)
-- 侧边栏已配置 API Key
-
-**测试步骤**:
-1. 滚动到"语义足迹扩展"区域
-2. 设置扩展数量为 30
-3. 选择合并策略为"追加"
-4. 点击"开始语义扩展"按钮
-5. 等待扩展完成
-
-**预期结果**:
-- ✅ 显示加载状态:"正在扩展关键词(目标:30 个)..."
-- ✅ 扩展完成后,关键词列表总数增加
-- ✅ 显示成功提示:"✅ 语义扩展完成!新增 X 个关键词,总计 Y 个"
-- ✅ 显示扩展统计信息(6 个指标)
-- ✅ 可以查看扩展详情和覆盖面分析
-
----
-
-### 测试用例 9:话题集群生成
-
-**前置条件**:
-- 已生成关键词(至少 20 个)
-- 侧边栏已配置 API Key
-
-**测试步骤**:
-1. 滚动到"话题集群生成"区域
-2. 设置话题集群数量为 5
-3. 点击"生成话题集群"按钮
-4. 等待生成完成
-
-**预期结果**:
-- ✅ 显示加载状态
-- ✅ 生成 5 个话题集群
-- ✅ 显示统计信息(4 个指标)
-- ✅ 显示话题集群列表(可展开查看详情)
-- ✅ 显示话题关联关系表格
-- ✅ 显示话题网络图(可视化)
-- ✅ 显示内容规划建议
-
----
-
-### 测试用例 10:清空功能
-
-**前置条件**:
-- 已生成关键词
-- 已执行扩展或集群生成
-
-**测试步骤**:
-1. 点击"清空结果"按钮
-2. 观察页面状态
-
-**预期结果**:
-- ✅ 显示 Toast 提示:"已清空所有关键词和相关数据"
-- ✅ `st.session_state.keywords` 被清空
-- ✅ `st.session_state.expanded_keywords` 被清空
-- ✅ `st.session_state.topic_clusters` 被清空
-- ✅ `st.session_state.mined_keywords` 被清空
-- ✅ 关键词列表区域消失
-- ✅ 扩展功能区域消失
-- ✅ 页面显示提示:"💡 提示:在左侧完成配置后,点击"生成关键词"开始使用。"
-
----
-
-### 测试用例 11:搜索和筛选功能
-
-**前置条件**:
-- 已生成关键词(包含扩展关键词)
-
-**测试步骤**:
-1. 在关键词列表的搜索框输入关键词(如"AI")
-2. 观察列表过滤结果
-3. 勾选"仅显示原始关键词"
-4. 观察列表变化
-
-**预期结果**:
-- ✅ 搜索框实时过滤关键词
-- ✅ 显示匹配的关键词数量
-- ✅ 勾选"仅显示原始关键词"后,只显示原始关键词(不包含扩展的)
-- ✅ 分页功能正常工作(如果关键词 > 20 个)
-
----
-
-### 测试用例 12:智能挖掘功能
-
-**前置条件**:
-- 已生成关键词
-- 侧边栏已配置 API Key
-
-**测试步骤**:
-1. 展开"智能关键词挖掘与趋势分析"区域
-2. 切换到"🌐 行业热点挖掘"标签
-3. 输入行业领域(如"AI工具")
-4. 设置挖掘数量为 20
-5. 点击"开始挖掘"按钮
-6. 等待挖掘完成
-7. 点击某个挖掘结果的"添加"按钮
-
-**预期结果**:
-- ✅ 显示加载状态
-- ✅ 挖掘完成,显示挖掘结果列表
-- ✅ 每个结果显示关键词、类别、意图、预估价值
-- ✅ 点击"添加"后,关键词被添加到主列表
-- ✅ 显示成功提示:"已添加"
-- ✅ 页面刷新,新关键词出现在列表中
-
----
-
-### 测试用例 13:词库管理 - 编辑词库
-
-**前置条件**:
-- 选择"托词工具"或"混合模式"
-- 词库已初始化
-
-**测试步骤**:
-1. 在"词库管理"区域,选择"编辑词库"标签
-2. 选择一个词库类型(如"核心词")
-3. 在文本框中添加新词汇(每行一个)
-4. 点击"更新词库"按钮
-5. 观察成功提示
-6. 重新生成关键词,验证新词汇被使用
-
-**预期结果**:
-- ✅ 显示当前词库内容
-- ✅ 可以编辑文本
-- ✅ 点击"更新词库"后,显示成功提示:"核心词 已更新(X 个词汇)"
-- ✅ 新词汇在生成关键词时被使用
-
----
-
-### 测试用例 14:词库管理 - 导入导出
-
-**前置条件**:
-- 选择"托词工具"或"混合模式"
-- 词库已初始化
-
-**测试步骤**:
-1. 在"词库管理"区域,选择"导入/导出"标签
-2. 点击"导出词库(JSON)"按钮
-3. 下载 JSON 文件
-4. 修改 JSON 文件(添加新词汇)
-5. 点击"导入词库(JSON)"按钮
-6. 上传修改后的 JSON 文件
-7. 观察导入结果
-
-**预期结果**:
-- ✅ 成功下载 JSON 文件
-- ✅ JSON 文件格式正确(包含所有词库类型)
-- ✅ 上传后显示成功提示:"词库导入成功!"
-- ✅ 页面刷新,新词库内容生效
-- ✅ 如果 JSON 格式错误,显示错误提示
-
----
-
-### 测试用例 15:边界条件测试
-
-**测试步骤**:
-1. 生成数量设置为最小值(5)
-2. 生成数量设置为最大值(200)
-3. 生成数量设置为手动输入(如 150)
-4. 在空状态下点击"生成关键词"(无配置)
-5. 在 API Key 无效时点击"生成关键词"
-
-**预期结果**:
-- ✅ 最小值/最大值正常工作
-- ✅ 手动输入值被正确应用
-- ✅ 空状态显示友好提示
-- ✅ API Key 无效时显示具体错误信息
-
----
-
-## 最小改动 MVP 优化清单(1 天内完成)
-
-### 优先级 P0(必须完成,约 4 小时)
-
-#### 1. 去掉 Tab 名称序号(5 分钟)
-- [ ] 修改第 913 行,去掉序号,保留 emoji
-- [ ] 测试所有 Tab 切换正常
-
-#### 2. 修复空值检查(30 分钟)
-- [ ] 在 `run_kw` 点击时,验证所有必需参数
-- [ ] 添加分场景错误提示
-- [ ] 测试各种缺失参数的情况
-
-#### 3. 修复状态同步问题(30 分钟)
-- [ ] 在生成新关键词时,清空扩展/集群相关状态
-- [ ] 在清空按钮中,清空所有相关状态
-- [ ] 测试状态同步
-
-#### 4. 添加并发点击防护(30 分钟)
-- [ ] 使用 `st.session_state.kw_generating` 标记
-- [ ] 在生成过程中禁用按钮
-- [ ] 测试并发点击
-
-#### 5. 优化错误提示(1 小时)
-- [ ] 分场景错误提示(AI生成/托词工具/混合模式)
-- [ ] 提供解决建议
-- [ ] 使用 markdown 格式化
-- [ ] 测试各种错误场景
-
-#### 6. 修复缓存问题(30 分钟)
-- [ ] 在生成前重新加载最新配置
-- [ ] 测试词库更新后的生成
-
-#### 7. 改进布局结构(1 小时)
-- [ ] 添加区域划分(注释 + 分隔线)
-- [ ] 使用容器分组主要功能
-- [ ] 智能挖掘默认折叠
-- [ ] 测试布局显示
-
-### 优先级 P1(建议完成,约 2 小时)
-
-#### 8. 添加搜索和筛选(30 分钟)
-- [ ] 添加搜索框
-- [ ] 添加"仅显示原始关键词"筛选
-- [ ] 测试搜索和筛选功能
-
-#### 9. 改进加载状态(30 分钟)
-- [ ] 使用进度条和详细状态文本
-- [ ] 分阶段进度提示
-- [ ] 测试加载状态显示
-
-#### 10. 优化默认值(20 分钟)
-- [ ] 确保 `kw_last_num` 默认值初始化
-- [ ] 扩大滑块范围(5-200)
-- [ ] 测试默认值
-
-#### 11. 改进结果展示(40 分钟)
-- [ ] 添加分页功能(如果关键词 > 20 个)
-- [ ] 改进原始/扩展关键词区分
-- [ ] 测试结果展示
-
-### 优先级 P2(可选,约 1 小时)
-
-#### 12. 添加撤销功能(30 分钟)
-- [ ] 在清空按钮添加确认对话框
-- [ ] 或添加撤销按钮(保存上一次状态)
-- [ ] 测试撤销功能
-
-#### 13. 优化行业热点挖掘默认值(10 分钟)
-- [ ] 默认使用 `brand` 作为行业领域
-- [ ] 允许覆盖
-- [ ] 测试默认值
-
-#### 14. 添加多格式导出(20 分钟)
-- [ ] 添加 JSON 格式导出
-- [ ] 添加 TXT 格式导出
-- [ ] 测试导出功能
-
----
-
-## 总结
-
-### 核心改动
-
-1. **去掉序号**:Tab 名称去掉序号,保留 emoji,提升现代感
-2. **布局优化**:明确区域划分,使用容器分组,智能挖掘默认折叠
-3. **逻辑修复**:空值检查、状态同步、并发防护、错误提示
-4. **体验提升**:搜索筛选、加载状态、分页、默认值优化
-
-### 预计工作量
-
-- **P0 改动**:4 小时(必须完成)
-- **P1 改动**:2 小时(建议完成)
-- **P2 改动**:1 小时(可选)
-- **总计**:7 小时(1 天内可完成 P0 + P1)
-
-### 风险控制
-
-- **逐步实施**:先完成 P0,测试通过后再做 P1
-- **保持功能**:只改布局和体验,不改核心逻辑
-- **充分测试**:每个改动后立即测试,确保功能正常
-
----
-
-**文档版本**:v1.0
-**最后更新**:2026-01-28
-**下一步**:开始实施 P0 改动
diff --git a/docs/analysis/TAB2_CODE_REVIEW.md b/docs/analysis/TAB2_CODE_REVIEW.md
deleted file mode 100644
index 194de0c..0000000
--- a/docs/analysis/TAB2_CODE_REVIEW.md
+++ /dev/null
@@ -1,546 +0,0 @@
-# Tab2(自动创作)代码审查报告
-
-## 📋 审查范围
-- 代码逻辑完整性
-- 错误处理和异常捕获
-- 边界条件和空值处理
-- 状态管理(session_state)
-- AI提示词质量和一致性
-- 性能优化点
-- 并发和竞态条件
-
----
-
-## 🔴 P0 严重问题(必须修复)
-
-### 1. **缺少内容生成异常处理**
-**位置**: 第2774行 `content = chain.invoke(...)`
-
-**问题**:
-- `chain.invoke()` 可能抛出异常(网络错误、API错误、超时等)
-- 当前代码没有 try-except 包裹,会导致整个生成流程中断
-- 批量生成时,一个失败会导致所有后续内容无法生成
-
-**影响**:
-- 用户体验差:生成过程中断,无明确错误提示
-- 数据丢失:已生成的内容可能丢失
-- 成本浪费:部分内容已生成但无法保存
-
-**修复建议**:
-```python
-try:
- content = chain.invoke({"keyword": keyword, "brand": brand, "advantages": advantages})
- if not content or not content.strip():
- raise ValueError("生成的内容为空")
-except Exception as e:
- error_msg = str(e)
- st.error(f"❌ 生成失败({keyword} - {plat}):{error_msg}")
- # 记录失败,但继续生成其他内容
- contents.append({
- "keyword": keyword,
- "platform": plat,
- "content": "",
- "ext": ext,
- "filename": filename,
- "score": None,
- "json_ld": None,
- "error": error_msg
- })
- continue # 跳过当前项,继续生成下一个
-```
-
-### 2. **批量生成时缺少空值检查**
-**位置**: 第2340行、第2356行
-
-**问题**:
-- 单篇模式:`selected_keyword` 可能为空(如果keywords列表为空)
-- 批量模式:`selected_keywords` 可能为空列表
-- `keywords_to_generate` 可能为空列表,但代码没有检查
-
-**影响**:
-- 用户点击生成按钮后,可能进入空循环
-- 进度条显示异常
-- 用户体验差
-
-**修复建议**:
-```python
-# 在生成按钮前添加检查
-if not keywords_to_generate:
- st.warning("⚠️ 请至少选择一个关键词")
- st.stop()
-
-# 或者在循环开始前检查
-if not keywords_to_generate:
- st.warning("⚠️ 没有可生成的内容")
- return
-```
-
-### 3. **进度条在异常情况下可能不清理**
-**位置**: 第2406-2407行、第2872-2873行
-
-**问题**:
-- 如果生成过程中抛出异常,进度条和状态文本可能不会被清理
-- 导致界面上残留进度显示
-
-**修复建议**:
-```python
-try:
- # ... 生成逻辑
-finally:
- # 确保进度显示被清理
- progress_bar.empty()
- status_text.empty()
-```
-
-### 4. **ZIP文件生成缺少异常处理**
-**位置**: 第2409行 `with zipfile.ZipFile(...)`
-
-**问题**:
-- ZIP文件操作可能失败(磁盘空间不足、权限问题等)
-- 当前没有异常处理
-
-**修复建议**:
-```python
-try:
- with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
- # ... 生成逻辑
-except Exception as e:
- st.error(f"❌ ZIP文件生成失败:{e}")
- # 即使ZIP失败,也应该保存单个文件
-```
-
----
-
-## 🟡 P1 重要问题(建议修复)
-
-### 5. **内容生成缺少超时控制**
-**位置**: 第2774行
-
-**问题**:
-- `chain.invoke()` 没有超时设置
-- 如果API响应慢或卡死,用户需要等待很长时间
-- 批量生成时,一个慢请求会阻塞整个流程
-
-**修复建议**:
-- 添加超时设置(建议30-60秒)
-- 超时后显示明确提示,允许重试
-
-### 6. **评分失败后的重试机制不完善**
-**位置**: 第2842-2847行、第3103-3120行
-
-**问题**:
-- 评分失败后,虽然有重试按钮,但重试时可能再次失败
-- 没有重试次数限制
-- 没有区分临时错误(网络)和永久错误(API配置)
-
-**修复建议**:
-- 添加重试次数限制(最多3次)
-- 区分错误类型,给出针对性提示
-- 对于API配置错误,提示用户检查配置
-
-### 7. **批量生成时缺少并发控制**
-**位置**: 第2410行循环
-
-**问题**:
-- 批量生成是串行的,如果生成10篇内容,每篇需要30秒,总共需要5分钟
-- 没有提供"取消生成"的机制
-- 用户无法中断长时间运行的生成任务
-
-**修复建议**:
-- 添加"取消生成"按钮(使用session_state标志)
-- 考虑异步生成(如果Streamlit支持)
-
-### 8. **内容为空或格式错误的处理**
-**位置**: 第2774行之后
-
-**问题**:
-- 生成的内容可能为空字符串
-- 生成的内容可能格式错误(不符合平台要求)
-- 当前没有验证生成内容的质量
-
-**修复建议**:
-```python
-content = chain.invoke(...)
-# 验证内容
-if not content or len(content.strip()) < 50:
- st.warning(f"⚠️ 生成的内容过短,可能不完整:{keyword}")
- # 可以选择重试或继续
-```
-
-### 9. **状态同步问题**
-**位置**: 多处使用 `st.session_state.generated_contents`
-
-**问题**:
-- 在详情页面修改内容后(如替换为图文版本),需要更新 `generated_contents`
-- 但更新逻辑可能不完整,导致状态不一致
-
-**修复建议**:
-- 统一内容更新逻辑
-- 添加状态验证函数
-
----
-
-## 🟢 P2 优化建议(可选)
-
-### 10. **提示词优化建议**
-
-#### 10.1 提示词格式不一致
-**问题**:
-- 部分平台有E-E-A-T要求,部分没有
-- 格式标记不统一(有的用【】,有的用-)
-
-**建议**:
-- 统一所有提示词的格式
-- 为所有平台添加E-E-A-T要求(如果适用)
-
-#### 10.2 提示词长度优化
-**问题**:
-- 部分提示词过长,可能影响生成质量
-- 关键要求可能被淹没
-
-**建议**:
-- 精简提示词,突出核心要求
-- 使用更清晰的结构化格式
-
-#### 10.3 品牌名称在提示词中的使用
-**位置**: 多处提示词中使用 `{brand}`
-
-**问题**:
-- 如果brand为空,提示词中会出现空字符串
-- 可能导致生成内容质量下降
-
-**修复建议**:
-```python
-# 在生成前检查
-if not brand or not brand.strip():
- st.error("❌ 品牌名称不能为空,请在侧边栏配置")
- st.stop()
-```
-
-### 11. **性能优化**
-
-#### 11.1 评分可以异步进行
-**位置**: 第2831-2847行
-
-**问题**:
-- 评分是同步的,会阻塞生成流程
-- 批量生成时,每篇都要等待评分完成
-
-**建议**:
-- 评分可以延迟到生成完成后统一进行
-- 或者使用后台任务
-
-#### 11.2 重复初始化对象
-**位置**: 第2402行、第2819行等
-
-**问题**:
-- 每次生成都创建新的 `ContentScorer()` 和 `SchemaGenerator()`
-- 可以复用对象
-
-**建议**:
-- 在循环外初始化这些对象
-
-### 12. **用户体验优化**
-
-#### 12.1 生成时间估算
-**问题**:
-- 用户不知道生成需要多长时间
-- 批量生成时,无法预估完成时间
-
-**建议**:
-- 根据历史数据估算时间
-- 显示预计完成时间
-
-#### 12.2 生成结果预览
-**问题**:
-- 批量生成时,用户需要等待所有内容生成完成才能查看
-- 无法实时查看已生成的内容
-
-**建议**:
-- 每生成一篇,立即添加到列表
-- 允许用户边生成边查看
-
----
-
-## 📝 AI提示词质量检查
-
-### ✅ 优点
-1. **结构清晰**: 所有提示词都使用了【】标记,结构清晰
-2. **要求明确**: 每个平台都有明确的要求列表
-3. **品牌融入**: 所有提示词都考虑了品牌自然提及
-
-### ⚠️ 需要改进
-
-#### 1. **E-E-A-T要求不一致**
-- **有E-E-A-T要求的平台**: 知乎、CSDN
-- **缺少E-E-A-T要求的平台**: 小红书、B站、头条号、抖音等
-
-**建议**: 为所有平台添加E-E-A-T要求(如果适用)
-
-#### 2. **提示词长度差异大**
-- 最短:B站(约10行)
-- 最长:微信公众号(约15行)
-
-**建议**: 统一提示词长度和详细程度
-
-#### 3. **格式标记不统一**
-- 大部分使用【】标记
-- 但E-E-A-T部分使用 `-` 标记
-
-**建议**: 统一使用【】标记
-
-#### 4. **缺少输出格式示例**
-**问题**: 提示词中只说了"清晰标题顺序输出",但没有给出具体示例
-
-**建议**: 为关键平台添加输出格式示例
-
----
-
-## 🔍 代码逻辑检查
-
-### ✅ 正确的逻辑
-1. **状态初始化**: 生成前正确清空旧状态
-2. **进度显示**: 正确更新进度条和状态文本
-3. **错误处理**: 评分和数据库保存都有异常处理
-4. **文件扩展名**: 正确根据平台选择扩展名
-
-### ⚠️ 潜在问题
-
-#### 1. **边界条件**
-```python
-# 问题:如果keywords_to_generate为空,total_items为0,会导致除零错误
-total_items = len(keywords_to_generate) # 可能为0
-progress = (idx + 1) / total_items # 如果total_items=0会报错
-```
-
-**修复**:
-```python
-if total_items == 0:
- st.warning("⚠️ 没有可生成的内容")
- return
-```
-
-#### 2. **空值检查不完整**
-```python
-# 问题:如果content为空,后续处理可能出错
-content = chain.invoke(...)
-# 没有检查content是否为空
-```
-
-**修复**:
-```python
-content = chain.invoke(...)
-if not content or not content.strip():
- st.warning(f"⚠️ 生成的内容为空:{keyword}")
- continue
-```
-
-#### 3. **文件名可能重复**
-**问题**: 如果同一个关键词生成多次,文件名可能重复
-
-**修复**: 添加时间戳或序号
-
----
-
-## 🧪 测试用例建议
-
-### 1. **正常流程测试**
-- ✅ 单篇生成:选择1个关键词 + 1个平台 → 生成成功
-- ✅ 批量生成:选择3个关键词 + 1个平台 → 生成3篇内容
-- ✅ 批量生成:选择1个关键词 + 3个平台 → 生成3篇内容(需要修改代码支持)
-
-### 2. **边界条件测试**
-- ⚠️ 空关键词列表 → 应显示提示,不允许生成
-- ⚠️ 批量生成时选择0个关键词 → 应显示警告
-- ⚠️ 生成内容为空 → 应显示错误提示
-- ⚠️ API超时 → 应显示超时错误,允许重试
-
-### 3. **异常情况测试**
-- ⚠️ 网络中断 → 应捕获异常,显示错误
-- ⚠️ API Key无效 → 应显示配置错误
-- ⚠️ 生成过程中刷新页面 → 应正确处理状态
-
-### 4. **并发测试**
-- ⚠️ 快速连续点击生成按钮 → 应防止重复生成
-- ⚠️ 生成过程中点击其他功能 → 应正确处理
-
----
-
-## 📊 优先级修复清单
-
-### 立即修复(P0)- ✅ 已完成
-1. ✅ **已修复** - 添加内容生成的异常处理(第2774行)
- - 添加了try-except包裹chain.invoke()
- - 添加了内容验证(空值、长度检查)
- - 失败时记录错误但继续生成其他内容
-
-2. ✅ **已修复** - 添加空值检查(第2340、2356行)
- - 生成前检查keywords_to_generate是否为空
- - 检查brand和advantages是否为空
- - 检查total_items是否为0
-
-3. ✅ **已修复** - 添加进度条清理的finally块
- - 使用try-finally确保进度显示被清理
- - 即使发生异常也能清理UI元素
-
-4. ✅ **已修复** - 添加ZIP生成的异常处理
- - ZIP操作包裹在try-except中
- - 即使ZIP失败,也保存已生成的内容
-
-5. ✅ **已修复** - 修复索引查找问题
- - 使用更安全的方法查找原始索引
- - 避免重复项导致的索引错误
- - 添加边界检查确保索引有效
-
-6. ✅ **已修复** - 改进错误提示
- - 区分不同类型的错误(网络、API配置、其他)
- - 提供更明确的错误信息
- - 区分生成失败和评分失败
-
-### 尽快修复(P1)
-5. ⚠️ 添加超时控制
-6. ⚠️ 完善重试机制
-7. ⚠️ 添加取消生成功能
-8. ⚠️ 添加内容验证
-
-### 优化改进(P2)
-9. ⚠️ 统一提示词格式
-10. ⚠️ 性能优化(对象复用)
-11. ⚠️ 用户体验优化(时间估算、实时预览)
-
----
-
-## 🔧 具体修复代码
-
-### 修复1: 添加内容生成异常处理
-```python
-try:
- content = chain.invoke({"keyword": keyword, "brand": brand, "advantages": advantages})
- if not content or not content.strip():
- raise ValueError("生成的内容为空")
-except Exception as e:
- error_msg = str(e)
- st.error(f"❌ 生成失败({keyword} - {plat}):{error_msg}")
- # 记录失败,但继续生成其他内容
- contents.append({
- "keyword": keyword,
- "platform": plat,
- "content": f"[生成失败:{error_msg}]",
- "ext": "txt",
- "filename": f"{sanitize_filename(plat,30)}_{sanitize_filename(brand,30)}_{sanitize_filename(keyword,60)}_ERROR.txt",
- "score": None,
- "json_ld": None,
- "error": error_msg
- })
- continue
-```
-
-### 修复2: 添加空值检查
-```python
-# 在生成按钮前
-if not keywords_to_generate:
- st.warning("⚠️ 请至少选择一个关键词进行生成")
- st.stop()
-
-# 在循环开始前
-if len(keywords_to_generate) == 0:
- st.warning("⚠️ 没有可生成的内容")
- return
-```
-
-### 修复3: 添加进度条清理
-```python
-try:
- # ... 生成逻辑
-finally:
- # 确保进度显示被清理
- if 'progress_bar' in locals():
- progress_bar.empty()
- if 'status_text' in locals():
- status_text.empty()
-```
-
----
-
-## 📌 总结
-
-### 主要问题
-1. ✅ **已修复** - 缺少关键异常处理 - 内容生成没有try-except,容易中断
-2. ✅ **已修复** - 边界条件检查不足 - 空值、空列表没有充分检查
-3. ⚠️ **待优化** - 提示词格式不统一 - 需要统一格式和E-E-A-T要求
-
-### 已完成的修复
-1. ✅ 添加了内容生成的完整异常处理
-2. ✅ 添加了空值检查(keywords、brand、advantages)
-3. ✅ 添加了进度条清理的finally块
-4. ✅ 添加了ZIP生成的异常处理
-5. ✅ 修复了索引查找问题(避免重复项错误)
-6. ✅ 改进了错误提示(区分错误类型)
-7. ✅ 添加了内容验证(空值、长度检查)
-8. ✅ 优化了对象初始化(避免重复创建)
-
-### 待优化项目(P1/P2)
-1. ⚠️ 添加超时控制(建议30-60秒)
-2. ⚠️ 完善重试机制(添加重试次数限制)
-3. ⚠️ 添加取消生成功能
-4. ⚠️ 统一提示词格式(E-E-A-T要求、格式标记)
-5. ⚠️ 性能优化(异步评分、对象复用)
-
-### 测试重点
-- ✅ 异常情况下的行为(已修复)
-- ✅ 边界条件的处理(已修复)
-- ⚠️ 批量生成的稳定性(需要测试)
-- ⚠️ 提示词质量(需要验证生成效果)
-
----
-
-## 🔍 提示词详细分析
-
-### 当前状态
-- **有E-E-A-T要求的平台**: 知乎、CSDN(2个)
-- **缺少E-E-A-T要求的平台**: 其他18个平台
-
-### 建议
-1. **为专业平台添加E-E-A-T要求**:
- - 微信公众号(长文)
- - 百家号(资讯)
- - 网易号(资讯)
- - 新浪新闻(资讯)
- - 东方财富(财经)
- - 原创力文档(文档)
- - 邦阅网(外贸)
-
-2. **为生活化平台保持简洁**(不需要E-E-A-T):
- - 小红书(生活种草)
- - 抖音图文(短内容)
- - QQ空间(社交)
- - B站(视频脚本)- 视频脚本不需要E-E-A-T
-
-3. **统一格式标记**:
- - 所有要求使用数字编号:`1) 2) 3)`
- - E-E-A-T要求使用 `-` 标记(保持现状)
- - 所有平台都有【格式】标记
-
-### 提示词质量评分
-- **完整性**: ⭐⭐⭐⭐☆ (4/5) - 大部分平台要求明确
-- **一致性**: ⭐⭐⭐☆☆ (3/5) - E-E-A-T要求不一致
-- **清晰度**: ⭐⭐⭐⭐☆ (4/5) - 要求表述清晰
-- **专业性**: ⭐⭐⭐⭐☆ (4/5) - 符合GEO原则
-
----
-
-## 📋 修复验证清单
-
-### 已修复项目验证
-- [ ] 测试:空关键词列表 → 应显示警告
-- [ ] 测试:空brand/advantages → 应显示错误
-- [ ] 测试:生成失败 → 应显示错误但继续生成其他
-- [ ] 测试:评分失败 → 应显示警告,可重试
-- [ ] 测试:ZIP失败 → 应保存单个文件
-- [ ] 测试:进度条 → 异常时也应清理
-
-### 待测试项目
-- [ ] 批量生成10篇内容
-- [ ] 网络中断时的行为
-- [ ] API超时的处理
-- [ ] 快速连续点击的防护
diff --git a/docs/analysis/TAB2_COMPREHENSIVE_REVIEW.md b/docs/analysis/TAB2_COMPREHENSIVE_REVIEW.md
deleted file mode 100644
index 0396139..0000000
--- a/docs/analysis/TAB2_COMPREHENSIVE_REVIEW.md
+++ /dev/null
@@ -1,556 +0,0 @@
-# Tab2(自动创作)全面代码审查报告
-
-> 对Tab2中的所有代码逻辑、AI提示词、错误处理、性能优化进行全面检查
-> 审查日期:2026-01-28
-
----
-
-## 📋 审查范围
-
-1. **AI提示词质量**:20个平台的提示词一致性、完整性、专业性
-2. **代码逻辑完整性**:边界条件、状态管理、数据流
-3. **错误处理**:异常捕获、错误提示、容错机制
-4. **性能优化**:对象复用、异步处理、资源管理
-5. **用户体验**:交互流程、反馈提示、功能完整性
-
----
-
-## 🔍 一、AI提示词质量检查
-
-### 1.1 提示词结构一致性
-
-#### ✅ 优点
-- 所有平台都使用统一的【】标记格式
-- 都有明确的【要求】、【格式】、【开始】结构
-- 品牌和优势参数统一使用 `{brand}` 和 `{advantages}`
-
-#### ⚠️ 问题
-
-**问题1:E-E-A-T要求不一致**
-- **有E-E-A-T要求的平台**(2个):
- - 知乎(专业问答)- 第2451-2455行
- - CSDN(技术博客)- 第2486-2490行
-- **缺少E-E-A-T要求的平台**(18个):
- - 小红书、B站、头条号、微信公众号、抖音图文、百家号、网易号、企鹅号、简书、新浪博客、新浪新闻、搜狐号、QQ空间、邦阅网、一点号、东方财富、原创力文档、GitHub
-
-**影响**:
-- 专业平台(如微信公众号、百家号、东方财富)应该也有E-E-A-T要求
-- 内容质量可能不一致
-
-**建议**:
-```python
-# 为以下平台添加E-E-A-T要求:
-- 微信公众号(长文)- 专业长文平台
-- 百家号(资讯)- 百度搜索优化
-- 网易号(资讯)- 专业资讯平台
-- 新浪新闻(资讯)- 新闻平台
-- 东方财富(财经)- 专业财经平台
-- 原创力文档(文档)- 专业文档平台
-- 邦阅网(外贸)- 专业外贸平台
-```
-
----
-
-### 1.2 提示词内容质量
-
-#### ✅ 优点
-- 每个平台都有明确的角色定位("你是GEO专家 + XXX作者")
-- 要求具体明确(如"3个标题备选"、"自然提及品牌2-4次")
-- 格式要求清晰(如"标题-正文-标签-搜索词")
-
-#### ⚠️ 问题
-
-**问题2:部分平台提示词过于简单**
-- **B站(视频脚本)**(第2494-2505行):
- - 只有4条要求,缺少详细说明
- - 没有字数要求
- - 没有E-E-A-T要求
-
-**问题3:部分平台缺少关键要求**
-- **头条号(资讯软文)**(第2507-2518行):
- - 缺少字数要求
- - 缺少格式详细说明
- - 没有E-E-A-T要求
-
-**问题4:GitHub README提示词位置错误**
-- **GitHub(README/文档)**(第2763-2781行):
- - 在 `else` 分支中,如果平台名称不匹配会使用GitHub模板
- - 应该明确放在 `elif plat == "GitHub(README/文档)"` 分支中
-
----
-
-### 1.3 提示词参数使用
-
-#### ✅ 优点
-- 所有提示词都正确使用 `{keyword}`, `{brand}`, `{advantages}` 参数
-- 参数在 `content_template.format()` 中正确传递
-
-#### ⚠️ 问题
-
-**问题5:品牌名称在提示词中的硬编码**
-- **CSDN(技术博客)**(第2483行):
- - 使用 `{brand}案例(匿名)`,但应该更通用
- - 建议改为:"品牌案例(匿名)"或"相关案例"
-
----
-
-### 1.4 优化技巧集成
-
-#### ✅ 优点
-- 支持通过 `OptimizationTechniqueManager` 动态增强提示词
-- 在生成前正确应用优化技巧(第2784-2789行)
-
-#### ⚠️ 问题
-
-**问题6:优化技巧可能覆盖E-E-A-T要求**
-- 如果优化技巧修改了提示词,可能覆盖原有的E-E-A-T要求
-- 需要确保优化技巧不会删除关键要求
-
----
-
-## 🔍 二、代码逻辑完整性检查
-
-### 2.1 边界条件处理
-
-#### ✅ 优点
-- 生成前检查 `keywords_to_generate` 是否为空(第2396-2398行)
-- 检查 `brand` 和 `advantages` 是否为空(第2401-2407行)
-- 检查 `total_items` 是否为0(第2419-2421行)
-- 内容生成后验证内容是否为空(第2802-2803行)
-- 内容长度验证(第2805-2806行)
-
-#### ⚠️ 问题
-
-**问题7:selected_keyword 可能为空**
-- 单篇模式下,如果 `st.session_state.keywords` 为空列表,`selected_keyword` 可能为 `None`
-- 虽然外层有检查,但应该在 `selectbox` 后立即检查
-
-**问题8:批量模式下 selected_keywords 可能为空列表**
-- 第2345行的 `multiselect` 可能返回空列表
-- 虽然有第2396行的检查,但应该在form提交前就提示用户
-
----
-
-### 2.2 状态管理
-
-#### ✅ 优点
-- 使用 `st.session_state` 正确管理状态
-- 生成前清空旧状态(第2408-2412行)
-- 使用 `ss_init()` 初始化状态(多处)
-
-#### ⚠️ 问题
-
-**问题9:状态同步可能不一致**
-- 在详情页面修改内容后(如替换为图文版本),需要更新 `generated_contents`
-- 第3583-3585行有更新逻辑,但可能在某些情况下不完整
-
-**问题10:selected_content_idx 边界检查**
-- 第3089-3092行有边界检查,但应该在每次使用前都检查
-- 如果 `generated_contents` 被清空,`selected_idx` 可能无效
-
----
-
-### 2.3 数据流完整性
-
-#### ✅ 优点
-- 内容生成 → 评分 → 保存到数据库 → 添加到ZIP → 更新状态,流程完整
-- 每个步骤都有异常处理
-
-#### ⚠️ 问题
-
-**问题11:数据库保存失败不影响主流程**
-- 第2921-2924行:数据库保存失败只显示警告,不影响内容生成
-- 这是合理的,但应该考虑重试机制或队列机制
-
-**问题12:ZIP文件生成失败时的处理**
-- 第2931-2938行:ZIP失败时保存单个文件
-- 但如果 `contents` 为空,用户无法下载任何内容
-- 应该至少保存第一篇文章
-
----
-
-## 🔍 三、错误处理检查
-
-### 3.1 异常捕获
-
-#### ✅ 优点
-- 内容生成有完整的 try-except(第2798-2823行)
-- ZIP文件生成有异常处理(第2931-2938行)
-- 评分失败有异常处理(第2895-2907行)
-- JSON-LD生成有异常处理(第2878-2879行)
-- 数据库保存有异常处理(第2921-2924行)
-
-#### ⚠️ 问题
-
-**问题13:缺少超时控制**
-- `chain.invoke()` 没有超时设置
-- 如果API响应慢或卡死,用户需要等待很长时间
-- 批量生成时,一个慢请求会阻塞整个流程
-
-**问题14:错误类型区分不够细致**
-- 第2899-2904行:只区分了网络错误、API配置错误、其他错误
-- 应该更细致地区分:超时、网络中断、API限流、内容违规等
-
----
-
-### 3.2 错误提示
-
-#### ✅ 优点
-- 错误提示明确(如"❌ 生成失败({keyword} - {plat}):{error_msg}")
-- 区分不同类型的错误(网络、API配置等)
-- 失败时继续生成其他内容,不中断整个流程
-
-#### ⚠️ 问题
-
-**问题15:错误信息可能对用户不友好**
-- 某些技术错误信息(如API错误码)可能用户不理解
-- 应该转换为更友好的提示,并提供解决建议
-
----
-
-### 3.3 容错机制
-
-#### ✅ 优点
-- 生成失败时记录错误但继续生成其他内容
-- ZIP失败时保存已生成的内容
-- 评分失败时标记但保留内容
-
-#### ⚠️ 问题
-
-**问题16:重试机制不完善**
-- 评分失败后有重试按钮(第3200行),但没有重试次数限制
-- 内容生成失败后没有自动重试机制
-- 应该添加重试次数限制和退避策略
-
----
-
-## 🔍 四、性能优化检查
-
-### 4.1 对象复用
-
-#### ✅ 优点
-- `ContentScorer()` 在循环外初始化(第2427行)
-- `SchemaGenerator()` 延迟初始化(第2428行,第2868行)
-- 避免重复创建对象
-
-#### ⚠️ 问题
-
-**问题17:每次循环都创建新的 chain**
-- 第2791-2792行:每次循环都创建新的 `PromptTemplate` 和 `chain`
-- 虽然影响不大,但可以优化为复用模板
-
-**问题18:评分是同步的,阻塞生成流程**
-- 第2886-2888行:评分是同步的,会阻塞生成流程
-- 批量生成时,每篇都要等待评分完成
-- 可以考虑异步评分或延迟评分
-
----
-
-### 4.2 资源管理
-
-#### ✅ 优点
-- ZIP文件使用 `with` 语句管理(第2431行)
-- 进度条使用 `finally` 确保清理(第2939-2944行)
-
-#### ⚠️ 问题
-
-**问题19:内存使用可能过大**
-- 批量生成时,所有内容都保存在内存中(`contents` 列表)
-- 如果生成大量内容,可能占用大量内存
-- 应该考虑流式处理或分批保存
-
----
-
-### 4.3 并发控制
-
-#### ⚠️ 问题
-
-**问题20:没有并发控制**
-- 批量生成是串行的,如果生成10篇内容,每篇需要30秒,总共需要5分钟
-- 没有提供"取消生成"的机制
-- 用户无法中断长时间运行的生成任务
-
-**建议**:
-```python
-# 添加取消生成功能
-if 'cancel_generation' not in st.session_state:
- st.session_state.cancel_generation = False
-
-# 在生成循环中添加检查
-for idx, (keyword, plat) in enumerate(keywords_to_generate):
- if st.session_state.cancel_generation:
- st.warning("⚠️ 生成已取消")
- break
-```
-
----
-
-## 🔍 五、用户体验检查
-
-### 5.1 交互流程
-
-#### ✅ 优点
-- 生成模式选择清晰(单篇/批量)
-- 高级优化技巧折叠,默认收起
-- 生成后显示统计卡片和内容列表
-- 使用Tabs组织详情(内容预览、质量分析、增强工具)
-
-#### ⚠️ 问题
-
-**问题21:生成时间无法预估**
-- 用户不知道生成需要多长时间
-- 批量生成时,无法预估完成时间
-- 应该显示预计完成时间或进度百分比
-
-**问题22:生成结果无法实时预览**
-- 批量生成时,用户需要等待所有内容生成完成才能查看
-- 无法实时查看已生成的内容
-- 受Streamlit限制,但可以考虑分批显示
-
----
-
-### 5.2 反馈提示
-
-#### ✅ 优点
-- 进度条和状态文本实时更新(第2434-2436行)
-- 生成完成后显示成功/失败统计(第2947-2964行)
-- 错误提示明确
-
-#### ⚠️ 问题
-
-**问题23:进度条可能不准确**
-- 如果某篇内容生成失败,进度条仍然会前进
-- 但实际生成的内容数量可能少于预期
-- 应该显示"成功生成 X/Y 篇"
-
----
-
-### 5.3 功能完整性
-
-#### ✅ 优点
-- 支持单篇和批量生成
-- 支持优化技巧选择
-- 支持内容评分
-- 支持图片生成
-- 支持JSON-LD生成(GitHub平台)
-
-#### ⚠️ 问题
-
-**问题24:批量生成时无法选择不同平台**
-- 批量模式下,所有关键词使用同一个平台
-- 无法为不同关键词选择不同平台
-- 这是一个设计选择,但可以添加"多平台批量生成"功能
-
----
-
-## 📊 问题优先级总结
-
-### 🔴 P0 严重问题(必须修复)
-
-1. **GitHub README提示词位置错误**(问题4)
- - 应该在明确的 `elif` 分支中,而不是 `else` 分支
-
-2. **selected_keyword 可能为空**(问题7)
- - 应该在 `selectbox` 后立即检查
-
-3. **selected_content_idx 边界检查不完整**(问题10)
- - 应该在每次使用前都检查
-
-### 🟡 P1 重要问题(建议修复)
-
-4. **E-E-A-T要求不一致**(问题1)
- - 为专业平台添加E-E-A-T要求
-
-5. **缺少超时控制**(问题13)
- - 添加超时设置(建议30-60秒)
-
-6. **重试机制不完善**(问题16)
- - 添加重试次数限制和退避策略
-
-7. **没有并发控制**(问题20)
- - 添加"取消生成"功能
-
-8. **部分平台提示词过于简单**(问题2、3)
- - 完善B站、头条号等平台的提示词
-
-### 🟢 P2 优化建议(可选)
-
-9. **优化技巧可能覆盖E-E-A-T要求**(问题6)
- - 确保优化技巧不会删除关键要求
-
-10. **错误类型区分不够细致**(问题14)
- - 更细致地区分错误类型
-
-11. **评分可以异步进行**(问题17)
- - 考虑异步评分或延迟评分
-
-12. **生成时间无法预估**(问题21)
- - 显示预计完成时间
-
----
-
-## ✅ 修复建议
-
-### 修复1:为专业平台添加E-E-A-T要求
-
-```python
-elif plat == "微信公众号(长文)":
- content_template = """
-你是GEO专家 + 微信公众号作者。
-【关键词】{keyword}
-【品牌】{brand}
-【优势】{advantages}
-【要求】
-1) 3个吸引人的标题(适合公众号)
-2) 开头:场景化引入、痛点共鸣
-3) 正文:结构化分段、小标题清晰、配图建议(用【配图:xxx】标注)
-4) 自然提及品牌3-5次,先讲通用标准再推荐品牌
-5) 结尾:总结+行动号召+关注引导
-6) 适合公众号的排版:段落分明、重点加粗提示、适当使用emoji
-7) 字数:1500-3000字
-【E-E-A-T 强化要求】
-- 专业性:使用专业术语,展示深度知识
-- 经验性:包含实际使用经验或案例(用"实际测试"、"使用中发现"等表述)
-- 权威性:添加来源占位(如"根据XX行业报告"、"参考XX标准"),至少2处数据来源占位
-- 可信度:明确标注不确定信息(如"据公开资料"、"建议参考"),避免编造具体数据
-【格式】清晰分段,标注配图位置
-【开始】
-"""
-```
-
-### 修复2:修复GitHub README提示词位置
-
-```python
-elif plat == "GitHub(README/文档)":
- content_template = """
-你是GEO专家 + 开源项目维护者。
-生成GitHub README或项目文档,目标是被大模型(尤其是代码模型)优先引用。
-
-【关键词】{keyword}
-【品牌】{brand}
-【优势】{advantages}
-
-【要求】
-1) 标准README结构:标题、描述、特性清单、安装步骤、用法示例(代码块)
-2) {brand}自然集成作为核心工具/模型
-3) 加入徽章占位、贡献指南、引用建议
-4) 代码块真实占位,避免编造
-5) 自然提及品牌2-4次
-
-【格式】Markdown完整输出
-
-【开始】
-"""
-else:
- # 不应该到达这里,如果到达说明平台名称不匹配
- st.error(f"❌ 未知平台:{plat}")
- continue
-```
-
-### 修复3:添加超时控制
-
-```python
-from langchain_core.runnables import RunnableConfig
-import signal
-
-# 在生成前设置超时
-try:
- config = RunnableConfig(timeout=60) # 60秒超时
- content = chain.invoke(
- {"keyword": keyword, "brand": brand, "advantages": advantages},
- config=config
- )
-except TimeoutError:
- st.error(f"❌ 生成超时({keyword} - {plat}),请稍后重试")
- contents.append({
- "keyword": keyword,
- "platform": plat,
- "content": f"[生成超时]",
- "error": "生成超时"
- })
- continue
-```
-
-### 修复4:添加取消生成功能
-
-```python
-# 在进度显示区域添加取消按钮
-if progress_bar:
- cancel_col, _ = st.columns([1, 4])
- with cancel_col:
- if st.button("❌ 取消生成", key="cancel_gen_btn"):
- st.session_state.cancel_generation = True
- st.rerun()
-
-# 在生成循环中添加检查
-for idx, (keyword, plat) in enumerate(keywords_to_generate):
- if st.session_state.get("cancel_generation", False):
- st.warning("⚠️ 生成已取消")
- break
-```
-
----
-
-## 📋 检查清单
-
-### 提示词质量
-- [x] 检查所有20个平台的提示词
-- [x] 检查E-E-A-T要求一致性
-- [x] 检查提示词结构一致性
-- [x] 检查参数使用正确性
-- [ ] 为专业平台添加E-E-A-T要求(待修复)
-- [ ] 完善简单平台的提示词(待修复)
-
-### 代码逻辑
-- [x] 检查边界条件处理
-- [x] 检查状态管理
-- [x] 检查数据流完整性
-- [ ] 修复GitHub README提示词位置(待修复)
-- [ ] 完善边界检查(待修复)
-
-### 错误处理
-- [x] 检查异常捕获
-- [x] 检查错误提示
-- [x] 检查容错机制
-- [ ] 添加超时控制(待修复)
-- [ ] 完善重试机制(待修复)
-
-### 性能优化
-- [x] 检查对象复用
-- [x] 检查资源管理
-- [x] 检查并发控制
-- [ ] 优化chain创建(可选)
-- [ ] 考虑异步评分(可选)
-
-### 用户体验
-- [x] 检查交互流程
-- [x] 检查反馈提示
-- [x] 检查功能完整性
-- [ ] 添加取消生成功能(待修复)
-- [ ] 显示预计完成时间(可选)
-
----
-
-## 📊 总体评价
-
-### ✅ 优点
-1. **代码质量高**:异常处理完善,边界条件检查充分
-2. **用户体验好**:交互流程清晰,反馈及时
-3. **功能完整**:支持单篇/批量生成、优化技巧、评分、图片生成等
-4. **提示词结构统一**:所有平台都使用统一的格式
-
-### ⚠️ 需要改进
-1. **E-E-A-T要求不一致**:只有2个平台有E-E-A-T要求,应该为专业平台添加
-2. **缺少超时控制**:API调用可能无限等待
-3. **缺少取消功能**:无法中断长时间运行的生成任务
-4. **部分提示词过于简单**:B站、头条号等平台需要完善
-
-### 📈 改进优先级
-1. **高优先级**:修复GitHub README提示词位置、添加E-E-A-T要求、添加超时控制
-2. **中优先级**:完善重试机制、添加取消功能、完善简单平台的提示词
-3. **低优先级**:异步评分、显示预计完成时间、优化chain创建
-
----
-
-*报告生成时间:2026-01-28*
-*审查代码版本:geo_tool.py (7229行)*
diff --git a/docs/analysis/TAB2_FIXES_SUMMARY.md b/docs/analysis/TAB2_FIXES_SUMMARY.md
deleted file mode 100644
index 1fd5497..0000000
--- a/docs/analysis/TAB2_FIXES_SUMMARY.md
+++ /dev/null
@@ -1,285 +0,0 @@
-# Tab2(自动创作)修复总结
-
-> 修复日期:2026-01-28
-> 基于 `TAB2_COMPREHENSIVE_REVIEW.md` 的全面检查报告
-
----
-
-## ✅ 已完成的修复
-
-### 🔴 P0 严重问题(已全部修复)
-
-#### 1. ✅ 修复GitHub README提示词位置错误
-**问题**:GitHub README提示词在 `else` 分支中,如果平台名称不匹配会错误使用GitHub模板
-
-**修复**:
-- 将GitHub README提示词移到明确的 `elif plat == "GitHub(README/文档)"` 分支
-- 在 `else` 分支中添加错误处理,提示未知平台
-
-**代码位置**:`geo_tool.py` 第2824-2843行
-
----
-
-#### 2. ✅ 修复selected_keyword可能为空的问题
-**问题**:单篇模式下,如果 `st.session_state.keywords` 为空列表,`selected_keyword` 可能为 `None`
-
-**修复**:
-- 在 `selectbox` 后立即检查 `selected_keyword` 是否为空
-- 只有选择了关键词才添加到 `keywords_to_generate` 列表
-
-**代码位置**:`geo_tool.py` 第2327-2341行
-
----
-
-#### 3. ✅ 完善selected_content_idx边界检查
-**问题**:`selected_content_idx` 边界检查不完整,如果 `generated_contents` 被清空,索引可能无效
-
-**修复**:
-- 在每次使用前都检查边界
-- 添加对 `generated_contents` 是否为空的检查
-- 确保索引始终有效
-
-**代码位置**:`geo_tool.py` 第3210-3227行
-
----
-
-### 🟡 P1 重要问题(已全部修复)
-
-#### 4. ✅ 为专业平台添加E-E-A-T要求
-**问题**:只有知乎和CSDN有E-E-A-T要求,其他18个平台缺少
-
-**修复**:为以下专业平台添加了E-E-A-T要求:
-- ✅ 微信公众号(长文)
-- ✅ 百家号(资讯)
-- ✅ 网易号(资讯)
-- ✅ 新浪新闻(资讯)
-- ✅ 东方财富(财经)
-- ✅ 原创力文档(文档)
-- ✅ 邦阅网(外贸)
-
-**代码位置**:
-- 微信公众号:`geo_tool.py` 第2519-2538行
-- 百家号:`geo_tool.py` 第2553-2572行
-- 网易号:`geo_tool.py` 第2570-2590行
-- 新浪新闻:`geo_tool.py` 第2639-2660行
-- 东方财富:`geo_tool.py` 第2726-2748行
-- 原创力文档:`geo_tool.py` 第2804-2823行
-- 邦阅网:`geo_tool.py` 第2692-2713行
-
----
-
-#### 5. ✅ 添加超时控制和重试机制
-**问题**:`chain.invoke()` 没有超时设置,如果API响应慢或卡死,用户需要等待很长时间
-
-**修复**:
-- 添加重试机制(最多重试2次)
-- 区分可重试的错误(超时、网络错误、限流)和不可重试的错误
-- 使用递增等待时间(2秒、4秒)
-- 在重试循环中检查取消标志
-
-**代码位置**:`geo_tool.py` 第2876-2922行
-
-**实现细节**:
-```python
-max_retries = 2 # 最多重试2次
-retry_count = 0
-content = None
-
-while retry_count <= max_retries:
- try:
- # 检查是否取消生成
- if st.session_state.get("cancel_generation", False):
- break
-
- # 尝试生成内容
- content = chain.invoke({...})
- break # 成功生成,退出重试循环
-
- except Exception as e:
- error_msg = str(e)
- retry_count += 1
-
- # 判断是否为可重试的错误
- is_retryable = (
- "timeout" in error_msg.lower() or
- "connection" in error_msg.lower() or
- "network" in error_msg.lower() or
- "rate limit" in error_msg.lower() or
- "429" in error_msg.lower()
- )
-
- if retry_count <= max_retries and is_retryable:
- # 可重试的错误,等待后重试
- wait_time = retry_count * 2 # 递增等待时间
- st.warning(f"⚠️ 生成失败,{wait_time}秒后重试...")
- time.sleep(wait_time)
- continue
- else:
- # 不可重试的错误或已达到最大重试次数
- raise
-```
-
----
-
-#### 6. ✅ 添加取消生成功能
-**问题**:批量生成时,没有提供"取消生成"的机制,用户无法中断长时间运行的生成任务
-
-**修复**:
-- 添加 `cancel_generation` 状态标志
-- 在进度显示区域添加"取消生成"按钮
-- 在生成循环中检查取消标志
-- 如果取消,立即停止生成并显示提示
-
-**代码位置**:
-- 取消按钮:`geo_tool.py` 第2430-2439行
-- 取消检查:`geo_tool.py` 第2448-2450行、第2884-2885行、第2918-2920行
-
----
-
-#### 7. ✅ 完善重试机制(评分)
-**问题**:评分失败后有重试按钮,但没有重试次数限制
-
-**修复**:
-- 添加重试次数限制(最多3次)
-- 使用 `session_state` 跟踪每个内容的重试次数
-- 区分错误类型,给出针对性提示
-- 对于API配置错误,提示用户检查配置
-
-**代码位置**:`geo_tool.py` 第3270-3305行
-
----
-
-#### 8. ✅ 完善简单平台的提示词
-**问题**:B站和头条号的提示词过于简单,缺少详细说明
-
-**修复**:
-- **B站(视频脚本)**:
- - 添加开场钩子要求
- - 添加时间戳分段说明
- - 添加画面建议说明
- - 添加字数要求(800-2000字)
- - 添加格式说明
-
-- **头条号(资讯软文)**:
- - 添加开头要求
- - 添加正文结构说明
- - 添加字数要求(800-2000字)
- - 添加结尾要求
- - 添加格式说明
-
-**代码位置**:
-- B站:`geo_tool.py` 第2493-2510行
-- 头条号:`geo_tool.py` 第2507-2523行
-
----
-
-## 📊 修复统计
-
-### 修复数量
-- **P0 严重问题**:3/3 ✅ (100%)
-- **P1 重要问题**:5/5 ✅ (100%)
-- **总计**:8/8 ✅ (100%)
-
-### 代码修改
-- **修改的文件**:1个(`geo_tool.py`)
-- **修改的行数**:约200行
-- **新增功能**:
- - 重试机制(内容生成)
- - 取消生成功能
- - E-E-A-T要求(7个平台)
- - 重试次数限制(评分)
-
----
-
-## 🎯 修复效果
-
-### 1. 提示词质量提升
-- ✅ 所有专业平台都有E-E-A-T要求
-- ✅ 提示词结构更完整
-- ✅ 要求更明确具体
-
-### 2. 错误处理增强
-- ✅ 自动重试机制(最多2次)
-- ✅ 区分可重试和不可重试的错误
-- ✅ 更友好的错误提示
-
-### 3. 用户体验改善
-- ✅ 可以取消长时间运行的生成任务
-- ✅ 重试机制减少失败率
-- ✅ 边界检查更完善,避免索引错误
-
-### 4. 代码质量提升
-- ✅ 边界条件检查更完整
-- ✅ 状态管理更安全
-- ✅ 错误处理更健壮
-
----
-
-## 📋 修复验证清单
-
-### 提示词质量
-- [x] GitHub README提示词位置正确
-- [x] 7个专业平台都有E-E-A-T要求
-- [x] B站和头条号提示词已完善
-- [x] 所有平台提示词结构统一
-
-### 代码逻辑
-- [x] selected_keyword检查已添加
-- [x] selected_content_idx边界检查已完善
-- [x] 取消生成功能已实现
-- [x] 重试机制已添加
-
-### 错误处理
-- [x] 超时和网络错误自动重试
-- [x] 重试次数限制已添加
-- [x] 错误类型区分更细致
-- [x] 取消生成时正确处理
-
----
-
-## 🔍 测试建议
-
-### 1. 功能测试
-- [ ] 测试单篇生成(正常流程)
-- [ ] 测试批量生成(正常流程)
-- [ ] 测试取消生成功能
-- [ ] 测试重试机制(模拟网络错误)
-- [ ] 测试边界条件(空关键词、空内容等)
-
-### 2. 提示词测试
-- [ ] 测试所有20个平台的提示词生成
-- [ ] 验证E-E-A-T要求是否正确应用
-- [ ] 检查生成内容的质量
-
-### 3. 错误处理测试
-- [ ] 模拟网络超时
-- [ ] 模拟API限流(429错误)
-- [ ] 模拟API配置错误
-- [ ] 测试重试机制是否正常工作
-
----
-
-## 📝 后续优化建议(可选)
-
-### P2 优化建议
-1. **异步评分**:考虑将评分改为异步进行,不阻塞生成流程
-2. **生成时间估算**:根据历史数据估算生成时间
-3. **实时预览**:批量生成时,每生成一篇立即显示(受Streamlit限制)
-
----
-
-## ✅ 总结
-
-所有P0和P1问题已全部修复,代码质量显著提升:
-
-1. **提示词质量**:7个专业平台已添加E-E-A-T要求,B站和头条号提示词已完善
-2. **错误处理**:添加了重试机制和取消功能,错误处理更健壮
-3. **用户体验**:可以取消生成,重试机制减少失败率
-4. **代码质量**:边界检查更完善,状态管理更安全
-
-**修复完成度**:✅ 100% (8/8)
-
----
-
-*修复完成时间:2026-01-28*
-*修复代码版本:geo_tool.py (约7300行)*
diff --git a/docs/analysis/TAB2_IMPLEMENTATION_STATUS.md b/docs/analysis/TAB2_IMPLEMENTATION_STATUS.md
deleted file mode 100644
index 3f8a859..0000000
--- a/docs/analysis/TAB2_IMPLEMENTATION_STATUS.md
+++ /dev/null
@@ -1,542 +0,0 @@
-# Tab2(自动创作)实现状态分析报告
-
-> 基于 `TAB2_CODE_REVIEW.md` 的审查建议,检查实际代码实现情况
-> 分析日期:2026-01-28
-
----
-
-## 📊 总体实现情况
-
-### ✅ 已完全实现(P0 严重问题)
-- **4/4 项** P0 问题已全部修复 ✅
-
-### ⚠️ 部分实现(P1 重要问题)
-- **2/5 项** P1 问题已实现
-- **3/5 项** P1 问题待实现
-
-### ⚠️ 部分实现(P2 优化建议)
-- **1/3 项** P2 优化已实现
-- **2/3 项** P2 优化待实现
-
----
-
-## 🔴 P0 严重问题(必须修复)- 实现状态
-
-### ✅ 1. 内容生成异常处理
-**文档位置**: 第16-50行
-**代码位置**: `geo_tool.py` 第2798-2823行
-
-**实现状态**: ✅ **已完全实现**
-
-**实现细节**:
-```python
-# 第2798-2823行
-try:
- content = chain.invoke({"keyword": keyword, "brand": brand, "advantages": advantages})
-
- # 验证生成的内容
- if not content or not content.strip():
- raise ValueError("生成的内容为空")
-
- if len(content.strip()) < 50:
- st.warning(f"⚠️ 生成的内容过短({len(content.strip())}字),可能不完整:{keyword}")
-
-except Exception as e:
- error_msg = str(e)
- st.error(f"❌ 生成失败({keyword} - {plat}):{error_msg}")
-
- # 记录失败,但继续生成其他内容
- contents.append({
- "keyword": keyword,
- "platform": plat,
- "content": f"[生成失败:{error_msg}]",
- "ext": "txt",
- "filename": f"{sanitize_filename(plat,30)}_{sanitize_filename(brand,30)}_{sanitize_filename(keyword,60)}_ERROR.txt",
- "score": None,
- "json_ld": None,
- "error": error_msg
- })
- continue # 跳过当前项,继续生成下一个
-```
-
-**验证结果**: ✅ 完全符合文档建议,包含:
-- ✅ try-except 包裹 chain.invoke()
-- ✅ 内容空值检查
-- ✅ 内容长度验证(<50字警告)
-- ✅ 错误记录但继续生成其他内容
-- ✅ 明确的错误提示
-
----
-
-### ✅ 2. 批量生成时缺少空值检查
-**文档位置**: 第52-76行
-**代码位置**: `geo_tool.py` 第2395-2421行
-
-**实现状态**: ✅ **已完全实现**
-
-**实现细节**:
-```python
-# 第2395-2407行:生成前检查
-if not keywords_to_generate or len(keywords_to_generate) == 0:
- st.warning("⚠️ 请至少选择一个关键词进行生成")
- st.stop()
-
-# 检查品牌和优势
-if not brand or not brand.strip():
- st.error("❌ 品牌名称不能为空,请在侧边栏配置品牌信息")
- st.stop()
-
-if not advantages or not advantages.strip():
- st.error("❌ 核心优势不能为空,请在侧边栏配置核心优势")
- st.stop()
-
-# 第2418-2421行:循环前再次检查
-total_items = len(keywords_to_generate)
-if total_items == 0:
- st.warning("⚠️ 没有可生成的内容")
- st.stop()
-```
-
-**验证结果**: ✅ 完全符合文档建议,包含:
-- ✅ 生成前检查 keywords_to_generate 是否为空
-- ✅ 检查 brand 是否为空
-- ✅ 检查 advantages 是否为空
-- ✅ 循环前检查 total_items 是否为0
-- ✅ 所有检查都有明确的错误提示
-
----
-
-### ✅ 3. 进度条在异常情况下可能不清理
-**文档位置**: 第78-93行
-**代码位置**: `geo_tool.py` 第2939-2944行
-
-**实现状态**: ✅ **已完全实现**
-
-**实现细节**:
-```python
-# 第2930-2944行
-try:
- with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
- # ... 生成逻辑
-except Exception as e:
- # ZIP文件生成失败的处理
- error_msg = str(e)
- st.error(f"❌ ZIP文件生成失败:{error_msg}")
- # 即使ZIP失败,也保存已生成的内容(单个文件)
- if contents:
- st.session_state.generated_contents = contents
- st.warning("⚠️ 部分内容已生成,但ZIP打包失败。可以单独下载每篇内容。")
-finally:
- # 确保进度显示被清理
- if 'progress_bar' in locals():
- progress_bar.empty()
- if 'status_text' in locals():
- status_text.empty()
-```
-
-**验证结果**: ✅ 完全符合文档建议,包含:
-- ✅ 使用 try-finally 确保进度显示被清理
-- ✅ 检查变量是否存在(locals())
-- ✅ 即使发生异常也能清理UI元素
-
----
-
-### ✅ 4. ZIP文件生成缺少异常处理
-**文档位置**: 第95-110行
-**代码位置**: `geo_tool.py` 第2430-2938行
-
-**实现状态**: ✅ **已完全实现**
-
-**实现细节**:
-```python
-# 第2430-2938行
-try:
- with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
- for idx, (keyword, plat) in enumerate(keywords_to_generate):
- # ... 生成逻辑
- zip_file.writestr(filename, content)
-
- zip_buffer.seek(0)
- st.session_state.generated_contents = contents
- st.session_state.zip_bytes = zip_buffer.getvalue()
- st.session_state.zip_filename = f"{sanitize_filename(brand,40)}_GEO内容包.zip"
-
-except Exception as e:
- # ZIP文件生成失败的处理
- error_msg = str(e)
- st.error(f"❌ ZIP文件生成失败:{error_msg}")
- # 即使ZIP失败,也保存已生成的内容(单个文件)
- if contents:
- st.session_state.generated_contents = contents
- st.warning("⚠️ 部分内容已生成,但ZIP打包失败。可以单独下载每篇内容。")
-```
-
-**验证结果**: ✅ 完全符合文档建议,包含:
-- ✅ ZIP操作包裹在try-except中
-- ✅ 即使ZIP失败,也保存已生成的内容
-- ✅ 明确的错误提示
-
----
-
-## 🟡 P1 重要问题(建议修复)- 实现状态
-
-### ⚠️ 5. 内容生成缺少超时控制
-**文档位置**: 第116-127行
-**代码位置**: `geo_tool.py` 第2799行
-
-**实现状态**: ❌ **未实现**
-
-**当前状态**:
-- `chain.invoke()` 调用没有超时设置
-- 如果API响应慢或卡死,用户需要等待很长时间
-- 批量生成时,一个慢请求会阻塞整个流程
-
-**建议实现**:
-```python
-# 需要添加超时控制
-from langchain_core.runnables import RunnableConfig
-import asyncio
-
-try:
- # 方式1:使用 timeout 参数(如果LLM支持)
- config = RunnableConfig(timeout=60) # 60秒超时
- content = chain.invoke(
- {"keyword": keyword, "brand": brand, "advantages": advantages},
- config=config
- )
-except TimeoutError:
- st.error(f"❌ 生成超时({keyword} - {plat}),请稍后重试")
- # 记录失败,继续生成其他内容
- continue
-```
-
-**优先级**: 🟡 P1(建议修复)
-
----
-
-### ⚠️ 6. 评分失败后的重试机制不完善
-**文档位置**: 第128-139行
-**代码位置**: `geo_tool.py` 第3198-3217行
-
-**实现状态**: ⚠️ **部分实现**
-
-**当前实现**:
-```python
-# 第3198-3217行:有重试按钮,但没有重试次数限制
-if score_data.get("error"):
- st.warning(f"⚠️ 内容评分失败:{score_data.get('error')}")
- if st.button("🔄 重新评分", use_container_width=True, key="retry_score",
- disabled=(not st.session_state.cfg_valid) or (gen_llm is None)):
- with st.spinner("正在重新评分..."):
- try:
- retry_scorer = ContentScorer()
- score_chain = PromptTemplate.from_template("{input}") | gen_llm | StrOutputParser()
- new_score = retry_scorer.score_content(...)
- # ... 更新评分
- except Exception as e:
- st.error(f"重新评分失败:{e}")
-```
-
-**已实现**:
-- ✅ 有重试按钮
-- ✅ 区分错误类型(网络、API配置等)
-- ✅ 错误提示明确
-
-**未实现**:
-- ❌ 没有重试次数限制(可能无限重试)
-- ❌ 没有区分临时错误和永久错误
-- ❌ 没有针对API配置错误的特殊提示
-
-**建议改进**:
-```python
-# 添加重试次数限制
-retry_count_key = f"score_retry_count_{item['keyword']}_{item['platform']}"
-retry_count = st.session_state.get(retry_count_key, 0)
-max_retries = 3
-
-if retry_count >= max_retries:
- st.error(f"❌ 已达到最大重试次数({max_retries}次),请检查API配置")
-else:
- if st.button("🔄 重新评分", use_container_width=True, key="retry_score"):
- st.session_state[retry_count_key] = retry_count + 1
- # ... 重试逻辑
-```
-
-**优先级**: 🟡 P1(建议修复)
-
----
-
-### ❌ 7. 批量生成时缺少并发控制
-**文档位置**: 第141-152行
-**代码位置**: `geo_tool.py` 第2432行(循环)
-
-**实现状态**: ❌ **未实现**
-
-**当前状态**:
-- 批量生成是串行的
-- 没有提供"取消生成"的机制
-- 用户无法中断长时间运行的生成任务
-
-**建议实现**:
-```python
-# 添加取消生成功能
-if 'cancel_generation' not in st.session_state:
- st.session_state.cancel_generation = False
-
-# 在生成循环中添加检查
-for idx, (keyword, plat) in enumerate(keywords_to_generate):
- if st.session_state.cancel_generation:
- st.warning("⚠️ 生成已取消")
- break
-
- # ... 生成逻辑
-
-# 在进度显示区域添加取消按钮
-if progress_bar:
- cancel_col, _ = st.columns([1, 4])
- with cancel_col:
- if st.button("❌ 取消生成", key="cancel_gen_btn"):
- st.session_state.cancel_generation = True
- st.rerun()
-```
-
-**优先级**: 🟡 P1(建议修复)
-
----
-
-### ✅ 8. 内容为空或格式错误的处理
-**文档位置**: 第153-168行
-**代码位置**: `geo_tool.py` 第2802-2806行
-
-**实现状态**: ✅ **已实现**
-
-**实现细节**:
-```python
-# 第2802-2806行
-# 验证生成的内容
-if not content or not content.strip():
- raise ValueError("生成的内容为空")
-
-if len(content.strip()) < 50:
- st.warning(f"⚠️ 生成的内容过短({len(content.strip())}字),可能不完整:{keyword}")
-```
-
-**验证结果**: ✅ 完全符合文档建议,包含:
-- ✅ 空值检查
-- ✅ 内容长度验证(<50字警告)
-
----
-
-### ⚠️ 9. 状态同步问题
-**文档位置**: 第170-179行
-**代码位置**: 多处使用 `st.session_state.generated_contents`
-
-**实现状态**: ⚠️ **部分实现**
-
-**当前状态**:
-- ✅ 在详情页面修改内容后,会更新 `generated_contents`(第3213行)
-- ⚠️ 但更新逻辑可能不完整,需要进一步验证
-
-**建议**:
-- 添加状态验证函数
-- 统一内容更新逻辑
-
-**优先级**: 🟡 P1(建议修复)
-
----
-
-## 🟢 P2 优化建议(可选)- 实现状态
-
-### ⚠️ 10. 提示词优化建议
-
-#### 10.1 提示词格式不一致
-**文档位置**: 第187-194行
-**代码位置**: `geo_tool.py` 第2439-2781行(各平台提示词)
-
-**实现状态**: ⚠️ **部分统一**
-
-**当前状态**:
-- ✅ 所有提示词都使用【】标记(统一)
-- ⚠️ E-E-A-T要求不一致:
- - **有E-E-A-T要求的平台**: 知乎(第2451行)、CSDN(第2486行)- 仅2个
- - **缺少E-E-A-T要求的平台**: 其他18个平台
-
-**建议**:
-根据文档建议(第504-522行),应该:
-1. 为专业平台添加E-E-A-T要求:
- - 微信公众号(长文)
- - 百家号(资讯)
- - 网易号(资讯)
- - 新浪新闻(资讯)
- - 东方财富(财经)
- - 原创力文档(文档)
- - 邦阅网(外贸)
-
-2. 为生活化平台保持简洁(不需要E-E-A-T):
- - 小红书(生活种草)
- - 抖音图文(短内容)
- - QQ空间(社交)
- - B站(视频脚本)
-
-**优先级**: 🟢 P2(可选优化)
-
----
-
-#### 10.2 提示词长度优化
-**文档位置**: 第196-203行
-
-**实现状态**: ⚠️ **需要评估**
-
-**当前状态**:
-- 提示词长度差异较大
-- 需要实际测试生成效果来评估是否需要优化
-
-**优先级**: 🟢 P2(可选优化)
-
----
-
-#### 10.3 品牌名称在提示词中的使用
-**文档位置**: 第205-218行
-**代码位置**: `geo_tool.py` 第2401-2403行
-
-**实现状态**: ✅ **已实现**
-
-**实现细节**:
-```python
-# 第2401-2403行:生成前检查brand
-if not brand or not brand.strip():
- st.error("❌ 品牌名称不能为空,请在侧边栏配置品牌信息")
- st.stop()
-```
-
-**验证结果**: ✅ 完全符合文档建议
-
----
-
-### ✅ 11. 性能优化
-
-#### 11.1 评分可以异步进行
-**文档位置**: 第222-231行
-
-**实现状态**: ❌ **未实现**
-
-**当前状态**:
-- 评分是同步的,会阻塞生成流程
-- 批量生成时,每篇都要等待评分完成
-
-**优先级**: 🟢 P2(可选优化)
-
----
-
-#### 11.2 重复初始化对象
-**文档位置**: 第233-241行
-**代码位置**: `geo_tool.py` 第2426-2428行
-
-**实现状态**: ✅ **已实现**
-
-**实现细节**:
-```python
-# 第2426-2428行:在循环外初始化对象
-# 初始化对象(性能优化:避免重复创建)
-scorer = ContentScorer()
-schema_gen = None # 延迟初始化,只在需要时创建
-```
-
-**验证结果**: ✅ 完全符合文档建议
-
----
-
-### ⚠️ 12. 用户体验优化
-
-#### 12.1 生成时间估算
-**文档位置**: 第245-252行
-
-**实现状态**: ❌ **未实现**
-
-**当前状态**:
-- 用户不知道生成需要多长时间
-- 批量生成时,无法预估完成时间
-
-**优先级**: 🟢 P2(可选优化)
-
----
-
-#### 12.2 生成结果预览
-**文档位置**: 第254-261行
-**代码位置**: `geo_tool.py` 第2926-2965行
-
-**实现状态**: ✅ **已实现**
-
-**实现细节**:
-- ✅ 每生成一篇,立即添加到 `contents` 列表
-- ✅ 生成完成后统一显示结果
-- ⚠️ 但无法在生成过程中实时查看(Streamlit限制)
-
-**优先级**: 🟢 P2(可选优化,受Streamlit限制)
-
----
-
-## 📋 实现状态总结
-
-### ✅ 已完全实现(9项)
-1. ✅ 内容生成异常处理(P0)
-2. ✅ 批量生成时缺少空值检查(P0)
-3. ✅ 进度条在异常情况下可能不清理(P0)
-4. ✅ ZIP文件生成缺少异常处理(P0)
-5. ✅ 内容为空或格式错误的处理(P1)
-6. ✅ 品牌名称在提示词中的使用(P2)
-7. ✅ 重复初始化对象(P2)
-8. ✅ 生成结果预览(P2,部分受Streamlit限制)
-9. ✅ 状态同步(P1,部分实现)
-
-### ⚠️ 部分实现(2项)
-1. ⚠️ 评分失败后的重试机制(P1)- 有重试按钮,但缺少重试次数限制
-2. ⚠️ 提示词格式不一致(P2)- 格式统一,但E-E-A-T要求不一致
-
-### ❌ 未实现(5项)
-1. ❌ 内容生成缺少超时控制(P1)
-2. ❌ 批量生成时缺少并发控制(P1)
-3. ❌ 评分可以异步进行(P2)
-4. ❌ 生成时间估算(P2)
-5. ❌ 提示词长度优化(P2,需要评估)
-
----
-
-## 🎯 建议优先级
-
-### 🔴 高优先级(P1 - 建议尽快修复)
-1. **添加超时控制** - 提升用户体验,避免长时间等待
-2. **完善重试机制** - 添加重试次数限制,区分错误类型
-3. **添加取消生成功能** - 允许用户中断长时间运行的生成任务
-
-### 🟢 低优先级(P2 - 可选优化)
-1. **统一E-E-A-T要求** - 为专业平台添加E-E-A-T要求
-2. **生成时间估算** - 提升用户体验
-3. **异步评分** - 性能优化(如果Streamlit支持)
-
----
-
-## 📊 实现完成度统计
-
-- **P0 严重问题**: 4/4 ✅ (100%)
-- **P1 重要问题**: 2/5 ⚠️ (40%)
-- **P2 优化建议**: 1/3 ⚠️ (33%)
-- **总体完成度**: 9/12 ✅ (75%)
-
----
-
-## ✅ 结论
-
-**核心功能已完全实现**:所有P0严重问题都已修复,代码质量良好,异常处理完善。
-
-**建议后续优化**:
-1. 添加超时控制和取消生成功能(P1)
-2. 完善重试机制(P1)
-3. 统一E-E-A-T要求(P2)
-
-**整体评价**:✅ **实现质量优秀**,核心问题已全部解决,剩余为优化项。
-
----
-
-*报告生成时间:2026-01-28*
-*基于代码版本:geo_tool.py (7225行)*
diff --git a/docs/analysis/TAB2_OPTIMIZATION_PLAN.md b/docs/analysis/TAB2_OPTIMIZATION_PLAN.md
deleted file mode 100644
index f3893cf..0000000
--- a/docs/analysis/TAB2_OPTIMIZATION_PLAN.md
+++ /dev/null
@@ -1,842 +0,0 @@
-# Tab2(自动创作)优化方案
-
-## 一、重排后的 Tab 结构草图
-
-```
-Tab2: 自动创作
-├── 【区域1:快速生成区】(默认展开,紧凑布局)
-│ ├── 生成模式选择(单篇/批量)- 横向radio
-│ ├── 关键词选择(单篇:selectbox | 批量:multiselect)
-│ ├── 平台选择(单篇:selectbox | 批量:selectbox统一平台)
-│ ├── 高级优化技巧(折叠,默认收起)
-│ └── 生成按钮(主按钮,醒目)
-│
-├── 【区域2:生成结果概览】(生成后显示)
-│ ├── 生成统计卡片(3列:总篇数、平均评分、生成时间)
-│ ├── 批量生成:内容列表(可展开查看每篇)
-│ └── 单篇生成:直接进入详情
-│
-├── 【区域3:内容详情区】(单篇或选中篇)
-│ ├── Tab组:内容预览 | 质量分析 | 增强工具
-│ │ ├── Tab1: 内容预览
-│ │ │ ├── 内容展示(代码块/文本区)
-│ │ │ ├── 快速操作(下载、复制、优化)
-│ │ │ └── JSON-LD(仅GitHub平台,折叠)
-│ │ │
-│ │ ├── Tab2: 质量分析
-│ │ │ ├── 内容评分(5个指标卡片)
-│ │ │ ├── E-E-A-T评估(折叠)
-│ │ │ └── 事实密度评估(折叠)
-│ │ │
-│ │ └── Tab3: 增强工具
-│ │ ├── 多模态提示生成(配图/视频)
-│ │ ├── 图片生成(通义万相)
-│ │ └── E-E-A-T强化(折叠)
-│ │
-│ └── 下载区(底部固定)
-│ ├── 单篇下载
-│ └── 批量ZIP下载(批量模式)
-│
-└── 【区域4:辅助工具】(折叠,默认收起)
- ├── JSON-LD Schema生成(移至Tab3或独立Tab)
- └── 技术配置生成(移至Tab3或独立Tab)
-```
-
-## 二、问题清单(含严重级别)
-
-### P0 - 必改(逻辑漏洞/体验阻塞)
-
-1. **批量生成无法查看所有内容** ⚠️ P0
- - 问题:只显示最后一篇预览,其他内容无法查看
- - 影响:用户无法验证批量生成的所有内容
- - 位置:3124-4118行
-
-2. **内容评分可能静默失败** ⚠️ P0
- - 问题:评分失败只显示warning,但用户可能看不到
- - 影响:用户以为内容已评分,实际未评分
- - 位置:3086-3099行
-
-3. **图片生成入口混乱** ⚠️ P0
- - 问题:有"直接生成"、"基于描述生成"、"多模态生成"多个入口
- - 影响:用户不知道用哪个,容易重复操作
- - 位置:3173-3756行
-
-4. **JSON-LD和技术配置功能归属错误** ⚠️ P0
- - 问题:放在内容生成Tab,但属于"优化"范畴
- - 影响:用户流程混乱,应该在优化Tab使用
- - 位置:2271-2572行
-
-5. **批量生成时ZIP下载位置不明确** ⚠️ P0
- - 问题:ZIP下载按钮在单篇预览下方,批量模式时位置不明显
- - 影响:用户找不到批量下载入口
- - 位置:4093-4101行
-
-### P1 - 建议(体验优化)
-
-6. **信息层级混乱** ⚠️ P1
- - 问题:评分、多模态、E-E-A-T等功能平铺,没有分组
- - 影响:页面冗长,用户需要滚动很多才能找到功能
- - 位置:3124-4118行
-
-7. **生成状态反馈不足** ⚠️ P1
- - 问题:批量生成时,只有spinner,没有进度条和完成提示
- - 影响:用户不知道生成进度,可能误以为卡死
- - 位置:2669-3122行
-
-8. **优化技巧选择器位置不当** ⚠️ P1
- - 问题:放在表单中间,容易被忽略
- - 影响:用户可能不知道有这个功能
- - 位置:2628-2652行
-
-9. **内容预览区域过大** ⚠️ P1
- - 问题:单篇内容预览占用大量空间,其他功能被挤到下方
- - 影响:用户需要大量滚动才能看到其他功能
- - 位置:4010-4091行
-
-10. **缺少内容对比功能** ⚠️ P1
- - 问题:批量生成时,无法对比不同关键词生成的内容
- - 影响:用户无法快速筛选优质内容
- - 位置:无此功能
-
-### P2 - 可做(锦上添花)
-
-11. **缺少内容模板预览** ⚠️ P2
- - 问题:用户选择平台时,看不到该平台的模板特点
- - 影响:用户可能选错平台
- - 位置:2591-2612行
-
-12. **批量生成缺少筛选功能** ⚠️ P2
- - 问题:批量生成后,无法按评分、平台、关键词筛选
- - 影响:内容多时难以管理
- - 位置:3124行后
-
-13. **缺少内容历史记录快速入口** ⚠️ P2
- - 问题:无法快速查看之前生成的内容
- - 影响:用户需要切换到Tab5查看
- - 位置:无此功能
-
-14. **优化技巧说明不够直观** ⚠️ P2
- - 问题:只有文字说明,没有示例
- - 影响:用户不理解技巧的实际效果
- - 位置:2642-2652行
-
-15. **缺少批量操作功能** ⚠️ P2
- - 问题:无法批量下载、批量优化、批量删除
- - 影响:内容多时操作繁琐
- - 位置:无此功能
-
-## 三、改动方案(含具体交互/文案/状态设计)
-
-### 改动1:重组布局结构(P0)
-
-**目标**:将内容生成作为核心,其他功能按需展示
-
-**具体改动**:
-
-```python
-with tab2:
- # === 区域1:快速生成区 ===
- st.markdown("### ✍️ 内容生成")
-
- with st.container(border=True):
- with st.form("content_form", clear_on_submit=False):
- # 第一行:模式选择
- mode = st.radio(
- "生成模式",
- ["单篇生成", "批量生成"],
- horizontal=True,
- key="content_mode"
- )
-
- # 第二行:关键词和平台(紧凑布局)
- if mode == "单篇生成":
- col1, col2 = st.columns([2, 1])
- with col1:
- selected_keyword = st.selectbox(
- "选择关键词",
- st.session_state.keywords,
- key="content_kw_single"
- )
- with col2:
- platform = st.selectbox(
- "平台",
- platforms,
- key="content_platform_single"
- )
- else:
- col1, col2 = st.columns([3, 1])
- with col1:
- selected_keywords = st.multiselect(
- "选择关键词(可多选)",
- st.session_state.keywords,
- key="content_kw_multi"
- )
- with col2:
- platform = st.selectbox(
- "统一平台",
- platforms,
- key="content_platform_multi"
- )
-
- # 第三行:高级优化技巧(折叠)
- with st.expander("🎨 高级优化技巧(可选)", expanded=False):
- technique_manager = OptimizationTechniqueManager()
- all_techniques = technique_manager.list_techniques()
- technique_options = [f"{tech['icon']} {tech['name']}" for tech in all_techniques]
-
- selected_technique_names = st.multiselect(
- "选择优化技巧",
- options=technique_options,
- default=[],
- key="content_techniques",
- help="选择要应用的优化技巧,可多选"
- )
-
- if selected_technique_names:
- st.caption("已选择:" + "、".join(selected_technique_names))
-
- # 生成按钮
- run_content_disabled = (not st.session_state.cfg_valid) or (gen_llm is None) or (not keywords_to_generate)
- run_content = st.form_submit_button(
- "🚀 生成内容",
- use_container_width=True,
- disabled=run_content_disabled,
- type="primary"
- )
-
- # === 区域2:生成结果概览 ===
- if st.session_state.generated_contents:
- # 统计卡片
- col1, col2, col3 = st.columns(3)
- with col1:
- st.metric("生成篇数", len(st.session_state.generated_contents))
- with col2:
- avg_score = sum(
- item.get("score", {}).get("scores", {}).get("total", 0)
- for item in st.session_state.generated_contents
- if item.get("score")
- ) / len([item for item in st.session_state.generated_contents if item.get("score")]) if any(item.get("score") for item in st.session_state.generated_contents) else 0
- st.metric("平均评分", f"{avg_score:.1f}/100" if avg_score > 0 else "未评分")
- with col3:
- st.metric("生成时间", datetime.now().strftime("%H:%M:%S"))
-
- # 批量模式:内容列表
- if len(st.session_state.generated_contents) > 1:
- st.markdown("#### 📋 生成内容列表")
-
- # 添加筛选和排序
- filter_col1, filter_col2, filter_col3 = st.columns(3)
- with filter_col1:
- filter_platform = st.selectbox(
- "筛选平台",
- ["全部"] + list(set(item["platform"] for item in st.session_state.generated_contents)),
- key="content_filter_platform"
- )
- with filter_col2:
- sort_by = st.selectbox(
- "排序方式",
- ["生成顺序", "评分降序", "评分升序", "关键词"],
- key="content_sort_by"
- )
- with filter_col3:
- if st.button("📥 批量下载ZIP", use_container_width=True):
- # ZIP下载逻辑
- pass
-
- # 内容列表(可展开)
- for idx, item in enumerate(st.session_state.generated_contents):
- with st.expander(
- f"{idx+1}. {item['keyword']} - {item['platform']} | 评分: {item.get('score', {}).get('scores', {}).get('total', 'N/A')}/100",
- expanded=False
- ):
- # 快速预览和操作
- col1, col2 = st.columns([3, 1])
- with col1:
- st.text_area(
- "内容预览",
- item["content"][:500] + "..." if len(item["content"]) > 500 else item["content"],
- height=150,
- disabled=True,
- key=f"preview_{idx}"
- )
- with col2:
- if st.button("查看详情", key=f"view_{idx}"):
- st.session_state.selected_content_idx = idx
- if st.button("下载", key=f"dl_{idx}"):
- # 下载逻辑
- pass
-
- # 单篇模式或选中篇:详情展示
- selected_idx = st.session_state.get("selected_content_idx", 0) if len(st.session_state.generated_contents) > 1 else 0
- item = st.session_state.generated_contents[selected_idx]
-
- # 使用Tabs组织详情
- detail_tab1, detail_tab2, detail_tab3 = st.tabs(["📄 内容预览", "📊 质量分析", "🎨 增强工具"])
-
- with detail_tab1:
- # 内容预览
- st.markdown(f"**关键词**: {item['keyword']} | **平台**: {item['platform']}")
- if item["ext"] == "md":
- st.code(item["content"], language="markdown")
- else:
- st.text_area("内容", item["content"], height=400, key="content_preview")
-
- # 快速操作
- col1, col2, col3 = st.columns(3)
- with col1:
- st.download_button("下载单篇", item["content"], f"{item['filename']}", use_container_width=True)
- with col2:
- if st.button("🔧 优化内容", use_container_width=True):
- # 跳转到Tab3或打开优化面板
- pass
- with col3:
- if st.button("📋 复制内容", use_container_width=True):
- # 复制到剪贴板
- pass
-
- # JSON-LD(仅GitHub平台,折叠)
- if item.get("json_ld") or item["platform"] == "GitHub(README/文档)":
- with st.expander("📋 JSON-LD Schema(可选)", expanded=False):
- if item.get("json_ld"):
- st.code(item["json_ld"], language="json")
- else:
- # 生成JSON-LD
- pass
-
- with detail_tab2:
- # 质量分析
- if item.get("score"):
- # 评分卡片
- score_data = item["score"]
- scores = score_data.get("scores", {})
- col1, col2, col3, col4, col5 = st.columns(5)
- with col1:
- st.metric("总分", f"{scores.get('total', 0)}/100")
- with col2:
- st.metric("结构化", f"{scores.get('structure', 0)}/25")
- with col3:
- st.metric("品牌提及", f"{scores.get('brand_mention', 0)}/25")
- with col4:
- st.metric("权威性", f"{scores.get('authority', 0)}/25")
- with col5:
- st.metric("可引用性", f"{scores.get('citations', 0)}/25")
-
- # 详细分析(折叠)
- with st.expander("📝 详细分析", expanded=False):
- # 显示详细评分和改进建议
- pass
- else:
- st.info("内容未评分,点击下方按钮进行评估")
- if st.button("📊 评估内容质量", use_container_width=True):
- # 评估逻辑
- pass
-
- # E-E-A-T和事实密度评估(折叠)
- with st.expander("🎯 E-E-A-T 评估", expanded=False):
- # E-E-A-T评估逻辑
- pass
-
- with st.expander("📊 事实密度评估", expanded=False):
- # 事实密度评估逻辑
- pass
-
- with detail_tab3:
- # 增强工具
- st.markdown("#### 🎨 多模态增强")
-
- # 图片生成(统一入口)
- image_gen_col1, image_gen_col2 = st.columns([2, 1])
- with image_gen_col1:
- image_gen_mode = st.radio(
- "图片生成方式",
- ["直接生成(基于内容)", "基于配图描述生成"],
- horizontal=True,
- key="image_gen_mode"
- )
- with image_gen_col2:
- num_images = st.selectbox("生成数量", [1, 2, 3], key="num_images")
-
- if st.button("🎨 生成图片", use_container_width=True, type="primary"):
- # 统一的图片生成逻辑
- pass
-
- # 视频脚本生成(仅B站)
- if "B站" in item["platform"]:
- if st.button("🎬 生成视频脚本", use_container_width=True):
- # 视频脚本生成逻辑
- pass
-
- # E-E-A-T强化(折叠)
- with st.expander("✨ E-E-A-T 强化", expanded=False):
- # E-E-A-T强化逻辑
- pass
-```
-
-### 改动2:修复批量生成查看问题(P0)
-
-**目标**:批量生成后,用户可以查看所有生成的内容
-
-**具体改动**:
-
-```python
-# 在区域2中添加内容列表
-if len(st.session_state.generated_contents) > 1:
- st.markdown("#### 📋 生成内容列表")
-
- # 使用DataFrame展示列表
- list_data = []
- for idx, item in enumerate(st.session_state.generated_contents):
- list_data.append({
- "序号": idx + 1,
- "关键词": item["keyword"],
- "平台": item["platform"],
- "评分": item.get("score", {}).get("scores", {}).get("total", "未评分"),
- "字数": len(item["content"]),
- "操作": f"查看_{idx}"
- })
-
- df = pd.DataFrame(list_data)
-
- # 可交互的表格
- selected_rows = st.dataframe(
- df,
- use_container_width=True,
- hide_index=True,
- on_select="rerun",
- selection_mode="single-row"
- )
-
- # 根据选择显示详情
- if selected_rows.selection.rows:
- selected_idx = selected_rows.selection.rows[0]
- st.session_state.selected_content_idx = selected_idx
- st.rerun()
-```
-
-### 改动3:统一图片生成入口(P0)
-
-**目标**:将多个图片生成入口合并为一个,通过选项区分
-
-**具体改动**:
-
-```python
-# 在detail_tab3中
-st.markdown("#### 🖼️ 图片生成")
-
-# 生成方式选择
-gen_mode = st.radio(
- "生成方式",
- ["智能生成(推荐)", "基于配图描述", "自定义Prompt"],
- key="image_gen_mode",
- help="智能生成:AI自动分析内容生成图片;基于描述:使用已生成的配图描述;自定义:手动输入Prompt"
-)
-
-if gen_mode == "智能生成(推荐)":
- # 直接基于内容生成
- if st.button("🎨 生成图片", use_container_width=True, type="primary"):
- # 调用直接生成逻辑
- pass
-elif gen_mode == "基于配图描述":
- # 检查是否有配图描述
- if st.session_state.get("image_descriptions"):
- if st.button("🎨 基于描述生成", use_container_width=True, type="primary"):
- # 调用基于描述生成逻辑
- pass
- else:
- st.info("💡 请先生成配图描述")
- if st.button("📝 生成配图描述", use_container_width=True):
- # 生成配图描述
- pass
-else:
- # 自定义Prompt
- custom_prompt = st.text_area("输入图片生成Prompt", height=100)
- if st.button("🎨 生成图片", use_container_width=True, type="primary"):
- # 使用自定义Prompt生成
- pass
-```
-
-### 改动4:改进生成进度反馈(P1)
-
-**目标**:批量生成时显示清晰的进度
-
-**具体改动**:
-
-```python
-if run_content:
- # 初始化进度
- total_items = len(keywords_to_generate)
- progress_bar = st.progress(0)
- status_text = st.empty()
-
- contents = []
- zip_buffer = io.BytesIO()
-
- with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
- for idx, (keyword, plat) in enumerate(keywords_to_generate):
- # 更新进度
- progress = (idx + 1) / total_items
- progress_bar.progress(progress)
- status_text.text(f"正在生成 {idx + 1}/{total_items}: {keyword} - {plat}")
-
- # 生成逻辑
- with st.spinner(f"生成 {plat}:{keyword}"):
- # ... 原有生成逻辑 ...
- contents.append({...})
-
- # 完成提示
- if idx == total_items - 1:
- status_text.success(f"✅ 全部完成!共生成 {len(contents)} 篇内容")
-
- # 清理进度显示
- progress_bar.empty()
- status_text.empty()
-```
-
-### 改动5:移动JSON-LD和技术配置到Tab3(P0)
-
-**目标**:将不属于"创作"的功能移到"优化"Tab
-
-**具体改动**:
-
-```python
-# 从Tab2中删除2271-2572行的JSON-LD和技术配置代码
-
-# 在Tab3(文章优化)中添加:
-with tab3:
- # ... 原有优化功能 ...
-
- # 新增:结构化数据生成
- st.markdown("---")
- st.markdown("#### 📋 结构化数据生成")
-
- struct_tab1, struct_tab2 = st.tabs(["JSON-LD Schema", "技术配置"])
-
- with struct_tab1:
- # JSON-LD生成逻辑(从Tab2移过来)
- pass
-
- with struct_tab2:
- # 技术配置生成逻辑(从Tab2移过来)
- pass
-```
-
-## 四、测试用例清单
-
-### 测试用例1:单篇生成流程
-
-**前置条件**:
-- 已配置API Key
-- 已生成关键词(至少1个)
-
-**测试步骤**:
-1. 进入Tab2
-2. 选择"单篇生成"
-3. 选择一个关键词
-4. 选择一个平台(如"知乎")
-5. 点击"生成内容"按钮
-
-**预期结果**:
-- 显示生成进度(spinner)
-- 生成完成后显示内容预览
-- 自动显示内容评分(如果有)
-- 可以下载单篇内容
-- 可以查看质量分析
-- 可以使用增强工具
-
-### 测试用例2:批量生成流程
-
-**前置条件**:
-- 已配置API Key
-- 已生成关键词(至少3个)
-
-**测试步骤**:
-1. 进入Tab2
-2. 选择"批量生成"
-3. 选择3个关键词
-4. 选择一个平台
-5. 点击"生成内容"按钮
-
-**预期结果**:
-- 显示进度条和状态文本
-- 逐个生成内容
-- 生成完成后显示内容列表
-- 可以查看每篇内容的详情
-- 可以批量下载ZIP
-- 可以筛选和排序内容
-
-### 测试用例3:内容评分失败处理
-
-**前置条件**:
-- 已配置API Key
-- 已生成关键词
-
-**测试步骤**:
-1. 生成一篇内容
-2. 模拟评分失败(如API错误)
-
-**预期结果**:
-- 内容仍然生成成功
-- 显示明确的警告信息:"内容已生成,但评分失败"
-- 提供"重新评分"按钮
-- 不影响其他功能使用
-
-### 测试用例4:图片生成入口
-
-**前置条件**:
-- 已生成一篇内容
-- 已配置通义万相API Key
-
-**测试步骤**:
-1. 进入"增强工具"Tab
-2. 查看图片生成选项
-3. 选择"智能生成"
-4. 点击"生成图片"
-
-**预期结果**:
-- 只显示一个图片生成入口
-- 生成方式选择清晰
-- 生成过程有进度提示
-- 生成成功后显示图片预览
-
-### 测试用例5:批量生成查看所有内容
-
-**前置条件**:
-- 已批量生成5篇内容
-
-**测试步骤**:
-1. 查看内容列表
-2. 点击不同行的内容
-3. 使用筛选功能
-4. 使用排序功能
-
-**预期结果**:
-- 列表显示所有生成的内容
-- 可以点击查看每篇详情
-- 筛选功能正常工作
-- 排序功能正常工作
-
-### 测试用例6:优化技巧应用
-
-**前置条件**:
-- 已配置API Key
-- 已生成关键词
-
-**测试步骤**:
-1. 展开"高级优化技巧"
-2. 选择2-3个技巧
-3. 生成内容
-
-**预期结果**:
-- 技巧选择器清晰可见
-- 已选择的技巧有明确提示
-- 生成的内容应用了选择的技巧
-- 技巧效果在内容中体现
-
-### 测试用例7:边界条件测试
-
-**测试场景**:
-1. 关键词列表为空
-2. 批量生成时未选择关键词
-3. API Key无效
-4. 生成过程中网络中断
-5. 批量生成20篇内容(压力测试)
-
-**预期结果**:
-- 所有边界条件都有明确的错误提示
-- 不会导致页面崩溃
-- 用户可以清楚知道问题所在
-- 提供解决方案或重试选项
-
-## 五、最小改动 MVP 优化清单(1天内完成)
-
-### 优先级排序
-
-**P0(必须完成)**:
-1. ✅ 修复批量生成查看问题(2小时)
-2. ✅ 统一图片生成入口(1小时)
-3. ✅ 改进生成进度反馈(1小时)
-4. ✅ 修复内容评分失败提示(0.5小时)
-
-**P1(建议完成)**:
-5. ✅ 重组布局结构(使用Tabs组织详情)(2小时)
-6. ✅ 优化技巧选择器位置(0.5小时)
-7. ✅ 添加批量生成内容列表(1小时)
-
-**P2(可选)**:
-8. ⚠️ 添加内容筛选和排序(1小时)
-9. ⚠️ 移动JSON-LD到Tab3(1小时)
-
-### 实施顺序
-
-1. **第一步**(2小时):修复批量生成查看问题
- - 添加内容列表展示
- - 实现内容选择功能
- - 测试批量生成流程
-
-2. **第二步**(2小时):重组布局结构
- - 使用Tabs组织详情区域
- - 调整功能分组
- - 优化信息层级
-
-3. **第三步**(1.5小时):统一图片生成入口
- - 合并多个生成入口
- - 添加生成方式选择
- - 测试图片生成流程
-
-4. **第四步**(1小时):改进进度反馈
- - 添加进度条
- - 添加状态文本
- - 测试批量生成进度
-
-5. **第五步**(0.5小时):修复评分失败提示
- - 改进错误提示
- - 添加重试按钮
- - 测试错误处理
-
-**总计**:约7小时,可以在1个工作日内完成
-
-## 六、额外风险与体验优化
-
-### 额外风险(至少5条)
-
-1. **状态同步问题** ⚠️ 高风险
- - 风险:批量生成时,如果用户刷新页面,可能丢失生成进度
- - 解决:将生成进度保存到session_state,支持断点续传
-
-2. **API调用频率限制** ⚠️ 中风险
- - 风险:批量生成时,可能触发API频率限制
- - 解决:添加请求间隔,实现重试机制
-
-3. **内容过长导致页面卡顿** ⚠️ 中风险
- - 风险:生成的内容很长时,页面渲染可能卡顿
- - 解决:使用虚拟滚动或分页加载
-
-4. **并发生成冲突** ⚠️ 低风险
- - 风险:用户快速点击生成按钮,可能触发多次生成
- - 解决:添加生成状态锁,防止并发
-
-5. **ZIP文件过大** ⚠️ 低风险
- - 风险:批量生成大量内容时,ZIP文件可能过大
- - 解决:添加文件大小检查,超过限制时分批下载
-
-### 体验优化建议
-
-1. **添加快捷操作** 💡
- - 在内容预览区域添加快捷按钮:复制、优化、下载
- - 使用图标按钮,节省空间
-
-2. **添加内容对比功能** 💡
- - 批量生成时,可以选择2篇内容进行对比
- - 帮助用户快速筛选优质内容
-
-3. **添加内容模板预览** 💡
- - 选择平台时,显示该平台的模板特点
- - 帮助用户选择合适的平台
-
-4. **优化错误提示** 💡
- - 使用更友好的错误提示
- - 提供具体的解决方案
-
-5. **添加操作历史** 💡
- - 记录用户的操作历史
- - 支持快速重做上次操作
-
-## 七、功能归属检查
-
-### ✅ 属于本Tab的功能
-
-1. **内容生成** ✅
- - 单篇生成
- - 批量生成
- - 平台模板选择
- - 优化技巧应用
-
-2. **内容预览** ✅
- - 内容展示
- - 快速操作(下载、复制)
- - 内容列表(批量模式)
-
-3. **质量分析** ✅
- - 内容评分
- - E-E-A-T评估
- - 事实密度评估
-
-4. **增强工具** ✅
- - 图片生成
- - 视频脚本生成
- - 多模态提示生成
-
-### ⚠️ 建议调整的功能
-
-1. **JSON-LD Schema生成** ⚠️
- - 当前位置:Tab2
- - 建议位置:Tab3(文章优化)或独立Tab
- - 理由:属于"优化"范畴,不是"创作"核心功能
-
-2. **技术配置生成** ⚠️
- - 当前位置:Tab2
- - 建议位置:Tab3(文章优化)或独立Tab
- - 理由:属于"优化"范畴,不是"创作"核心功能
-
-3. **E-E-A-T强化** ⚠️
- - 当前位置:Tab2(内容详情中)
- - 建议位置:Tab3(文章优化)
- - 理由:属于"优化"操作,应该在优化Tab进行
-
-### ❌ 不应放在这里的功能
-
-1. **工作流自动化** ❌
- - 当前位置:Tab7
- - 理由:已在独立Tab,无需调整
-
-2. **平台同步** ❌
- - 当前位置:Tab9
- - 理由:已在独立Tab,无需调整
-
-## 八、实施建议
-
-### 分阶段实施
-
-**第一阶段(MVP)**:完成P0问题修复
-- 修复批量生成查看问题
-- 统一图片生成入口
-- 改进进度反馈
-- 修复评分失败提示
-
-**第二阶段(优化)**:完成P1优化
-- 重组布局结构
-- 优化技巧选择器
-- 添加内容列表
-
-**第三阶段(增强)**:完成P2功能
-- 添加筛选和排序
-- 移动JSON-LD到Tab3
-- 添加内容对比功能
-
-### 代码重构建议
-
-1. **提取内容生成逻辑**
- - 将内容生成逻辑提取到独立函数
- - 便于测试和维护
-
-2. **统一状态管理**
- - 使用统一的状态管理方式
- - 避免状态冲突
-
-3. **优化组件复用**
- - 提取可复用的UI组件
- - 减少代码重复
-
-### 测试建议
-
-1. **单元测试**
- - 测试内容生成逻辑
- - 测试状态管理
-
-2. **集成测试**
- - 测试完整生成流程
- - 测试错误处理
-
-3. **用户体验测试**
- - 邀请真实用户测试
- - 收集反馈意见
diff --git a/docs/analysis/TAB_SPLIT_PATTERN.md b/docs/analysis/TAB_SPLIT_PATTERN.md
new file mode 100644
index 0000000..f00baa5
--- /dev/null
+++ b/docs/analysis/TAB_SPLIT_PATTERN.md
@@ -0,0 +1,140 @@
+# Tab 拆分模式说明(基于 Tab1 / Tab2 实践)
+
+> 本文档描述当前项目中「关键词蒸馏」Tab 与「自动创作」Tab 的拆分方式,供后续 Tab3–Tab10 迁移时复用同一模式。
+
+## 一、项目与目录结构概览
+
+```
+geo_tool/
+├── geo_tool.py # Streamlit 主入口(唯一入口,streamlit run geo_tool.py)
+├── modules/
+│ ├── *.py # 业务模块(data_storage, keyword_tool, content_scorer, schema_generator 等)
+│ └── ui/
+│ ├── __init__.py # 导出 tab_keywords, tab_autowrite(后续增加 tab_optimize 等)
+│ ├── state.py # ss_init(), init_session_state()
+│ ├── theme.py # inject_global_theme()
+│ ├── tab_keywords.py # Tab1:关键词蒸馏
+│ └── tab_autowrite.py # Tab2:自动创作
+├── platform_sync/ # 平台同步(GitHub 发布、一键复制等)
+├── docs/
+└── scripts/
+```
+
+- **主入口**:只做页面配置、侧栏、全局变量(cfg, brand, advantages, gen_llm, verify_llms, storage)、Tabs 创建与**路由**(每个 `with tabN:` 内只调用对应 `render_tab_*`)。
+- **各 Tab 逻辑**:全部放在 `modules/ui/tab_*.py` 的 `render_tab_*` 中,通过参数从主入口拿到依赖,不反向引用 `geo_tool`,避免循环依赖。
+
+---
+
+## 二、Tab 模块结构(统一模式)
+
+每个 `tab_xxx.py` 大致分为三块:
+
+### 1. 顶部:导入 + 本 Tab 用到的工具函数
+
+- **只导入本 Tab 用到的**:`streamlit`、LangChain、以及 `modules` 下用到的业务类(如 `ContentScorer`, `SchemaGenerator`)。
+- **不导入** `geo_tool` 或 `modules.ui` 里会间接依赖主入口的模块,避免循环依赖。
+- 若该 Tab 用到了主入口里的**工具函数**(如 `sanitize_filename`、`safe_decode_uploaded`),在 Tab 模块里**复制一份**实现,并注释说明「从 geo_tool 复制,避免循环依赖」。
+
+示例(tab_autowrite.py):
+
+```python
+import io, json, re, time, zipfile
+from datetime import datetime
+import streamlit as st
+from langchain_core.output_parsers import StrOutputParser
+from langchain_core.prompts import PromptTemplate
+from modules.content_scorer import ContentScorer
+from modules.schema_generator import SchemaGenerator
+# ...
+
+INVALID_FS_CHARS = r'<>:"/\\|?*\n\r\t'
+
+def sanitize_filename(name: str, max_len: int = 80) -> str:
+ """Copy of utility from geo_tool, kept local to avoid circular imports."""
+ # ...
+```
+
+### 2. 入口函数签名:`render_tab_*(...) -> None`
+
+- 函数名:`render_tab_keywords` / `render_tab_autowrite` / 后续 `render_tab_optimize` 等。
+- 参数:由主入口「按需传入」该 Tab 用到的所有依赖,常见包括:
+ - `storage`:数据存储
+ - `ss_init`:会话状态初始化
+ - `gen_llm`:生成用 LLM(若该 Tab 有调用)
+ - `brand`, `advantages`:品牌与优势文案
+ - `cfg`:当前配置(若该 Tab 需要读 gen_provider、tongyi_wanxiang_api_key 等)
+ - `record_api_cost`, `model_defaults`:若该 Tab 需要记录 API 成本
+ - 其他 Tab 特有依赖(如 Tab4 可能需 `verify_llms`)
+- 返回值:`None`,仅负责渲染和写 `st.session_state`。
+
+**Tab1(关键词蒸馏)** 不需要记录成本,参数较少:
+
+```python
+def render_tab_keywords(storage, ss_init, gen_llm, brand: str, advantages: str) -> None:
+```
+
+**Tab2(自动创作)** 需要记录成本与 cfg,参数更多:
+
+```python
+def render_tab_autowrite(
+ storage, ss_init, gen_llm, brand: str, advantages: str,
+ cfg: dict, record_api_cost, model_defaults,
+) -> None:
+```
+
+### 3. 函数体:原样迁移「该 Tab 内部」的 UI + 逻辑
+
+- 原主入口里是:`with tab1:` / `with tab2:` 下面的**整块代码**(标题、表单、结果区、子 Tabs 等)。
+- 迁移时:把这块代码**整体**挪到 `render_tab_*` 里,作为函数体;**缩进**保持与原先在 `with tabN:` 下一致(相当于原 4 空格变成函数体第一层缩进)。
+- **不要**在 Tab 模块里改业务逻辑或变量命名,只做「剪切 + 粘贴 + 补参数」,保证行为一致。
+
+**表单提交后的变量**:若在 `if run_opt:` / `if run_content:` 等分支里用到了表单里选的(如「优化技巧」),提交后下一轮 rerun 时表单控件可能还未再执行,需要在分支开头从 `st.session_state` 取一次(例如 `opt_selected_technique_names = st.session_state.get("opt_techniques", [])`),与 tab_autowrite 里对 `content_techniques` 的处理一致。
+
+---
+
+## 三、主入口中的调用方式(geo_tool.py)
+
+- 主导航:`tab1, tab2, ..., tab10 = st.tabs([...])` 不变。
+- 每个 Tab 只做两件事:进入上下文 + 调用对应 `render_*`:
+
+```python
+# =======================
+# Tab1:关键词蒸馏
+# =======================
+with tab1:
+ tab_keywords.render_tab_keywords(storage, ss_init, gen_llm, brand, advantages)
+
+# =======================
+# Tab2:自动创作内容(含批量 ZIP / GitHub 模板)
+# =======================
+with tab2:
+ tab_autowrite.render_tab_autowrite(
+ storage, ss_init, gen_llm, brand, advantages,
+ cfg, record_api_cost, model_defaults
+ )
+
+# Tab3 及以后:同样模式,with tab3: tab_optimize.render_tab_optimize(...)
+```
+
+- 主入口保留:侧栏、cfg、brand/advantages、gen_llm/verify_llms、storage、`record_api_cost` / `model_defaults` 等**跨 Tab 共享**的构造与配置;Tab 内部不创建这些,只通过参数使用。
+
+---
+
+## 四、state 与 theme(已集中)
+
+- **state.py**:`ss_init(key, default)` + `init_session_state()`。主入口在启动时调用 `init_session_state()`;各 Tab 在需要时调用 `ss_init("xxx", default)`。
+- **theme.py**:`inject_global_theme()`。主入口在页面配置后调用一次即可。
+
+Tab 模块只接收并调用 `ss_init`,不直接依赖 state/theme 的实现细节。
+
+---
+
+## 五、新增 Tab 时的检查清单
+
+1. 在 `modules/ui/` 下新建 `tab_.py`。
+2. 按上面「结构」写好:导入、本 Tab 用到的工具函数(必要时从 geo_tool 复制)、`render_tab_(...)` 及完整函数体。
+3. 在 `modules/ui/__init__.py` 中增加 `from . import tab_`(并对外暴露)。
+4. 在 `geo_tool.py` 中:删除该 Tab 对应的整块内联代码,改为 `with tabN: tab_.render_tab_(...)`,并传入该 Tab 所需的全部参数。
+5. 确认:无对 `geo_tool` 或会反向依赖主入口的模块的 import;表单提交后若用到表单值,从 `st.session_state` 按 key 读取。
+
+按此模式,Tab3(文章优化)及后续 Tab 可与 Tab1/Tab2 保持一致、可维护的拆分方式。
diff --git a/docs/features/AI_SEARCH_VERIFIER_FEATURE.md b/docs/features/AI_SEARCH_VERIFIER_FEATURE.md
new file mode 100644
index 0000000..23658a4
--- /dev/null
+++ b/docs/features/AI_SEARCH_VERIFIER_FEATURE.md
@@ -0,0 +1,155 @@
+# AI 搜索验证功能说明
+
+## 功能概述
+
+AI 搜索验证模块支持使用真实的 AI 搜索引擎(如 Perplexity)验证品牌是否被提及,解决传统验证方式的"自我确认偏差"问题。
+
+## 核心问题
+
+传统验证方式的问题:
+
+```
+用 LLM A 生成内容 → 用 LLM A 验证内容是否被引用 → 存在自我确认偏差
+```
+
+AI 搜索验证的解决方案:
+
+```
+用 LLM A 生成内容 → 用 Perplexity 真实搜索引擎验证 → 获得真实反馈
+```
+
+## 功能特性
+
+### 1. Perplexity API 集成
+
+- 接入 Perplexity 实时搜索引擎
+- 获取真实的搜索结果和引用来源
+- 支持搜索结果中的引用分析
+
+### 2. 语义级提及检测
+
+```python
+# 支持多种提及形式
+"YourBrand" # 直接提及
+"YourBrand ERP" # 带后缀
+"YB" # 英文缩写
+```
+
+### 3. 情感分析
+
+分析品牌提及的语境情感:
+
+| 情感类型 | 示例 |
+|---------|------|
+| ✅ 正面 | "YourBrand是行业领先的解决方案" |
+| ➖ 中性 | "YourBrand提供管理功能" |
+| ❌ 负面 | "YourBrand存在一些稳定性问题" |
+
+### 4. 提及位置分析
+
+分析品牌在 AI 回答中的位置:
+
+| 位置 | 权重 | 说明 |
+|------|------|------|
+| 前 1/3 | ⭐⭐⭐ | 用户最可能看到 |
+| 中 1/3 | ⭐⭐ | 可能看到 |
+| 后 1/3 | ⭐ | 可能被忽略 |
+
+### 5. 批量验证报告
+
+```python
+report = {
+ "total_queries": 20,
+ "mentioned_count": 15,
+ "mention_rate": 0.75,
+ "sentiment_distribution": {
+ "positive": 10,
+ "neutral": 4,
+ "negative": 1
+ }
+}
+```
+
+## 使用方式
+
+### 1. 配置 API Key
+
+在 `.streamlit/secrets.toml` 中添加:
+
+```toml
+[api_keys]
+perplexity = "pplx-xxxxxxxxxxxx"
+```
+
+### 2. 使用验证功能
+
+在"多模型验证"或"AI 数据报表"Tab 中:
+- 选择使用 AI 搜索验证
+- 输入测试问题
+- 查看真实搜索结果中的品牌提及情况
+
+### 3. 查看验证报告
+
+验证报告包含:
+- 品牌提及率
+- 提及位置分布
+- 情感分析结果
+- 竞品对比数据
+
+## 技术实现
+
+### 核心模块
+
+| 文件 | 说明 |
+|------|------|
+| `modules/ai_search_verifier.py` | AI 搜索验证器 |
+
+### API 接口
+
+```python
+from modules.ai_search_verifier import AISearchVerifier
+
+# 初始化
+verifier = AISearchVerifier(perplexity_api_key="pplx-xxx")
+
+# 单次验证
+result = verifier.verify_with_perplexity(
+ query="最好的管理软件是什么?",
+ brand="YourBrand"
+)
+
+# 批量验证
+results = verifier.batch_verify(
+ queries=["问题1", "问题2", ...],
+ brand="YourBrand"
+)
+
+# 生成报告
+report = verifier.generate_verification_report(results)
+```
+
+## 验证指标说明
+
+| 指标 | 说明 | 目标值 |
+|------|------|--------|
+| mention_rate | 品牌被提及的问题比例 | > 60% |
+| avg_mentions_per_query | 每个问题平均提及次数 | > 1.5 |
+| positive_ratio | 正面提及占比 | > 70% |
+| front_position_ratio | 前 1/3 位置占比 | > 50% |
+
+## 与传统验证的区别
+
+| 维度 | 传统验证 | AI 搜索验证 |
+|------|---------|------------|
+| 数据来源 | LLM 模拟 | 真实搜索引擎 |
+| 实时性 | 静态 | 实时 |
+| 可信度 | 低(自我验证) | 高(第三方验证) |
+| 成本 | 低 | 需要 API 费用 |
+| 引用来源 | 无 | 有真实来源 |
+
+## 后续优化方向
+
+1. **接入更多搜索引擎**:ChatGPT Search、Google SGE
+2. **自动化定期验证**:定时任务自动验证品牌提及
+3. **竞品监控**:自动监控竞品的 AI 搜索表现
+4. **历史趋势**:跟踪品牌提及率的变化趋势
diff --git a/docs/features/CONFIG_OPTIMIZER_FEATURE.md b/docs/features/CONFIG_OPTIMIZER_FEATURE.md
index 8cfcc03..ddc9d94 100644
--- a/docs/features/CONFIG_OPTIMIZER_FEATURE.md
+++ b/docs/features/CONFIG_OPTIMIZER_FEATURE.md
@@ -1,142 +1,53 @@
-# 配置优化助手功能文档
+# 配置优化助手功能说明
-## 📋 功能概述
+## 功能概述
-配置优化助手是 GEO 工具的核心功能之一,用于分析品牌名称和核心优势是否 GEO 友好,并提供优化建议。这个功能解决了"源头配置不优,后续环节偏差放大"的问题。
+配置优化助手帮助用户优化 GEO 工具的配置参数,包括品牌名称、核心优势、竞品列表等,以提升品牌在 AI 搜索中的提及率。
-### 核心价值
+## 功能特性
-- **源头优化**:在配置阶段就确保品牌名和优势描述符合 GEO 最佳实践
-- **提升效果**:优化后的配置可提升品牌提及率 40%+
-- **差异化定位**:通过竞品对比分析,强化品牌差异化优势
-- **智能指导**:从"被动生成"变成"智能指导",帮助用户建立竞争优势
+### 1. 配置评估
----
+- 分析当前配置的合理性
+- 识别配置中的问题
+- 提供量化评分
-## 🎯 功能位置
+### 2. 优化建议
-**位置**:Tab10 - 配置优化助手(主内容区)
-
-**入口**:点击顶部Tab栏的"10 配置优化助手",然后点击"🔍 分析配置优化"按钮
-
----
-
-## 📚 功能说明
-
-### 1. 分析维度
-
-配置优化助手从以下维度全面评估当前配置:
-
-#### 1.1 品牌名独特性分析
-- 是否过于泛化(如"AI助手"、"智能系统"等通用词)
-- 是否容易被混淆或误认为是其他品牌
-- 是否具有搜索友好性(用户容易搜索到)
-- 是否在AI回答中容易被识别和提及
-
-#### 1.2 优势描述分析
-- 是否具体、可量化(避免"强大"、"优秀"等模糊词)
-- 是否具有差异化(与竞品有明显区别)
-- 是否包含E-E-A-T信号(专业性、经验性、权威性、可信度)
-- 是否便于AI提取和引用
-
-#### 1.3 竞品对比分析
-- 当前配置在竞品中是否具有明显优势
-- 哪些方面容易被竞品超越
-- 如何强化差异化定位
-
-#### 1.4 GEO友好度评估
-- 品牌名是否容易被AI优先提及
-- 优势描述是否符合GEO最佳实践
-- 整体配置是否有助于提升提及率
-
----
-
-### 2. 输出内容
-
-#### 2.1 评估总结
-- 200-300字的总结
-- 概括当前配置的优势和不足
-
-#### 2.2 优化建议
-- **品牌名优化建议**:
- - 问题:指出当前品牌名的问题
- - 建议:给出优化建议
-
-- **优势描述优化建议**:
- - 问题:指出当前优势描述的问题
- - 建议:给出优化建议
-
-- **差异化强化建议**:
- - 竞品对比:与竞品的对比分析
- - 差异化策略:如何强化差异化
-
-#### 2.3 推荐版本
-提供3个优化后的配置版本(从保守到激进):
-
-- **版本1(保守优化)**:
- - 品牌名:优化后的品牌名
- - 优势描述:优化后的优势描述
- - 理由:为什么这样优化
-
-- **版本2(平衡优化)**:
- - 品牌名:优化后的品牌名
- - 优势描述:优化后的优势描述
- - 理由:为什么这样优化
-
-- **版本3(激进优化)**:
- - 品牌名:优化后的品牌名
- - 优势描述:优化后的优势描述
- - 理由:为什么这样优化
-
-#### 2.4 预期效果
-- 提及率提升预期:预计提升幅度
-- GEO友好度提升:预计提升幅度
-- 差异化优势:预计强化效果
-
----
+- 基于 GEO 最佳实践提供建议
+- 多版本优化方案(保守/平衡/激进)
+- 竞品差异化分析
### 3. 一键应用
-每个推荐版本都提供"应用版本"按钮,点击后:
-- 自动更新全局配置中的品牌名和优势描述
-- 提示用户重新点击"应用配置"以生效
-- 自动刷新页面
+- 确认后一键应用优化配置
+- 自动备份原配置
+- 支持回滚
----
+## 使用方式
-## 🔧 技术实现
+### 1. 访问配置优化助手
-### 模块结构
+在应用中点击 **🛠️ 配置优化助手** Tab。
-- **文件**:`modules/config_optimizer.py`
-- **类**:`ConfigOptimizer`
-- **主要方法**:
- - `optimize_config()`:执行配置优化分析
- - `_parse_optimization_result()`:解析优化结果
+### 2. 查看当前配置
-### 工作流程
+系统会显示当前的品牌名称、核心优势、竞品列表等配置。
-1. 用户进入Tab10(配置优化助手)
-2. 系统检查配置是否有效
-3. 系统检查配置hash,如果配置变化则清除旧结果
-4. 用户点击"🔍 分析配置优化"按钮
-5. 调用 `ConfigOptimizer.optimize_config()` 方法
-6. 使用生成LLM进行分析(临时构建LLM实例)
-7. 解析分析结果并存储到 `st.session_state.config_optimization_result`
-8. 在主内容区显示优化建议和推荐版本
-9. 用户可选择应用推荐版本
+### 3. 获取优化建议
-### 结果保存机制
+点击"开始分析"按钮,系统会:
+- 评估当前配置
+- 生成优化建议
+- 提供多版本方案
-- **自动保存**:优化结果保存在 `st.session_state.config_optimization_result` 中,刷新页面后仍可查看
-- **配置变化检测**:使用配置hash(品牌名+优势+竞品)检测配置变化
-- **自动清除**:当品牌名、优势描述或竞品列表变化时,自动清除旧结果,需要重新分析
+### 4. 应用优化
----
+选择合适的优化方案,点击"应用配置"按钮。
-## 📊 使用示例
+## 配置优化示例
-### 示例1:品牌名过于泛化
+### 示例1:品牌名称优化
**当前配置**:
- 品牌名:AI助手
@@ -144,17 +55,17 @@
**优化建议**:
- 品牌名问题:过于泛化,无法区分品牌
-- 建议:使用更具体的品牌名,如"汇信云AI软件"
+- 建议:使用更具体的品牌名
**推荐版本**:
-- 版本1:汇信云AI软件(保守)
-- 版本2:汇信云AI外贸ERP(平衡)
-- 版本3:汇信云AI驱动型外贸ERP(激进)
+- 版本1:YourBrand(保守)
+- 版本2:YourBrand AI(平衡)
+- 版本3:YourBrand 智能解决方案(激进)
-### 示例2:优势描述模糊
+### 示例2:优势描述优化
**当前配置**:
-- 品牌名:汇信云AI软件
+- 品牌名:YourBrand
- 优势:强大、优秀、好用
**优化建议**:
@@ -162,13 +73,13 @@
- 建议:使用具体、可量化的优势描述
**推荐版本**:
-- 版本1:AI赋能外贸ERP、打造外贸智能新引擎(保守)
-- 版本2:AI驱动型ERP、赋能外贸全流程管理(平衡)
-- 版本3:AI驱动型ERP、全链路价值闭环、实时知识更新(激进)
+- 版本1:核心优势1、核心优势2(保守)
+- 版本2:核心优势1、核心优势2、核心优势3(平衡)
+- 版本3:核心优势1、核心优势2、核心优势3、核心优势4(激进)
---
-## ✅ 最佳实践
+## 最佳实践
### 1. 使用时机
@@ -190,35 +101,25 @@
---
-## 🎯 预期效果
-
-根据 GEO 社区验证(Reddit、FirstPageSage 等):
+## 预期效果
+根据 GEO 最佳实践验证:
- **提及率提升**:优化后提及率可提升 40%+
- **差异化优势**:通过竞品对比分析,强化品牌差异化定位
-- **GEO友好度**:优化后的配置更符合 GEO 最佳实践
+- **GEO友好**:优化后的配置更符合 GEO 最佳实践
- **用户体验**:避免"配置错了还不知道"的痛点
---
-## 📝 注意事项
+## 技术实现
-1. **配置有效性**:需要先完成配置并点击"应用配置"才能进行分析
-2. **LLM依赖**:需要生成LLM正常工作才能进行分析
-3. **成本考虑**:每次分析会消耗一次LLM调用,建议在必要时使用
-4. **应用生效**:应用推荐版本后,需要返回侧边栏重新点击"应用配置"才能生效
-5. **结果保存**:优化结果会自动保存,刷新页面后仍可查看
-6. **配置变化**:当修改品牌名、优势描述或竞品列表后,系统会自动清除旧结果,需要重新分析
+### 核心模块
----
+| 文件 | 说明 |
+|------|------|
+| `modules/config_optimizer.py` | 配置优化器 |
-## 🔗 相关文档
+### 依赖模块
-- `docs/analysis/GEO_COMPLIANCE_ANALYSIS.md` - GEO 合规性分析报告
-- `README.md` - 项目主文档
-- `docs/implementation/FEATURES_COMPLETE_LIST.md` - 完整功能列表
-
----
-
-**最后更新**:2025-01-27
-**功能状态**:✅ 已实现
+- `modules/llm_factory.py` - LLM 客户端构建
+- `modules/data_storage.py` - 数据存储
diff --git a/docs/features/CONTENT_METRICS_FEATURE.md b/docs/features/CONTENT_METRICS_FEATURE.md
index 8f43ad0..4e471c7 100644
--- a/docs/features/CONTENT_METRICS_FEATURE.md
+++ b/docs/features/CONTENT_METRICS_FEATURE.md
@@ -24,10 +24,12 @@
**定义**:每100字信任信号数
**计算方式**:
+
- 统计内容中的信任信号(来源占位、数据点、案例等)
- 计算每100字的信任信号数量
**信任信号包括**:
+
- 来源占位:如"根据XX报告"、"参考XX研究"、"来自XX数据"
- 数据点:百分比、数量、倍数等(如"80%"、"3倍"、"100个")
- 案例:如"某企业案例"、"实际测试表明"、"使用中发现"
@@ -41,6 +43,7 @@
**定义**:品牌引用比例(品牌提及次数 / 总提及次数)
**计算方式**:
+
- 统计内容中品牌名称的提及次数
- 计算品牌提及在总提及中的比例
@@ -53,6 +56,7 @@
**定义**:权威性得分(0-100分)
**计算方式**:
+
- 来源占位得分(最多30分):每个来源占位 +5分
- 信任信号密度得分(最多40分):基于信任信号密度
- 数据点得分(最多30分):每个数据点 +2分
@@ -66,6 +70,7 @@
**定义**:参与度潜力(0-100分)
**计算方式**:
+
- 标题得分(最多20分):每个标题 +2分
- 列表得分(最多25分):每个列表项 +1.5分
- FAQ 得分(最多25分):每个FAQ对 +3分
@@ -95,6 +100,7 @@
### 3. 优化建议
基于指标数据,系统会提供优化建议:
+
- 低 Trust Density:建议增加来源占位、数据点、案例
- 低 Citation Share:建议自然增加品牌提及
- 低 Authority Score:建议增加来源占位和数据点
@@ -105,6 +111,7 @@
### 指标概览
显示关键指标的平均值:
+
- **平均 Trust Density**:每100字信任信号数
- **平均 Citation Share**:品牌引用比例
- **平均 Authority Score**:权威性得分(0-100)
@@ -113,6 +120,7 @@
### 详细指标分析
显示每篇文章的详细指标:
+
- 关键词
- 平台
- Trust Density
@@ -126,18 +134,17 @@
### 指标可视化
1. **分布图**:
- - Trust Density 分布
- - Authority Score 分布
-
+ - Trust Density 分布
+ - Authority Score 分布
2. **热力图**:
- - 各平台平均指标热力图(按平台对比)
-
+ - 各平台平均指标热力图(按平台对比)
3. **相关性分析**:
- - 指标相关性矩阵(分析指标之间的关联)
+ - 指标相关性矩阵(分析指标之间的关联)
### Top 内容排名
显示各项指标的 Top 5 内容:
+
- Top 5 Trust Density
- Top 5 Citation Share
- Top 5 Authority Score
@@ -164,6 +171,7 @@
### 4. 优化内容生成
基于指标数据,调整内容生成策略:
+
- 低 Trust Density:在 Prompt 中强调添加来源占位和数据点
- 低 Authority Score:使用"证据驱动"优化技巧
- 低 Engagement Potential:使用"对话式设计"或"步骤式指南"优化技巧
@@ -185,6 +193,7 @@
### 指标计算
使用正则表达式模式匹配识别:
+
- 信任信号模式
- 来源占位模式
- 结构化元素模式
@@ -200,4 +209,4 @@
---
**版本**:1.0.0
-**最后更新**:2025-01-26
+**最后更新**:2025-01-26
\ No newline at end of file
diff --git a/docs/features/CONTENT_SCORER_FEATURE.md b/docs/features/CONTENT_SCORER_FEATURE.md
index 9072880..f504d3b 100644
--- a/docs/features/CONTENT_SCORER_FEATURE.md
+++ b/docs/features/CONTENT_SCORER_FEATURE.md
@@ -26,6 +26,7 @@
### 1. 结构化程度(25分)
**评估标准**:
+
- 是否有清晰的标题层级?
- 是否包含清单、列表、FAQ 等结构化元素?
- 内容层次是否清晰?
@@ -38,6 +39,7 @@
### 2. 品牌提及质量(25分)
**评估标准**:
+
- 品牌提及次数是否合适(2-4次)?
- 品牌提及位置是否靠前(前1/3优先)?
- 品牌提及是否自然(先通用标准,再品牌适用)?
@@ -50,6 +52,7 @@
### 3. 内容权威性(25分)
**评估标准**:
+
- 是否有数据支撑或案例引用?
- 是否有评估维度或选择标准?
- 是否避免编造数据(使用占位建议)?
@@ -62,6 +65,7 @@
### 4. 可引用性(25分)
**评估标准**:
+
- 信息密度是否高?
- 结论是否先行?
- 是否容易被 AI 提取和引用?
@@ -76,21 +80,18 @@
### 自动评分流程
1. **内容生成**
- - 在 Tab2 生成内容(单篇或批量)
-
+ - 在 Tab2 生成内容(单篇或批量)
2. **自动评分**
- - 系统自动调用评分系统
- - 使用 LLM 对内容进行全面评估
- - 生成多维度评分结果
-
+ - 系统自动调用评分系统
+ - 使用 LLM 对内容进行全面评估
+ - 生成多维度评分结果
3. **结果展示**
- - 显示总分(0-100分)
- - 显示各维度得分(结构化、品牌提及、权威性、可引用性)
- - 显示详细评估和改进建议
-
+ - 显示总分(0-100分)
+ - 显示各维度得分(结构化、品牌提及、权威性、可引用性)
+ - 显示详细评估和改进建议
4. **优化建议**
- - 根据评分结果,提供具体的改进建议
- - 识别内容优点和不足
+ - 根据评分结果,提供具体的改进建议
+ - 识别内容优点和不足
---
@@ -101,15 +102,12 @@
- **90-100分**:优秀(绿色)
- 内容质量很高,符合 GEO 原则
- 可以直接使用或仅需微调
-
- **75-89分**:良好(蓝色)
- 内容质量良好,基本符合 GEO 原则
- 建议根据改进建议进行优化
-
- **60-74分**:中等(橙色)
- 内容质量中等,部分符合 GEO 原则
- 建议重点优化低分维度
-
- **0-59分**:需改进(红色)
- 内容质量较低,不符合 GEO 原则
- 建议重新生成或大幅优化
@@ -163,18 +161,16 @@
### 评分算法
1. **LLM 评估**:
- - 使用 LLM 对内容进行全面评估
- - 基于 GEO 原则和最佳实践
- - 生成多维度评分和改进建议
-
+ - 使用 LLM 对内容进行全面评估
+ - 基于 GEO 原则和最佳实践
+ - 生成多维度评分和改进建议
2. **结果解析**:
- - 解析 LLM 返回的 JSON 格式结果
- - 如果解析失败,使用备用方案从文本中提取信息
-
+ - 解析 LLM 返回的 JSON 格式结果
+ - 如果解析失败,使用备用方案从文本中提取信息
3. **快速评估**(可选):
- - 基于规则的快速评估
- - 不调用 LLM,用于初步评估
- - 检查标题、列表、FAQ、品牌提及等基础元素
+ - 基于规则的快速评估
+ - 不调用 LLM,用于初步评估
+ - 检查标题、列表、FAQ、品牌提及等基础元素
---
@@ -214,20 +210,17 @@
## ⚠️ 注意事项
1. **需要 LLM 配置**
- - 内容质量评分需要配置生成 LLM 的 API Key
- - 如果 LLM 未配置,评分功能将无法使用
-
+ - 内容质量评分需要配置生成 LLM 的 API Key
+ - 如果 LLM 未配置,评分功能将无法使用
2. **评分准确性**
- - 评分结果基于 LLM 的评估,可能存在一定主观性
- - 建议结合人工检查,综合判断内容质量
-
+ - 评分结果基于 LLM 的评估,可能存在一定主观性
+ - 建议结合人工检查,综合判断内容质量
3. **API 成本**
- - 每次评分会消耗 API 调用
- - 批量生成时,建议关注 API 成本
-
+ - 每次评分会消耗 API 调用
+ - 批量生成时,建议关注 API 成本
4. **评分时间**
- - LLM 评分需要一定时间
- - 批量生成时,评分会增加总耗时
+ - LLM 评分需要一定时间
+ - 批量生成时,评分会增加总耗时
---
@@ -243,21 +236,18 @@
## 🎯 最佳实践
1. **生成后立即评分**
- - 内容生成后立即查看评分
- - 根据评分结果决定是否需要优化
-
+ - 内容生成后立即查看评分
+ - 根据评分结果决定是否需要优化
2. **关注低分维度**
- - 重点关注得分较低的维度
- - 根据改进建议针对性优化
-
+ - 重点关注得分较低的维度
+ - 根据改进建议针对性优化
3. **对比分析**
- - 对比不同内容的评分
- - 分析高分内容的特征
- - 总结成功经验
-
+ - 对比不同内容的评分
+ - 分析高分内容的特征
+ - 总结成功经验
4. **持续优化**
- - 根据评分结果持续优化内容生成策略
- - 调整 Prompt 模板,提升内容质量
+ - 根据评分结果持续优化内容生成策略
+ - 调整 Prompt 模板,提升内容质量
---
@@ -266,18 +256,16 @@
使用内容质量评分功能后:
1. **提升内容质量**
- - 量化内容质量,识别问题
- - 针对性优化,提升 GEO 效果
-
+ - 量化内容质量,识别问题
+ - 针对性优化,提升 GEO 效果
2. **优化生成策略**
- - 基于评分数据优化 Prompt 模板
- - 提升内容生成质量
-
+ - 基于评分数据优化 Prompt 模板
+ - 提升内容生成质量
3. **数据驱动决策**
- - 基于评分数据决定内容是否发布
- - 优先发布高质量内容
+ - 基于评分数据决定内容是否发布
+ - 优先发布高质量内容
---
**版本**:1.0.0
-**最后更新**:2025-01-27
+**最后更新**:2025-01-27
\ No newline at end of file
diff --git a/docs/features/CONTENT_UNIQUENESS_FEATURE.md b/docs/features/CONTENT_UNIQUENESS_FEATURE.md
new file mode 100644
index 0000000..b463f05
--- /dev/null
+++ b/docs/features/CONTENT_UNIQUENESS_FEATURE.md
@@ -0,0 +1,154 @@
+# 内容独特性检测功能说明
+
+## 功能概述
+
+内容独特性检测模块用于检测批量生成内容的相似度,避免"多篇文章说同一件事"的问题,确保每篇内容都有独特的价值和角度。
+
+## 核心问题
+
+批量生成内容时常见问题:
+
+```
+生成 20 篇内容 → 多篇文章内容高度相似 → 用户体验差 → AI 搜索降权
+```
+
+解决方案:
+
+```
+生成 20 篇内容 → 独特性检测 → 标记相似内容 → 提供修改建议 → 确保内容差异化
+```
+
+## 功能特性
+
+### 1. 多维度相似度计算
+
+
+| 维度 | 权重 | 说明 |
+| ------- | --- | ----------- |
+| 词汇重叠度 | 40% | Jaccard 相似度 |
+| 结构相似度 | 30% | 标题、列表、段落结构 |
+| 关键信息重叠度 | 30% | 数字、引号、专业术语 |
+
+
+### 2. 批量检测
+
+```python
+# 检测多篇内容的独特性
+contents = ["内容1", "内容2", "内容3", ...]
+result = checker.check_batch_uniqueness(contents)
+```
+
+### 3. 重复句子检测
+
+自动找出在多篇内容中重复出现的句子:
+
+```python
+duplicates = checker.find_duplicate_sentences(contents)
+# 返回:
+# [
+# {"sentence": "重复的句子", "appears_in": [0, 2, 5], "count": 3},
+# ...
+# ]
+```
+
+### 4. 独特性评分
+
+```python
+report = {
+ "uniqueness_score": 85, # 0-100,越高越独特
+ "high_similarity_pairs": [...], # 高度相似的内容对
+ "duplicate_sentences": [...], # 重复句子
+ "suggestions": [...] # 改进建议
+}
+```
+
+## 使用方式
+
+### 1. 批量生成时检测
+
+在"自动创作"Tab 中批量生成内容后,系统会自动检测内容独特性。
+
+### 2. 查看检测报告
+
+检测报告包含:
+
+- 整体独特性评分
+- 高度相似的内容对
+- 重复句子列表
+- 针对性改进建议
+
+### 3. 根据建议修改
+
+针对检测结果,可以:
+
+- 调整相似内容的角度
+- 替换重复句子
+- 添加独特的案例或数据
+
+## 技术实现
+
+### 核心模块
+
+
+| 文件 | 说明 |
+| ------------------------------- | --------- |
+| `modules/content_uniqueness.py` | 内容独特性检测模块 |
+
+
+### API 接口
+
+```python
+from modules.content_uniqueness import ContentUniquenessChecker
+
+# 初始化
+checker = ContentUniquenessChecker(similarity_threshold=0.7)
+
+# 批量检测
+result = checker.check_batch_uniqueness(contents)
+
+# 生成报告
+report = checker.generate_uniqueness_report(contents)
+
+# 查找重复句子
+duplicates = checker.find_duplicate_sentences(contents)
+
+# 检查两段内容的相似度
+from modules.content_uniqueness import check_content_similarity
+result = check_content_similarity(content1, content2)
+```
+
+## 相似度阈值说明
+
+
+| 阈值 | 含义 | 建议操作 |
+| --------- | ----- | -------- |
+| < 0.3 | 低相似度 | 内容独特性良好 |
+| 0.3 - 0.5 | 中等相似度 | 可接受,但可优化 |
+| 0.5 - 0.7 | 较高相似度 | 建议修改 |
+| > 0.7 | 高度相似 | 必须修改 |
+
+
+## 最佳实践
+
+### 确保内容差异化的策略
+
+1. **选择不同角度**
+ - 产品功能 vs 客户案例
+ - 技术架构 vs 使用体验
+ - 行业趋势 vs 具体应用
+2. **添加独特元素**
+ - 真实客户案例
+ - 具体数据和指标
+ - 独特的见解和观点
+3. **调整表达方式**
+ - 不同的开头方式
+ - 不同的段落结构
+ - 不同的专业术语
+
+## 后续优化方向
+
+1. **语义相似度**:接入 Embedding 模型,支持语义级别的相似度检测
+2. **自动改写建议**:基于相似度分析,自动生成差异化改写建议
+3. **内容模板库**:提供多样化的内容模板,从源头避免内容雷同
+4. **实时检测**:在生成过程中实时检测,避免生成后再修改
+
diff --git a/docs/features/EEAT_FEATURE.md b/docs/features/EEAT_FEATURE.md
index 28ab759..9828f40 100644
--- a/docs/features/EEAT_FEATURE.md
+++ b/docs/features/EEAT_FEATURE.md
@@ -18,16 +18,15 @@ E-E-A-T 强化模块是 GEO 工具的核心功能之一,用于提升内容的
在文章优化完成后,可以:
1. **📊 评估 E-E-A-T**:评估当前内容的 E-E-A-T 水平
- - 显示四个维度的得分(每个维度 0-25 分,总分 100 分)
- - 提供详细的评估说明
- - 检查来源占位情况
- - 提供改进建议
-
+ - 显示四个维度的得分(每个维度 0-25 分,总分 100 分)
+ - 提供详细的评估说明
+ - 检查来源占位情况
+ - 提供改进建议
2. **✨ 强化 E-E-A-T**:自动优化内容以提升 E-E-A-T
- - 增强专业性表述
- - 添加经验性描述
- - 插入来源占位(数据来源、案例来源、标准来源、专家观点)
- - 提升可信度标记
+ - 增强专业性表述
+ - 添加经验性描述
+ - 插入来源占位(数据来源、案例来源、标准来源、专家观点)
+ - 提升可信度标记
### 2. 内容生成模块(Tab2)
@@ -41,24 +40,28 @@ E-E-A-T 强化模块是 GEO 工具的核心功能之一,用于提升内容的
系统会自动添加以下类型的来源占位:
### 1. 数据来源占位(至少 2 处)
+
- 格式:"根据XX行业报告"、"XX数据显示"、"据XX统计"
- 示例:
- "根据2024年外贸软件行业报告显示"
- "据公开市场调研数据显示"
### 2. 案例来源占位(至少 1 处)
+
- 格式:"某企业案例"、"参考XX实践"、"XX公司案例"
- 示例:
- "参考某大型外贸企业的实际应用案例"
- "某知名企业的成功实践表明"
### 3. 标准来源占位(至少 1 处)
+
- 格式:"按照XX标准"、"参考XX规范"、"符合XX要求"
- 示例:
- "按照ISO质量管理体系标准"
- "参考行业最佳实践规范"
### 4. 专家观点占位(可选,1 处)
+
- 格式:"行业专家认为"、"XX机构指出"、"权威分析显示"
- 示例:
- "行业专家普遍认为"
@@ -69,21 +72,18 @@ E-E-A-T 强化模块是 GEO 工具的核心功能之一,用于提升内容的
### 推荐工作流程
1. **生成或优化内容**
- - 在 Tab2 生成内容,或在 Tab3 优化现有文章
-
+ - 在 Tab2 生成内容,或在 Tab3 优化现有文章
2. **评估 E-E-A-T**
- - 点击"📊 评估 E-E-A-T"按钮
- - 查看四个维度的得分
- - 查看详细评估和改进建议
-
+ - 点击"📊 评估 E-E-A-T"按钮
+ - 查看四个维度的得分
+ - 查看详细评估和改进建议
3. **强化 E-E-A-T**(如需要)
- - 如果评估分数较低(<75分),点击"✨ 强化 E-E-A-T"
- - 系统会自动添加来源占位和提升 E-E-A-T 元素
- - 查看来源占位清单
-
+ - 如果评估分数较低(<75分),点击"✨ 强化 E-E-A-T"
+ - 系统会自动添加来源占位和提升 E-E-A-T 元素
+ - 查看来源占位清单
4. **验证效果**
- - 在 Tab4 进行多模型验证
- - 查看品牌提及率是否提升
+ - 在 Tab4 进行多模型验证
+ - 查看品牌提及率是否提升
## 📊 评分标准
@@ -93,17 +93,14 @@ E-E-A-T 强化模块是 GEO 工具的核心功能之一,用于提升内容的
- 使用专业术语和准确的技术描述
- 提供专业见解和分析
- 展示对该领域的专业理解
-
- **经验性(25分)**
- 包含实际使用经验或案例
- 有第一手体验描述
- 分享实践中的洞察和教训
-
- **权威性(25分)**
- 引用权威来源或数据
- 提及行业标准、研究报告或官方文档
- 有明确的来源占位建议
-
- **可信度(25分)**
- 避免编造数据或虚假信息
- 明确标注不确定信息
@@ -140,4 +137,4 @@ E-E-A-T 强化模块是 GEO 工具的核心功能之一,用于提升内容的
---
**版本**:v1.0
-**更新日期**:2025-01-26
+**更新日期**:2025-01-26
\ No newline at end of file
diff --git a/docs/features/FACT_DENSITY_FEATURE.md b/docs/features/FACT_DENSITY_FEATURE.md
index 3513336..80e1051 100644
--- a/docs/features/FACT_DENSITY_FEATURE.md
+++ b/docs/features/FACT_DENSITY_FEATURE.md
@@ -17,13 +17,12 @@
在内容生成后,可以:
1. **📊 评估事实密度**:评估生成内容的事实密度和结构化程度
- - 显示事实密度得分(0-50分)
- - 显示结构化得分(0-50分)
- - 提供详细的事实分析和结构化分析
-
+ - 显示事实密度得分(0-50分)
+ - 显示结构化得分(0-50分)
+ - 提供详细的事实分析和结构化分析
2. **✨ 强化事实密度**:自动优化内容以提升事实密度和结构化
- - 添加数据信息、案例信息、标准信息等
- - 添加标题层级、清单列表、FAQ等结构化块
+ - 添加数据信息、案例信息、标准信息等
+ - 添加标题层级、清单列表、FAQ等结构化块
### 2. 文章优化模块(Tab3)
@@ -39,56 +38,44 @@
评估内容中包含的事实性信息:
1. **数据信息**(10分)
- - 具体数字、百分比、统计数据
- - 示例:"80%的用户"、"2024年数据显示"
-
+ - 具体数字、百分比、统计数据
+ - 示例:"80%的用户"、"2024年数据显示"
2. **案例信息**(10分)
- - 具体案例、实例、应用场景
- - 示例:"某企业案例"、"实际应用表明"
-
+ - 具体案例、实例、应用场景
+ - 示例:"某企业案例"、"实际应用表明"
3. **标准信息**(8分)
- - 行业标准、规范、要求
- - 示例:"ISO标准"、"行业规范"
-
+ - 行业标准、规范、要求
+ - 示例:"ISO标准"、"行业规范"
4. **对比信息**(8分)
- - 对比数据、差异说明
- - 示例:"相比传统方案提升30%"
-
+ - 对比数据、差异说明
+ - 示例:"相比传统方案提升30%"
5. **时间信息**(7分)
- - 时间节点、时效性
- - 示例:"2024年"、"最新版本"
-
+ - 时间节点、时效性
+ - 示例:"2024年"、"最新版本"
6. **来源信息**(7分)
- - 数据来源、案例来源
- - 示例:"根据XX报告"、"参考XX研究"
+ - 数据来源、案例来源
+ - 示例:"根据XX报告"、"参考XX研究"
### 结构化块(50分)
评估内容的结构化元素:
1. **标题层级**(7分)
- - 是否有清晰的标题层级(H1/H2/H3)
-
+ - 是否有清晰的标题层级(H1/H2/H3)
2. **结论摘要**(7分)
- - 是否有开头的结论摘要(80-120字)
-
+ - 是否有开头的结论摘要(80-120字)
3. **清单列表**(7分)
- - 是否有清单、列表、要点(- 或 1. 格式)
-
+ - 是否有清单、列表、要点(- 或 1. 格式)
4. **FAQ部分**(7分)
- - 是否有常见问题解答
-
+ - 是否有常见问题解答
5. **代码块**(6分)
- - 技术内容是否有代码示例(如适用)
-
+ - 技术内容是否有代码示例(如适用)
6. **对比表格**(6分)
- - 是否有对比表格或对比列表
-
+ - 是否有对比表格或对比列表
7. **步骤说明**(5分)
- - 是否有步骤、流程说明
-
+ - 是否有步骤、流程说明
8. **总结部分**(5分)
- - 是否有结尾总结
+ - 是否有结尾总结
## 🔄 强化功能
@@ -97,28 +84,23 @@
系统会自动添加以下类型的事实信息:
1. **数据信息**
- - 在合适位置添加数据占位
- - 示例:"根据XX数据显示,约XX%的企业"
-
+ - 在合适位置添加数据占位
+ - 示例:"根据XX数据显示,约XX%的企业"
2. **案例信息**
- - 添加实际案例或应用场景(用占位符)
- - 示例:"某企业案例表明"
-
+ - 添加实际案例或应用场景(用占位符)
+ - 示例:"某企业案例表明"
3. **标准信息**
- - 提及相关标准或规范
- - 示例:"按照XX标准"、"参考XX规范"
-
+ - 提及相关标准或规范
+ - 示例:"按照XX标准"、"参考XX规范"
4. **对比信息**
- - 添加对比数据或差异说明
- - 示例:"相比传统方案,提升约XX%"
-
+ - 添加对比数据或差异说明
+ - 示例:"相比传统方案,提升约XX%"
5. **时间信息**
- - 明确时间节点或时效性
- - 示例:"2024年最新"、"当前版本"
-
+ - 明确时间节点或时效性
+ - 示例:"2024年最新"、"当前版本"
6. **来源信息**
- - 标注数据来源
- - 示例:"根据XX行业报告"、"参考XX研究"
+ - 标注数据来源
+ - 示例:"根据XX行业报告"、"参考XX研究"
### 结构化块强化
@@ -138,21 +120,18 @@
### 推荐工作流程
1. **生成或优化内容**
- - 在 Tab2 生成内容,或在 Tab3 优化现有文章
-
+ - 在 Tab2 生成内容,或在 Tab3 优化现有文章
2. **评估事实密度**
- - 点击"📊 评估事实密度"按钮
- - 查看事实密度和结构化得分
- - 查看详细的事实分析和结构化分析
-
+ - 点击"📊 评估事实密度"按钮
+ - 查看事实密度和结构化得分
+ - 查看详细的事实分析和结构化分析
3. **强化事实密度**(如需要)
- - 如果评估分数较低(<75分),点击"✨ 强化事实密度"
- - 系统会自动添加事实信息和结构化块
- - 查看强化详情
-
+ - 如果评估分数较低(<75分),点击"✨ 强化事实密度"
+ - 系统会自动添加事实信息和结构化块
+ - 查看强化详情
4. **验证效果**
- - 在 Tab4 进行多模型验证
- - 查看品牌提及率是否提升
+ - 在 Tab4 进行多模型验证
+ - 查看品牌提及率是否提升
## 📊 评分标准
@@ -193,21 +172,19 @@
## 🎯 最佳实践
1. **分阶段优化**:
- - 首先生成或优化内容
- - 然后评估事实密度和结构化
- - 最后根据评估结果进行强化
-
+ - 首先生成或优化内容
+ - 然后评估事实密度和结构化
+ - 最后根据评估结果进行强化
2. **质量优先**:
- - 关注事实密度分析中的各类信息数量
- - 确保各类事实信息都有一定数量
- - 确保结构化块完整
-
+ - 关注事实密度分析中的各类信息数量
+ - 确保各类事实信息都有一定数量
+ - 确保结构化块完整
3. **验证优化**:
- - 使用强化后的内容生成内容
- - 在 Tab4 验证提及率
- - 根据验证结果调整策略
+ - 使用强化后的内容生成内容
+ - 在 Tab4 验证提及率
+ - 根据验证结果调整策略
---
**版本**:v1.0
-**更新日期**:2025-01-26
+**更新日期**:2025-01-26
\ No newline at end of file
diff --git a/docs/features/JSON_LD_SCHEMA_FEATURE.md b/docs/features/JSON_LD_SCHEMA_FEATURE.md
index d57e2f9..4094e3b 100644
--- a/docs/features/JSON_LD_SCHEMA_FEATURE.md
+++ b/docs/features/JSON_LD_SCHEMA_FEATURE.md
@@ -1,168 +1,107 @@
# JSON-LD Schema.org 结构化数据生成功能说明
-## 📋 功能概述
+## 功能概述
-JSON-LD Schema.org 结构化数据生成模块是 GEO 工具的核心功能之一,用于生成符合 Schema.org 规范的 JSON-LD 代码,直接提升品牌在 AI 模型中的实体识别和权威性。
+JSON-LD Schema.org 结构化数据生成模块帮助用户生成符合 Schema.org 规范的 JSON-LD 代码,提升品牌在 AI 模型中的实体识别和权威性。
-### 核心价值
+## 为什么需要 Schema.org?
-- **直接提升实体识别**:2026 年 AI 模型越来越依赖结构化数据识别实体
-- **立竿见影的效果**:用户可直接将代码贴到官网/GitHub,无需等待索引
-- **权威性提升**:结构化数据明确标识品牌信息,提升在知识图谱中的权威性
-- **符合标准**:使用 Schema.org 标准,被 Google、百度、AI 模型广泛支持
+1. **帮助 AI 理解**:结构化数据让 AI 更容易理解您的品牌和产品
+2. **提升权威性**:Schema.org 是国际标准,使用它能增加内容的可信度
+3. **富媒体展示**:搜索引擎可以使用 Schema 数据生成富媒体搜索结果
-## 🎯 功能位置
+## 支持的 Schema 类型
-### 1. Tab2(自动创作)- 独立生成模块
+| 类型 | 说明 | 适用场景 |
+|------|------|----------|
+| Organization | 组织/公司 | 企业品牌介绍 |
+| SoftwareApplication | 软件应用 | SaaS 产品、软件工具 |
+| Product | 产品 | 实体产品或数字产品 |
+| Service | 服务 | 服务类业务 |
+| FAQPage | FAQ 页面 | 常见问题解答 |
+| HowTo | 操作指南 | 教程、步骤说明 |
+| Article | 文章 | 博客、新闻文章 |
+| Review | 评价 | 产品/服务评价 |
-在 Tab2 顶部,提供独立的 JSON-LD Schema 生成功能:
+## 使用方式
-1. **选择 Schema 类型**
- - Organization(组织/公司)
- - SoftwareApplication(软件应用)
- - Product(产品)
- - Service(服务)
- - 组合(Organization + SoftwareApplication)
+### 1. 生成 Schema
-2. **一键生成**
- - 点击"🚀 生成 JSON-LD"按钮
- - 自动基于品牌信息和优势生成 Schema
+在内容优化或自动创作完成后,系统会提示是否生成 Schema。
-3. **查看和下载**
- - 查看 JSON-LD 代码
- - 查看 HTML Script 标签
- - 下载 JSON 文件或 HTML 文件
+### 2. 选择 Schema 类型
-### 2. Tab2(自动创作)- 自动生成
+根据内容类型选择合适的 Schema 类型。
-在生成 GitHub README 时,系统会自动生成对应的 JSON-LD Schema:
+### 3. 嵌入到网页
-- 自动生成 SoftwareApplication 类型的 Schema
-- 在内容预览区域显示 JSON-LD 代码
-- 提供下载功能
+将生成的 JSON-LD 代码嵌入到网页的 `` 标签中。
-## 📊 支持的 Schema 类型
+## 代码示例
-### 1. Organization(组织/公司)
+### 基本用法
-适合:企业品牌、公司官网
+```python
+from modules.schema_generator import SchemaGenerator
-**包含字段:**
-- name(名称)
-- description(描述)
-- url(官网 URL)
-- logo(Logo URL)
-- foundingDate(成立日期)
-- contactPoint(联系方式)
+generator = SchemaGenerator()
-**使用场景:**
-- 公司官网
-- 企业介绍页面
-- 关于我们页面
+# 生成 Organization Schema
+schema = generator.generate_organization_schema(
+ brand_name="YourBrand",
+ description="YourBrand description",
+ url="https://example.com"
+)
-### 2. SoftwareApplication(软件应用)
+# 生成 HTML 标签
+html_tag = generator.generate_html_script_tag(schema)
+print(html_tag)
+```
-适合:SaaS 产品、软件工具、应用程序
+### 生成 FAQ Schema
-**包含字段:**
-- name(应用名称)
-- description(应用描述)
-- url(应用 URL)
-- applicationCategory(应用类别)
-- operatingSystem(操作系统)
-- publisher(发布者)
-- featureList(功能列表)
-- offers(价格信息)
-- aggregateRating(评分信息)
+```python
+# 从内容中自动提取 Q&A
+faq_schema = generator.auto_generate_faq_schema(content)
-**使用场景:**
-- GitHub 项目
-- 软件官网
-- 应用商店页面
+# 手动创建 FAQ
+faq_items = [
+ {"question": "产品有什么优势?", "answer": "我们的产品具有..."},
+ {"question": "如何开始使用?", "answer": "只需三步..."}
+]
+faq_schema = generator.generate_faq_schema(faq_items)
+```
-### 3. Product(产品)
+### 生成 HowTo Schema
-适合:实体产品或数字产品
+```python
+steps = [
+ {"name": "注册账号", "text": "访问官网注册账号"},
+ {"name": "配置设置", "text": "完成基础配置"},
+ {"name": "开始使用", "text": "开始使用核心功能"}
+]
+howto_schema = generator.generate_howto_schema(
+ title="如何开始使用",
+ steps=steps,
+ description="快速入门指南"
+)
+```
-**包含字段:**
-- name(产品名称)
-- description(产品描述)
-- url(产品 URL)
-- category(产品类别)
-- brand(品牌信息)
-- offers(价格信息)
-- aggregateRating(评分信息)
+## 嵌入示例
-**使用场景:**
-- 产品详情页
-- 电商页面
-- 产品介绍页
-
-### 4. Service(服务)
-
-适合:服务类业务
-
-**包含字段:**
-- name(服务名称)
-- description(服务描述)
-- url(服务 URL)
-- serviceType(服务类型)
-- provider(服务提供者)
-- areaServed(服务区域)
-- offers(价格信息)
-
-**使用场景:**
-- 服务介绍页
-- 服务详情页
-- 业务介绍页
-
-### 5. 组合 Schema
-
-同时生成 Organization + SoftwareApplication/Product/Service
-
-**优势:**
-- 同时标识组织信息和产品/服务信息
-- 建立更完整的品牌知识图谱
-- 提升权威性
-
-## 🔄 使用方法
-
-### 方法一:独立生成(推荐)
-
-1. **进入 Tab2(自动创作)**
-2. **选择 Schema 类型**
- - 根据您的业务类型选择(软件产品选 SoftwareApplication,公司选 Organization)
-3. **点击"🚀 生成 JSON-LD"**
-4. **查看生成的代码**
- - JSON-LD 代码:可直接使用
- - HTML Script 标签:可直接嵌入网页
-5. **下载代码**
- - 下载 JSON 文件:用于 GitHub 等平台
- - 下载 HTML 文件:用于官网嵌入
-
-### 方法二:自动生成(GitHub README)
-
-1. **在 Tab2 生成 GitHub README 内容**
-2. **系统自动生成对应的 JSON-LD Schema**
-3. **在内容预览区域查看 JSON-LD 代码**
-4. **下载并添加到 GitHub 项目**
-
-## 📝 使用示例
-
-### 示例 1:嵌入官网
+### 示例 1:添加到网页 HTML
```html
- 我的品牌
@@ -187,81 +126,37 @@ JSON-LD Schema.org 结构化数据生成模块是 GEO 工具的核心功能之
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
- ...
+ "name": "YourBrand"
}
```
-## 🎯 最佳实践
+## 最佳实践
-1. **选择合适的 Schema 类型**
- - SaaS 产品:SoftwareApplication
- - 企业品牌:Organization
- - 实体产品:Product
- - 服务业务:Service
+1. **选择正确的类型**:根据内容选择最合适的 Schema 类型
+2. **提供完整信息**:尽可能填写所有相关字段
+3. **保持更新**:内容更新时同步更新 Schema
+4. **验证有效性**:使用 Google Rich Results Test 验证
-2. **完善 Schema 信息**
- - 添加官网 URL
- - 添加 Logo URL
- - 添加联系方式
- - 添加功能列表(如适用)
+## 验证工具
-3. **验证 Schema**
- - 使用 [Google Rich Results Test](https://search.google.com/test/rich-results) 验证
- - 使用 [Schema.org Validator](https://validator.schema.org/) 验证
-
-4. **多平台部署**
- - 官网:嵌入 HTML Script 标签
- - GitHub:添加到 README.md
- - 其他平台:根据平台要求调整
-
-## ⚠️ 注意事项
-
-1. **不要编造信息**
- - 所有信息必须真实
- - URL 必须可访问
- - Logo 必须存在
-
-2. **保持更新**
- - 品牌信息变化时及时更新 Schema
- - 定期检查 Schema 有效性
-
-3. **遵循规范**
- - 使用标准的 Schema.org 类型
- - 确保 JSON 格式正确
- - 避免使用不支持的属性
-
-4. **测试验证**
- - 部署前使用验证工具测试
- - 确保 AI 模型能正确解析
-
-## 🔗 相关资源
-
-- [Schema.org 官方文档](https://schema.org/)
- [Google Rich Results Test](https://search.google.com/test/rich-results)
- [Schema.org Validator](https://validator.schema.org/)
-- [JSON-LD 规范](https://json-ld.org/)
-## 💡 预期效果
+## 技术实现
-使用 JSON-LD Schema.org 结构化数据后:
+### 核心模块
-1. **AI 模型识别**
- - AI 模型能更准确地识别品牌实体
- - 提升品牌在知识图谱中的权威性
- - 改善品牌信息的准确性
+| 文件 | 说明 |
+|------|------|
+| `modules/schema_generator.py` | Schema 生成器 |
-2. **搜索引擎优化**
- - 提升在 Google、百度等搜索引擎中的展示
- - 可能获得 Rich Results(富媒体结果)
- - 提升点击率
+### API 接口
-3. **知识图谱**
- - 建立品牌在知识图谱中的节点
- - 与其他实体建立关联
- - 提升品牌权威性
-
----
-
-**版本**:v1.0
-**更新日期**:2025-01-26
+- `generate_organization_schema()` - 生成组织 Schema
+- `generate_software_application_schema()` - 生成软件应用 Schema
+- `generate_faq_schema()` - 生成 FAQ Schema
+- `generate_howto_schema()` - 生成 HowTo Schema
+- `generate_article_schema()` - 生成文章 Schema
+- `generate_review_schema()` - 生成评价 Schema
+- `auto_generate_faq_schema()` - 从内容自动提取 Q&A 并生成 Schema
diff --git a/docs/features/KEYWORD_DATA_ENHANCER_FEATURE.md b/docs/features/KEYWORD_DATA_ENHANCER_FEATURE.md
new file mode 100644
index 0000000..1e60b43
--- /dev/null
+++ b/docs/features/KEYWORD_DATA_ENHANCER_FEATURE.md
@@ -0,0 +1,148 @@
+# 关键词数据增强功能说明
+
+## 功能概述
+
+关键词数据增强模块通过分析历史验证数据,提取高价值关键词,反哺关键词生成策略,实现数据驱动的关键词优化。
+
+## 核心价值
+
+| 问题 | 解决方案 |
+|------|----------|
+| 关键词生成靠 LLM 推理 | 基于真实验证数据优化 |
+| 不知道哪些关键词有效 | 自动分析关键词表现 |
+| 关键词策略缺乏数据支撑 | 数据驱动的决策 |
+
+## 功能特性
+
+### 1. 历史数据分析
+
+分析维度:
+
+| 指标 | 说明 |
+|------|------|
+| 提及率 | 品牌被提及的问题比例 |
+| 平均提及次数 | 每个问题平均提及次数 |
+| 提及位置 | 前 1/3、中 1/3、后 1/3 |
+| 价值分数 | 综合评估 0-100 分 |
+
+### 2. 高价值关键词识别
+
+```python
+high_value_keywords = [
+ {
+ "keyword": "管理软件哪个好",
+ "mention_rate": 0.85,
+ "avg_mentions": 2.3,
+ "value_score": 92,
+ "suggested_action": "✅ 高价值关键词,继续保持"
+ },
+ ...
+]
+```
+
+### 3. 搜索意图分布分析
+
+```python
+intent_distribution = {
+ "对比": 5, # "XX vs XX", "哪个好"
+ "评测": 3, # "XX怎么样"
+ "使用": 4, # "XX怎么用"
+ "购买": 2, # "XX多少钱"
+ "问题": 3, # "XX报错怎么办"
+ "推荐": 3 # "XX推荐"
+}
+```
+
+### 4. 增强提示词生成
+
+基于历史数据生成更精准的关键词生成提示词:
+
+```python
+prompt = enhancer.generate_enhanced_keyword_prompt(
+ brand="YourBrand",
+ advantages="核心优势描述",
+ existing_keywords=["已有关键词1", "已有关键词2"]
+)
+```
+
+## 使用方式
+
+### 1. 查看关键词分析
+
+在"AI 数据报表"Tab 中:
+- 查看关键词表现排名
+- 查看搜索意图分布
+- 查看优化建议
+
+### 2. 使用增强提示词
+
+在"关键词蒸馏"Tab 中:
+- 选择"数据增强"模式
+- 系统自动基于历史数据生成提示词
+- 生成更精准的关键词
+
+### 3. 应用优化建议
+
+根据分析结果:
+- 保留高价值关键词
+- 优化低效关键词
+- 扩展高潜力关键词
+
+## 技术实现
+
+### 核心模块
+
+| 文件 | 说明 |
+|------|------|
+| `modules/keyword_data_enhancer.py` | 关键词数据增强模块 |
+
+### API 接口
+
+```python
+from modules.keyword_data_enhancer import KeywordDataEnhancer
+
+# 初始化
+enhancer = KeywordDataEnhancer(storage)
+
+# 分析历史表现
+analysis = enhancer.analyze_historical_performance(brand="YourBrand", days=30)
+
+# 生成增强提示词
+prompt = enhancer.generate_enhanced_keyword_prompt(
+ brand="YourBrand",
+ advantages="核心优势描述"
+)
+
+# 获取关键词趋势
+trends = enhancer.get_keyword_trends(
+ brand="YourBrand",
+ keyword="管理软件哪个好"
+)
+```
+
+## 价值分数计算
+
+```
+价值分数 = 提及率 × 40 + 平均提及次数 × 30 + 前1/3位置比例 × 30
+```
+
+| 分数范围 | 评价 | 建议操作 |
+|---------|------|----------|
+| ≥ 70 | 高价值 | 继续保持 |
+| 40-69 | 中等价值 | 有提升空间 |
+| < 40 | 低价值 | 考虑替换 |
+
+## 优化建议类型
+
+| 类型 | 触发条件 | 建议内容 |
+|------|----------|----------|
+| replace | 价值分数 < 30 | 建议替换关键词 |
+| expand | 价值分数 ≥ 70 | 建议扩展相关内容 |
+| optimize | 提及率 30%-50% | 建议优化内容 |
+
+## 后续优化方向
+
+1. **接入搜索量数据**:接入百度指数、Google Trends 等数据
+2. **竞品关键词分析**:分析竞品的高价值关键词
+3. **自动关键词更新**:根据验证结果自动更新关键词库
+4. **A/B 测试**:对比不同关键词策略的效果
diff --git a/docs/features/KEYWORD_MINING_FEATURE.md b/docs/features/KEYWORD_MINING_FEATURE.md
index f3618cb..57858f0 100644
--- a/docs/features/KEYWORD_MINING_FEATURE.md
+++ b/docs/features/KEYWORD_MINING_FEATURE.md
@@ -2,124 +2,124 @@
## 📋 功能概述
-智能关键词挖掘与趋势分析功能帮助用户发现高价值关键词,分析竞争度,预测趋势,优化关键词策略,提升 ROI。
+智能关键词挖掘与趋势分析功能帮助用户发现高价值关键词,分析竞争度,预测趋势,优化关键词策略,提升 ROI?
## 🎯 核心功能
### 1. 行业热点挖掘 🌐
- **功能**:基于行业趋势自动挖掘高价值关键词
-- **输入**:行业领域、品牌信息、核心优势
-- **输出**:挖掘的关键词列表,包含类别、意图、预估价值
-- **特点**:
+- **输入**:行业领域、品牌信息、核心优?
+- **输出**:挖掘的关键词列表,包含类别、意图、预估价?
+- **特点**?
- 使用 LLM 分析行业趋势
- - 覆盖不同搜索意图(对比、评测、使用、购买等)
+ - 覆盖不同搜索意图(对比、评测、使用、购买等?
- 提供预估价值评分(1-10分)
-### 2. 竞争度分析 📊
-- **功能**:分析关键词在 AI 中的提及频率和竞争程度
+### 2. 竞争度分?📊
+- **功能**:分析关键词?AI 中的提及频率和竞争程?
- **输入**:关键词列表
- **输出**:每个关键词的竞争度分析结果
-- **指标**:
- - 提及率:品牌在验证结果中的提及频率
- - 竞争级别:低/中/高
+- **指标**?
+ - 提及率:品牌在验证结果中的提及频?
+ - 竞争级别:低/??
- 竞品提及次数
- - 总提及次数
- - 数据点数量
+ - 总提及次?
+ - 数据点数?
### 3. 趋势预测 📈
- **功能**:基于历史数据预测关键词热度变化趋势
-- **输入**:关键词列表、预测天数
-- **输出**:每个关键词的趋势预测结果
-- **指标**:
- - 趋势方向:上升/下降/稳定
- - 趋势强度:0-1
- - 预测提及率:未来 N 天的预测值
- - 置信度:预测的可靠程度
- - 当前提及率:当前的实际值
+- **输入**:关键词列表、预测天?
+- **输出**:每个关键词的趋势预测结?
+- **指标**?
+ - 趋势方向:上?下降/稳定
+ - 趋势强度?-1
+ - 预测提及率:未来 N 天的预测?
+ - 置信度:预测的可靠程?
+ - 当前提及率:当前的实际?
-### 4. 价值矩阵分析 💎
-- **功能**:分析关键词的价值和竞争度,找到最优投入策略
+### 4. 价值矩阵分?💎
+- **功能**:分析关键词的价值和竞争度,找到最优投入策?
- **输入**:关键词列表、竞争度数据、预估价值(可选)
- **输出**:价值矩阵分析结果和智能推荐
-- **矩阵位置**:
+- **矩阵位置**?
- **高价值低竞争**:强烈推荐,优先投入
- - **高价值高竞争**:谨慎投入,需要持续优化
+ - **高价值高竞争**:谨慎投入,需要持续优?
- **低价值低竞争**:可考虑,适合长尾策略
- - **低价值高竞争**:不推荐,避免投入
+ - **低价值高竞争**:不推荐,避免投?
-### 5. 智能推荐 ⭐
+### 5. 智能推荐 ?
- **功能**:基于价值矩阵、竞争度、趋势数据,智能推荐最优关键词
-- **算法**:推荐分数 = 价值分数 - 竞争分数/2 + 趋势加分
-- **输出**:按推荐度排序的关键词列表
+- **算法**:推荐分?= 价值分?- 竞争分数/2 + 趋势加分
+- **输出**:按推荐度排序的关键词列?
## 🚀 使用指南
### 行业热点挖掘
-1. **进入 Tab1(关键词蒸馏)**
-2. **滚动到"智能关键词挖掘与趋势分析"部分**
-3. **点击"🌐 行业热点挖掘"标签页**
+1. **进入 Tab1(关键词蒸馏?*
+2. **滚动?智能关键词挖掘与趋势分析"部分**
+3. **点击"🌐 行业热点挖掘"标签?*
4. **输入行业领域**(如:外贸ERP、AI工具、SaaS产品等)
-5. **设置挖掘数量**(10-50个)
-6. **点击"🚀 开始挖掘"**
-7. **查看挖掘结果**,点击"添加"按钮将关键词添加到列表
+5. **设置挖掘数量**?0-50个)
+6. **点击"🚀 开始挖?**
+7. **查看挖掘结果**,点?添加"按钮将关键词添加到列?
-### 竞争度分析
+### 竞争度分?
-1. **在"📊 竞争度分析"标签页**
-2. **选择要分析的关键词**(可多选)
-3. **点击"📊 开始分析"**
-4. **查看分析结果**:
+1. **?📊 竞争度分?标签?*
+2. **选择要分析的关键?*(可多选)
+3. **点击"📊 开始分?**
+4. **查看分析结果**?
- 数据表格:显示每个关键词的竞争度指标
- 可视化图表:柱状图展示竞争度对比
### 趋势预测
-1. **在"📈 趋势预测"标签页**
-2. **选择要预测的关键词**(可多选)
-3. **设置预测天数**(7-90天)
-4. **点击"🔮 开始预测"**
-5. **查看预测结果**:数据表格显示趋势信息
+1. **?📈 趋势预测"标签?*
+2. **选择要预测的关键?*(可多选)
+3. **设置预测天数**?-90天)
+4. **点击"🔮 开始预?**
+5. **查看预测结果**:数据表格显示趋势信?
-### 价值矩阵分析
+### 价值矩阵分?
-1. **在"💎 价值矩阵分析"标签页**
-2. **选择要分析的关键词**(可多选)
-3. **点击"💎 开始分析"**
-4. **查看分析结果**:
- - 数据表格:显示价值矩阵数据
+1. **?💎 价值矩阵分?标签?*
+2. **选择要分析的关键?*(可多选)
+3. **点击"💎 开始分?**
+4. **查看分析结果**?
+ - 数据表格:显示价值矩阵数?
- 散点图:可视化价值矩阵(X轴:竞争度,Y轴:价值)
- - 智能推荐:按推荐度排序的关键词列表
+ - 智能推荐:按推荐度排序的关键词列?
## 📊 数据说明
-### 竞争度级别判断标准
-- **低竞争**:提及率 ≥ 60%
-- **中竞争**:提及率 30-60%
-- **高竞争**:提及率 < 30%
+### 竞争度级别判断标?
+- **低竞?*:提及率 ?60%
+- **中竞?*:提及率 30-60%
+- **高竞?*:提及率 < 30%
-### 价值矩阵位置判断
-- **高价值**:价值分数 ≥ 6
-- **低价值**:价值分数 < 6
-- **低竞争**:竞争分数 ≤ 4
-- **高竞争**:竞争分数 > 4
+### 价值矩阵位置判?
+- **高价?*:价值分??6
+- **低价?*:价值分?< 6
+- **低竞?*:竞争分??4
+- **高竞?*:竞争分?> 4
### 趋势预测算法
-- 使用简单线性回归分析历史数据
-- 基于时间序列的斜率判断趋势方向
-- 置信度基于数据点数量(10个数据点达到最高置信度)
+- 使用简单线性回归分析历史数?
+- 基于时间序列的斜率判断趋势方?
+- 置信度基于数据点数量?0个数据点达到最高置信度?
## ⚠️ 注意事项
1. **数据依赖**
- - 竞争度分析需要历史验证数据
+ - 竞争度分析需要历史验证数?
- 趋势预测需要足够的历史数据点(建议至少 5 个)
- - 数据点越多,分析结果越准确
+ - 数据点越多,分析结果越准?
-2. **预估价值**
- - 行业热点挖掘会提供预估价值
+2. **预估价?*
+ - 行业热点挖掘会提供预估价?
- 如果没有预估价值,系统会基于提及率估算
- - 建议先进行行业热点挖掘,再进行分析
+ - 建议先进行行业热点挖掘,再进行分?
3. **分析顺序建议**
- 先进行行业热点挖掘,发现新关键词
@@ -127,32 +127,32 @@
- 再进行趋势预测,了解未来趋势
- 最后进行价值矩阵分析,获得综合推荐
-## 🎯 最佳实践
+## 🎯 最佳实?
1. **定期挖掘**
- 定期进行行业热点挖掘,发现新机会
- 关注行业变化,及时更新关键词策略
2. **综合分析**
- - 结合竞争度、趋势、价值矩阵进行综合判断
+ - 结合竞争度、趋势、价值矩阵进行综合判?
- 优先投入"高价值低竞争"的关键词
3. **数据积累**
- - 定期进行验证,积累历史数据
+ - 定期进行验证,积累历史数?
- 数据越多,分析结果越准确
4. **持续优化**
- - 根据分析结果调整关键词策略
- - 关注趋势变化,及时调整投入
+ - 根据分析结果调整关键词策?
+ - 关注趋势变化,及时调整投?
## 📈 预期效果
-- **发现蓝海关键词**:通过行业热点挖掘发现高价值、低竞争的关键词
-- **优化投入分配**:通过价值矩阵分析,将资源投入到最有价值的关键词
-- **预测未来趋势**:提前布局上升趋势的关键词,避免投入下降趋势的关键词
-- **提升 ROI**:数据驱动的关键词策略,最大化投入产出比
+- **发现蓝海关键?*:通过行业热点挖掘发现高价值、低竞争的关键词
+- **优化投入分配**:通过价值矩阵分析,将资源投入到最有价值的关键?
+- **预测未来趋势**:提前布局上升趋势的关键词,避免投入下降趋势的关键?
+- **提升 ROI**:数据驱动的关键词策略,最大化投入产出?
---
-**创建日期**:2025-01-26
-**版本**:1.0.0
+**创建日期**?025-01-26
+**版本**?.0.0
diff --git a/docs/features/KNOWLEDGE_BASE_FEATURE.md b/docs/features/KNOWLEDGE_BASE_FEATURE.md
new file mode 100644
index 0000000..987e62d
--- /dev/null
+++ b/docs/features/KNOWLEDGE_BASE_FEATURE.md
@@ -0,0 +1,147 @@
+# 品牌知识库(RAG)功能说明
+
+## 功能概述
+
+品牌知识库是 GEO 工具的核心模块之一,基于 RAG(检索增强生成)技术,让用户能够上传品牌文档、产品手册、案例库等内容,在 AI 生成内容时自动检索并引用相关信息,确保生成内容的真实性和专业性。
+
+## 核心价值
+
+| 问题 | 解决方案 |
+|------|----------|
+| 品牌信息靠用户手动输入 | 自动从知识库检索相关内容 |
+| 生成内容缺乏真实来源 | 引用真实的品牌文档和案例 |
+| 批量内容同质化严重 | 基于不同文档片段生成差异化内容 |
+
+## 功能特性
+
+### 1. 多类型文档支持
+
+| 文档类型 | 说明 | 分块策略 |
+|---------|------|----------|
+| 📝 通用文本 | 品牌介绍、行业分析等 | 按段落长度分块 |
+| ❓ FAQ 问答 | 常见问题解答 | 按 Q&A 对分块 |
+| 📦 产品文档 | 功能说明、技术架构 | 按章节标题分块 |
+| 💼 客户案例 | 成功案例、客户证言 | 按段落长度分块 |
+| 📑 Markdown 文档 | 技术文档、README | 按章节标题分块 |
+
+### 2. 智能分块算法
+
+```python
+# FAQ 文档:按 Q&A 对分块
+Q:你们的产品有什么优势?
+A:我们的产品具有以下核心优势...
+
+# 产品文档:按章节分块
+# 核心功能
+功能说明内容...
+
+# 通用文档:按长度分块(500字符/块,50字符重叠)
+```
+
+### 3. 关键词检索
+
+- 基于关键词匹配的检索算法
+- 支持文档类型过滤
+- 返回相关度评分
+
+### 4. 内容生成集成
+
+生成内容时自动检索知识库,将相关片段注入 LLM 上下文:
+
+```
+用户选择关键词 + 平台
+ ↓
+知识库检索相关文档片段
+ ↓
+将检索结果注入 Prompt 上下文
+ ↓
+LLM 基于真实信息生成内容
+```
+
+## 使用方式
+
+### 1. 访问知识库
+
+在应用中点击 **📚 品牌知识库** Tab。
+
+### 2. 上传文档
+
+**方式一:上传文件**
+- 支持 TXT、Markdown、CSV 格式
+- 选择文档类型
+- 点击"导入知识库"
+
+**方式二:粘贴文本**
+- 输入文档名称
+- 粘贴文档内容
+- 选择文档类型
+- 点击"导入知识库"
+
+### 3. 搜索测试
+
+在"搜索测试"区域输入查询,验证文档是否被正确索引和检索。
+
+### 4. 来源验证
+
+在"来源验证"区域粘贴 AI 生成的内容,检查其中的来源引用是否真实可信。
+
+## 技术实现
+
+### 核心模块
+
+| 文件 | 说明 |
+|------|------|
+| `modules/knowledge_base.py` | 知识库核心模块 |
+| `modules/ui/tab_knowledge.py` | 知识库管理 UI |
+
+### 数据存储
+
+知识库数据存储在 `knowledge_base/` 目录:
+
+```
+knowledge_base/
+├── documents.json # 文档元数据
+└── chunks.json # 文档分块数据
+```
+
+### API 接口
+
+```python
+from modules.knowledge_base import KnowledgeBase
+
+# 初始化
+kb = KnowledgeBase(storage_path="knowledge_base")
+
+# 添加文档
+kb.add_document(filename="产品说明.md", content="...", doc_type="product")
+
+# 搜索
+results = kb.search(query="产品有什么优势", top_k=5)
+
+# 获取生成上下文
+context = kb.get_context_for_generation(query="产品优势", brand="品牌名", platform="知乎")
+```
+
+## 最佳实践
+
+### 文档准备建议
+
+1. **FAQ 文档**:使用 `Q:` / `A:` 格式,每个 Q&A 对独立
+2. **产品文档**:使用 Markdown 标题分隔不同功能模块
+3. **案例文档**:包含具体的客户名称、使用场景、效果数据
+4. **品牌介绍**:包含品牌定位、核心优势、差异化特点
+
+### 文档质量要求
+
+- ✅ 包含真实的品牌信息和数据
+- ✅ 使用清晰的结构和标题
+- ✅ 定期更新维护
+- ❌ 避免过时或错误的信息
+- ❌ 避免过于营销化的语言
+
+## 后续优化方向
+
+1. **向量检索**:接入 FAISS/ChromaDB,支持语义检索
+2. **自动更新**:支持从官网/文档自动同步更新
+3. **多语言支持**:支持中英文混合检索
+4. **检索质量评估**:自动评估检索结果的相关性
diff --git a/docs/features/LLM_FACTORY_FEATURE.md b/docs/features/LLM_FACTORY_FEATURE.md
new file mode 100644
index 0000000..27cc320
--- /dev/null
+++ b/docs/features/LLM_FACTORY_FEATURE.md
@@ -0,0 +1,130 @@
+# LLM 工厂模块说明
+
+## 模块概述
+
+LLM 工厂模块提供统一的 LLM(大语言模型)客户端构建接口,支持多种 LLM 提供商的初始化和管理。
+
+## 支持的 LLM 提供商
+
+| 提供商 | 模型 | 说明 |
+|-------|------|------|
+| DeepSeek | deepseek-chat | 深度求索 |
+| OpenAI | gpt-4o-mini, gpt-4 | OpenAI GPT 系列 |
+| 通义千问 | qwen-max, qwen-turbo | 阿里云 |
+| Groq | llama3-70b-8192 | Groq 推理加速 |
+| Moonshot | moonshot-v1-128k | Kimi |
+| 豆包 | 自定义 | 字节跳动 |
+| 文心一言 | ernie-bot-turbo | 百度 |
+
+## 使用方式
+
+### 基本用法
+
+```python
+from modules.llm_factory import build_llm
+
+# 构建 LLM 实例
+llm = build_llm(
+ provider="DeepSeek",
+ api_key="sk-xxx",
+ model="deepseek-chat",
+ temperature=0.7
+)
+
+# 使用 LLM
+response = llm.invoke("你好")
+```
+
+### 获取默认模型
+
+```python
+from modules.llm_factory import get_default_model
+
+model = get_default_model("DeepSeek")
+# 返回: "deepseek-chat"
+```
+
+### 获取支持的提供商列表
+
+```python
+from modules.llm_factory import get_supported_providers
+
+providers = get_supported_providers()
+# 返回: ["DeepSeek", "OpenAI (GPT)", "Tongyi (通义千问)", ...]
+```
+
+## 技术实现
+
+### 核心文件
+
+| 文件 | 说明 |
+|------|------|
+| `modules/llm_factory.py` | LLM 工厂模块 |
+| `modules/chat_doubao.py` | 豆包聊天模型封装 |
+
+### 设计模式
+
+采用工厂模式,统一管理多种 LLM 提供商:
+
+```
+build_llm(provider, api_key, model, temperature)
+ ├── DeepSeek → ChatDeepSeek
+ ├── OpenAI → ChatOpenAI
+ ├── Tongyi → ChatTongyi
+ ├── Groq → ChatGroq
+ ├── Moonshot → ChatMoonshot
+ ├── 豆包 → ChatDoubao (自定义)
+ └── 文心一言 → QianfanChatEndpoint
+```
+
+### 缓存机制
+
+在 `geo_tool.py` 中使用 Streamlit 的 `@st.cache_resource` 装饰器缓存 LLM 实例:
+
+```python
+@st.cache_resource(show_spinner=False)
+def build_llm(provider, api_key, model, temperature):
+ from modules.llm_factory import build_llm as _build_llm
+ return _build_llm(provider, api_key, model, temperature)
+```
+
+## 特殊配置
+
+### 豆包 API Key 格式
+
+豆包使用特殊的 API Key 格式:
+
+```
+access_key:secret_key:endpoint_id
+```
+
+示例:
+```
+AKLTxxx:SKxxx:ep-xxx
+```
+
+### 文心一言 API Key 格式
+
+文心一言使用百度千帆平台的 API Key:
+
+```
+app_key:app_secret
+```
+
+## 错误处理
+
+模块提供清晰的错误提示:
+
+```python
+try:
+ llm = build_llm("Unknown", api_key, model, temperature)
+except ValueError as e:
+ print(e) # "不支持的 LLM 提供商: Unknown"
+```
+
+## 后续优化方向
+
+1. **负载均衡**:支持多个 API Key 轮询使用
+2. **自动降级**:主提供商失败时自动切换到备用
+3. **成本优化**:根据任务复杂度自动选择合适的模型
+4. **性能监控**:记录每个提供商的响应时间和成功率
diff --git a/docs/features/MULTIMODAL_FEATURE.md b/docs/features/MULTIMODAL_FEATURE.md
index 344d6e1..d76bf70 100644
--- a/docs/features/MULTIMODAL_FEATURE.md
+++ b/docs/features/MULTIMODAL_FEATURE.md
@@ -1,24 +1,24 @@
-# 多模态提示生成功能说明
+# 多模态提示生成功能说?
## 📋 功能概述
-多模态提示生成模块是 GEO 工具的高级功能,用于为内容生成详细的配图描述和视频脚本描述,提升内容的视觉吸引力和传播效果。
+多模态提示生成模块是 GEO 工具的高级功能,用于为内容生成详细的配图描述和视频脚本描述,提升内容的视觉吸引力和传播效果?
-### 核心价值
+### 核心价?
-- **提升内容吸引力**:详细的配图描述帮助创作更吸引人的视觉内容
-- **平台适配**:针对不同平台(小红书、抖音、微信公众号、B站)生成适配的配图描述
+- **提升内容吸引?*:详细的配图描述帮助创作更吸引人的视觉内?
+- **平台适配**:针对不同平台(小红书、抖音、微信公众号、B站)生成适配的配图描?
- **视频脚本支持**:为B站等视频平台生成详细的画面描述和镜头语言
-- **品牌融入**:配图描述自然融入品牌元素,保持内容一致性
+- **品牌融入**:配图描述自然融入品牌元素,保持内容一致?
## 🎯 功能位置
-### Tab2(自动创作)- 内容生成后
+### Tab2(自动创作)- 内容生成?
在生成单篇内容后,可以:
-1. **🎨 生成配图/视频描述**:一键生成详细的配图描述或视频脚本描述
-2. **📸 配图描述详情**:查看每个配图的详细描述(风格、色调、构图、关键元素等)
+1. **🎨 生成配图/视频描述**:一键生成详细的配图描述或视频脚本描?
+2. **📸 配图描述详情**:查看每个配图的详细描述(风格、色调、构图、关键元素等?
3. **🎬 视频脚本描述**:查看视频片段的画面描述、镜头语言、音效建议等
## 🔄 工作流程
@@ -26,64 +26,64 @@
### 配图描述生成流程
1. **内容生成**
- - 在 Tab2 生成内容(小红书、抖音、微信公众号等支持配图的平台)
+ - ?Tab2 生成内容(小红书、抖音、微信公众号等支持配图的平台?
- 内容中应包含配图占位符(【配图:xxx】)
2. **生成配图描述**
- 点击"🎨 生成配图/视频描述"按钮
- - 系统自动识别内容中的配图占位符
+ - 系统自动识别内容中的配图占位?
- 为每个配图位置生成详细的配图描述
3. **查看配图描述**
- - 查看每个配图的详细描述
+ - 查看每个配图的详细描?
- 了解配图的风格、色调、构图、关键元素等
- 根据描述进行图片创作或使用AI生图工具
### 视频脚本描述生成流程
1. **内容生成**
- - 在 Tab2 生成 B站视频脚本内容
+ - ?Tab2 生成 B站视频脚本内?
2. **生成视频脚本描述**
- 点击"🎨 生成配图/视频描述"按钮
- - 系统自动识别为视频平台
+ - 系统自动识别为视频平?
- 为内容片段生成详细的画面描述
3. **查看视频脚本描述**
- - 查看每个片段的画面描述
+ - 查看每个片段的画面描?
- 了解镜头类型、镜头运动、转场、音效建议等
- - 根据描述进行视频拍摄或制作
+ - 根据描述进行视频拍摄或制?
## 📊 配图描述内容
### 描述维度
1. **详细描述**
- - 图片应该包含的主要元素(人物、物品、场景等)
- - 图片的风格(写实、插画、图表、截图等)
- - 图片的色调和氛围(明亮、专业、温馨等)
+ - 图片应该包含的主要元素(人物、物品、场景等?
+ - 图片的风格(写实、插画、图表、截图等?
+ - 图片的色调和氛围(明亮、专业、温馨等?
- 图片的构图(居中、左右布局、上下布局等)
2. **平台适配**
- - **小红书**:生活化、美观、有吸引力
- - **抖音**:视觉冲击力强、简洁明了
- - **微信公众号**:专业、清晰、符合文章风格
- - **B站**:适合视频封面、有动感
+ - **小红?*:生活化、美观、有吸引?
+ - **抖音**:视觉冲击力强、简洁明?
+ - **微信公众?*:专业、清晰、符合文章风?
+ - **B?*:适合视频封面、有动感
3. **品牌融入**
- 如果内容涉及品牌,配图应自然融入品牌元素
- - 但不要过于商业化,保持自然
+ - 但不要过于商业化,保持自?
### 输出格式
-每个配图描述包含:
+每个配图描述包含?
- **位置**:在内容中的位置描述
-- **原始提示**:内容中的原始配图提示
-- **详细描述**:50-150字的详细配图描述
-- **风格**:写实/插画/图表/截图等
-- **色调**:明亮/专业/温馨/商务等
-- **构图**:居中/左右/上下等
-- **关键元素**:图片应包含的主要元素列表
+- **原始提示**:内容中的原始配图提?
+- **详细描述**?0-150字的详细配图描述
+- **风格**:写?插画/图表/截图?
+- **色调**:明?专业/温馨/商务?
+- **构图**:居?左右/上下?
+- **关键元素**:图片应包含的主要元素列?
- **平台特定要求**:针对平台的特定要求
## 🎬 视频脚本描述内容
@@ -91,39 +91,39 @@
### 描述维度
1. **画面描述**
- - 画面应该展示的内容(场景、人物、物品、动作等)
- - 画面类型(实拍、动画、截图、演示等)
- - 画面节奏(快切、慢镜头、定格等)
+ - 画面应该展示的内容(场景、人物、物品、动作等?
+ - 画面类型(实拍、动画、截图、演示等?
+ - 画面节奏(快切、慢镜头、定格等?
2. **镜头语言**
- - 镜头类型(特写、中景、全景等)
- - 镜头运动(推拉、摇移、跟随等)
- - 画面转场(切换、淡入淡出、划入等)
+ - 镜头类型(特写、中景、全景等?
+ - 镜头运动(推拉、摇移、跟随等?
+ - 画面转场(切换、淡入淡出、划入等?
-3. **音效和字幕**
- - 建议的音效(背景音乐、音效等)
+3. **音效和字?*
+ - 建议的音效(背景音乐、音效等?
- 字幕要点(关键信息、强调内容)
4. **时长建议**
- - 该片段的建议时长(秒)
+ - 该片段的建议时长(秒?
### 输出格式
-每个视频片段描述包含:
-- **时间戳**:片段的时间范围(如"00:30-01:00")
+每个视频片段描述包含?
+- **时间?*:片段的时间范围(如"00:30-01:00"?
- **画面描述**:详细的画面内容描述
-- **镜头类型**:特写/中景/全景等
-- **镜头运动**:推拉/摇移/跟随/固定等
-- **转场**:切换/淡入淡出/划入等
+- **镜头类型**:特?中景/全景?
+- **镜头运动**:推?摇移/跟随/固定?
+- **转场**:切?淡入淡出/划入?
- **音效建议**:背景音乐、音效等建议
-- **字幕要点**:关键信息列表
+- **字幕要点**:关键信息列?
- **建议时长**:片段时长(秒)
-## 🖼️ 生图API集成(可选功能)
+## 🖼?生图API集成(可选功能)
-### 支持的生图模型
+### 支持的生图模?
-模块已集成以下生图API支持(可选使用):
+模块已集成以下生图API支持(可选使用)?
1. **OpenAI DALL-E 3**
- 需要:OpenAI API Key
@@ -135,9 +135,9 @@
- 特点:开源、可定制
- 使用:`generate_image_with_stable_diffusion()`
-3. **通义万相(阿里云)**
- - 需要:阿里云 API Key
- - 特点:国内服务、速度快
+3. **通义万相(阿里云?*
+ - 需要:阿里?API Key
+ - 特点:国内服务、速度?
- 使用:`generate_image_with_tongyi()`
### 使用生图API
@@ -166,7 +166,7 @@ if result["success"]:
1. **内容创作时添加占位符**
- 在生成内容时,系统会自动在某些平台添加配图占位符
- - 也可以手动在内容中添加【配图:xxx】格式的占位符
+ - 也可以手动在内容中添加【配图:xxx】格式的占位?
2. **生成详细描述**
- 内容生成后,点击"生成配图/视频描述"按钮
@@ -178,70 +178,70 @@ if result["success"]:
### 视频脚本使用
-1. **生成B站视频脚本**
- - 在 Tab2 选择"B站(视频脚本)"平台生成内容
+1. **生成B站视频脚?*
+ - ?Tab2 选择"B站(视频脚本?平台生成内容
2. **生成视频脚本描述**
- 内容生成后,点击"生成配图/视频描述"按钮
- - 系统会自动识别为视频平台并生成画面描述
+ - 系统会自动识别为视频平台并生成画面描?
3. **使用描述制作视频**
- - 根据画面描述进行视频拍摄或制作
+ - 根据画面描述进行视频拍摄或制?
- 参考镜头语言、转场、音效建议等
## ⚠️ 注意事项
-1. **需要 LLM**:多模态提示生成功能需要配置生成 LLM 的 API Key
-2. **API 调用**:生成过程会调用 LLM API,注意 API 费用
-3. **配图占位符**:内容中需要包含【配图:xxx】格式的占位符才能生成配图描述
-4. **平台识别**:系统会自动识别平台类型(配图平台或视频平台)
+1. **需?LLM**:多模态提示生成功能需要配置生?LLM ?API Key
+2. **API 调用**:生成过程会调用 LLM API,注?API 费用
+3. **配图占位?*:内容中需要包含【配图:xxx】格式的占位符才能生成配图描?
+4. **平台识别**:系统会自动识别平台类型(配图平台或视频平台?
5. **生图API**:生图API功能是可选的,需要额外配置API Key
## 🔗 相关功能
- **内容生成**:在 Tab2 生成包含配图占位符的内容
- **内容质量评分**:在 Tab2 评估内容质量
-- **E-E-A-T 强化**:在 Tab2 强化内容的专业性
+- **E-E-A-T 强化**:在 Tab2 强化内容的专业?
-## 🎯 最佳实践
+## 🎯 最佳实?
-1. **合理使用占位符**
+1. **合理使用占位?*
- 在关键位置添加配图占位符(如开头、重点段落、结尾)
- - 占位符提示要简洁明了(如"【配图:产品界面】")
+ - 占位符提示要简洁明了(?【配图:产品界面??
2. **平台适配**
- 不同平台的配图风格不同,系统会自动适配
- - 小红书:生活化、美观
- - 抖音:视觉冲击力强
- - 微信公众号:专业、清晰
+ - 小红书:生活化、美?
+ - 抖音:视觉冲击力?
+ - 微信公众号:专业、清?
3. **品牌融入**
- - 配图描述会自然融入品牌元素
- - 但不要过于商业化,保持自然
+ - 配图描述会自然融入品牌元?
+ - 但不要过于商业化,保持自?
4. **使用AI生图工具**
- 根据详细描述使用AI生图工具(如DALL-E、Midjourney等)
- 或使用集成的生图API直接生成图片
5. **视频制作**
- - 根据视频脚本描述进行视频拍摄或制作
+ - 根据视频脚本描述进行视频拍摄或制?
- 参考镜头语言、转场、音效建议等
## 📈 预期效果
### 短期效果
-- **提升内容吸引力**:详细的配图描述帮助创作更吸引人的视觉内容
+- **提升内容吸引?*:详细的配图描述帮助创作更吸引人的视觉内?
- **提升传播效果**:图文并茂的内容更容易被用户分享
-- **提升专业度**:详细的视频脚本描述提升视频制作的专业度
+- **提升专业?*:详细的视频脚本描述提升视频制作的专业度
### 长期效果
-- **建立视觉风格**:通过统一的配图描述建立品牌视觉风格
-- **提升内容质量**:多模态内容提升整体内容质量
-- **增强品牌识别**:品牌元素自然融入配图,增强品牌识别度
+- **建立视觉风格**:通过统一的配图描述建立品牌视觉风?
+- **提升内容质量**:多模态内容提升整体内容质?
+- **增强品牌识别**:品牌元素自然融入配图,增强品牌识别?
---
**版本**:v1.0
-**更新日期**:2025-01-26
+**更新日期**?025-01-26
diff --git a/docs/features/SEMANTIC_EXPANSION_FEATURE.md b/docs/features/SEMANTIC_EXPANSION_FEATURE.md
index 087f687..c8cb1bb 100644
--- a/docs/features/SEMANTIC_EXPANSION_FEATURE.md
+++ b/docs/features/SEMANTIC_EXPANSION_FEATURE.md
@@ -2,164 +2,164 @@
## 📋 功能概述
-语义足迹扩展模块是 GEO 工具的关键功能之一,用于基于现有关键词,通过语义相似度扩展出更多相关关键词,从而提升关键词覆盖面,扩大内容投放的搜索意图覆盖范围。
+语义足迹扩展模块?GEO 工具的关键功能之一,用于基于现有关键词,通过语义相似度扩展出更多相关关键词,从而提升关键词覆盖面,扩大内容投放的搜索意图覆盖范围?
-### 核心价值
+### 核心价?
-- **提升覆盖面**:从不同角度扩展关键词,覆盖更多搜索意图
-- **语义相关性**:确保扩展的关键词与原始关键词在语义上相关
-- **多样性**:生成同义词、场景词、问题词、功能词、长尾词等多种类型
+- **提升覆盖?*:从不同角度扩展关键词,覆盖更多搜索意图
+- **语义相关?*:确保扩展的关键词与原始关键词在语义上相?
+- **多样?*:生成同义词、场景词、问题词、功能词、长尾词等多种类?
- **智能去重**:自动过滤重复和过于相似的关键词
## 🎯 功能位置
-### 关键词蒸馏模块(Tab1)
+### 关键词蒸馏模块(Tab1?
-在生成关键词后,可以:
+在生成关键词后,可以?
-1. **🚀 开始语义扩展**:基于现有关键词进行语义扩展
- - 设置扩展数量(10-100 个)
- - 选择合并策略(追加/替换/交替)
+1. **🚀 开始语义扩?*:基于现有关键词进行语义扩展
+ - 设置扩展数量?0-100 个)
+ - 选择合并策略(追?替换/交替?
- 一键扩展关键词
-2. **📊 扩展统计**:查看扩展结果统计
+2. **📊 扩展统计**:查看扩展结果统?
- 扩展总数
- 各类扩展数量(同义、场景、问题、功能、长尾)
-3. **📈 覆盖面分析**:分析扩展效果
+3. **📈 覆盖面分?*:分析扩展效?
- 扩展比例
- - 唯一关键词数量
- - 关键词类别分布
+ - 唯一关键词数?
+ - 关键词类别分?
## 🔄 扩展策略
系统采用多种扩展策略,确保关键词的多样性和覆盖面:
### 1. 同义扩展
-使用同义词替换关键词中的核心词
-- **示例**:
- - "外贸ERP软件" → "外贸管理系统"、"外贸业务软件"
- - "CRM系统" → "客户关系管理系统"、"客户管理软件"
+使用同义词替换关键词中的核心?
+- **示例**?
+ - "外贸ERP软件" ?"外贸管理系统"?外贸业务软件"
+ - "CRM系统" ?"客户关系管理系统"?客户管理软件"
### 2. 场景扩展
-添加使用场景或应用场景
-- **示例**:
- - "外贸ERP" → "小型企业外贸ERP"、"跨境电商ERP"
- - "CRM系统" → "销售团队CRM"、"客服CRM系统"
+添加使用场景或应用场?
+- **示例**?
+ - "外贸ERP" ?"小型企业外贸ERP"?跨境电商ERP"
+ - "CRM系统" ?"销售团队CRM"?客服CRM系统"
### 3. 问题扩展
-转换为问题形式
-- **示例**:
- - "外贸ERP推荐" → "外贸ERP哪个好"、"如何选择外贸ERP"
- - "CRM系统" → "CRM系统怎么选"、"什么CRM系统好用"
+转换为问题形?
+- **示例**?
+ - "外贸ERP推荐" ?"外贸ERP哪个??如何选择外贸ERP"
+ - "CRM系统" ?"CRM系统怎么??什么CRM系统好用"
### 4. 功能扩展
-突出不同功能点
-- **示例**:
- - "外贸ERP" → "外贸订单管理软件"、"外贸库存管理ERP"
- - "CRM系统" → "客户跟进CRM"、"销售数据分析CRM"
+突出不同功能?
+- **示例**?
+ - "外贸ERP" ?"外贸订单管理软件"?外贸库存管理ERP"
+ - "CRM系统" ?"客户跟进CRM"?销售数据分析CRM"
### 5. 长尾扩展
-生成更具体的长尾词
-- **示例**:
- - "外贸ERP" → "适合小企业的外贸ERP软件"、"支持多语言的外贸ERP系统"
- - "CRM系统" → "免费版CRM系统推荐"、"云端CRM系统对比"
+生成更具体的长尾?
+- **示例**?
+ - "外贸ERP" ?"适合小企业的外贸ERP软件"?支持多语言的外贸ERP系统"
+ - "CRM系统" ?"免费版CRM系统推荐"?云端CRM系统对比"
## 📊 合并策略
### 1. 追加(推荐)
在现有关键词后添加扩展关键词
- **适用场景**:希望保留所有原始关键词
-- **结果**:原始关键词 + 扩展关键词
+- **结果**:原始关键词 + 扩展关键?
### 2. 替换
-用扩展关键词替换现有关键词
-- **适用场景**:希望用扩展关键词完全替换原始列表
-- **结果**:仅包含扩展关键词
+用扩展关键词替换现有关键?
+- **适用场景**:希望用扩展关键词完全替换原始列?
+- **结果**:仅包含扩展关键?
### 3. 交替
-交替插入原始关键词和扩展关键词
-- **适用场景**:希望混合原始和扩展关键词
+交替插入原始关键词和扩展关键?
+- **适用场景**:希望混合原始和扩展关键?
- **结果**:原始关键词和扩展关键词交替排列
## 🔄 工作流程
### 推荐工作流程
-1. **生成初始关键词**
- - 在 Tab1 使用"AI生成"、"托词工具"或"混合模式"生成关键词
+1. **生成初始关键?*
+ - ?Tab1 使用"AI生成"?托词工具"?混合模式"生成关键?
2. **语义扩展**
- - 设置扩展数量(建议 20-50 个)
- - 选择合并策略(推荐"追加")
- - 点击"🚀 开始语义扩展"
+ - 设置扩展数量(建?20-50 个)
+ - 选择合并策略(推?追加"?
+ - 点击"🚀 开始语义扩?
3. **查看扩展结果**
- 查看扩展统计信息
- - 查看覆盖面分析
+ - 查看覆盖面分?
- 查看扩展详情(可选)
-4. **使用扩展后的关键词**
- - 在 Tab2 使用扩展后的关键词生成内容
- - 在 Tab4 使用扩展后的关键词进行验证
+4. **使用扩展后的关键?*
+ - ?Tab2 使用扩展后的关键词生成内?
+ - ?Tab4 使用扩展后的关键词进行验?
## 📈 扩展效果评估
### 扩展统计指标
-- **扩展总数**:成功扩展的关键词数量
-- **同义扩展**:使用同义词扩展的数量
+- **扩展总数**:成功扩展的关键词数?
+- **同义扩展**:使用同义词扩展的数?
- **场景扩展**:添加场景的扩展数量
-- **问题扩展**:转换为问题形式的数量
+- **问题扩展**:转换为问题形式的数?
- **功能扩展**:突出功能的扩展数量
-- **长尾扩展**:生成长尾词的数量
+- **长尾扩展**:生成长尾词的数?
-### 覆盖面分析
+### 覆盖面分?
-- **扩展比例**:扩展关键词数量 / 原始关键词数量
-- **唯一关键词**:去重后的唯一关键词数量
-- **类别分布**:各类关键词的分布情况
+- **扩展比例**:扩展关键词数量 / 原始关键词数?
+- **唯一关键?*:去重后的唯一关键词数?
+- **类别分布**:各类关键词的分布情?
## 💡 使用建议
-1. **先生成基础关键词**:使用 AI 生成或托词工具生成初始关键词
-2. **适度扩展**:建议扩展数量为原始关键词的 0.5-2 倍
+1. **先生成基础关键?*:使?AI 生成或托词工具生成初始关键词
+2. **适度扩展**:建议扩展数量为原始关键词的 0.5-2 ?
3. **使用追加策略**:保留原始关键词,追加扩展关键词
-4. **检查扩展质量**:查看扩展详情,确保扩展关键词质量
-5. **验证效果**:在 Tab4 使用扩展后的关键词进行验证
+4. **检查扩展质?*:查看扩展详情,确保扩展关键词质?
+5. **验证效果**:在 Tab4 使用扩展后的关键词进行验?
## ⚠️ 注意事项
-1. **需要 LLM**:语义扩展功能需要配置生成 LLM 的 API Key
-2. **API 调用**:扩展过程会调用 LLM API,注意 API 费用
+1. **需?LLM**:语义扩展功能需要配置生?LLM ?API Key
+2. **API 调用**:扩展过程会调用 LLM API,注?API 费用
3. **扩展质量**:扩展质量取决于 LLM 的能力和 Prompt 设计
-4. **去重机制**:系统会自动去重,但可能仍有少量相似关键词
-5. **数量限制**:一次最多处理 50 个原始关键词进行扩展
+4. **去重机制**:系统会自动去重,但可能仍有少量相似关键?
+5. **数量限制**:一次最多处?50 个原始关键词进行扩展
## 🔗 相关功能
-- **关键词生成**:在 Tab1 生成初始关键词
-- **内容生成**:在 Tab2 使用扩展后的关键词生成内容
-- **多模型验证**:在 Tab4 验证扩展后的关键词效果
-- **历史记录**:在 Tab5 查看历史关键词记录
+- **关键词生?*:在 Tab1 生成初始关键?
+- **内容生成**:在 Tab2 使用扩展后的关键词生成内?
+- **多模型验?*:在 Tab4 验证扩展后的关键词效?
+- **历史记录**:在 Tab5 查看历史关键词记?
-## 🎯 最佳实践
+## 🎯 最佳实?
-1. **分阶段扩展**:
+1. **分阶段扩?*?
- 首先生成 20-30 个核心关键词
- 然后扩展 30-50 个相关关键词
- - 最后根据效果决定是否继续扩展
+ - 最后根据效果决定是否继续扩?
-2. **质量优先**:
+2. **质量优先**?
- 关注扩展统计中的各类扩展数量
- - 确保各类扩展都有一定数量,保持多样性
+ - 确保各类扩展都有一定数量,保持多样?
-3. **验证优化**:
- - 使用扩展后的关键词生成内容
- - 在 Tab4 验证提及率
+3. **验证优化**?
+ - 使用扩展后的关键词生成内?
+ - ?Tab4 验证提及?
- 根据验证结果调整扩展策略
---
**版本**:v1.0
-**更新日期**:2025-01-26
+**更新日期**?025-01-26
diff --git a/docs/guides/ADVANCED_OPTIMIZATION_PLAN.md b/docs/guides/ADVANCED_OPTIMIZATION_PLAN.md
deleted file mode 100644
index f07a90e..0000000
--- a/docs/guides/ADVANCED_OPTIMIZATION_PLAN.md
+++ /dev/null
@@ -1,188 +0,0 @@
-# 深度目录结构优化方案
-
-## 📊 当前问题分析
-
-### 根目录文件统计
-
-**Python 文件(根目录)**:约30+个
-- 主程序:1个(`geo_tool.py` - 必须保留)
-- 重复模块文件:18个(已在 `modules/` 中,应删除根目录版本)
-- 工具脚本:5个(可移动到 `scripts/` 目录)
-- 配置文件:1个(`requirements.txt` - 必须保留)
-
-**问题**:
-1. ❌ 根目录有大量重复的模块文件(既在根目录又在 `modules/`)
-2. ❌ 工具脚本混在根目录
-3. ❌ 根目录文件过多,不够整洁
-
-## 🎯 优化目标
-
-### 理想根目录结构
-
-```
-geo_tool/
-├── README.md # 项目主文档
-├── DOCS.md # 文档索引
-├── requirements.txt # 依赖文件
-├── .gitignore # Git配置
-├── .streamlit/ # Streamlit配置
-│ └── config.toml
-├── geo_tool.py # 主程序(唯一入口)
-│
-├── modules/ # 功能模块
-│ └── [所有模块文件]
-│
-├── platform_sync/ # 平台同步模块
-│ └── [平台同步文件]
-│
-├── scripts/ # 工具脚本(新增)
-│ ├── cleanup_duplicate_docs.py
-│ ├── move_reorganization_docs.py
-│ ├── reorganize_files.py
-│ ├── update_imports.py
-│ └── update_doc_references.py
-│
-└── docs/ # 文档目录
- ├── features/
- ├── analysis/
- ├── guides/
- └── implementation/
-```
-
-## 📋 优化步骤
-
-### 步骤1:清理重复模块文件
-
-**需要删除的根目录文件**(18个,已在 `modules/` 中):
-- `config_optimizer.py`
-- `content_metrics.py`
-- `content_scorer.py`
-- `data_storage.py`
-- `eeat_enhancer.py`
-- `fact_density_enhancer.py`
-- `keyword_mining.py`
-- `keyword_tool.py`
-- `multimodal_prompt.py`
-- `negative_monitor.py`
-- `optimization_techniques.py`
-- `resource_recommender.py`
-- `roi_analyzer.py`
-- `schema_generator.py`
-- `semantic_expander.py`
-- `storage_example.py`
-- `technical_config_generator.py`
-- `topic_cluster.py`
-- `workflow_automation.py`
-
-**影响评估**:
-- ⚠️ **中等影响**:需要更新 `geo_tool.py` 中的导入路径
-- ✅ **安全**:`modules/` 中已有完整版本
-
-### 步骤2:移动工具脚本
-
-**需要移动到 `scripts/` 的文件**(5个):
-- `scripts/cleanup_duplicate_docs.py`
-- `scripts/move_reorganization_docs.py`
-- `scripts/reorganize_files.py`
-- `scripts/update_imports.py`
-- `scripts/update_doc_references.py`
-
-**影响评估**:
-- ✅ **低影响**:这些是工具脚本,不参与主程序运行
-- ✅ **安全**:只是位置移动,不影响功能
-
-### 步骤3:更新导入路径
-
-**需要更新的文件**:
-1. `geo_tool.py` - 更新所有模块导入(从 `from xxx import` 改为 `from modules.xxx import`)
-2. `modules/storage_example.py` - 更新 `data_storage` 导入
-
-**影响评估**:
-- ⚠️ **关键影响**:必须正确更新,否则程序无法运行
-- ✅ **自动化**:可以使用 `scripts/update_imports.py` 脚本自动更新
-
-## 🔧 实施计划
-
-### 阶段1:准备(低风险)
-
-1. ✅ 创建 `scripts/` 目录
-2. ✅ 创建清理脚本
-3. ✅ 备份关键文件
-
-### 阶段2:移动工具脚本(低风险)
-
-1. 移动工具脚本到 `scripts/`
-2. 更新脚本中的路径引用(如果需要)
-3. 验证脚本仍可正常运行
-
-### 阶段3:清理重复文件(中等风险)
-
-1. 确认 `modules/` 中所有文件完整
-2. 更新 `geo_tool.py` 的导入路径
-3. 删除根目录的重复文件
-4. 测试程序运行
-
-### 阶段4:验证(必须)
-
-1. 运行 `python -c "from modules.data_storage import DataStorage"`
-2. 运行 `streamlit run geo_tool.py`
-3. 测试所有功能模块
-
-## ⚠️ 风险评估
-
-### 改动影响
-
-| 操作 | 影响程度 | 风险等级 | 回滚难度 |
-|------|---------|---------|---------|
-| 移动工具脚本 | 低 | 低 | 容易 |
-| 删除重复模块文件 | 中 | 中 | 中等 |
-| 更新导入路径 | 高 | 中 | 容易(有脚本) |
-
-### 风险缓解措施
-
-1. **Git 提交**:每步完成后立即提交,便于回滚
-2. **自动化脚本**:使用脚本自动更新,减少人为错误
-3. **分步执行**:分阶段执行,每步验证后再继续
-4. **备份**:关键文件备份
-
-## 📝 执行命令
-
-### 快速执行(推荐)
-
-```powershell
-# 1. 创建 scripts 目录
-New-Item -ItemType Directory -Force -Path scripts
-
-# 2. 移动工具脚本
-Move-Item cleanup_duplicate_docs.py scripts\ -Force
-Move-Item move_reorganization_docs.py scripts\ -Force
-Move-Item reorganize_files.py scripts\ -Force
-Move-Item update_imports.py scripts\ -Force
-Move-Item update_doc_references.py scripts\ -Force
-
-# 3. 更新导入路径(在移动脚本后)
-python scripts/update_imports.py
-
-# 4. 删除重复模块文件(在更新导入后)
-# 使用 cleanup_duplicate_modules.py 脚本
-```
-
-## ✅ 优化后的优势
-
-1. **根目录极简**:只保留必要的入口文件
-2. **结构清晰**:功能模块、工具脚本、文档分类明确
-3. **易于维护**:新功能添加时,只需在对应目录操作
-4. **符合最佳实践**:遵循Python项目标准结构
-
-## 🎯 最终根目录文件(约5个)
-
-```
-geo_tool/
-├── README.md # 项目主文档
-├── DOCS.md # 文档索引
-├── requirements.txt # 依赖文件
-├── geo_tool.py # 主程序
-└── .gitignore # Git配置
-```
-
-**对比**:从 30+ 个文件减少到 5 个核心文件!
diff --git a/docs/guides/DECISION_GUIDE.md b/docs/guides/DECISION_GUIDE.md
index 00f4182..9049fd2 100644
--- a/docs/guides/DECISION_GUIDE.md
+++ b/docs/guides/DECISION_GUIDE.md
@@ -12,12 +12,14 @@
### 方案对比
-| 方案 | 实现成本 | 用户价值 | GEO效果 | 技术风险 | 推荐度 |
-|------|---------|---------|---------|---------|--------|
-| **方案A:完整实现(20平台)** | 10周 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
-| **方案B:MVP版本(5平台)** | 3-4周 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
-| **方案C:仅新增平台内容生成** | 1-2周 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐ |
-| **方案D:暂不实现** | 0 | ⭐ | ⭐ | ⭐ | ⭐⭐ |
+
+| 方案 | 实现成本 | 用户价值 | GEO效果 | 技术风险 | 推荐度 |
+| ------------------ | ---- | ----- | ----- | ---- | ----- |
+| **方案A:完整实现(20平台)** | 10周 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
+| **方案B:MVP版本(5平台)** | 3-4周 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
+| **方案C:仅新增平台内容生成** | 1-2周 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐ |
+| **方案D:暂不实现** | 0 | ⭐ | ⭐ | ⭐ | ⭐⭐ |
+
---
@@ -35,9 +37,11 @@
## 🚀 阶段一:MVP版本(强烈推荐先做)⭐⭐⭐⭐⭐
### 目标
+
**3-4周实现5个核心平台的发布功能**
### 选择平台
+
1. ✅ **GitHub**(1-2天)- 最简单,验证架构
2. ✅ **微信公众号**(3-4天)- 用户价值最高
3. ✅ **B站**(3-4天)- 用户量大,API完善
@@ -45,18 +49,21 @@
5. ✅ **CSDN**(3-4天)- 技术平台,API支持
### 为什么选择这5个?
+
- ✅ **API支持好**:都有完善的API文档
- ✅ **用户价值高**:覆盖主要内容平台
- ✅ **技术风险低**:实现难度中等
- ✅ **验证价值大**:能验证整体架构
### 同时完成
+
- ✅ **新增8个平台的内容生成**(1-2周)
- 只需创建Prompt模板
- 工作量小,价值高
- 满足运营需求
### 产出
+
- 5个平台自动发布功能
- 8个新增平台内容生成
- 验证架构可行性
@@ -69,22 +76,26 @@
### 决策点1:MVP版本用户反馈如何?
**如果反馈好** → 继续阶段二
+
- 实现剩余3个API平台(百家号、企鹅号、网易号)
- 实现一键复制功能(12个平台)
- 添加批量发布功能
**如果反馈一般** → 暂停扩展
+
- 优化现有5个平台
- 专注于其他GEO功能
### 决策点2:运营需求是否紧急?
**如果紧急** → 快速实现一键复制
+
- 12个平台一键复制功能(1-2周)
- 满足"覆盖20个平台"的需求
- 虽然不能自动发布,但能大幅提升效率
**如果不紧急** → 按计划实施
+
- 先完善API平台
- 再实现一键复制
@@ -95,45 +106,54 @@
### 1. 对GEO效果的直接影响
**文章同步功能的价值**:
+
- ✅ **扩大内容投放**:更多平台 = 更多曝光
- ✅ **提升发布效率**:自动化减少人工成本
- ⚠️ **间接影响**:内容已经生成,同步只是效率提升
**对比其他功能**:
+
- JSON-LD Schema:直接提升实体识别 ⭐⭐⭐⭐⭐
- 语义扩展:从"点"到"面"占领 ⭐⭐⭐⭐
- 文章同步:提升发布效率 ⭐⭐⭐
### 2. 实现成本
-| 方案 | 工作量 | 开发周期 | 维护成本 |
-|------|--------|---------|---------|
-| MVP(5平台) | 15-20天 | 3-4周 | 低 |
-| 完整版(20平台) | 60-82天 | 10周 | 中-高 |
-| 仅内容生成(8平台) | 5-7天 | 1-2周 | 低 |
+
+| 方案 | 工作量 | 开发周期 | 维护成本 |
+| ---------- | ------ | ---- | ---- |
+| MVP(5平台) | 15-20天 | 3-4周 | 低 |
+| 完整版(20平台) | 60-82天 | 10周 | 中-高 |
+| 仅内容生成(8平台) | 5-7天 | 1-2周 | 低 |
+
### 3. 用户价值
**高价值场景**:
+
- 需要多平台发布的内容团队
- 需要批量发布的运营团队
- 需要追踪发布状态的管理者
**中等价值场景**:
+
- 偶尔发布内容的个人用户
- 只关注1-2个平台的用户
### 4. 技术风险
**低风险**:
+
- GitHub、微信公众号、B站、知乎、CSDN(API完善)
- 一键复制功能(技术简单)
**中风险**:
+
- 百家号、企鹅号、网易号(API状态需确认)
- OAuth2.0认证流程(复杂度中等)
**高风险**:
+
- 平台API变更
- 账号被封禁风险
@@ -144,31 +164,32 @@
### 推荐方案:**分阶段实施**
#### 第一步:立即开始(1-2周)
-1. ✅ **实现GitHub发布功能**(1-2天)
- - 验证架构可行性
- - 风险最低
- - 快速看到效果
+1. ✅ **实现GitHub发布功能**(1-2天)
+ - 验证架构可行性
+ - 风险最低
+ - 快速看到效果
2. ✅ **新增8个平台的内容生成**(5-7天)
- - 满足运营需求
- - 工作量小
- - 价值高
+ - 满足运营需求
+ - 工作量小
+ - 价值高
#### 第二步:MVP版本(3-4周)
-3. ✅ **实现5个核心平台发布**(15-20天)
- - GitHub、微信公众号、B站、知乎、CSDN
- - 验证用户需求
- - 收集反馈
+
+1. ✅ **实现5个核心平台发布**(15-20天)
+ - GitHub、微信公众号、B站、知乎、CSDN
+ - 验证用户需求
+ - 收集反馈
#### 第三步:根据反馈决定(可选)
-4. ⚠️ **如果反馈好** → 继续扩展
- - 剩余3个API平台
- - 一键复制功能
- - 批量发布
-5. ⚠️ **如果反馈一般** → 暂停扩展
- - 优化现有功能
- - 专注其他GEO功能
+1. ⚠️ **如果反馈好** → 继续扩展
+ - 剩余3个API平台
+ - 一键复制功能
+ - 批量发布
+2. ⚠️ **如果反馈一般** → 暂停扩展
+ - 优化现有功能
+ - 专注其他GEO功能
---
@@ -176,33 +197,28 @@
### 开始前需要确认
-- [ ] **运营需求是否紧急?**
+- **运营需求是否紧急?**
- 紧急 → 优先实现一键复制(快速满足需求)
- 不紧急 → 按MVP方案实施
-
-- [ ] **是否有开发资源?**
+- **是否有开发资源?**
- 有1-2个开发者 → 可以开始
- 资源紧张 → 先做新增平台内容生成
-
-- [ ] **用户是否真的需要?**
+- **用户是否真的需要?**
- 有明确需求 → 开始实施
- 需求不明确 → 先做MVP验证
-
-- [ ] **是否有平台账号?**
+- **是否有平台账号?**
- 有企业认证账号 → 可以接入API平台
- 只有个人账号 → 优先一键复制平台
### 实施中需要监控
-- [ ] **技术可行性验证**
+- **技术可行性验证**
- GitHub发布是否成功?
- 架构是否合理?
-
-- [ ] **用户反馈收集**
+- **用户反馈收集**
- 用户是否使用?
- 是否有问题?
-
-- [ ] **成本控制**
+- **成本控制**
- 开发时间是否超预期?
- 是否需要调整计划?
@@ -213,10 +229,12 @@
### MVP版本(5平台)
**成本**:
+
- 开发时间:3-4周
- 维护成本:低(每月1-2天)
**收益**:
+
- 5个主要平台自动发布
- 8个新增平台内容生成
- 验证架构可行性
@@ -227,10 +245,12 @@
### 完整版本(20平台)
**成本**:
+
- 开发时间:10周
- 维护成本:中-高(每月3-4天)
**收益**:
+
- 20个平台全覆盖
- 批量发布功能
- 完整的发布管理系统
@@ -244,6 +264,7 @@
### 强烈推荐:**先做MVP版本**
**理由**:
+
1. ✅ **快速验证**:3-4周看到效果
2. ✅ **降低风险**:避免一次性投入过大
3. ✅ **灵活调整**:根据反馈决定是否扩展
@@ -252,14 +273,17 @@
### 实施路径
**第1-2周**:
+
- GitHub发布功能(验证架构)
- 新增8个平台内容生成(满足运营需求)
**第3-4周**:
+
- 微信公众号、B站、知乎、CSDN发布功能
- 测试和优化
**第5周**:
+
- 收集用户反馈
- 决定是否继续扩展
@@ -281,6 +305,7 @@
**实施时间**:3-4周
**下一步行动**:
+
1. 实现GitHub发布功能(1-2天)
2. 新增8个平台内容生成(5-7天)
3. 实现剩余4个API平台发布(15-20天)
@@ -290,4 +315,4 @@
---
**决策日期**:2025-01-26
-**决策依据**:项目现状分析 + 成本效益分析 + 风险评估
+**决策依据**:项目现状分析 + 成本效益分析 + 风险评估
\ No newline at end of file
diff --git a/docs/guides/DOCUMENTATION_CLEANUP_GUIDE.md b/docs/guides/DOCUMENTATION_CLEANUP_GUIDE.md
deleted file mode 100644
index f18ccb8..0000000
--- a/docs/guides/DOCUMENTATION_CLEANUP_GUIDE.md
+++ /dev/null
@@ -1,124 +0,0 @@
-# 文档清理指南
-
-## 📋 根目录文档保留策略
-
-### ✅ 必须保留在根目录的文档
-
-1. **README.md** - 项目主文档(GitHub/GitLab 标准要求)
-2. **DOCS.md** - 文档索引(方便快速查找所有文档)
-
-### 📌 重组相关文档(建议移动到 docs/guides/)
-
-以下重组相关的文档建议移动到 `docs/guides/` 目录:
-
-- `PROJECT_STRUCTURE_ANALYSIS.md` - 项目结构分析
-- `QUICK_REORGANIZE.md` - 快速重组指南
-- `REORGANIZATION_SUMMARY.md` - 重组总结
-- `DOCUMENTATION_CLEANUP_GUIDE.md` - 文档清理指南
-
-**建议**:这些文档可以移动到 `docs/guides/` 目录,保持根目录整洁。使用 `scripts/move_reorganization_docs.py` 脚本可以自动移动。
-
-### ❌ 需要删除的文档(已在 docs/ 目录下)
-
-所有以下文档在根目录的重复版本都应该删除,因为已经在 `docs/` 目录下分类存放:
-
-#### 功能文档(15个)
-- `*_FEATURE.md` 文件(已在 `docs/features/`)
-
-#### 分析报告(7个)
-- `*_ANALYSIS.md` 和 `*_REPORT.md` 文件(已在 `docs/analysis/`)
-
-#### 指南文档(5个)
-- `*_GUIDE.md` 文件(已在 `docs/guides/`)
-
-#### 实现文档(7个)
-- 实现相关文档(已在 `docs/implementation/`)
-
-## 🚀 执行清理
-
-### 方法1:使用自动清理脚本(推荐)
-
-```powershell
-python scripts/cleanup_duplicate_docs.py
-```
-
-这个脚本会:
-- 自动检查根目录中的重复文档
-- 确认 `docs/` 目录下存在对应文件
-- 安全删除根目录的重复版本
-- 保留必要的文档(README.md, DOCS.md 等)
-
-### 方法2:手动清理
-
-如果脚本执行失败(文件被占用),可以:
-
-1. **关闭所有打开的文件**(IDE、编辑器等)
-2. **手动删除**根目录中的重复文档文件
-3. **验证** `docs/` 目录下存在对应文件
-
-## 📁 清理后的根目录结构
-
-```
-geo_tool/
-├── README.md # ✅ 保留
-├── DOCS.md # ✅ 保留(文档索引)
-│
-├── geo_tool.py # 主程序
-├── requirements.txt # 依赖文件
-├── .gitignore # Git配置
-│
-├── modules/ # 功能模块
-├── platform_sync/ # 平台同步
-└── docs/ # 所有文档(分类存放)
- ├── features/
- ├── analysis/
- ├── guides/ # 包含重组相关文档
- └── implementation/
-```
-
-## ✅ 清理验证清单
-
-清理完成后,请验证:
-
-- [ ] `README.md` 仍在根目录
-- [ ] `DOCS.md` 已在根目录创建
-- [ ] 根目录不再有 `*_FEATURE.md` 文件
-- [ ] 根目录不再有 `*_ANALYSIS.md` 文件
-- [ ] 根目录不再有 `*_GUIDE.md` 文件(除了可选保留的)
-- [ ] 所有文档在 `docs/` 目录下可以找到
-- [ ] `DOCS.md` 中的链接都能正常访问
-
-## 💡 最佳实践
-
-1. **README.md** 应该简洁,包含:
- - 项目简介
- - 快速开始
- - 主要功能概览
- - 链接到 DOCS.md 获取详细文档
-
-2. **DOCS.md** 作为文档索引,提供:
- - 所有文档的分类导航
- - 快速查找功能
- - 清晰的文档结构说明
-
-3. **详细文档** 放在 `docs/` 目录下分类管理,保持根目录整洁
-
-## 🆘 遇到问题?
-
-### 问题1:文件被占用无法删除
-**解决方案**:
-1. 关闭所有IDE和编辑器
-2. 检查是否有Python进程在运行
-3. 如果仍有问题,重启计算机后再试
-
-### 问题2:误删了重要文档
-**解决方案**:
-1. 检查 `docs/` 目录下是否有对应文件
-2. 如果有,说明只是删除了重复版本
-3. 如果没有,可以从Git历史恢复
-
-### 问题3:文档链接失效
-**解决方案**:
-1. 运行 `python scripts/update_doc_references.py` 更新文档引用
-2. 检查 `DOCS.md` 中的链接是否正确
-3. 使用相对路径而不是绝对路径
diff --git a/docs/guides/FINAL_OPTIMIZATION_GUIDE.md b/docs/guides/FINAL_OPTIMIZATION_GUIDE.md
deleted file mode 100644
index 9f78210..0000000
--- a/docs/guides/FINAL_OPTIMIZATION_GUIDE.md
+++ /dev/null
@@ -1,203 +0,0 @@
-# 最终目录结构优化指南
-
-## ✅ 当前状态检查
-
-### 已完成的优化
-- ✅ `geo_tool.py` 已更新导入路径(使用 `modules.xxx`)
-- ✅ `modules/storage_example.py` 已更新导入路径
-- ✅ 文档已分类移动到 `docs/` 目录
-- ✅ 重组相关文档已移动到 `docs/guides/`
-
-### 待优化的部分
-- ⏳ 根目录仍有18个重复的模块文件(已在 `modules/` 中)
-- ⏳ 根目录有5个工具脚本(可移动到 `scripts/`)
-
-## 🎯 优化目标
-
-### 优化前(根目录约30+个文件)
-```
-geo_tool/
-├── README.md
-├── DOCS.md
-├── geo_tool.py
-├── requirements.txt
-├── [18个重复模块文件] ❌
-├── [5个工具脚本] ❌
-└── ...
-```
-
-### 优化后(根目录约5个文件)
-```
-geo_tool/
-├── README.md # ✅ 保留
-├── DOCS.md # ✅ 保留
-├── geo_tool.py # ✅ 保留(主程序)
-├── requirements.txt # ✅ 保留
-├── .gitignore # ✅ 保留
-│
-├── modules/ # ✅ 功能模块
-├── platform_sync/ # ✅ 平台同步
-├── scripts/ # ✅ 工具脚本(新增)
-└── docs/ # ✅ 文档目录
-```
-
-## 📋 优化步骤(安全执行)
-
-### 步骤1:移动工具脚本(低风险,先执行)
-
-**操作**:将工具脚本移动到 `scripts/` 目录
-
-**执行**:
-```powershell
-python move_scripts.py
-```
-
-**影响**:
-- ✅ 低风险:工具脚本不参与主程序运行
-- ✅ 可回滚:如有问题可轻松恢复
-
-**验证**:
-```powershell
-# 测试脚本是否仍可运行
-python scripts/update_imports.py --help
-```
-
-### 步骤2:清理重复模块文件(中等风险,需谨慎)
-
-**前提条件**:
-- ✅ 已确认 `geo_tool.py` 使用 `modules.xxx` 导入
-- ✅ 已测试程序可以正常运行
-
-**执行**:
-```powershell
-python cleanup_duplicate_modules.py
-```
-
-**影响**:
-- ⚠️ 中等风险:删除后无法直接恢复(需从Git恢复)
-- ✅ 安全:`modules/` 中已有完整版本
-
-**验证**:
-```powershell
-# 1. 测试导入
-python -c "from modules.data_storage import DataStorage; print('✓ 导入成功')"
-
-# 2. 测试主程序
-streamlit run geo_tool.py
-```
-
-## 🔍 详细文件清单
-
-### 需要删除的重复模块文件(18个)
-
-这些文件在根目录和 `modules/` 目录都存在,删除根目录版本:
-
-1. `config_optimizer.py`
-2. `content_metrics.py`
-3. `content_scorer.py`
-4. `data_storage.py`
-5. `eeat_enhancer.py`
-6. `fact_density_enhancer.py`
-7. `keyword_mining.py`
-8. `keyword_tool.py`
-9. `multimodal_prompt.py`
-10. `negative_monitor.py`
-11. `optimization_techniques.py`
-12. `resource_recommender.py`
-13. `roi_analyzer.py`
-14. `schema_generator.py`
-15. `semantic_expander.py`
-16. `storage_example.py`
-17. `technical_config_generator.py`
-18. `topic_cluster.py`
-19. `workflow_automation.py`
-
-### 需要移动的工具脚本(5个)
-
-这些脚本移动到 `scripts/` 目录:
-
-1. `scripts/cleanup_duplicate_docs.py`
-2. `scripts/move_reorganization_docs.py`
-3. `scripts/reorganize_files.py`
-4. `scripts/update_imports.py`
-5. `scripts/update_doc_references.py`
-
-## ⚠️ 风险评估与缓解
-
-### 风险1:删除重复文件后程序无法运行
-
-**概率**:低(已确认导入路径已更新)
-
-**缓解措施**:
-1. 先测试:`python -c "from modules.data_storage import DataStorage"`
-2. 再删除:确认测试通过后再执行删除
-3. Git备份:删除前先提交,便于回滚
-
-### 风险2:工具脚本移动后无法使用
-
-**概率**:极低(只是位置移动)
-
-**缓解措施**:
-1. 更新使用方式:`python scripts/script_name.py`
-2. 测试脚本:移动后立即测试
-
-### 风险3:遗漏某些导入路径
-
-**概率**:低(已检查 `geo_tool.py`)
-
-**缓解措施**:
-1. 全局搜索:`grep -r "from data_storage import" .`
-2. 运行测试:完整测试所有功能
-
-## 📝 完整执行清单
-
-### 准备阶段
-- [ ] 确认 Git 仓库状态(建议先提交当前更改)
-- [ ] 备份关键文件(可选,Git已提供版本控制)
-- [ ] 阅读本指南
-
-### 执行阶段
-- [ ] 步骤1:运行 `python move_scripts.py`
-- [ ] 验证:测试脚本是否仍可运行
-- [ ] 步骤2:运行 `python cleanup_duplicate_modules.py`
-- [ ] 验证:测试导入 `python -c "from modules.data_storage import DataStorage"`
-- [ ] 验证:运行主程序 `streamlit run geo_tool.py`
-
-### 验证阶段
-- [ ] 检查根目录文件数量(应该只有5个左右)
-- [ ] 测试所有主要功能模块
-- [ ] 确认没有导入错误
-- [ ] Git 提交优化结果
-
-## 🎉 优化后的优势
-
-1. **根目录极简**:从30+个文件减少到5个核心文件
-2. **结构清晰**:功能模块、工具脚本、文档分类明确
-3. **易于维护**:新功能添加时,只需在对应目录操作
-4. **符合最佳实践**:遵循Python项目标准结构
-5. **便于协作**:团队成员更容易理解项目结构
-
-## 📚 相关文档
-
-- `ADVANCED_OPTIMIZATION_PLAN.md` - 详细优化方案
-- `cleanup_duplicate_modules.py` - 清理重复模块脚本
-- `move_scripts.py` - 移动工具脚本脚本
-
-## 🆘 遇到问题?
-
-### 问题1:删除文件后程序无法运行
-**解决方案**:
-1. 从Git恢复:`git checkout HEAD -- filename.py`
-2. 检查导入路径:确认使用 `modules.xxx` 格式
-3. 重新测试:`python -c "from modules.data_storage import DataStorage"`
-
-### 问题2:工具脚本无法运行
-**解决方案**:
-1. 使用完整路径:`python scripts/script_name.py`
-2. 或在 `scripts/` 目录下运行:`cd scripts && python script_name.py`
-
-### 问题3:遗漏某些文件
-**解决方案**:
-1. 检查 `modules/` 目录:确认所有模块文件都在
-2. 检查导入错误:运行程序查看具体错误信息
-3. 从Git恢复:`git checkout HEAD -- .`
diff --git a/docs/guides/LAYOUT_UPGRADE_GUIDE.md b/docs/guides/LAYOUT_UPGRADE_GUIDE.md
index 3c2d67a..2f31ff5 100644
--- a/docs/guides/LAYOUT_UPGRADE_GUIDE.md
+++ b/docs/guides/LAYOUT_UPGRADE_GUIDE.md
@@ -7,6 +7,7 @@
### 为什么是中等复杂度?
✅ **有利因素**:
+
- 你已经有了基础的 CSS 样式系统
- 已经使用了 `st.tabs` 作为主导航
- 已经部分使用了 `st.container(border=True)` 卡片式设计
@@ -14,6 +15,7 @@
- 代码结构相对清晰
⚠️ **需要注意**:
+
- 文件较大(5864行),需要系统化改动
- 有 37 个 `st.expander` 需要评估是否改为 tabs/container
- 需要保持功能逻辑不变,只改布局
@@ -25,18 +27,16 @@
### 核心改动点
1. **CSS 增强**(30分钟)
- - 增强卡片样式
- - 优化侧边栏视觉层次
- - 统一间距和阴影
-
+ - 增强卡片样式
+ - 优化侧边栏视觉层次
+ - 统一间距和阴影
2. **Expander → Container/Tabs 转换**(2-3小时)
- - 将功能相关的多个 expander 合并为 tabs
- - 将单个 expander 改为 container(border=True)
- - 保持次要信息用 expander
-
+ - 将功能相关的多个 expander 合并为 tabs
+ - 将单个 expander 改为 container(border=True)
+ - 保持次要信息用 expander
3. **侧边栏优化**(30分钟)
- - 添加分组容器
- - 优化视觉层次
+ - 添加分组容器
+ - 优化视觉层次
---
@@ -93,16 +93,19 @@ hr {
#### 2.1 识别需要改动的 Expander
**改为 Tabs 的情况**(功能相关的多个选项):
+
- ✅ Tab1 中的"组合模式选择" + "词库管理" → 可以合并为一个 tabs
- ✅ Tab1 中的"智能关键词挖掘"已经有 tabs(保持)
- ✅ Tab3 中的多个分析 expander → 可以合并为 tabs
**改为 Container 的情况**(主要功能区域):
+
- ✅ Tab1 中的"组合模式选择"区域 → 已经是 container,保持
- ✅ Tab2 中的主要输入区域 → 确保用 container
- ✅ Tab3 中的优化结果展示 → 用 container
**保留 Expander 的情况**(次要/详细信息):
+
- ✅ "查看技巧说明" → 保持 expander
- ✅ "详细评分与改进建议" → 保持 expander
- ✅ "预览最后一篇" → 保持 expander
@@ -112,12 +115,14 @@ hr {
**示例 1:Tab1 中的词库管理区域**
**当前代码**(第 797 行):
+
```python
with st.expander("词库管理", expanded=False):
# 词库编辑和导入导出
```
**改为**:
+
```python
with st.container(border=True):
st.markdown("### 📚 词库管理")
@@ -132,6 +137,7 @@ with st.container(border=True):
**示例 2:Tab3 中的分析结果**
**当前代码**(多个 expander):
+
```python
with st.expander("📈 事实密度分析", expanded=False):
# 分析内容
@@ -141,6 +147,7 @@ with st.expander("🏗️ 结构化块分析", expanded=False):
```
**改为**:
+
```python
analysis_tabs = st.tabs(["📈 事实密度", "🏗️ 结构化块", "📝 强化详情"])
@@ -158,6 +165,7 @@ with analysis_tabs[1]:
### 阶段 3:侧边栏优化(低风险,30分钟)
**当前结构**(第 563-697 行):
+
```python
with st.sidebar:
st.header("全局配置")
@@ -166,6 +174,7 @@ with st.sidebar:
```
**优化后**:
+
```python
with st.sidebar:
st.header("⚙️ 全局配置")
@@ -192,23 +201,27 @@ with st.sidebar:
## ⏱️ 时间估算
-| 阶段 | 任务 | 时间 | 风险 |
-|------|------|------|------|
-| 阶段 1 | CSS 增强 | 30分钟 | ⭐ 极低 |
-| 阶段 2 | Expander 重构 | 2-3小时 | ⭐⭐ 中等 |
-| 阶段 3 | 侧边栏优化 | 30分钟 | ⭐ 极低 |
-| **总计** | | **3-4小时** | |
+
+| 阶段 | 任务 | 时间 | 风险 |
+| ------ | ----------- | --------- | ----- |
+| 阶段 1 | CSS 增强 | 30分钟 | ⭐ 极低 |
+| 阶段 2 | Expander 重构 | 2-3小时 | ⭐⭐ 中等 |
+| 阶段 3 | 侧边栏优化 | 30分钟 | ⭐ 极低 |
+| **总计** | | **3-4小时** | |
+
---
## 🎨 改动前后对比
### 改动前
+
- 大量 expander 折叠,需要点击展开
- 视觉层次不够清晰
- 卡片样式不统一
### 改动后
+
- 主要功能用 tabs,一目了然
- 次要信息用 expander,保持简洁
- 卡片样式统一,视觉更现代
@@ -232,13 +245,12 @@ with st.sidebar:
如果时间紧张,可以先做这些:
1. **只增强 CSS**(30分钟)
- - 添加卡片内边距和背景
- - 优化侧边栏样式
-
+ - 添加卡片内边距和背景
+ - 优化侧边栏样式
2. **改 3-5 个关键 Expander**(30分钟)
- - Tab1 的词库管理
- - Tab3 的分析结果区域
- - 其他明显需要改的
+ - Tab1 的词库管理
+ - Tab3 的分析结果区域
+ - 其他明显需要改的
这样就能看到明显改善,后续再逐步完善。
@@ -248,23 +260,25 @@ with st.sidebar:
实施完成后检查:
-- [ ] 所有主要功能区域都用 container(border=True) 或 tabs
-- [ ] 次要信息保持用 expander
-- [ ] 侧边栏有清晰的分组
-- [ ] 卡片样式统一(圆角、阴影、内边距)
-- [ ] 所有功能正常工作
-- [ ] 响应式布局正常(不同屏幕尺寸)
+- 所有主要功能区域都用 container(border=True) 或 tabs
+- 次要信息保持用 expander
+- 侧边栏有清晰的分组
+- 卡片样式统一(圆角、阴影、内边距)
+- 所有功能正常工作
+- 响应式布局正常(不同屏幕尺寸)
---
## 💡 建议
**推荐实施顺序**:
+
1. ✅ 先做阶段 1(CSS),立即看到效果
2. ✅ 再做阶段 3(侧边栏),改动小风险低
3. ✅ 最后做阶段 2(Expander),需要仔细测试
**如果遇到问题**:
+
- 先回退到上一个稳定版本
- 逐个 tab 测试,不要一次性改完
- 保留原代码注释,方便对比
@@ -274,9 +288,10 @@ with st.sidebar:
## 🎯 预期效果
实施完成后,你的工具将:
+
- ✨ 视觉更现代,更像产品级应用
- 🎯 功能组织更清晰,减少滚动疲劳
- 📱 用户体验更好,操作更直观
- 🎨 保持专业感,同时更美观
-**复杂度总结**:中等,但可以分阶段实施,风险可控!
+**复杂度总结**:中等,但可以分阶段实施,风险可控!
\ No newline at end of file
diff --git a/docs/guides/MANUAL_CLEANUP_GUIDE.md b/docs/guides/MANUAL_CLEANUP_GUIDE.md
deleted file mode 100644
index 9e3fd16..0000000
--- a/docs/guides/MANUAL_CLEANUP_GUIDE.md
+++ /dev/null
@@ -1,185 +0,0 @@
-# 手动清理指南
-
-## ⚠️ 当前状态
-
-由于文件被占用(可能被IDE或其他程序打开),自动脚本无法执行删除/移动操作。
-
-## 📋 需要手动完成的操作
-
-### 步骤1:关闭所有打开的文件
-
-**重要**:在执行清理前,请:
-1. 关闭所有IDE和编辑器
-2. 关闭可能占用文件的程序
-3. 确保没有Python进程在运行
-
-### 步骤2:清理重复模块文件(19个)
-
-这些文件在根目录和 `modules/` 目录都存在,**删除根目录版本**:
-
-**需要删除的文件**:
-1. `config_optimizer.py`
-2. `content_metrics.py`
-3. `content_scorer.py`
-4. `data_storage.py`
-5. `eeat_enhancer.py`
-6. `fact_density_enhancer.py`
-7. `keyword_mining.py`
-8. `keyword_tool.py`
-9. `multimodal_prompt.py`
-10. `negative_monitor.py`
-11. `optimization_techniques.py`
-12. `resource_recommender.py`
-13. `roi_analyzer.py`
-14. `schema_generator.py`
-15. `semantic_expander.py`
-16. `storage_example.py`
-17. `technical_config_generator.py`
-18. `topic_cluster.py`
-19. `workflow_automation.py`
-
-**验证**:删除前确认 `modules/` 目录中有对应文件
-
-### 步骤3:整理优化相关文件(可选)
-
-**移动到 `docs/guides/` 的文档**:
-- `ADVANCED_OPTIMIZATION_PLAN.md`
-- `FINAL_OPTIMIZATION_GUIDE.md`
-- `REFERENCE_UPDATE_SUMMARY.md`
-- `OPTIMIZATION_STATUS.md`
-
-**移动到 `scripts/` 的脚本**:
-- `cleanup_duplicate_modules.py`
-- `move_scripts.py`
-- `update_script_references.py`
-- `cleanup_optimization_files.py`
-
-## 🚀 快速执行(PowerShell)
-
-### 方法1:使用脚本(推荐)
-
-关闭所有文件后,运行:
-
-```powershell
-# 清理重复模块
-python cleanup_duplicate_modules.py
-# 输入 yes
-
-# 整理优化文件
-python cleanup_optimization_files.py
-```
-
-### 方法2:手动删除/移动
-
-```powershell
-# 删除重复模块文件
-$modules = @(
- "config_optimizer.py", "content_metrics.py", "content_scorer.py",
- "data_storage.py", "eeat_enhancer.py", "fact_density_enhancer.py",
- "keyword_mining.py", "keyword_tool.py", "multimodal_prompt.py",
- "negative_monitor.py", "optimization_techniques.py", "resource_recommender.py",
- "roi_analyzer.py", "schema_generator.py", "semantic_expander.py",
- "storage_example.py", "technical_config_generator.py", "topic_cluster.py",
- "workflow_automation.py"
-)
-
-foreach ($file in $modules) {
- if (Test-Path $file) {
- Remove-Item $file -Force
- Write-Host "✓ 已删除: $file"
- }
-}
-
-# 移动优化文档
-$docs = @(
- "ADVANCED_OPTIMIZATION_PLAN.md",
- "FINAL_OPTIMIZATION_GUIDE.md",
- "REFERENCE_UPDATE_SUMMARY.md",
- "OPTIMIZATION_STATUS.md"
-)
-
-foreach ($doc in $docs) {
- if (Test-Path $doc) {
- Move-Item $doc -Destination "docs\guides\" -Force
- Write-Host "✓ 已移动: $doc"
- }
-}
-
-# 移动优化脚本
-$scripts = @(
- "cleanup_duplicate_modules.py",
- "move_scripts.py",
- "update_script_references.py",
- "cleanup_optimization_files.py"
-)
-
-foreach ($script in $scripts) {
- if (Test-Path $script) {
- Move-Item $script -Destination "scripts\" -Force
- Write-Host "✓ 已移动: $script"
- }
-}
-```
-
-## ✅ 验证清单
-
-清理完成后,请验证:
-
-- [ ] 根目录只有5个核心文件:
- - README.md
- - DOCS.md
- - geo_tool.py
- - requirements.txt
- - .gitignore
-- [ ] 所有模块文件在 `modules/` 目录中
-- [ ] 所有工具脚本在 `scripts/` 目录中
-- [ ] 所有文档在 `docs/` 子目录中
-- [ ] 测试导入:`python -c "from modules.data_storage import DataStorage"`
-- [ ] 测试主程序:`streamlit run geo_tool.py`
-
-## 🎯 优化后的最终结构
-
-```
-geo_tool/
-├── README.md # ✅ 项目主文档
-├── DOCS.md # ✅ 文档索引
-├── geo_tool.py # ✅ 主程序
-├── requirements.txt # ✅ 依赖文件
-├── .gitignore # ✅ Git配置
-│
-├── modules/ # ✅ 功能模块(19个文件)
-├── platform_sync/ # ✅ 平台同步模块
-├── scripts/ # ✅ 工具脚本(9个文件)
-└── docs/ # ✅ 文档目录
- ├── features/ # 功能文档(15个)
- ├── analysis/ # 分析报告(7个)
- ├── guides/ # 指南文档(13个)
- └── implementation/ # 实现文档(7个)
-```
-
-## 📊 优化效果
-
-- **根目录文件**:从 50+ 个减少到 5 个(减少 90%)
-- **文档分类**:34个文档按类型分类存放
-- **模块组织**:19个模块集中在 `modules/` 目录
-- **脚本整理**:9个工具脚本集中在 `scripts/` 目录
-
-## 🆘 遇到问题?
-
-### 问题1:文件仍被占用
-**解决方案**:
-1. 检查是否有Python进程:`tasklist | findstr python`
-2. 关闭所有IDE和编辑器
-3. 重启计算机后再试
-
-### 问题2:删除后程序无法运行
-**解决方案**:
-1. 从Git恢复:`git checkout HEAD -- filename.py`
-2. 检查 `modules/` 目录:确认文件存在
-3. 检查导入路径:确认使用 `modules.xxx` 格式
-
-### 问题3:不确定是否安全删除
-**解决方案**:
-1. 先备份:`git add . && git commit -m "备份:清理前"`
-2. 再删除:如有问题可回滚
-3. 验证:删除后立即测试程序
diff --git a/docs/guides/OPTIMIZATION_STATUS.md b/docs/guides/OPTIMIZATION_STATUS.md
deleted file mode 100644
index b0def96..0000000
--- a/docs/guides/OPTIMIZATION_STATUS.md
+++ /dev/null
@@ -1,157 +0,0 @@
-# 目录结构优化状态报告
-
-## ✅ 已完成的优化
-
-### 1. 文档分类 ✅
-- ✅ 所有功能文档已移动到 `docs/features/`(15个)
-- ✅ 所有分析报告已移动到 `docs/analysis/`(7个)
-- ✅ 所有指南文档已移动到 `docs/guides/`(9个)
-- ✅ 所有实现文档已移动到 `docs/implementation/`(7个)
-- ✅ 重组相关文档已移动到 `docs/guides/`
-
-### 2. 工具脚本整理 ✅
-- ✅ 所有工具脚本已移动到 `scripts/` 目录(5个)
- - `cleanup_duplicate_docs.py`
- - `move_reorganization_docs.py`
- - `reorganize_files.py`
- - `update_imports.py`
- - `update_doc_references.py`
-
-### 3. 引用路径更新 ✅
-- ✅ `geo_tool.py` 导入路径已更新为 `modules.xxx`
-- ✅ `storage_example.py` 导入路径已更新
-- ✅ 所有文档中的脚本引用已更新为 `scripts/` 前缀
-- ✅ 所有文档中的模块路径引用已更新
-
-## ⏳ 待完成的优化
-
-### 1. 清理重复模块文件(19个)
-
-**状态**:文件被占用,无法自动删除
-
-**需要删除的根目录文件**(已在 `modules/` 中):
-1. `config_optimizer.py`
-2. `content_metrics.py`
-3. `content_scorer.py`
-4. `data_storage.py`
-5. `eeat_enhancer.py`
-6. `fact_density_enhancer.py`
-7. `keyword_mining.py`
-8. `keyword_tool.py`
-9. `multimodal_prompt.py`
-10. `negative_monitor.py`
-11. `optimization_techniques.py`
-12. `resource_recommender.py`
-13. `roi_analyzer.py`
-14. `schema_generator.py`
-15. `semantic_expander.py`
-16. `storage_example.py`
-17. `technical_config_generator.py`
-18. `topic_cluster.py`
-19. `workflow_automation.py`
-
-**解决方案**:
-1. 关闭所有IDE和编辑器
-2. 关闭可能占用文件的程序
-3. 重新运行:`python cleanup_duplicate_modules.py`
-4. 或手动删除这些文件
-
-### 2. 整理优化相关文档和脚本
-
-**根目录的优化相关文件**(可移动到 `docs/guides/` 或 `scripts/`):
-- `ADVANCED_OPTIMIZATION_PLAN.md` → `docs/guides/`
-- `FINAL_OPTIMIZATION_GUIDE.md` → `docs/guides/`
-- `REFERENCE_UPDATE_SUMMARY.md` → `docs/guides/`
-- `OPTIMIZATION_STATUS.md` → `docs/guides/`(本文件)
-- `cleanup_duplicate_modules.py` → `scripts/`
-- `move_scripts.py` → `scripts/`
-- `update_script_references.py` → `scripts/`
-
-## 📊 当前根目录文件统计
-
-**当前根目录文件**(约30个):
-- 核心文件:5个(README.md, DOCS.md, geo_tool.py, requirements.txt, .gitignore)
-- 重复模块文件:19个(待删除)
-- 优化相关文档:4个(可移动)
-- 优化相关脚本:3个(可移动)
-
-**优化后根目录文件**(约5个):
-- README.md
-- DOCS.md
-- geo_tool.py
-- requirements.txt
-- .gitignore
-
-## 🎯 下一步操作
-
-### 步骤1:关闭所有打开的文件
-**重要**:关闭所有IDE、编辑器和其他可能占用文件的程序
-
-### 步骤2:清理重复模块文件
-
-**方法A:使用脚本(推荐)**
-```powershell
-python cleanup_duplicate_modules.py
-# 输入 yes 确认
-```
-
-**方法B:手动删除**
-- 确认 `modules/` 目录中有对应文件
-- 手动删除根目录的重复文件
-
-### 步骤3:整理优化相关文件(可选)
-
-**移动优化文档到 `docs/guides/`**:
-```powershell
-Move-Item ADVANCED_OPTIMIZATION_PLAN.md docs\guides\ -Force
-Move-Item FINAL_OPTIMIZATION_GUIDE.md docs\guides\ -Force
-Move-Item REFERENCE_UPDATE_SUMMARY.md docs\guides\ -Force
-Move-Item OPTIMIZATION_STATUS.md docs\guides\ -Force
-```
-
-**移动优化脚本到 `scripts/`**:
-```powershell
-Move-Item cleanup_duplicate_modules.py scripts\ -Force
-Move-Item move_scripts.py scripts\ -Force
-Move-Item update_script_references.py scripts\ -Force
-```
-
-### 步骤4:验证
-
-```powershell
-# 测试导入
-python -c "from modules.data_storage import DataStorage; print('✓ 导入成功')"
-
-# 测试主程序
-streamlit run geo_tool.py
-```
-
-## 📈 优化进度
-
-- [x] 文档分类整理(100%)
-- [x] 工具脚本整理(100%)
-- [x] 引用路径更新(100%)
-- [ ] 清理重复模块文件(0% - 文件被占用)
-- [ ] 整理优化相关文件(0% - 可选)
-
-## 🎉 优化效果
-
-### 优化前
-- 根目录:50+ 个文件混在一起
-- 文档:34个文档文件在根目录
-- 模块:18个模块文件在根目录
-- 脚本:5个工具脚本在根目录
-
-### 优化后(完成所有步骤后)
-- 根目录:5个核心文件
-- 文档:分类存放在 `docs/` 子目录
-- 模块:集中在 `modules/` 目录
-- 脚本:集中在 `scripts/` 目录
-
-**文件减少率**:从 50+ 个减少到 5 个(减少 90%)
-
-## 📚 相关文档
-
-- `docs/guides/FINAL_OPTIMIZATION_GUIDE.md` - 完整优化指南
-- `docs/guides/ADVANCED_OPTIMIZATION_PLAN.md` - 详细优化方案
-- `docs/guides/REFERENCE_UPDATE_SUMMARY.md` - 引用路径更新总结
diff --git a/docs/guides/PLATFORM_SETUP.md b/docs/guides/PLATFORM_SETUP.md
index 48aab85..edb9466 100644
--- a/docs/guides/PLATFORM_SETUP.md
+++ b/docs/guides/PLATFORM_SETUP.md
@@ -3,6 +3,7 @@
## 已支持的平台
### 基础平台(已包含依赖)
+
- DeepSeek
- OpenAI (GPT)
- Tongyi (通义千问)
@@ -14,20 +15,25 @@
#### 1. 豆包(字节跳动)
**安装命令:**
+
```bash
pip install 'volcengine-python-sdk[ark]'
```
**API Key 格式:**
+
```
access_key:secret_key:endpoint_id
```
+
用冒号分隔三个值:
+
- `access_key`: 火山引擎 Access Key
- `secret_key`: 火山引擎 Secret Key
- `endpoint_id`: 接入点名称(Endpoint ID)
**获取方式:**
+
1. 访问 [火山引擎官网](https://www.volcengine.com/)
2. 注册账号并完成实名认证
3. 在控制台获取 Access Key 和 Secret Key
@@ -41,19 +47,24 @@ access_key:secret_key:endpoint_id
#### 2. 文心一言(百度)
**安装命令:**
+
```bash
pip install qianfan
```
**API Key 格式:**
+
```
app_key:app_secret
```
+
用冒号分隔两个值:
+
- `app_key`: 百度智能云 App Key
- `app_secret`: 百度智能云 App Secret
**获取方式:**
+
1. 访问 [百度智能云千帆平台](https://cloud.baidu.com/product/qianfan.html)
2. 注册账号并完成认证
3. 创建应用,获取 App Key 和 App Secret
@@ -84,11 +95,14 @@ pip install 'volcengine-python-sdk[ark]' qianfan
## 故障排除
### 豆包安装失败
+
- 确保 Python 版本 >= 3.7
- Windows 系统可能需要启用长路径支持
- 尝试:`pip install 'volcengine-python-sdk[ark]' -U`
### 文心一言初始化失败
+
- 确保已安装 `qianfan` 包
- 检查 API Key 格式是否正确(app_key:app_secret)
- 确认环境变量或参数中的 AK/SK 是否正确
+
diff --git a/docs/guides/PROJECT_STRUCTURE_ANALYSIS.md b/docs/guides/PROJECT_STRUCTURE_ANALYSIS.md
deleted file mode 100644
index e620770..0000000
--- a/docs/guides/PROJECT_STRUCTURE_ANALYSIS.md
+++ /dev/null
@@ -1,231 +0,0 @@
-# 项目目录结构分析报告
-
-## 📊 当前项目结构分析
-
-### 文件统计
-
-**Python 文件(.py)**:24个
-- 主程序:1个(geo_tool.py)
-- 功能模块:18个
-- 平台同步模块:3个(在platform_sync/目录)
-- 示例/工具:2个
-
-**文档文件(.md)**:34个
-- 功能文档(*_FEATURE.md):15个
-- 分析报告(*_ANALYSIS.md, *_REPORT.md):7个
-- 指南文档(*_GUIDE.md):5个
-- 实现文档:7个
-
-**配置文件**:3个
-- requirements.txt
-- .gitignore
-- .streamlit/config.toml
-
-### 目录结构
-
-```
-geo_tool/
-├── 根目录文件(50+个文件混在一起)
-│ ├── Python模块文件(18个)
-│ ├── 文档文件(34个)
-│ ├── 配置文件(3个)
-│ └── 主程序(1个)
-│
-└── platform_sync/(已有子目录)
- ├── __init__.py
- ├── base_publisher.py
- ├── github_publisher.py
- └── copy_manager.py
-```
-
-## 🔍 文件引用关系分析
-
-### Python 文件导入关系
-
-**geo_tool.py** 导入的模块(18个):
-```python
-from data_storage import DataStorage
-from keyword_tool import KeywordTool
-from content_scorer import ContentScorer
-from eeat_enhancer import EEATEnhancer
-from semantic_expander import SemanticExpander
-from fact_density_enhancer import FactDensityEnhancer
-from schema_generator import SchemaGenerator
-from topic_cluster import TopicCluster
-from multimodal_prompt import MultimodalPromptGenerator
-from roi_analyzer import ROIAnalyzer
-from workflow_automation import WorkflowManager, WorkflowStep
-from keyword_mining import KeywordMining
-from optimization_techniques import OptimizationTechniqueManager
-from content_metrics import ContentMetricsAnalyzer
-from technical_config_generator import TechnicalConfigGenerator
-from negative_monitor import NegativeMonitor
-from resource_recommender import ResourceRecommender
-```
-
-**platform_sync** 模块:
-- 使用相对导入,已有包结构
-
-### 文档引用关系
-
-**README.md** 引用的文档:
-- `INTEGRATION_NOTES.md`
-- `STORAGE_GUIDE.md`
-- `PLATFORM_SETUP.md`
-- `IMPLEMENTATION_SUMMARY.md`
-- `PLATFORM_SYNC_IMPLEMENTATION.md`
-- 15个 `*_FEATURE.md` 文件
-
-**功能文档** 之间的引用:
-- 各功能文档相互引用
-- 引用实现模块(.py文件)
-- 引用其他分析文档
-
-## 🎯 优化建议
-
-### 1. 目录结构优化
-
-**建议结构**:
-```
-geo_tool/
-├── README.md
-├── requirements.txt
-├── .gitignore
-├── .streamlit/
-├── geo_tool.py
-├── modules/ # 新增:功能模块目录
-│ ├── __init__.py
-│ └── [18个模块文件]
-├── platform_sync/ # 已有
-└── docs/ # 新增:文档目录
- ├── features/ # 功能文档
- ├── analysis/ # 分析报告
- ├── guides/ # 指南文档
- └── implementation/ # 实现文档
-```
-
-### 2. 导入路径更新
-
-**需要更新的导入**:
-- `geo_tool.py`:所有模块导入添加 `modules.` 前缀
-- `storage_example.py`:更新 `data_storage` 导入
-
-### 3. 文档路径更新
-
-**需要更新的文档引用**:
-- 所有 `.md` 文件中的文档路径引用
-- README.md 中的文档链接
-
-## 📋 实施计划
-
-### 阶段1:准备(已完成)
-- ✅ 创建优化方案文档
-- ✅ 创建目录结构
-- ✅ 创建更新脚本
-
-### 阶段2:文件移动(待执行)
-1. 关闭所有打开的文件(IDE、编辑器等)
-2. 运行文件移动脚本或手动移动
-3. 验证文件移动成功
-
-### 阶段3:路径更新(待执行)
-1. 运行 `scripts/update_imports.py` 更新Python导入
-2. 运行 `scripts/update_doc_references.py` 更新文档引用
-3. 手动检查关键文件
-
-### 阶段4:验证(待执行)
-1. 运行 `python geo_tool.py` 测试导入
-2. 检查文档链接是否正常
-3. 验证所有功能是否正常
-
-## 📝 详细文件清单
-
-### 需要移动到 modules/ 的文件(18个)
-
-1. `data_storage.py`
-2. `keyword_tool.py`
-3. `content_scorer.py`
-4. `eeat_enhancer.py`
-5. `semantic_expander.py`
-6. `fact_density_enhancer.py`
-7. `schema_generator.py`
-8. `topic_cluster.py`
-9. `multimodal_prompt.py`
-10. `roi_analyzer.py`
-11. `workflow_automation.py`
-12. `keyword_mining.py`
-13. `optimization_techniques.py`
-14. `content_metrics.py`
-15. `technical_config_generator.py`
-16. `negative_monitor.py`
-17. `resource_recommender.py`
-18. `config_optimizer.py`
-19. `storage_example.py`(可选)
-
-### 需要移动到 docs/features/ 的文件(15个)
-
-1. `CONFIG_OPTIMIZER_FEATURE.md`
-2. `CONTENT_METRICS_FEATURE.md`
-3. `CONTENT_SCORER_FEATURE.md`
-4. `EEAT_FEATURE.md`
-5. `FACT_DENSITY_FEATURE.md`
-6. `JSON_LD_SCHEMA_FEATURE.md`
-7. `KEYWORD_MINING_FEATURE.md`
-8. `MULTIMODAL_FEATURE.md`
-9. `NEGATIVE_MONITOR_FEATURE.md`
-10. `OPTIMIZATION_TECHNIQUES_FEATURE.md`
-11. `RESOURCE_RECOMMENDER_FEATURE.md`
-12. `ROI_ANALYSIS_FEATURE.md`
-13. `SEMANTIC_EXPANSION_FEATURE.md`
-14. `TECHNICAL_CONFIG_FEATURE.md`
-15. `TOPIC_CLUSTER_FEATURE.md`
-16. `WORKFLOW_AUTOMATION_FEATURE.md`
-
-### 需要移动到 docs/analysis/ 的文件(7个)
-
-1. `ANALYSIS_ACCURACY_REPORT.md`
-2. `CODE_DOCUMENTATION_ANALYSIS.md`
-3. `DOCUMENTATION_REVERSE_VERIFICATION.md`
-4. `FEATURE_ANALYSIS.md`
-5. `FEATURE_PRIORITY_ANALYSIS.md`
-6. `FUNCTION_VERIFICATION_REPORT.md`
-7. `GEO_COMPLIANCE_ANALYSIS.md`
-
-### 需要移动到 docs/guides/ 的文件(5个)
-
-1. `QUICK_START_GUIDE.md`
-2. `STORAGE_GUIDE.md`
-3. `PLATFORM_SETUP.md`
-4. `LAYOUT_UPGRADE_GUIDE.md`
-5. `DECISION_GUIDE.md`
-
-### 需要移动到 docs/implementation/ 的文件(7个)
-
-1. `IMPLEMENTATION_SUMMARY.md`
-2. `PLATFORM_SYNC_ANALYSIS.md`
-3. `PLATFORM_SYNC_IMPLEMENTATION.md`
-4. `PLATFORM_SYNC_TEST.md`
-5. `INTEGRATION_NOTES.md`
-6. `FEATURES_COMPLETE_LIST.md`
-7. `ADVANCED_FEATURES.md`
-
-## ✅ 优化后的优势
-
-1. **清晰的目录结构**:文件按功能分类,易于查找和管理
-2. **模块化组织**:功能模块集中管理,便于维护和扩展
-3. **文档分类清晰**:功能文档、分析报告、指南文档分开管理
-4. **符合最佳实践**:遵循Python项目标准目录结构
-5. **便于协作**:团队成员更容易理解项目结构
-
-## 🚨 注意事项
-
-1. **文件占用**:移动文件前,确保所有文件未被IDE或其他程序打开
-2. **路径更新**:移动文件后,必须运行更新脚本更新所有引用路径
-3. **测试验证**:完成移动和更新后,务必测试程序是否正常运行
-4. **Git提交**:建议分步骤提交,便于回滚和追踪变更
-
-## 📚 相关文档
-
-- `docs/DIRECTORY_STRUCTURE_OPTIMIZATION.md` - 详细的优化方案和实施步骤
-- `scripts/update_imports.py` - 自动更新Python导入路径的脚本
-- `scripts/update_doc_references.py` - 自动更新文档路径引用的脚本
diff --git a/docs/guides/QUICK_REORGANIZE.md b/docs/guides/QUICK_REORGANIZE.md
deleted file mode 100644
index 72babe6..0000000
--- a/docs/guides/QUICK_REORGANIZE.md
+++ /dev/null
@@ -1,139 +0,0 @@
-# 快速重组项目目录结构
-
-## ⚡ 快速执行步骤
-
-### 步骤1:关闭所有打开的文件
-**重要**:在移动文件之前,请关闭所有在IDE或编辑器中打开的文件,否则会出现"文件被占用"的错误。
-
-### 步骤2:执行文件移动
-
-#### 方法A:使用PowerShell脚本(推荐)
-
-在项目根目录打开PowerShell,执行:
-
-```powershell
-# 移动功能模块
-$modules = @(
- "data_storage.py", "keyword_tool.py", "content_scorer.py", "eeat_enhancer.py",
- "semantic_expander.py", "fact_density_enhancer.py", "schema_generator.py",
- "topic_cluster.py", "multimodal_prompt.py", "roi_analyzer.py",
- "workflow_automation.py", "keyword_mining.py", "optimization_techniques.py",
- "content_metrics.py", "technical_config_generator.py", "negative_monitor.py",
- "resource_recommender.py", "config_optimizer.py", "storage_example.py"
-)
-foreach ($file in $modules) {
- if (Test-Path $file) {
- Move-Item $file -Destination "modules\" -Force -ErrorAction SilentlyContinue
- Write-Host "✓ $file"
- }
-}
-
-# 移动功能文档
-Get-ChildItem -Filter "*_FEATURE.md" | ForEach-Object {
- Move-Item $_.FullName -Destination "docs\features\" -Force -ErrorAction SilentlyContinue
- Write-Host "✓ $($_.Name)"
-}
-
-# 移动分析报告
-$analysis = @(
- "ANALYSIS_ACCURACY_REPORT.md", "CODE_DOCUMENTATION_ANALYSIS.md",
- "DOCUMENTATION_REVERSE_VERIFICATION.md", "FEATURE_ANALYSIS.md",
- "FEATURE_PRIORITY_ANALYSIS.md", "FUNCTION_VERIFICATION_REPORT.md",
- "GEO_COMPLIANCE_ANALYSIS.md"
-)
-foreach ($file in $analysis) {
- if (Test-Path $file) {
- Move-Item $file -Destination "docs\analysis\" -Force -ErrorAction SilentlyContinue
- Write-Host "✓ $file"
- }
-}
-
-# 移动指南文档
-$guides = @(
- "QUICK_START_GUIDE.md", "STORAGE_GUIDE.md", "PLATFORM_SETUP.md",
- "LAYOUT_UPGRADE_GUIDE.md", "DECISION_GUIDE.md"
-)
-foreach ($file in $guides) {
- if (Test-Path $file) {
- Move-Item $file -Destination "docs\guides\" -Force -ErrorAction SilentlyContinue
- Write-Host "✓ $file"
- }
-}
-
-# 移动实现文档
-$implementation = @(
- "IMPLEMENTATION_SUMMARY.md", "PLATFORM_SYNC_ANALYSIS.md",
- "PLATFORM_SYNC_IMPLEMENTATION.md", "PLATFORM_SYNC_TEST.md",
- "INTEGRATION_NOTES.md", "FEATURES_COMPLETE_LIST.md", "ADVANCED_FEATURES.md"
-)
-foreach ($file in $implementation) {
- if (Test-Path $file) {
- Move-Item $file -Destination "docs\implementation\" -Force -ErrorAction SilentlyContinue
- Write-Host "✓ $file"
- }
-}
-
-Write-Host "`n✅ 文件移动完成!"
-```
-
-#### 方法B:手动移动(如果脚本失败)
-
-如果PowerShell脚本因为文件被占用而失败,请:
-
-1. 关闭所有IDE和编辑器
-2. 手动将文件拖拽到对应目录
-3. 参考 `PROJECT_STRUCTURE_ANALYSIS.md` 中的文件清单
-
-### 步骤3:更新导入路径
-
-```powershell
-python scripts/update_imports.py
-```
-
-### 步骤4:更新文档引用
-
-```powershell
-python scripts/update_doc_references.py
-```
-
-### 步骤5:验证
-
-```powershell
-# 测试导入是否正常
-python -c "from modules.data_storage import DataStorage; print('✓ 导入成功')"
-```
-
-## 📋 完整执行清单
-
-- [ ] 关闭所有打开的文件(IDE、编辑器等)
-- [ ] 执行文件移动(PowerShell脚本或手动)
-- [ ] 运行 `python scripts/update_imports.py`
-- [ ] 运行 `python scripts/update_doc_references.py`
-- [ ] 测试导入:`python -c "from modules.data_storage import DataStorage"`
-- [ ] 运行主程序:`streamlit run geo_tool.py`
-- [ ] 检查文档链接是否正常
-
-## 🆘 遇到问题?
-
-### 问题1:文件被占用
-**解决方案**:
-1. 关闭所有IDE和编辑器
-2. 检查是否有Python进程在运行:`tasklist | findstr python`
-3. 如果仍有问题,重启计算机后再试
-
-### 问题2:导入错误
-**解决方案**:
-1. 确认文件已移动到 `modules/` 目录
-2. 确认 `modules/__init__.py` 存在
-3. 检查 `geo_tool.py` 中的导入路径是否正确
-
-### 问题3:文档链接失效
-**解决方案**:
-1. 运行 `python scripts/update_doc_references.py`
-2. 手动检查 `README.md` 中的链接
-3. 使用相对路径而不是绝对路径
-
-## 📚 相关文档
-
-- `PROJECT_STRUCTURE_ANALYSIS.md` - 详细的项目结构分析
-- `docs/DIRECTORY_STRUCTURE_OPTIMIZATION.md` - 完整的优化方案
diff --git a/docs/guides/QUICK_START_GUIDE.md b/docs/guides/QUICK_START_GUIDE.md
index 6a90719..d597457 100644
--- a/docs/guides/QUICK_START_GUIDE.md
+++ b/docs/guides/QUICK_START_GUIDE.md
@@ -326,20 +326,18 @@ with tabs[5]: # 平台同步Tab
## ✅ 步骤6:测试
1. **获取GitHub Token**:
- - 访问 https://github.com/settings/tokens
- - 创建新的 Personal Access Token
- - 选择 `repo` 权限
-
+ - 访问 [https://github.com/settings/tokens](https://github.com/settings/tokens)
+ - 创建新的 Personal Access Token
+ - 选择 `repo` 权限
2. **运行测试**:
- ```bash
+ ```bash
streamlit run geo_tool.py
- ```
-
+ ```
3. **测试流程**:
- - 配置GitHub账号
- - 生成一篇文章
- - 发布到GitHub
- - 检查GitHub仓库是否成功创建文件
+ - 配置GitHub账号
+ - 生成一篇文章
+ - 发布到GitHub
+ - 检查GitHub仓库是否成功创建文件
## 🎉 完成!
@@ -353,3 +351,4 @@ with tabs[5]: # 平台同步Tab
- [GitHub API文档](https://docs.github.com/en/rest)
- [完整实现指南](./PLATFORM_SYNC_IMPLEMENTATION.md)
+
diff --git a/docs/guides/REFERENCE_UPDATE_SUMMARY.md b/docs/guides/REFERENCE_UPDATE_SUMMARY.md
deleted file mode 100644
index aea356a..0000000
--- a/docs/guides/REFERENCE_UPDATE_SUMMARY.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# 引用路径更新总结
-
-## ✅ 已完成的更新
-
-### 1. 工具脚本引用更新
-
-**更新的文档**(6个):
-- ✅ `FINAL_OPTIMIZATION_GUIDE.md`
-- ✅ `ADVANCED_OPTIMIZATION_PLAN.md`
-- ✅ `docs/guides/DOCUMENTATION_CLEANUP_GUIDE.md`
-- ✅ `docs/guides/PROJECT_STRUCTURE_ANALYSIS.md`
-- ✅ `docs/guides/QUICK_REORGANIZE.md`
-- ✅ `docs/guides/REORGANIZATION_SUMMARY.md`
-
-**更新内容**:
-- `python update_imports.py` → `python scripts/update_imports.py`
-- `python cleanup_duplicate_docs.py` → `python scripts/cleanup_duplicate_docs.py`
-- `update_imports.py` → `scripts/update_imports.py`
-- 等等...
-
-### 2. 模块导入路径更新
-
-**已更新的文件**:
-- ✅ `geo_tool.py` - 所有导入已更新为 `from modules.xxx import`
-- ✅ `modules/storage_example.py` - 导入已更新为 `from modules.data_storage import`
-- ✅ `storage_example.py` (根目录) - 导入已更新为 `from modules.data_storage import`
-
-## 📋 引用路径对照表
-
-### 工具脚本路径
-
-| 旧路径 | 新路径 |
-|--------|--------|
-| `python update_imports.py` | `python scripts/update_imports.py` |
-| `python update_doc_references.py` | `python scripts/update_doc_references.py` |
-| `python cleanup_duplicate_docs.py` | `python scripts/cleanup_duplicate_docs.py` |
-| `python move_reorganization_docs.py` | `python scripts/move_reorganization_docs.py` |
-| `python reorganize_files.py` | `python scripts/reorganize_files.py` |
-
-### 模块导入路径
-
-| 旧导入 | 新导入 |
-|--------|--------|
-| `from data_storage import DataStorage` | `from modules.data_storage import DataStorage` |
-| `from keyword_tool import KeywordTool` | `from modules.keyword_tool import KeywordTool` |
-| `from content_scorer import ContentScorer` | `from modules.content_scorer import ContentScorer` |
-| ... | ... (所有模块都已更新) |
-
-## ✅ 验证清单
-
-### Python 导入验证
-- [x] `geo_tool.py` - 所有导入使用 `modules.xxx`
-- [x] `modules/storage_example.py` - 导入已更新
-- [x] `storage_example.py` (根目录) - 导入已更新
-- [x] 测试导入:`python -c "from modules.data_storage import DataStorage"` ✓
-
-### 文档引用验证
-- [x] 所有文档中的工具脚本路径已更新为 `scripts/` 前缀
-- [x] 所有文档中的模块路径引用已更新(通过之前的 `update_doc_references.py`)
-
-### 脚本路径验证
-- [ ] 移动脚本后,测试脚本是否仍可运行
-- [ ] 确认所有文档中的脚本路径正确
-
-## 🎯 下一步操作
-
-### 执行优化前
-
-1. **确认所有引用已更新** ✅
- - Python 导入路径:已更新
- - 文档引用路径:已更新
-
-2. **执行文件移动**
- ```powershell
- # 步骤1:移动工具脚本
- python move_scripts.py
-
- # 步骤2:清理重复模块(在确认导入路径正确后)
- python cleanup_duplicate_modules.py
- ```
-
-3. **验证**
- ```powershell
- # 测试脚本
- python scripts/update_imports.py
-
- # 测试主程序
- streamlit run geo_tool.py
- ```
-
-## 📝 注意事项
-
-1. **工具脚本移动后**:
- - 使用方式:`python scripts/script_name.py`
- - 或在 `scripts/` 目录下运行:`cd scripts && python script_name.py`
-
-2. **模块文件删除后**:
- - 确保 `modules/` 目录中有完整版本
- - 确保所有导入使用 `modules.xxx` 格式
- - 如有问题,可从 Git 恢复
-
-3. **文档引用**:
- - 所有文档中的路径引用已自动更新
- - 如有遗漏,可手动检查并更新
-
-## 🆘 如果遇到问题
-
-### 问题1:脚本找不到
-**解决方案**:
-- 使用完整路径:`python scripts/script_name.py`
-- 或进入目录:`cd scripts && python script_name.py`
-
-### 问题2:导入错误
-**解决方案**:
-- 检查导入路径:确认使用 `from modules.xxx import`
-- 检查 `modules/__init__.py` 是否存在
-- 从 Git 恢复文件:`git checkout HEAD -- filename.py`
-
-### 问题3:文档链接失效
-**解决方案**:
-- 运行 `python scripts/update_doc_references.py` 更新文档引用
-- 运行 `python scripts/update_script_references.py` 更新脚本引用
diff --git a/docs/guides/REORGANIZATION_SUMMARY.md b/docs/guides/REORGANIZATION_SUMMARY.md
deleted file mode 100644
index c8378ac..0000000
--- a/docs/guides/REORGANIZATION_SUMMARY.md
+++ /dev/null
@@ -1,243 +0,0 @@
-# 项目目录结构优化总结
-
-## ✅ 已完成的工作
-
-### 1. 目录结构创建
-- ✅ 创建了 `docs/` 目录及其子目录:
- - `docs/features/` - 功能文档
- - `docs/analysis/` - 分析报告
- - `docs/guides/` - 指南文档
- - `docs/implementation/` - 实现文档
-- ✅ 创建了 `modules/` 目录
-- ✅ 创建了 `modules/__init__.py`
-
-### 2. 分析文档创建
-- ✅ `PROJECT_STRUCTURE_ANALYSIS.md` - 完整的项目结构分析
-- ✅ `docs/DIRECTORY_STRUCTURE_OPTIMIZATION.md` - 详细的优化方案
-- ✅ `QUICK_REORGANIZE.md` - 快速执行指南
-
-### 3. 自动化脚本创建
-- ✅ `scripts/update_imports.py` - 自动更新Python导入路径
-- ✅ `scripts/update_doc_references.py` - 自动更新文档路径引用
-- ✅ `scripts/reorganize_files.py` - 文件移动脚本(备用)
-
-## 📋 待执行的任务
-
-### 任务1:清理重复文件
-
-**当前状态**:部分文件已移动到 `modules/`,但根目录仍有重复文件。
-
-**需要操作**:
-1. 检查根目录和 `modules/` 目录中的重复文件
-2. 删除根目录中的重复文件(保留 `modules/` 中的版本)
-
-**重复文件列表**(需要确认):
-- `config_optimizer.py`
-- `content_metrics.py`
-- `content_scorer.py`
-- `data_storage.py`
-- `eeat_enhancer.py`
-- `fact_density_enhancer.py`
-- `keyword_mining.py`
-- `keyword_tool.py`
-- `multimodal_prompt.py`
-- `negative_monitor.py`
-- `optimization_techniques.py`
-- `resource_recommender.py`
-- `roi_analyzer.py`
-- `schema_generator.py`
-- `semantic_expander.py`
-- `storage_example.py`
-- `technical_config_generator.py`
-- `topic_cluster.py`
-- `workflow_automation.py`
-
-### 任务2:移动文档文件
-
-**需要移动到 `docs/features/` 的文件**(15个):
-- `CONFIG_OPTIMIZER_FEATURE.md`
-- `CONTENT_METRICS_FEATURE.md`
-- `CONTENT_SCORER_FEATURE.md`
-- `EEAT_FEATURE.md`
-- `FACT_DENSITY_FEATURE.md`
-- `JSON_LD_SCHEMA_FEATURE.md`
-- `KEYWORD_MINING_FEATURE.md`
-- `MULTIMODAL_FEATURE.md`
-- `NEGATIVE_MONITOR_FEATURE.md`
-- `OPTIMIZATION_TECHNIQUES_FEATURE.md`
-- `RESOURCE_RECOMMENDER_FEATURE.md`
-- `ROI_ANALYSIS_FEATURE.md`
-- `SEMANTIC_EXPANSION_FEATURE.md`
-- `TECHNICAL_CONFIG_FEATURE.md`
-- `TOPIC_CLUSTER_FEATURE.md`
-- `WORKFLOW_AUTOMATION_FEATURE.md`
-
-**需要移动到 `docs/analysis/` 的文件**(7个):
-- `ANALYSIS_ACCURACY_REPORT.md`
-- `CODE_DOCUMENTATION_ANALYSIS.md`
-- `DOCUMENTATION_REVERSE_VERIFICATION.md`
-- `FEATURE_ANALYSIS.md`
-- `FEATURE_PRIORITY_ANALYSIS.md`
-- `FUNCTION_VERIFICATION_REPORT.md`
-- `GEO_COMPLIANCE_ANALYSIS.md`
-
-**需要移动到 `docs/guides/` 的文件**(5个):
-- `QUICK_START_GUIDE.md`
-- `STORAGE_GUIDE.md`
-- `PLATFORM_SETUP.md`
-- `LAYOUT_UPGRADE_GUIDE.md`
-- `DECISION_GUIDE.md`
-
-**需要移动到 `docs/implementation/` 的文件**(7个):
-- `IMPLEMENTATION_SUMMARY.md`
-- `PLATFORM_SYNC_ANALYSIS.md`
-- `PLATFORM_SYNC_IMPLEMENTATION.md`
-- `PLATFORM_SYNC_TEST.md`
-- `INTEGRATION_NOTES.md`
-- `FEATURES_COMPLETE_LIST.md`
-- `ADVANCED_FEATURES.md`
-
-### 任务3:更新导入路径
-
-**需要更新的文件**:
-1. `geo_tool.py` - 更新所有模块导入
-2. `modules/storage_example.py` - 更新 `data_storage` 导入
-
-**执行命令**:
-```powershell
-python scripts/update_imports.py
-```
-
-### 任务4:更新文档引用
-
-**需要更新的文件**:
-- `README.md` - 更新所有文档路径引用
-- 所有 `.md` 文件中的文档和模块路径引用
-
-**执行命令**:
-```powershell
-python scripts/update_doc_references.py
-```
-
-## 🎯 优化后的目录结构
-
-```
-geo_tool/
-├── README.md # 项目主文档
-├── requirements.txt # 依赖文件
-├── .gitignore # Git配置
-├── .streamlit/ # Streamlit配置
-│ └── config.toml
-├── geo_tool.py # 主程序
-│
-├── modules/ # 功能模块(18个文件)
-│ ├── __init__.py
-│ ├── data_storage.py
-│ ├── keyword_tool.py
-│ ├── content_scorer.py
-│ ├── eeat_enhancer.py
-│ ├── semantic_expander.py
-│ ├── fact_density_enhancer.py
-│ ├── schema_generator.py
-│ ├── topic_cluster.py
-│ ├── multimodal_prompt.py
-│ ├── roi_analyzer.py
-│ ├── workflow_automation.py
-│ ├── keyword_mining.py
-│ ├── optimization_techniques.py
-│ ├── content_metrics.py
-│ ├── technical_config_generator.py
-│ ├── negative_monitor.py
-│ ├── resource_recommender.py
-│ ├── config_optimizer.py
-│ └── storage_example.py
-│
-├── platform_sync/ # 平台同步模块
-│ ├── __init__.py
-│ ├── base_publisher.py
-│ ├── github_publisher.py
-│ └── copy_manager.py
-│
-└── docs/ # 文档目录
- ├── features/ # 功能文档(15个)
- ├── analysis/ # 分析报告(7个)
- ├── guides/ # 指南文档(5个)
- └── implementation/ # 实现文档(7个)
-```
-
-## 📝 执行步骤
-
-### 快速执行(推荐)
-
-1. **关闭所有打开的文件**(IDE、编辑器等)
-
-2. **执行文件移动**:
- ```powershell
- # 参考 QUICK_REORGANIZE.md 中的PowerShell脚本
- ```
-
-3. **更新导入路径**:
- ```powershell
- python scripts/update_imports.py
- ```
-
-4. **更新文档引用**:
- ```powershell
- python scripts/update_doc_references.py
- ```
-
-5. **验证**:
- ```powershell
- python -c "from modules.data_storage import DataStorage; print('✓ 导入成功')"
- streamlit run geo_tool.py
- ```
-
-## 🔍 文件引用关系
-
-### Python 导入关系
-
-**geo_tool.py** 需要更新的导入(18个):
-```python
-# 旧导入
-from data_storage import DataStorage
-from keyword_tool import KeywordTool
-# ...
-
-# 新导入
-from modules.data_storage import DataStorage
-from modules.keyword_tool import KeywordTool
-# ...
-```
-
-### 文档引用关系
-
-**README.md** 中的文档路径需要更新:
-- `xxx_FEATURE.md` → `docs/features/xxx_FEATURE.md`
-- `xxx_GUIDE.md` → `docs/guides/xxx_GUIDE.md`
-- `xxx.md` (implementation) → `docs/implementation/xxx.md`
-
-## ✅ 验证清单
-
-完成重组后,请验证:
-
-- [ ] 根目录不再有重复的模块文件
-- [ ] 所有文档文件已分类移动到 `docs/` 子目录
-- [ ] `geo_tool.py` 中的导入路径已更新
-- [ ] 所有文档中的路径引用已更新
-- [ ] `python -c "from modules.data_storage import DataStorage"` 执行成功
-- [ ] `streamlit run geo_tool.py` 运行正常
-- [ ] 所有功能测试通过
-
-## 📚 相关文档
-
-- `QUICK_REORGANIZE.md` - 快速执行指南
-- `PROJECT_STRUCTURE_ANALYSIS.md` - 详细的项目结构分析
-- `docs/DIRECTORY_STRUCTURE_OPTIMIZATION.md` - 完整的优化方案
-
-## 🎉 优化后的优势
-
-1. **清晰的目录结构**:文件按功能分类,易于查找和管理
-2. **模块化组织**:功能模块集中管理,便于维护和扩展
-3. **文档分类清晰**:功能文档、分析报告、指南文档分开管理
-4. **符合最佳实践**:遵循Python项目标准目录结构
-5. **便于协作**:团队成员更容易理解项目结构
diff --git a/docs/guides/ROOT_DIRECTORY_RULES.md b/docs/guides/ROOT_DIRECTORY_RULES.md
index b5d8dc8..fe42819 100644
--- a/docs/guides/ROOT_DIRECTORY_RULES.md
+++ b/docs/guides/ROOT_DIRECTORY_RULES.md
@@ -5,6 +5,7 @@
### ✅ 允许在根目录的文件
**核心文件(必须保留)**:
+
1. `README.md` - 项目主文档
2. `DOCS.md` - 文档索引
3. `geo_tool.py` - 主程序
@@ -15,63 +16,65 @@
### ❌ 禁止在根目录创建的文件
1. **文档文件(.md)**
- - ❌ 禁止在根目录创建任何新的 `.md` 文档
- - ✅ 所有文档应放在 `docs/` 的相应子目录中
-
+ - ❌ 禁止在根目录创建任何新的 `.md` 文档
+ - ✅ 所有文档应放在 `docs/` 的相应子目录中
2. **功能模块文件(.py)**
- - ❌ 禁止在根目录创建功能模块文件
- - ✅ 所有功能模块应放在 `modules/` 目录
-
+ - ❌ 禁止在根目录创建功能模块文件
+ - ✅ 所有功能模块应放在 `modules/` 目录
3. **工具脚本文件(.py)**
- - ❌ 禁止在根目录创建工具脚本
- - ✅ 所有工具脚本应放在 `scripts/` 目录
+ - ❌ 禁止在根目录创建工具脚本
+ - ✅ 所有工具脚本应放在 `scripts/` 目录
## 📁 文件位置规则
### 文档文件
-| 文档类型 | 位置 | 示例 |
-|---------|------|------|
-| 功能文档 | `docs/features/` | `docs/features/CONFIG_OPTIMIZER_FEATURE.md` |
-| 分析报告 | `docs/analysis/` | `docs/analysis/FEATURE_ANALYSIS.md` |
-| 使用指南 | `docs/guides/` | `docs/guides/QUICK_START_GUIDE.md` |
+
+| 文档类型 | 位置 | 示例 |
+| ---- | ---------------------- | ----------------------------------------------- |
+| 功能文档 | `docs/features/` | `docs/features/CONFIG_OPTIMIZER_FEATURE.md` |
+| 分析报告 | `docs/analysis/` | `docs/analysis/FEATURE_ANALYSIS.md` |
+| 使用指南 | `docs/guides/` | `docs/guides/QUICK_START_GUIDE.md` |
| 实现文档 | `docs/implementation/` | `docs/implementation/IMPLEMENTATION_SUMMARY.md` |
+
### Python 文件
-| 文件类型 | 位置 | 示例 |
-|---------|------|------|
-| 功能模块 | `modules/` | `modules/data_storage.py` |
-| 工具脚本 | `scripts/` | `scripts/update_imports.py` |
-| 主程序 | 根目录 | `geo_tool.py` |
+
+| 文件类型 | 位置 | 示例 |
+| ---- | ---------------- | ----------------------------------- |
+| 功能模块 | `modules/` | `modules/data_storage.py` |
+| 工具脚本 | `scripts/` | `scripts/update_imports.py` |
+| 主程序 | 根目录 | `geo_tool.py` |
| 平台同步 | `platform_sync/` | `platform_sync/github_publisher.py` |
+
## 🎯 创建新文件时的检查
创建新文件前,请确认:
1. **如果是文档文件**:
- - [ ] 是否放在了正确的 `docs/` 子目录?
- - [ ] 是否更新了 `DOCS.md` 的索引?
-
+ - 是否放在了正确的 `docs/` 子目录?
+ - 是否更新了 `DOCS.md` 的索引?
2. **如果是功能模块**:
- - [ ] 是否放在了 `modules/` 目录?
- - [ ] 是否更新了导入路径?
-
+ - 是否放在了 `modules/` 目录?
+ - 是否更新了导入路径?
3. **如果是工具脚本**:
- - [ ] 是否放在了 `scripts/` 目录?
+ - 是否放在了 `scripts/` 目录?
## 📝 当前需要清理的根目录文件
以下文件应删除或移动到合适位置:
### 需要删除的重复文档(docs/guides/中已有):
+
- `ADVANCED_OPTIMIZATION_PLAN.md`
- `FINAL_OPTIMIZATION_GUIDE.md`
- `REFERENCE_UPDATE_SUMMARY.md`
- `OPTIMIZATION_STATUS.md`
### 需要移动的文档:
+
- `MANUAL_CLEANUP_GUIDE.md` → `docs/guides/`
## 🚀 快速清理命令
@@ -90,6 +93,7 @@ Move-Item MANUAL_CLEANUP_GUIDE.md -Destination "docs\guides\" -Force
## ✅ 清理后的根目录
清理完成后,根目录应该只有:
+
- `README.md`
- `DOCS.md`
- `geo_tool.py`
@@ -97,4 +101,4 @@ Move-Item MANUAL_CLEANUP_GUIDE.md -Destination "docs\guides\" -Force
- `.gitignore`
- `.streamlit/` (目录)
-**总计:5个核心文件 + 1个配置目录**
+**总计:5个核心文件 + 1个配置目录**
\ No newline at end of file
diff --git a/docs/guides/STORAGE_GUIDE.md b/docs/guides/STORAGE_GUIDE.md
index b0f5e1e..7536527 100644
--- a/docs/guides/STORAGE_GUIDE.md
+++ b/docs/guides/STORAGE_GUIDE.md
@@ -12,6 +12,7 @@
### 方案1:SQLite(⭐ 推荐)
**优点:**
+
- ✅ Python 内置支持(`sqlite3`),无需安装额外依赖
- ✅ 单文件数据库,易于备份和迁移
- ✅ 查询性能好,支持复杂查询
@@ -20,6 +21,7 @@
- ✅ 适合 MVP 到生产环境的平滑升级
**缺点:**
+
- ⚠️ 需要学习基本的 SQL(但很简单)
- ⚠️ 多进程写入需要处理锁(Streamlit 单进程,无此问题)
@@ -32,11 +34,13 @@
### 方案2:JSON 文件
**优点:**
+
- ✅ 最简单,无需学习 SQL
- ✅ 人类可读,易于调试
- ✅ 无需数据库知识
**缺点:**
+
- ❌ 查询性能差(需要加载整个文件)
- ❌ 数据量大时很慢
- ❌ 并发写入可能丢失数据
@@ -53,19 +57,16 @@
### 为什么推荐 SQLite?
1. **其实很简单**:只需要几行代码
- ```python
+ ```python
import sqlite3
conn = sqlite3.connect('data.db')
cursor = conn.cursor()
cursor.execute("INSERT INTO table VALUES (?)", (value,))
conn.commit()
conn.close()
- ```
-
+ ```
2. **性能好**:即使数据量增长到几万条,依然很快
-
3. **功能强大**:支持统计、查询、分析,为后续功能扩展打好基础
-
4. **零依赖**:Python 内置,无需安装任何包
---
@@ -163,12 +164,14 @@ with tab5:
## 性能对比(参考)
-| 数据量 | SQLite | JSON文件 |
-|--------|--------|----------|
-| 100条 | <10ms | <10ms |
-| 1000条 | <50ms | ~100ms |
-| 10000条 | ~200ms | ~5秒 |
-| 100000条 | ~1秒 | 很慢 |
+
+| 数据量 | SQLite | JSON文件 |
+| ------- | ------ | ------ |
+| 100条 | <10ms | <10ms |
+| 1000条 | <50ms | ~100ms |
+| 10000条 | ~200ms | ~5秒 |
+| 100000条 | ~1秒 | 很慢 |
+
---
@@ -191,4 +194,4 @@ with tab5:
2. 查看 `modules/storage_example.py` 了解使用方法
3. 在 `modules/geo_tool.py` 中集成(参考上面的最小改动示例)
-需要我帮你直接集成到 `modules/geo_tool.py` 吗?
+需要我帮你直接集成到 `modules/geo_tool.py` 吗?
\ No newline at end of file
diff --git a/docs/implementation/ADVANCED_FEATURES.md b/docs/implementation/ADVANCED_FEATURES.md
index 1f1828d..8b62f04 100644
--- a/docs/implementation/ADVANCED_FEATURES.md
+++ b/docs/implementation/ADVANCED_FEATURES.md
@@ -5,9 +5,11 @@
### 🧠 一、智能化增强(AI 驱动)
#### 1. **智能内容质量评分系统** ⭐⭐⭐⭐⭐
+
**价值**:自动评估内容是否符合 GEO 原则,提供改进建议
**功能点**:
+
- 自动分析生成内容的结构化程度(标题、清单、FAQ 等)
- 品牌提及位置和频率评分
- 内容权威性评估(数据支撑、案例引用)
@@ -15,6 +17,7 @@
- 内容 GEO 分数(0-100分)
**实现思路**:
+
- 使用 LLM 分析内容,输出结构化评分
- 建立评分标准(结构化、品牌提及、权威性、可引用性)
- 在内容生成后自动评分,并提供改进建议
@@ -24,9 +27,11 @@
---
#### 2. **智能关键词挖掘与趋势分析** ⭐⭐⭐⭐⭐
+
**价值**:发现新的高价值关键词,预测关键词趋势
**功能点**:
+
- 基于行业热点自动挖掘新关键词
- 分析关键词竞争度(在 AI 中的提及频率)
- 预测关键词趋势(上升/下降)
@@ -34,6 +39,7 @@
- 关键词组合建议(长尾词挖掘)
**实现思路**:
+
- 使用 LLM 分析行业趋势和用户搜索意图
- 结合历史验证数据,分析关键词效果
- 提供关键词价值矩阵(高价值/低价值 × 高竞争/低竞争)
@@ -43,15 +49,18 @@
---
#### 3. **A/B 测试与内容对比** ⭐⭐⭐⭐
+
**价值**:对比不同版本内容的效果,数据驱动优化
**功能点**:
+
- 为同一关键词生成多个版本内容(不同风格、结构)
- 同时验证多个版本,对比提及率
- 自动推荐最优版本
- 记录 A/B 测试历史,建立最佳实践库
**实现思路**:
+
- 在内容生成时支持"生成多个版本"
- 批量验证不同版本
- 对比分析提及率、位置等指标
@@ -64,9 +73,11 @@
### 📊 二、数据洞察增强
#### 4. **ROI 分析与成本优化** ⭐⭐⭐⭐⭐
+
**价值**:量化 GEO 投入产出比,优化成本结构
**功能点**:
+
- 计算每次验证的 API 成本
- 统计内容生成成本(按平台、按关键词)
- 分析提及率提升带来的价值(估算)
@@ -74,6 +85,7 @@
- 预算管理和成本预警
**实现思路**:
+
- 记录每次 API 调用的成本(基于各平台定价)
- 计算总投入成本
- 分析提及率提升幅度,估算品牌曝光价值
@@ -84,9 +96,11 @@
---
#### 5. **竞品监控与预警** ⭐⭐⭐⭐
+
**价值**:自动监控竞品在 AI 中的表现,及时调整策略
**功能点**:
+
- 定期自动验证竞品提及率
- 竞品提及率变化趋势
- 竞品内容策略分析(哪些关键词/平台效果好)
@@ -94,6 +108,7 @@
- 竞品对比报告(自动生成)
**实现思路**:
+
- 定时任务自动验证竞品
- 对比分析竞品和自身的数据
- 识别竞品的优势策略
@@ -104,15 +119,18 @@
---
#### 6. **内容效果预测模型** ⭐⭐⭐⭐
+
**价值**:预测内容发布后的效果,优化内容策略
**功能点**:
+
- 基于历史数据训练预测模型
- 预测新内容的提及率
- 预测不同平台的效果差异
- 推荐最优发布策略(平台组合、发布时间等)
**实现思路**:
+
- 收集历史数据(内容特征、平台、提及率)
- 使用机器学习模型预测效果
- 提供预测置信度
@@ -125,9 +143,11 @@
### 🔄 三、自动化增强
#### 7. **智能工作流自动化** ⭐⭐⭐⭐⭐
+
**价值**:一键完成从关键词到验证的完整流程
**功能点**:
+
- 自定义工作流(关键词生成 → 内容创作 → 自动验证)
- 定时任务(每天/每周自动验证)
- 条件触发(当提及率低于阈值时自动优化)
@@ -135,6 +155,7 @@
- 工作流模板(保存常用工作流)
**实现思路**:
+
- 创建工作流配置界面
- 支持条件判断和循环
- 集成定时任务(使用 APScheduler)
@@ -145,9 +166,11 @@
---
#### 8. **内容模板库与最佳实践** ⭐⭐⭐⭐
+
**价值**:积累成功经验,复用最佳内容模板
**功能点**:
+
- 自动保存高效果内容为模板
- 模板分类(按平台、按行业、按效果)
- 模板搜索和推荐
@@ -155,6 +178,7 @@
- 模板效果统计(使用次数、平均提及率)
**实现思路**:
+
- 识别高效果内容(提及率 > 阈值)
- 提取内容结构作为模板
- 模板参数化(品牌、优势等可替换)
@@ -165,15 +189,18 @@
---
#### 9. **智能内容去重与相似度检测** ⭐⭐⭐
+
**价值**:避免重复内容,确保内容多样性
**功能点**:
+
- 检测新生成内容与历史内容的相似度
- 自动去重(相似度 > 阈值时提示)
- 内容多样性分析(确保覆盖不同角度)
- 推荐内容角度(基于已有内容分析)
**实现思路**:
+
- 使用文本相似度算法(如余弦相似度)
- 对比新内容与历史内容
- 提供相似度评分和建议
@@ -185,15 +212,18 @@
### 🌐 四、平台与集成增强
#### 10. **多语言支持** ⭐⭐⭐⭐
+
**价值**:扩展国际市场,提升品牌全球影响力
**功能点**:
+
- 支持英文、日文等多语言内容生成
- 多语言关键词挖掘
- 多语言平台支持(Medium、Dev.to、Reddit 等)
- 多语言验证(使用海外 AI 平台)
**实现思路**:
+
- 扩展 Prompt 模板支持多语言
- 添加多语言平台列表
- 集成海外 AI 平台(Claude、Gemini 等)
@@ -203,9 +233,11 @@
---
#### 11. **API 接口与集成** ⭐⭐⭐⭐
+
**价值**:与其他系统集成,支持自动化流程
**功能点**:
+
- RESTful API 接口
- Webhook 支持(内容生成完成时通知)
- 与 CMS 系统集成
@@ -213,6 +245,7 @@
- API 文档和示例代码
**实现思路**:
+
- 使用 FastAPI 创建 API 服务
- 提供认证和限流
- 支持异步任务
@@ -223,9 +256,11 @@
---
#### 12. **团队协作与权限管理** ⭐⭐⭐
+
**价值**:支持团队使用,提升协作效率
**功能点**:
+
- 多用户支持(注册/登录)
- 角色权限管理(管理员、编辑、查看者)
- 内容审核流程
@@ -233,6 +268,7 @@
- 操作日志和审计
**实现思路**:
+
- 集成用户认证系统(如 Streamlit-Authenticator)
- 数据库添加用户和权限表
- 实现基于角色的访问控制
@@ -244,15 +280,18 @@
### 🎯 五、内容质量提升
#### 13. **内容个性化与定制** ⭐⭐⭐⭐
+
**价值**:根据目标受众定制内容风格和角度
**功能点**:
+
- 目标受众画像(技术专家、业务人员、决策者等)
- 内容风格选择(专业、通俗、故事化等)
- 内容角度选择(功能对比、使用教程、案例分析等)
- 个性化内容生成
**实现思路**:
+
- 在 Prompt 中加入受众和风格参数
- 提供预设的受众模板
- 根据受众调整内容深度和语言风格
@@ -262,15 +301,18 @@
---
#### 14. **内容结构化增强** ⭐⭐⭐
+
**价值**:确保内容符合 GEO 最佳实践
**功能点**:
+
- 自动检查内容结构完整性(标题、摘要、清单、FAQ 等)
- 结构化建议(缺失部分自动补充)
- 内容层次优化(确保逻辑清晰)
- Markdown/HTML 格式优化
**实现思路**:
+
- 使用 LLM 分析内容结构
- 识别缺失的结构元素
- 自动生成补充内容
@@ -282,15 +324,18 @@
### 📈 六、高级分析功能
#### 15. **预测性分析与趋势预测** ⭐⭐⭐⭐
+
**价值**:预测未来趋势,提前布局
**功能点**:
+
- 提及率趋势预测(未来 30 天)
- 关键词热度预测
- 竞品趋势预测
- 最佳行动时机推荐
**实现思路**:
+
- 使用时间序列分析(ARIMA、LSTM 等)
- 分析历史趋势
- 预测未来变化
@@ -301,15 +346,18 @@
---
#### 16. **内容关联分析** ⭐⭐⭐
+
**价值**:发现内容之间的关联,优化内容策略
**功能点**:
+
- 关键词关联分析(哪些关键词经常一起被提及)
- 平台关联分析(哪些平台组合效果好)
- 内容主题聚类
- 内容网络图可视化
**实现思路**:
+
- 使用关联规则挖掘(Apriori 算法)
- 构建内容关联图
- 可视化展示
@@ -321,20 +369,23 @@
## 🎯 推荐优先级(综合价值与实现难度)
### 🔥 第一优先级(高价值 + 中等难度)
+
1. **智能内容质量评分系统** - 直接提升内容质量
2. **ROI 分析与成本优化** - 量化价值,优化投入
3. **智能工作流自动化** - 大幅提升效率
4. **智能关键词挖掘** - 发现新机会
### 🟡 第二优先级(高价值 + 较高难度)
-5. **A/B 测试与内容对比** - 数据驱动优化
-6. **竞品监控与预警** - 保持竞争优势
-7. **内容效果预测模型** - 提前优化策略
+
+1. **A/B 测试与内容对比** - 数据驱动优化
+2. **竞品监控与预警** - 保持竞争优势
+3. **内容效果预测模型** - 提前优化策略
### 🟢 第三优先级(中等价值)
-8. **内容模板库** - 复用最佳实践
-9. **多语言支持** - 扩展市场
-10. **API 接口** - 企业级集成
+
+1. **内容模板库** - 复用最佳实践
+2. **多语言支持** - 扩展市场
+3. **API 接口** - 企业级集成
---
@@ -350,16 +401,19 @@
## 🚀 快速开始建议
**第一步**:实现"智能内容质量评分系统"
+
- 价值高,实现相对简单
- 可以立即提升用户体验
- 为后续功能打下基础
**第二步**:实现"ROI 分析与成本优化"
+
- 帮助用户量化价值
- 提升工具的商业价值
- 为定价策略提供依据
**第三步**:实现"智能工作流自动化"
+
- 大幅提升效率
- 增强用户粘性
-- 差异化竞争优势
+- 差异化竞争优势
\ No newline at end of file
diff --git a/docs/implementation/FEATURES_COMPLETE_LIST.md b/docs/implementation/FEATURES_COMPLETE_LIST.md
index c3974b1..67df056 100644
--- a/docs/implementation/FEATURES_COMPLETE_LIST.md
+++ b/docs/implementation/FEATURES_COMPLETE_LIST.md
@@ -12,6 +12,7 @@
## 📊 功能概览
### 核心功能模块
+
- **关键词管理**:AI生成、托词工具、语义扩展、话题集群
- **内容创作**:20个平台模板、批量生成、质量评分
- **内容优化**:E-E-A-T强化、事实密度、结构化优化
@@ -19,6 +20,7 @@
- **平台同步**:GitHub发布、一键复制(12个平台)
### 高级功能模块
+
- **数据分析**:ROI分析、内容指标、趋势预测
- **工作流自动化**:自定义工作流、批量处理
- **技术配置**:robots.txt、sitemap.xml、JSON-LD Schema
@@ -31,34 +33,40 @@
### Tab1:关键词蒸馏
#### 1. AI关键词生成 ✅
+
- **位置**:Tab1 - AI生成模式
- **功能**:使用LLM自动生成关键词
- **文档**:无独立文档(基础功能)
#### 2. 托词工具(AI蒸馏词)✅
+
- **位置**:Tab1 - 托词工具模式
- **功能**:词库管理、组合算法、LLM润色
- **特点**:支持10种组合模式、自动去重
- **文档**:无独立文档(基础功能)
#### 3. 混合模式 ✅
+
- **位置**:Tab1 - 混合模式
- **功能**:AI生成 + 托词工具组合
- **文档**:无独立文档(基础功能)
#### 4. 语义扩展 ✅
+
- **位置**:Tab1 - 语义扩展功能
- **功能**:从单一关键词扩展到8-15个关联词
- **模块**:`modules/semantic_expander.py`
- **文档**:`docs/features/docs/features/SEMANTIC_EXPANSION_FEATURE.md`
#### 5. 话题集群生成 ✅
+
- **位置**:Tab1 - 话题集群功能
- **功能**:语义聚类、话题命名、内容规划建议
- **模块**:`modules/topic_cluster.py`
- **文档**:`docs/features/docs/features/TOPIC_CLUSTER_FEATURE.md`
#### 6. 关键词挖掘 ✅
+
- **位置**:Tab1 - 智能关键词挖掘与趋势分析
- **功能**:
- 🌐 行业热点挖掘
@@ -73,29 +81,34 @@
### Tab2:自动创作
#### 7. 内容生成(20个平台)✅
+
- **位置**:Tab2 - 内容生成
- **功能**:为20个平台生成定制化内容
- **平台列表**:见 `README.md`
- **文档**:无独立文档(基础功能)
#### 8. 批量生成 ✅
+
- **位置**:Tab2 - 批量生成模式
- **功能**:一次性为多个关键词和平台生成内容
- **文档**:无独立文档(基础功能)
#### 9. 内容质量评分 ✅
+
- **位置**:Tab2 - 内容生成后自动评分
- **功能**:多维度评分(结构化、品牌提及、权威性、可引用性)
- **模块**:`modules/content_scorer.py`
- **文档**:`docs/features/docs/features/CONTENT_SCORER_FEATURE.md`
#### 10. JSON-LD Schema.org 生成 ✅
+
- **位置**:Tab2 - JSON-LD Schema.org 结构化数据生成
- **功能**:生成5种Schema类型(Organization、SoftwareApplication、Product、Service、组合)
- **模块**:`modules/schema_generator.py`
- **文档**:`docs/features/docs/features/JSON_LD_SCHEMA_FEATURE.md`
#### 11. 技术配置生成 ✅
+
- **位置**:Tab2 - 技术配置生成
- **功能**:
- 🤖 robots.txt 生成
@@ -104,18 +117,21 @@
- **文档**:`docs/features/docs/features/TECHNICAL_CONFIG_FEATURE.md`
#### 12. 多模态提示生成 ✅
+
- **位置**:Tab2 - 生成配图/视频描述
- **功能**:为内容生成配图描述和视频脚本
- **模块**:`modules/multimodal_prompt.py`
- **文档**:`docs/features/docs/features/MULTIMODAL_FEATURE.md`
#### 13. E-E-A-T 评估与强化 ✅
+
- **位置**:Tab2 - E-E-A-T 评估/强化按钮
- **功能**:评估和强化内容的专业性、经验性、权威性、可信度
- **模块**:`modules/eeat_enhancer.py`
- **文档**:`docs/features/docs/features/EEAT_FEATURE.md`
#### 14. 优化技巧选择 ✅
+
- **位置**:Tab2 - 优化技巧选择器
- **功能**:8种优化技巧(证据驱动、对话式、故事化等)
- **模块**:`modules/optimization_techniques.py`
@@ -126,28 +142,33 @@
### Tab3:文章优化
#### 15. 文章优化 ✅
+
- **位置**:Tab3 - 文章优化
- **功能**:优化现有文章内容
- **文档**:无独立文档(基础功能)
#### 16. E-E-A-T 强化 ✅
+
- **位置**:Tab3 - E-E-A-T 评估/强化
- **功能**:同Tab2的E-E-A-T功能
- **模块**:`modules/eeat_enhancer.py`
- **文档**:`docs/features/docs/features/EEAT_FEATURE.md`
#### 17. 事实密度增强 ✅
+
- **位置**:Tab3 - 事实密度增强
- **功能**:自动添加数据点,提升内容可信度
- **模块**:`modules/fact_density_enhancer.py`
- **文档**:`docs/features/docs/features/FACT_DENSITY_FEATURE.md`
#### 18. 结构化块优化 ✅
+
- **位置**:Tab3 - 结构化块优化
- **功能**:优化内容结构,添加标题、列表、FAQ等
- **文档**:无独立文档(基础功能)
#### 19. 优化技巧应用 ✅
+
- **位置**:Tab3 - 优化技巧选择
- **功能**:应用8种优化技巧
- **模块**:`modules/optimization_techniques.py`
@@ -158,17 +179,20 @@
### Tab4:多模型验证
#### 20. 多模型验证 ✅
+
- **位置**:Tab4 - 多模型验证
- **功能**:使用7个LLM平台验证品牌提及率
- **支持平台**:DeepSeek、OpenAI、通义千问、Groq、Moonshot、豆包、文心一言
- **文档**:无独立文档(基础功能)
#### 21. 竞品对比分析 ✅
+
- **位置**:Tab4 - 竞品对比
- **功能**:对比品牌与竞品的提及率
- **文档**:无独立文档(基础功能)
#### 22. 负面监控 ✅
+
- **位置**:Tab4 - 负面监控开关
- **功能**:负面提及检测、风险评分、澄清模板生成
- **模块**:`modules/negative_monitor.py`
@@ -179,6 +203,7 @@
### Tab5:历史记录
#### 23. 历史记录查看 ✅
+
- **位置**:Tab5 - 历史记录
- **功能**:查看关键词、文章、优化记录、验证结果
- **数据源**:SQLite数据库
@@ -189,37 +214,44 @@
### Tab6:AI 数据报表
#### 24. 自动验证任务 ✅
+
- **位置**:Tab6 - 自动验证任务
- **功能**:使用历史关键词自动验证
- **文档**:无独立文档(基础功能)
#### 25. 提及率趋势图 ✅
+
- **位置**:Tab6 - 提及率趋势图
- **功能**:按日期展示提及率变化趋势
- **文档**:无独立文档(基础功能)
#### 26. 平台贡献度分析 ✅
+
- **位置**:Tab6 - 平台贡献度分析
- **功能**:分析各平台的文章分布和贡献度
- **文档**:无独立文档(基础功能)
#### 27. 关键词效果排名 ✅
+
- **位置**:Tab6 - 关键词效果排名
- **功能**:Top 20 关键词效果排名
- **文档**:无独立文档(基础功能)
#### 28. 竞品对比分析 ✅
+
- **位置**:Tab6 - 竞品对比分析
- **功能**:多维度竞品对比
- **文档**:无独立文档(基础功能)
#### 29. ROI分析与成本优化 ✅
+
- **位置**:Tab6 - ROI分析与成本优化
- **功能**:API成本统计、ROI计算、成本优化建议
- **模块**:`modules/roi_analyzer.py`
- **文档**:`docs/features/docs/features/ROI_ANALYSIS_FEATURE.md`
#### 30. 内容质量指标分析 ✅
+
- **位置**:Tab6 - 内容质量指标分析
- **功能**:
- Trust Density(信任密度)
@@ -230,12 +262,14 @@
- **文档**:`docs/features/docs/features/CONTENT_METRICS_FEATURE.md`
#### 31. 话题集群分析 ✅
+
- **位置**:Tab6 - 话题集群分析
- **功能**:基于历史关键词生成话题集群分析
- **模块**:`modules/topic_cluster.py`
- **文档**:`docs/features/docs/features/TOPIC_CLUSTER_FEATURE.md`
#### 32. 数据导出 ✅
+
- **位置**:Tab6 - 数据导出
- **功能**:导出CSV格式数据
- **文档**:无独立文档(基础功能)
@@ -245,18 +279,21 @@
### Tab7:工作流自动化
#### 33. 工作流管理 ✅
+
- **位置**:Tab7 - 工作流列表
- **功能**:查看、创建、执行工作流
- **模块**:`modules/workflow_automation.py`
- **文档**:`docs/features/docs/features/WORKFLOW_AUTOMATION_FEATURE.md`
#### 34. 工作流创建 ✅
+
- **位置**:Tab7 - 创建工作流
- **功能**:自定义工作流步骤(关键词生成、内容创作、验证等)
- **模块**:`modules/workflow_automation.py`
- **文档**:`docs/features/docs/features/WORKFLOW_AUTOMATION_FEATURE.md`
#### 35. 工作流执行历史 ✅
+
- **位置**:Tab7 - 执行历史
- **功能**:查看工作流执行记录和结果
- **模块**:`modules/workflow_automation.py`
@@ -267,24 +304,28 @@
### Tab8:GEO 资源库
#### 36. GEO 代理推荐 ✅
+
- **位置**:Tab8 - GEO 代理
- **功能**:推荐专业的GEO代理服务
- **模块**:`modules/resource_recommender.py`
- **文档**:`docs/features/docs/features/RESOURCE_RECOMMENDER_FEATURE.md`
#### 37. 工具推荐 ✅
+
- **位置**:Tab8 - 工具推荐
- **功能**:推荐GEO相关工具和服务
- **模块**:`modules/resource_recommender.py`
- **文档**:`docs/features/docs/features/RESOURCE_RECOMMENDER_FEATURE.md`
#### 38. 论文/指南链接 ✅
+
- **位置**:Tab8 - 论文/指南
- **功能**:提供GEO相关的论文、指南、文档链接
- **模块**:`modules/resource_recommender.py`
- **文档**:`docs/features/docs/features/RESOURCE_RECOMMENDER_FEATURE.md`
#### 39. 社区资源 ✅
+
- **位置**:Tab8 - 社区资源
- **功能**:推荐GEO相关社区和论坛
- **模块**:`modules/resource_recommender.py`
@@ -295,12 +336,14 @@
### Tab9:平台同步
#### 40. GitHub API 发布 ✅
+
- **位置**:Tab9 - GitHub发布
- **功能**:通过GitHub API自动发布文章
- **模块**:`platform_sync/github_publisher.py`
- **文档**:`docs/implementation/IMPLEMENTATION_SUMMARY.md`、`docs/implementation/PLATFORM_SYNC_IMPLEMENTATION.md`
#### 41. 一键复制功能 ✅
+
- **位置**:Tab9 - 一键复制平台
- **功能**:为12个平台格式化内容并复制到剪贴板
- **支持平台**:见 `docs/implementation/IMPLEMENTATION_SUMMARY.md`
@@ -308,6 +351,7 @@
- **文档**:`docs/implementation/IMPLEMENTATION_SUMMARY.md`
#### 42. 发布记录查看 ✅
+
- **位置**:Tab9 - 发布记录
- **功能**:查看所有发布记录
- **文档**:无独立文档(基础功能)
@@ -317,6 +361,7 @@
## 🔧 数据持久化功能
#### 43. SQLite 数据存储 ✅
+
- **位置**:所有Tab(自动保存)
- **功能**:
- 关键词保存
@@ -333,6 +378,7 @@
## 📊 功能统计
### 按Tab统计
+
- **Tab1(关键词蒸馏)**:6个功能
- **Tab2(自动创作)**:8个功能
- **Tab3(文章优化)**:5个功能
@@ -347,11 +393,13 @@
**总计**:43个主要功能模块
### 按功能类型统计
+
- **核心功能**:20个
- **高级功能**:15个
- **辅助功能**:8个
### 文档覆盖情况
+
- **有独立文档的功能**:15个(*FEATURE.md)
- **基础功能(无独立文档)**:28个
- **文档覆盖率**:约35%(主要功能都有文档)
@@ -361,6 +409,7 @@
## 🔗 相关文档索引
### 功能文档(*FEATURE.md)
+
1. `docs/features/docs/features/EEAT_FEATURE.md` - E-E-A-T 评估与强化
2. `docs/features/docs/features/SEMANTIC_EXPANSION_FEATURE.md` - 语义扩展
3. `docs/features/docs/features/TOPIC_CLUSTER_FEATURE.md` - 话题集群生成
@@ -378,6 +427,7 @@
15. `docs/features/docs/features/RESOURCE_RECOMMENDER_FEATURE.md` - 资源推荐
### 分析报告
+
- `docs/analysis/CODE_DOCUMENTATION_ANALYSIS.md` - 代码与文档对比分析
- `docs/analysis/FUNCTION_VERIFICATION_REPORT.md` - 功能验证报告
- `docs/analysis/ANALYSIS_ACCURACY_REPORT.md` - 分析准确性报告
@@ -385,11 +435,13 @@
- `docs/analysis/FEATURE_PRIORITY_ANALYSIS.md` - 功能优先级分析
### 实现文档
+
- `docs/implementation/IMPLEMENTATION_SUMMARY.md` - 平台同步功能实现总结
- `docs/implementation/PLATFORM_SYNC_IMPLEMENTATION.md` - 平台同步实现指南
- `docs/implementation/PLATFORM_SYNC_TEST.md` - 平台同步测试指南
### 指南文档
+
- `README.md` - 项目主文档
- `docs/guides/QUICK_START_GUIDE.md` - 快速开始指南
- `docs/guides/STORAGE_GUIDE.md` - 数据存储指南
@@ -401,13 +453,16 @@
## 📝 功能状态说明
### ✅ 已完全实现
+
所有列出的功能都已完全实现并可用。
### ⏳ 部分实现
+
- **批量发布功能**:有发布记录,但无批量发布UI和队列管理
- **定时任务**:工作流支持执行,但不支持定时任务
### ❌ 未实现
+
- **更多平台API发布**:除GitHub外,其他7个平台的API发布器未实现
- **企业图库**:图片管理和自动匹配功能未实现
@@ -424,4 +479,4 @@
**文档版本**:1.0.0
**最后更新**:2025-01-27
-**维护者**:GEO工具开发团队
+**维护者**:GEO工具开发团队
\ No newline at end of file
diff --git a/geo_tool.py b/geo_tool.py
index ac974e9..e51db3b 100644
--- a/geo_tool.py
+++ b/geo_tool.py
@@ -1,35 +1,24 @@
import streamlit as st
-import pandas as pd
-from langchain_core.prompts import PromptTemplate
-from langchain_core.output_parsers import JsonOutputParser, StrOutputParser
-import os
from pathlib import Path
-import zipfile
-import io
-import plotly.express as px
-import plotly.graph_objects as go
-import re
import json
-import math
from typing import Optional
from modules.data_storage import DataStorage
from modules.keyword_tool import KeywordTool
-from modules.content_scorer import ContentScorer
-from modules.eeat_enhancer import EEATEnhancer
-from modules.semantic_expander import SemanticExpander
-from modules.fact_density_enhancer import FactDensityEnhancer
-from modules.schema_generator import SchemaGenerator
-from modules.topic_cluster import TopicCluster
-from modules.multimodal_prompt import MultimodalPromptGenerator
from modules.roi_analyzer import ROIAnalyzer
-from modules.workflow_automation import WorkflowManager, WorkflowStep
-from modules.keyword_mining import KeywordMining
-from modules.optimization_techniques import OptimizationTechniqueManager
-from modules.content_metrics import ContentMetricsAnalyzer
-from modules.technical_config_generator import TechnicalConfigGenerator
-from modules.negative_monitor import NegativeMonitor
-from modules.resource_recommender import ResourceRecommender
-from modules.ui import tab_keywords, tab_autowrite
+from modules.knowledge_base import KnowledgeBase
+from modules.ui import (
+ tab_keywords,
+ tab_autowrite,
+ tab_optimize,
+ tab_validation,
+ tab_history,
+ tab_reports,
+ tab_workflow,
+ tab_resources,
+ tab_platform_sync,
+ tab_config_optimizer,
+)
+from modules.ui.tab_knowledge import render_tab_knowledge
from modules.ui.state import ss_init, init_session_state
from modules.ui.theme import inject_global_theme
@@ -41,13 +30,15 @@ st.set_page_config(page_title="GEO 智能内容优化平台", layout="wide", ini
inject_global_theme()
init_session_state()
st.title(APP_TITLE)
-st.markdown("", unsafe_allow_html=True)
st.caption("🚀 AI 驱动的品牌内容策略 · 让您的品牌在 AI 对话中脱颖而出")
# ------------------- 初始化数据存储(SQLite) -------------------
storage = DataStorage(storage_type="sqlite", db_path="geo_data.db")
+# ------------------- 初始化知识库(RAG) -------------------
+kb = KnowledgeBase(storage_path="knowledge_base")
+
# ------------------- 成本记录辅助函数 -------------------
def estimate_tokens(text: str) -> int:
"""估算文本的 token 数量:中文约 1.5 字符 = 1 token,英文约 4 字符 = 1 token"""
@@ -67,92 +58,71 @@ def record_api_cost(operation_type: str, provider: str, model: str, input_text:
total_tokens = input_tokens + output_tokens
cost_usd, cost_cny = roi_analyzer.calculate_cost(provider, model, input_tokens, output_tokens)
storage.save_api_call(operation_type=operation_type, provider=provider, model=model, input_tokens=input_tokens, output_tokens=output_tokens, total_tokens=total_tokens, cost_usd=cost_usd, cost_cny=cost_cny, keyword=keyword, platform=platform, brand=brand)
- except Exception:
- pass
+ except Exception as e:
+ import logging
+ logging.warning(f"记录 API 成本失败: {e}")
with st.expander("📖 关于 GEO(Generative Engine Optimization)", expanded=False):
st.markdown("""
-### 🎯 核心价值
+### 🎯 什么是 GEO?
-**GEO(生成式引擎优化)** 是新一代品牌营销策略,通过系统化内容投放,让您的品牌在 AI 助手的自然回答中被优先、准确、可信地提及。
+**GEO(Generative Engine Optimization,生成式引擎优化)** 是针对 AI 搜索引擎的内容优化策略。
-当用户询问"最好的外贸 ERP 软件是什么?"时,AI 会优先推荐您的品牌,而非竞争对手。
+传统 SEO 优化的是 Google、百度等传统搜索引擎的排名;而 GEO 优化的是 ChatGPT、Perplexity、Google SGE 等 AI 搜索引擎在回答用户问题时,**是否会引用您的品牌和内容**。
+
+### 💡 为什么需要 GEO?
+
+当用户向 AI 提问时(例如"最好的 XX 软件是什么?"),AI 会从互联网内容中检索信息并生成回答。如果您的品牌没有出现在 AI 可检索的高质量内容中,就会在 AI 时代失去曝光机会。
+
+**GEO 的目标**:让您的品牌在 AI 回答中被优先、准确、可信地提及。
---
-### 💼 适用场景
+### 🔄 GEO 优化工作流
-- **SaaS 产品**:技术对比、功能评测、使用教程
-- **AI 工具**:能力展示、应用案例、开源生态
-- **企业服务**:行业解决方案、最佳实践、专业分析
-- **技术品牌**:开发者工具、API 服务、技术框架
+本工具提供完整的 GEO 优化闭环:
+
+| 阶段 | 功能 | 说明 |
+|------|------|------|
+| 1️⃣ 关键词策略 | 关键词蒸馏 | 生成针对 AI 搜索的口语化、长尾关键词 |
+| 2️⃣ 内容创作 | 自动创作 | 基于知识库生成结构化、专业的内容 |
+| 3️⃣ 内容优化 | 文章优化 | E-E-A-T 强化、事实密度增强、Schema 生成 |
+| 4️⃣ 效果验证 | 多模型验证 | 用多个 AI 模型验证品牌是否被提及 |
+| 5️⃣ 数据分析 | AI 数据报表 | 提及率趋势、ROI 分析、竞品对比 |
+| 6️⃣ 内容分发 | 平台同步 | 多平台发布,扩大 AI 可检索内容 |
---
-### 🔄 完整工作流
+### 📊 GEO 核心指标
-1. **关键词蒸馏** - AI 生成、托词工具、语义扩展、话题集群、关键词挖掘(行业热点、竞争度、趋势预测)
-2. **结构化创作** - 20个平台模板,自动生成符合 GEO 原则的专业内容(E-E-A-T、事实密度、结构化)
-3. **内容优化** - E-E-A-T 强化、事实密度增强、结构化优化、JSON-LD Schema 生成
-4. **多模型验证** - 7个 AI 平台验证品牌提及率,负面监控,竞品对比分析
-5. **数据驱动优化** - ROI 分析、内容质量指标、提及率趋势、平台贡献度、关键词效果排名
-6. **平台同步** - GitHub API 发布、12个平台一键复制,自动化内容分发
+| 指标 | 说明 |
+|------|------|
+| **品牌提及率** | AI 回答中提及品牌的频率 |
+| **E-E-A-T 评分** | 专业性、经验性、权威性、可信度 |
+| **事实密度** | 内容中可验证信息的密度 |
+| **引用位置** | 品牌在 AI 回答中的位置(前 1/3 优先) |
---
-### 🌐 覆盖平台
+### 🌐 支持平台
-**内容发布平台(20个)**:
-知乎、小红书、CSDN、B站、头条号、GitHub、微信公众号、抖音、百家号、网易号、企鹅号、简书、新浪博客、新浪新闻、搜狐号、QQ空间、邦阅网、一点号、东方财富、原创力文档
+**内容发布平台(20+)**:知乎、小红书、CSDN、B站、GitHub、微信公众号等
-**AI 验证平台(7个)**:
-DeepSeek、OpenAI、通义千问、Groq、Moonshot、豆包(字节跳动)、文心一言(百度)
-
-**平台同步**:
-- GitHub API 发布(1个)
-- 一键复制平台(12个):知乎、CSDN、B站、头条号、微信公众号、百家号、网易号、企鹅号、简书、新浪博客、搜狐号、一点号
+**AI 验证平台(7)**:DeepSeek、OpenAI、通义千问、Groq、Moonshot、豆包、文心一言
---
-### ⭐ 核心 GEO 功能
+### 📚 更多资源
-**内容质量优化**:
-- ✅ **E-E-A-T 评估与强化**:专业性、经验性、权威性、可信度(0-100分)
-- ✅ **事实密度增强**:数据信息、案例信息、标准信息、对比信息(0-100分)
-- ✅ **内容质量评分**:结构化、品牌提及、权威性、可引用性(0-100分)
-- ✅ **结构化数据**:JSON-LD Schema.org(5种类型)
-
-**智能分析**:
-- ✅ **语义扩展**:从单一关键词扩展到10-100个关联词
-- ✅ **话题集群**:语义聚类、话题命名、内容规划建议
-- ✅ **关键词挖掘**:行业热点、竞争度分析、趋势预测、价值矩阵
-- ✅ **多模态提示**:配图描述生成、视频脚本生成
-
-**数据驱动**:
-- ✅ **ROI 分析**:成本概览、趋势分析、分布统计、优化建议
-- ✅ **内容指标**:Trust Density、Citation Share、Authority Score、Engagement Potential
-- ✅ **负面监控**:负面查询生成、情感检测、风险等级、澄清模板
-
-**自动化**:
-- ✅ **工作流自动化**:自定义工作流、批量处理、执行历史
-- ✅ **技术配置**:robots.txt、sitemap.xml 自动生成
-
----
-
-### 📊 预期效果
-
-- ✅ **品牌提及率提升**:在 AI 回答中的出现频率显著增加(多模型验证)
-- ✅ **搜索排名优化**:内容被大模型优先引用,间接提升 SEO
-- ✅ **品牌权威性**:多平台、多角度内容建立专业形象(E-E-A-T 强化)
-- ✅ **竞品优势**:通过数据对比,发现并强化差异化优势
-- ✅ **ROI 最大化**:数据驱动的关键词策略,成本优化建议
-- ✅ **内容质量保证**:自动评分和改进建议,确保符合 GEO 最佳实践
+- [GEO 学术论文](https://arxiv.org/abs/2311.09735) - GEO 原始研究
+- [项目文档](DOCS.md) - 完整功能文档
+- [快速开始](docs/guides/QUICK_START_GUIDE.md) - 新手入门指南
""")
def load_default_cfg():
"""
从项目根目录的 config.json 读取默认配置,如果不存在则使用内置默认值。
- 这样可以在项目中维护密钥和品牌配置,而不依赖系统环境变量。
+ 敏感信息(API Keys)优先从 .streamlit/secrets.toml 读取。
"""
base_cfg = {
"gen_provider": "DeepSeek",
@@ -161,12 +131,14 @@ def load_default_cfg():
"verify_keys": {
"DeepSeek": ""
},
- "brand": "汇信云AI软件",
- "advantages": "AI赋能外贸ERP、打造外贸智能新引擎、AI驱动型ERP、赋能外贸全流程管理、全链路价值闭环",
- "competitors": "南北软件\n睿贝软件\n孚盟软件\n小满软件",
+ "tongyi_wanxiang_api_key": "",
+ "brand": "",
+ "advantages": "",
+ "competitors": "",
"temperature": 0.7,
}
+ # 从 config.json 读取非敏感配置
config_path = Path(__file__).with_name("config.json")
if config_path.exists():
try:
@@ -174,16 +146,39 @@ def load_default_cfg():
file_cfg = json.load(f)
if isinstance(file_cfg, dict):
base_cfg.update(file_cfg)
- except Exception:
- # 配置文件格式错误时回退到内置默认值,避免整个应用崩溃
- pass
+ except Exception as e:
+ import logging
+ logging.warning(f"配置文件加载失败: {e}")
+
+ # 从 st.secrets 读取敏感信息(优先级更高)
+ try:
+ if hasattr(st, 'secrets') and st.secrets:
+ # 读取 API Keys
+ if "api_keys" in st.secrets:
+ api_keys = st.secrets["api_keys"]
+ if "deepseek" in api_keys and api_keys["deepseek"]:
+ base_cfg["gen_api_key"] = api_keys["deepseek"]
+ base_cfg["verify_keys"]["DeepSeek"] = api_keys["deepseek"]
+ if "tongyi_wanxiang" in api_keys and api_keys["tongyi_wanxiang"]:
+ base_cfg["tongyi_wanxiang_api_key"] = api_keys["tongyi_wanxiang"]
+
+ # 读取应用配置(如果存在)
+ if "app_config" in st.secrets:
+ app_config = st.secrets["app_config"]
+ for key in ["brand", "advantages", "competitors", "temperature"]:
+ if key in app_config and app_config[key]:
+ base_cfg[key] = app_config[key]
+ except Exception:
+ # secrets.toml 不存在时静默忽略,用户可通过侧边栏配置
+ pass
+
return base_cfg
def save_cfg_to_file(cfg: dict) -> None:
"""
- 将当前生效的配置写入本地 config.json(已在 .gitignore 中,不会提交到仓库)。
- 只同步我们负责的几个键,其它自定义字段保持不变。
+ 将当前生效的非敏感配置写入本地 config.json。
+ 敏感信息(API Keys)不会保存到此文件,仅保存到 .streamlit/secrets.toml。
"""
config_path = Path(__file__).with_name("config.json")
try:
@@ -194,92 +189,68 @@ def save_cfg_to_file(cfg: dict) -> None:
loaded = json.load(f)
if isinstance(loaded, dict):
data.update(loaded)
- except Exception:
- # 如果原文件不可解析,丢弃旧内容,重新写入受管配置
+ except Exception as e:
+ import logging
+ logging.warning(f"读取配置文件失败: {e}")
data = {}
- for key in ["gen_provider", "gen_api_key", "verify_providers", "verify_keys", "tongyi_wanxiang_api_key", "brand", "advantages", "competitors", "temperature"]:
+
+ # 只保存非敏感配置
+ sensitive_keys = {"gen_api_key", "verify_keys", "tongyi_wanxiang_api_key"}
+ for key in ["gen_provider", "verify_providers", "brand", "advantages", "competitors", "temperature"]:
if key in cfg:
data[key] = cfg[key]
+
with config_path.open("w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
- except Exception:
- # 持久化失败不应阻断页面使用,只做提示
+
+ # 提示用户如何保存 API Keys
+ if any(key in cfg for key in sensitive_keys):
+ try:
+ st.info("💡 API Keys 需要在 `.streamlit/secrets.toml` 文件中手动配置。")
+ except Exception:
+ pass
+
+ except Exception as e:
+ import logging
+ logging.error(f"保存配置文件失败: {e}")
try:
st.warning("⚠️ 无法将配置写入本地 config.json,但当前会话已生效。请检查文件权限。")
except Exception:
- # 在非 Streamlit 环境下忽略 UI 提示错误
pass
ss_init("cfg", load_default_cfg())
-ss_init("cfg_applied", False)
-ss_init("cfg_valid", False)
-ss_init("cfg_errors", [])
-# 模块1:关键词
-ss_init("keywords", [])
-ss_init("kw_last_num", 40)
-ss_init("kw_generation_mode", "AI生成") # 生成模式:AI生成 / 托词工具 / 混合模式
-ss_init("wordbanks", None) # 词库字典
+# 模块1:关键词(补充 init_session_state 中未包含的)
ss_init("keyword_tool", KeywordTool()) # 托词工具实例
-# 模块2:内容
-ss_init("generated_contents", []) # list[dict]
-ss_init("zip_bytes", None)
-ss_init("zip_filename", "")
+# 模块2:内容(补充 init_session_state 中未包含的)
ss_init("multimodal_descriptions", {}) # 多模态描述(配图描述、视频脚本等)
ss_init("image_descriptions", []) # 图片描述列表
ss_init("detail_tab_active", "🎨 增强工具") # 保存当前激活的详情Tab
-# 模块3:文章优化
-ss_init("optimized_article", "")
-ss_init("opt_changes", "")
-ss_init("opt_platform", "通用优化")
-
-# 模块4:验证
-ss_init("verify_combined", None) # DataFrame or None
-ss_init("verify_last_queries", "")
-
# ------------------- 工具函数 -------------------
-INVALID_FS_CHARS = r'<>:"/\\|?*\n\r\t'
+from modules.ui.components import INVALID_FS_CHARS
def sanitize_filename(name: str, max_len: int = 80) -> str:
- if not name:
- return "untitled"
- name = name.strip()
- name = re.sub(rf"[{re.escape(INVALID_FS_CHARS)}]", "_", name)
- name = re.sub(r"_+", "_", name).strip("_")
- return name[:max_len] if len(name) > max_len else name
+ from modules.ui.components import sanitize_filename as _sanitize_filename
+ return _sanitize_filename(name, max_len)
def safe_decode_uploaded(uploaded) -> str:
- if not uploaded:
- return ""
- b = uploaded.getvalue()
- for enc in ("utf-8-sig", "utf-8", "gb18030"):
- try:
- return b.decode(enc)
- except Exception:
- pass
- return b.decode("utf-8", errors="replace")
+ from modules.ui.components import safe_decode_uploaded as _safe_decode_uploaded
+ return _safe_decode_uploaded(uploaded)
def extract_json_array(text: str):
"""从模型输出中抽取 JSON 数组(JsonOutputParser 失败时兜底)。"""
- if not text:
- return None
- m = re.search(r"\[[\s\S]*\]", text)
- if not m:
- return None
- try:
- return json.loads(m.group(0))
- except Exception:
- return None
+ from modules.ui.components import extract_json_array as _extract_json_array
+ return _extract_json_array(text)
def validate_cfg(cfg: dict):
- """保留你原本的“必须填写所有 API Key”约束,但不 st.stop:改为禁用按钮 + 提示。"""
+ """保留你原本的"必须填写所有 API Key"约束,但不 st.stop:改为禁用按钮 + 提示。"""
errors = []
if not cfg.get("gen_api_key", "").strip():
errors.append("生成&优化 LLM 的 API Key 未填写")
@@ -297,21 +268,8 @@ def validate_cfg(cfg: dict):
def model_defaults(provider: str) -> str:
- if provider == "DeepSeek":
- return "deepseek-chat"
- if provider == "OpenAI (GPT)":
- return "gpt-4o-mini"
- if provider == "Tongyi (通义千问)":
- return "qwen-max"
- if provider == "Groq":
- return "llama3-70b-8192"
- if provider == "Moonshot (Kimi)":
- return "moonshot-v1-128k"
- if provider == "豆包(字节跳动)":
- return "" # 豆包使用 ENDPOINT_ID,不需要模型名
- if provider == "文心一言(百度)":
- return "ernie-bot-turbo"
- return ""
+ from modules.llm_factory import get_default_model
+ return get_default_model(provider)
# ------------------- 缓存 LLM 客户端(显著降低“频繁 Loading”) -------------------
@@ -319,319 +277,107 @@ def model_defaults(provider: str) -> str:
def build_llm(provider: str, api_key: str, model: str, temperature: float):
"""
- 使用 cache_resource 缓存客户端,避免每次 rerun 重建
- - Tongyi / Moonshot:保留你原功能路径,同时提供更稳的 import 兜底
+ - 统一使用 llm_factory 模块构建 LLM
"""
- if provider == "DeepSeek":
- from langchain_deepseek import ChatDeepSeek
-
- return ChatDeepSeek(api_key=api_key, model=model, temperature=temperature)
-
- if provider == "OpenAI (GPT)":
- from langchain_openai import ChatOpenAI
-
- return ChatOpenAI(api_key=api_key, model=model, temperature=temperature)
-
- if provider == "Tongyi (通义千问)":
- try:
- from langchain_community.chat_models import ChatTongyi
-
- return ChatTongyi(api_key=api_key, model=model, model_kwargs={"temperature": temperature})
- except Exception:
- from langchain_aliyun import ChatTongyi # type: ignore
-
- return ChatTongyi(api_key=api_key, model=model, temperature=temperature)
-
- if provider == "Groq":
- from langchain_groq import ChatGroq
-
- return ChatGroq(api_key=api_key, model=model, temperature=temperature)
-
- if provider == "Moonshot (Kimi)":
- try:
- from langchain_moonshot import ChatMoonshot # type: ignore
-
- return ChatMoonshot(api_key=api_key, model=model, temperature=temperature)
- except Exception:
- from langchain_community.chat_models import MoonshotChat # type: ignore
-
- return MoonshotChat(api_key=api_key, model=model, temperature=temperature)
-
- if provider == "豆包(字节跳动)":
- try:
- # 尝试使用 volcengine-python-sdk[ark]
- from volcengine.ark import Ark
- from langchain_core.language_models.chat_models import BaseChatModel
- from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
- from langchain_core.outputs import ChatGeneration, ChatResult
- from typing import List, Optional, Any
-
- class ChatDoubao(BaseChatModel):
- """豆包聊天模型封装(LangChain 兼容)"""
- volc_ak: str
- volc_sk: str
- endpoint_id: str
- temperature: float = 0.7
-
- def __init__(self, volc_ak: str, volc_sk: str, endpoint_id: str, temperature: float = 0.7):
- super().__init__(temperature=temperature)
- self.volc_ak = volc_ak
- self.volc_sk = volc_sk
- self.endpoint_id = endpoint_id
- self.temperature = temperature
- self.client = Ark(ak=volc_ak, sk=volc_sk)
-
- def _generate(self, messages: List[BaseMessage], stop: Optional[List[str]] = None, run_manager: Optional[Any] = None, **kwargs: Any) -> ChatResult:
- # 转换消息格式
- volc_messages = []
- for msg in messages:
- if isinstance(msg, SystemMessage):
- volc_messages.append({"role": "system", "content": msg.content})
- elif isinstance(msg, HumanMessage):
- volc_messages.append({"role": "user", "content": msg.content})
- elif isinstance(msg, AIMessage):
- volc_messages.append({"role": "assistant", "content": msg.content})
- else:
- volc_messages.append({"role": "user", "content": str(msg.content)})
-
- response = self.client.chat.completions.create(
- model=self.endpoint_id,
- messages=volc_messages,
- temperature=self.temperature,
- )
-
- ai_message = AIMessage(content=response.choices[0].message.content)
- return ChatResult(generations=[ChatGeneration(message=ai_message)])
-
- @property
- def _llm_type(self) -> str:
- return "doubao"
-
- # 豆包的 api_key 格式:access_key:secret_key:endpoint_id
- parts = api_key.split(":")
- if len(parts) >= 3:
- return ChatDoubao(volc_ak=parts[0], volc_sk=parts[1], endpoint_id=parts[2], temperature=temperature)
- else:
- raise ValueError("豆包 API Key 格式错误,应为:access_key:secret_key:endpoint_id(用冒号分隔)")
- except ImportError:
- # 尝试其他导入方式
- try:
- from volcenginesdkarkruntime import Ark
- # 使用相同的 ChatDoubao 类
- from langchain_core.language_models.chat_models import BaseChatModel
- from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
- from langchain_core.outputs import ChatGeneration, ChatResult
- from typing import List, Optional, Any
-
- class ChatDoubao(BaseChatModel):
- """豆包聊天模型封装(LangChain 兼容)"""
- volc_ak: str
- volc_sk: str
- endpoint_id: str
- temperature: float = 0.7
-
- def __init__(self, volc_ak: str, volc_sk: str, endpoint_id: str, temperature: float = 0.7):
- super().__init__(temperature=temperature)
- self.volc_ak = volc_ak
- self.volc_sk = volc_sk
- self.endpoint_id = endpoint_id
- self.temperature = temperature
- self.client = Ark(ak=volc_ak, sk=volc_sk)
-
- def _generate(self, messages: List[BaseMessage], stop: Optional[List[str]] = None, run_manager: Optional[Any] = None, **kwargs: Any) -> ChatResult:
- volc_messages = []
- for msg in messages:
- if isinstance(msg, SystemMessage):
- volc_messages.append({"role": "system", "content": msg.content})
- elif isinstance(msg, HumanMessage):
- volc_messages.append({"role": "user", "content": msg.content})
- elif isinstance(msg, AIMessage):
- volc_messages.append({"role": "assistant", "content": msg.content})
- else:
- volc_messages.append({"role": "user", "content": str(msg.content)})
-
- response = self.client.chat.completions.create(
- model=self.endpoint_id,
- messages=volc_messages,
- temperature=self.temperature,
- )
-
- ai_message = AIMessage(content=response.choices[0].message.content)
- return ChatResult(generations=[ChatGeneration(message=ai_message)])
-
- @property
- def _llm_type(self) -> str:
- return "doubao"
-
- parts = api_key.split(":")
- if len(parts) >= 3:
- return ChatDoubao(volc_ak=parts[0], volc_sk=parts[1], endpoint_id=parts[2], temperature=temperature)
- else:
- raise ValueError("豆包 API Key 格式错误,应为:access_key:secret_key:endpoint_id(用冒号分隔)")
- except ImportError as e:
- raise ValueError(f"豆包初始化失败:缺少依赖库。请运行:pip install 'volcengine-python-sdk[ark]'。错误:{e}")
- except Exception as e:
- raise ValueError(f"豆包初始化失败:{e}。请确保 API Key 格式为:access_key:secret_key:endpoint_id")
-
- if provider == "文心一言(百度)":
- # 文心一言的 api_key 格式:app_key:app_secret
- parts = api_key.split(":")
- if len(parts) != 2:
- raise ValueError("文心一言 API Key 格式错误,应为:app_key:app_secret(用冒号分隔)")
-
- app_key, app_secret = parts
-
- # 优先使用 langchain-community 的千帆接口(已包含在依赖中)
- try:
- from langchain_community.chat_models import QianfanChatEndpoint
- import os
-
- os.environ["QIANFAN_AK"] = app_key
- os.environ["QIANFAN_SK"] = app_secret
- return QianfanChatEndpoint(
- model=model if model else "ernie-bot-turbo",
- temperature=temperature,
- )
- except ImportError:
- # 备选方案:尝试 langchain-wenxin
- try:
- from langchain_wenxin import ChatWenxin
- return ChatWenxin(
- baidu_api_key=app_key,
- baidu_secret_key=app_secret,
- model=model if model else "ernie-bot-turbo",
- temperature=temperature,
- )
- except ImportError as e:
- raise ValueError(f"文心一言初始化失败:缺少依赖库。请运行:pip install qianfan(或使用已安装的 langchain-community)。错误:{e}")
- except Exception as e:
- raise ValueError(f"文心一言初始化失败:{e}")
-
- raise ValueError(f"Unknown provider: {provider}")
+ from modules.llm_factory import build_llm as _build_llm
+ return _build_llm(provider, api_key, model, temperature)
-# ------------------- 侧边栏:全局配置(用 form 降低 rerun) -------------------
+# ------------------- 侧边栏:全局配置(分组折叠) -------------------
with st.sidebar:
st.header("⚙️ 全局配置")
- with st.form("global_config_form", clear_on_submit=False):
- gen_provider = st.selectbox(
+ # LLM 配置组
+ with st.expander("🤖 LLM 配置", expanded=True):
+ PROVIDER_LIST = ["DeepSeek", "OpenAI (GPT)", "Tongyi (通义千问)", "Groq", "Moonshot (Kimi)", "豆包(字节跳动)", "文心一言(百度)"]
+
+ gen_provider = st.selectbox(
"生成&优化 LLM",
- ["DeepSeek", "OpenAI (GPT)", "Tongyi (通义千问)", "Groq", "Moonshot (Kimi)", "豆包(字节跳动)", "文心一言(百度)"],
- index=["DeepSeek", "OpenAI (GPT)", "Tongyi (通义千问)", "Groq", "Moonshot (Kimi)", "豆包(字节跳动)", "文心一言(百度)"].index(
- st.session_state.cfg["gen_provider"]
- ) if st.session_state.cfg["gen_provider"] in ["DeepSeek", "OpenAI (GPT)", "Tongyi (通义千问)", "Groq", "Moonshot (Kimi)", "豆包(字节跳动)", "文心一言(百度)"] else 0,
+ PROVIDER_LIST,
+ index=PROVIDER_LIST.index(st.session_state.cfg["gen_provider"]) if st.session_state.cfg["gen_provider"] in PROVIDER_LIST else 0,
key="sb_gen_provider",
- )
- # API Key 输入提示
- if gen_provider == "豆包(字节跳动)":
- api_key_help = "格式:access_key:secret_key:endpoint_id(用冒号分隔)"
- elif gen_provider == "文心一言(百度)":
- api_key_help = "格式:app_key:app_secret(用冒号分隔)"
- else:
- api_key_help = ""
+ )
+
+ # API Key 输入提示
+ api_key_help = ""
+ if gen_provider == "豆包(字节跳动)":
+ api_key_help = "格式:access_key:secret_key:endpoint_id(用冒号分隔)"
+ elif gen_provider == "文心一言(百度)":
+ api_key_help = "格式:app_key:app_secret(用冒号分隔)"
+
+ gen_api_key = st.text_input(
+ f"{gen_provider} API Key(生成&优化用)",
+ type="password",
+ value=st.session_state.cfg.get("gen_api_key", ""),
+ key="sb_gen_api_key",
+ help=api_key_help if api_key_help else None,
+ )
+
+ # 验证配置组
+ with st.expander("🔍 验证配置", expanded=False):
+ verify_providers = st.multiselect(
+ "选择验证模型",
+ PROVIDER_LIST,
+ default=st.session_state.cfg.get("verify_providers", []),
+ key="sb_verify_providers",
+ )
+
+ verify_keys = {}
+ old_keys = st.session_state.cfg.get("verify_keys", {})
+ for vp in verify_providers:
+ vp_help = ""
+ if vp == "豆包(字节跳动)":
+ vp_help = "格式:access_key:secret_key:endpoint_id(用冒号分隔)"
+ elif vp == "文心一言(百度)":
+ vp_help = "格式:app_key:app_secret(用冒号分隔)"
- gen_api_key = st.text_input(
- f"{gen_provider} API Key(生成&优化用)",
+ verify_keys[vp] = st.text_input(
+ f"{vp} API Key(验证用)",
type="password",
- value=st.session_state.cfg.get("gen_api_key", ""),
- key="sb_gen_api_key",
- help=api_key_help if api_key_help else None,
+ value=old_keys.get(vp, ""),
+ key=f"sb_verify_key_{vp}",
+ help=vp_help if vp_help else None,
)
- st.markdown("### 验证用LLM(多选)")
- verify_providers = st.multiselect(
- "选择验证模型",
- ["DeepSeek", "OpenAI (GPT)", "Tongyi (通义千问)", "Groq", "Moonshot (Kimi)", "豆包(字节跳动)", "文心一言(百度)"],
- default=st.session_state.cfg.get("verify_providers", []),
- key="sb_verify_providers",
- )
+ # 品牌信息组
+ with st.expander("🏢 品牌信息", expanded=True):
+ brand = st.text_input("主品牌名称", value=st.session_state.cfg.get("brand", ""), key="sb_brand")
+
+ advantages = st.text_area(
+ "核心优势/卖点(AI专属)",
+ value=st.session_state.cfg.get("advantages", ""),
+ height=120,
+ key="sb_advantages",
+ )
+
+ competitors = st.text_area(
+ "竞品品牌(每行一个)",
+ value=st.session_state.cfg.get("competitors", ""),
+ height=100,
+ key="sb_competitors",
+ )
- verify_keys = {}
- old_keys = st.session_state.cfg.get("verify_keys", {})
- for vp in verify_providers:
- # API Key 输入提示
- if vp == "豆包(字节跳动)":
- api_key_help = "格式:access_key:secret_key:endpoint_id(用冒号分隔)"
- elif vp == "文心一言(百度)":
- api_key_help = "格式:app_key:app_secret(用冒号分隔)"
- else:
- api_key_help = None
-
- verify_keys[vp] = st.text_input(
- f"{vp} API Key(验证用)",
- type="password",
- value=old_keys.get(vp, ""),
- key=f"sb_verify_key_{vp}",
- help=api_key_help if api_key_help else None,
- )
-
- st.markdown("---")
- # 检查是否有待应用的版本更新
- if "_pending_brand_update" in st.session_state:
- brand_value = st.session_state.pop("_pending_brand_update")
- # 使用一个递增的计数器来强制更新widget(通过改变key)
- widget_counter = st.session_state.get("_widget_update_counter", 0) + 1
- st.session_state["_widget_update_counter"] = widget_counter
- # 使用带计数器的key来创建新的widget实例
- brand_key = f"sb_brand_{widget_counter}"
- brand = st.text_input("主品牌名称", value=brand_value, key=brand_key)
- # 同步到主key,以便后续使用
- st.session_state["sb_brand"] = brand
- else:
- brand = st.text_input("主品牌名称", value=st.session_state.cfg.get("brand", "汇信云AI软件"), key="sb_brand")
-
- # 检查是否有待应用的优势更新
- if "_pending_advantages_update" in st.session_state:
- advantages_value = st.session_state.pop("_pending_advantages_update")
- # 使用一个递增的计数器来强制更新widget(通过改变key)
- widget_counter = st.session_state.get("_widget_update_counter", 0)
- # 使用带计数器的key来创建新的widget实例
- advantages_key = f"sb_advantages_{widget_counter}"
- advantages = st.text_area(
- "核心优势/卖点(AI专属)",
- value=advantages_value,
- height=140,
- key=advantages_key,
- )
- # 同步到主key,以便后续使用
- st.session_state["sb_advantages"] = advantages
- else:
- advantages = st.text_area(
- "核心优势/卖点(AI专属)",
- value=st.session_state.cfg.get(
- "advantages", "AI赋能外贸ERP、打造外贸智能新引擎、AI驱动型ERP、赋能外贸全流程管理、全链路价值闭环"
- ),
- height=140,
- key="sb_advantages",
- )
- competitors = st.text_area(
- "竞品品牌(每行一个,用于对比验证)",
- value=st.session_state.cfg.get("competitors", "南北软件\n睿贝软件\n孚盟软件\n小满软件"),
- height=120,
- key="sb_competitors",
- )
-
- st.markdown("---")
- st.markdown("### 🖼️ 通义万相(图片生成)")
- tongyi_wanxiang_api_key = st.text_input(
- "通义万相 API Key(可选,用于图片生成)",
- type="password",
- value=st.session_state.cfg.get("tongyi_wanxiang_api_key", ""),
- key="sb_tongyi_wanxiang_api_key",
- help="阿里云 DashScope API Key,用于生成文章配图。免费额度每天 100-300 张。",
- )
-
- st.markdown("---")
- temperature = st.slider(
- "生成温度(更稳→更低)",
- 0.0,
- 1.0,
- float(st.session_state.cfg.get("temperature", 0.7)),
- 0.05,
- key="sb_temperature",
- )
-
- apply_cfg = st.form_submit_button("应用配置(推荐)", use_container_width=True)
+ # 高级设置组
+ with st.expander("⚙️ 高级设置", expanded=False):
+ temperature = st.slider(
+ "生成温度(更稳→更低)",
+ 0.0,
+ 1.0,
+ float(st.session_state.cfg.get("temperature", 0.7)),
+ 0.05,
+ key="sb_temperature",
+ )
+
+ tongyi_wanxiang_api_key = st.text_input(
+ "通义万相 API Key(图片生成)",
+ type="password",
+ value=st.session_state.cfg.get("tongyi_wanxiang_api_key", ""),
+ key="sb_tongyi_wanxiang_api_key",
+ help="阿里云 DashScope API Key,用于生成文章配图。",
+ )
+
+ # 应用配置按钮
+ apply_cfg = st.button("应用配置", use_container_width=True, type="primary")
if apply_cfg or not st.session_state.cfg_applied:
# 优先从主 key 读取值(如果使用了临时 key 更新,值已同步到主 key)
@@ -720,21 +466,15 @@ if st.session_state.cfg_valid:
# ------------------- KPI 总览(极简但更像产品) -------------------
k1, k2, k3, k4 = st.columns(4)
-try:
- k1.metric("关键词", len(st.session_state.keywords), border=True)
- k2.metric("内容包", len(st.session_state.generated_contents), border=True)
- k3.metric("文章优化", "已生成" if bool(st.session_state.optimized_article) else "未生成", border=True)
- k4.metric("验证结果", "已生成" if st.session_state.verify_combined is not None else "未生成", border=True)
-except TypeError:
- k1.metric("关键词", len(st.session_state.keywords))
- k2.metric("内容包", len(st.session_state.generated_contents))
- k3.metric("文章优化", "已生成" if bool(st.session_state.optimized_article) else "未生成")
- k4.metric("验证结果", "已生成" if st.session_state.verify_combined is not None else "未生成")
+k1.metric("关键词", len(st.session_state.keywords), border=True)
+k2.metric("内容包", len(st.session_state.generated_contents), border=True)
+k3.metric("文章优化", "已生成" if bool(st.session_state.optimized_article) else "未生成", border=True)
+k4.metric("验证结果", "已生成" if st.session_state.verify_combined is not None else "未生成", border=True)
st.markdown("---")
# ------------------- 主导航:Tabs(流程更清晰) -------------------
-tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, tab9, tab10 = st.tabs([
+tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, tab9, tab10, tab11 = st.tabs([
"🎯 关键词蒸馏",
"✍️ 自动创作",
"🔧 文章优化",
@@ -744,7 +484,8 @@ tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, tab9, tab10 = st.tabs([
"⚙️ 工作流自动化",
"📦 GEO 资源库",
"🔄 平台同步",
- "🛠️ 配置优化助手"
+ "🛠️ 配置优化助手",
+ "📚 品牌知识库"
])
# =======================
@@ -780,3693 +521,107 @@ with tab2:
# Tab3:文章优化
# =======================
with tab3:
- header_col1, header_col2 = st.columns([4, 1])
- with header_col1:
- st.markdown("**🔧 文章优化**")
- st.caption("优化已有文章,生成结构化数据和技术配置,提升 GEO 效果")
- with header_col2:
- st.markdown("") # 空行用于与左侧标题对齐
- if st.button("清空本模块结果", use_container_width=True, key="opt_clear"):
- st.session_state.optimized_article = ""
- st.session_state.opt_changes = ""
- st.toast("优化结果已清空。")
+ tab_optimize.render_tab_optimize(
+ storage,
+ ss_init,
+ gen_llm,
+ brand,
+ advantages,
+ cfg,
+ record_api_cost,
+ model_defaults,
+ )
- # === 文章优化功能(主流程) ===
- st.markdown("---")
- st.markdown("**✏️ 文章内容优化**")
-
- with st.container(border=True):
- st.markdown("粘贴或上传已写文章,一键提升 GEO 效果(结构化、可引用、自然植入品牌)")
-
- # 输入方式与文章内容放在表单外,以便粘贴/上传后能触发重跑,从而正确更新「开始优化」按钮的可用状态
- input_mode = st.radio(
- "输入方式",
- ["粘贴文本", "上传文件(TXT/MD)"],
- horizontal=True,
- key="opt_input_mode",
- )
- if input_mode == "粘贴文本":
- original_article = st.text_area(
- "粘贴文章内容", height=360, key="opt_text"
- )
- else:
- uploaded = st.file_uploader(
- "上传 TXT 或 MD 文件",
- type=["txt", "md"],
- key="opt_uploader",
- )
- original_article = ""
- if uploaded:
- try:
- original_article = safe_decode_uploaded(uploaded) or ""
- except Exception as e:
- st.error(f"上传文件解析失败:{e}")
- original_article = ""
- if original_article:
- st.text_area(
- "上传内容预览",
- original_article,
- height=200,
- disabled=True,
- key="opt_upload_preview",
- )
-
- with st.form("opt_form", clear_on_submit=False):
- target_platform = st.selectbox(
- "目标平台(影响文风,可选)",
- [
- "通用优化",
- "知乎(专业问答)",
- "CSDN(技术博客)",
- "GitHub(README/文档)",
- "B站(视频脚本)",
- "头条号(资讯软文)",
- "微信公众号(长文)",
- "抖音图文(短内容)",
- "百家号(资讯)",
- "网易号(资讯)",
- "企鹅号(资讯)",
- "简书(文艺)",
- ],
- index=[
- "通用优化",
- "知乎(专业问答)",
- "CSDN(技术博客)",
- "GitHub(README/文档)",
- "B站(视频脚本)",
- "头条号(资讯软文)",
- "微信公众号(长文)",
- "抖音图文(短内容)",
- "百家号(资讯)",
- "网易号(资讯)",
- "企鹅号(资讯)",
- "简书(文艺)",
- ].index(
- st.session_state.opt_platform
- if st.session_state.opt_platform
- in [
- "通用优化",
- "知乎(专业问答)",
- "CSDN(技术博客)",
- "GitHub(README/文档)",
- "B站(视频脚本)",
- "头条号(资讯软文)",
- "微信公众号(长文)",
- "抖音图文(短内容)",
- "百家号(资讯)",
- "网易号(资讯)",
- "企鹅号(资讯)",
- "简书(文艺)",
- ]
- else 0
- ),
- key="opt_platform_sel",
- )
-
- # 高级优化技巧选择器(可选)
- with st.expander("🎨 高级优化技巧(可选)", expanded=False):
- opt_technique_manager = OptimizationTechniqueManager()
- opt_all_techniques = opt_technique_manager.list_techniques()
- opt_technique_options = [
- f"{tech['icon']} {tech['name']}" for tech in opt_all_techniques
- ]
-
- opt_selected_technique_names = st.multiselect(
- "选择要应用的优化技巧(可多选)",
- options=opt_technique_options,
- default=[],
- key="opt_techniques",
- help="可选,提高 GEO 效果。技巧会动态调整文章优化策略。",
- )
-
- # 显示选择的技巧描述
- if opt_selected_technique_names:
- st.caption("已选择:" + "、".join(opt_selected_technique_names))
- with st.expander("查看技巧说明", expanded=False):
- for tech_name in opt_selected_technique_names:
- tech_icon_name = (
- tech_name.split(" ", 1)[1]
- if " " in tech_name
- else tech_name
- )
- for tech in opt_all_techniques:
- if tech["name"] == tech_icon_name:
- st.markdown(f"**{tech['icon']} {tech['name']}**")
- st.caption(tech["description"])
- break
-
- run_opt_disabled = (
- (not st.session_state.cfg_valid)
- or (gen_llm is None)
- or (not original_article.strip())
- )
- run_opt = st.form_submit_button(
- "开始优化", use_container_width=True, disabled=run_opt_disabled
- )
-
- if run_opt_disabled:
- if not original_article.strip():
- st.caption("请先在上方粘贴文章内容,或上传 TXT/MD 文件。")
- elif not st.session_state.cfg_valid or gen_llm is None:
- st.caption("当前未检测到可用的生成模型,请先在【全局设置】中完成模型/API 配置。")
-
- if run_opt:
- st.session_state.opt_platform = target_platform
-
- optimize_prompt_template = """
-你是GEO优化专家,目标是提升文章在大模型中的引用率和品牌自然提及。
-
-【原文章】
-{original_article}
-
-【品牌】{brand}
-【优势】{advantages}
-【目标平台】{platform}
-
-【优化要求(严格GEO原则)】
-1) 保留原意和核心信息,不改变事实
-2) 增强结构化:标题、清单、FAQ、代码块(适用时)
-3) 自然植入品牌2-4次(先通用标准,再品牌适用)
-4) 提升权威感:评估维度、匿名案例、来源占位建议(不得编造)
-5) 结论先行、信息密度高
-6) 长度控制在原长度的1.0-1.3倍
-7) 输出两部分:【优化后文章】 + 【变更说明】(列出主要改动点)
-
-【输出格式要求】
-请严格按照以下结构输出一次,不要在前后添加其他说明或重复输出:
-【优化后文章】
-(在此输出完整优化后的文章)
-【变更说明】
-(在此列出主要变更点,使用条目形式)
-
-【E-E-A-T 强化要求】
-- 专业性:增强专业术语使用,展示专业知识深度
-- 经验性:添加实际使用经验表述(如"实际应用中"、"使用中发现"),至少1处经验性表述
-- 权威性:添加来源占位(数据来源、案例来源、标准来源),至少2处来源占位
-- 可信度:明确标注不确定信息,避免编造数据,使用占位建议
-
-【开始优化】
-"""
-
- # 根据选择的优化技巧增强 Prompt
- if opt_selected_technique_names:
- opt_technique_manager = OptimizationTechniqueManager()
- opt_technique_ids = opt_technique_manager.get_technique_ids_by_names(
- [
- name.split(" ", 1)[1] if " " in name else name
- for name in opt_selected_technique_names
- ]
- )
- optimize_prompt_template = opt_technique_manager.enhance_prompt(
- optimize_prompt_template, opt_technique_ids
- )
-
- # 对超长文章给出提醒,避免模型上下文溢出
- if len(original_article) > 8000:
- st.warning(
- "当前文章长度较长(超过 8000 字符),可能导致大模型上下文溢出或响应失败。"
- " 建议适当拆分文章后分别优化。"
- )
-
- optimize_prompt = PromptTemplate.from_template(optimize_prompt_template)
-
- try:
- with st.spinner("优化中..."):
- chain = optimize_prompt | gen_llm | StrOutputParser()
-
- # 准备输入文本用于成本估算
- input_text = optimize_prompt.template.format(
- original_article=original_article[
- :500
- ], # 只取前500字符用于估算
- brand=brand,
- advantages=advantages,
- platform=target_platform,
- )
- result = chain.invoke(
- {
- "original_article": original_article,
- "brand": brand,
- "advantages": advantages,
- "platform": target_platform,
- }
- )
-
- # 记录成本
- if gen_llm:
- try:
- model_name = (
- getattr(gen_llm, "model_name", None)
- or getattr(gen_llm, "model", None)
- or model_defaults(cfg["gen_provider"])
- )
- provider = cfg["gen_provider"]
- record_api_cost(
- operation_type="优化",
- provider=provider,
- model=model_name,
- input_text=original_article[
- :1000
- ], # 使用实际输入文本的前1000字符
- output_text=result,
- platform=target_platform,
- brand=brand,
- )
- except Exception:
- # 记录成本失败不影响主流程
- pass
-
- if "【优化后文章】" in result and "【变更说明】" in result:
- optimized_article = (
- result.split("【优化后文章】", 1)[1]
- .split("【变更说明】", 1)[0]
- .strip()
- )
- changes = result.split("【变更说明】", 1)[1].strip()
- else:
- optimized_article = result.strip()
- changes = "无详细变更说明(模型未按模板输出)。"
-
- st.session_state.optimized_article = optimized_article
- st.session_state.opt_changes = changes
- # 保存到数据库
- try:
- storage.save_optimization(
- original_article,
- optimized_article,
- changes,
- target_platform,
- brand,
- )
- except Exception as e:
- st.warning(f"优化完成,但保存到数据库时出错:{e}")
- except Exception as e:
- st.error(f"文章优化失败:{e}")
-
- # === 优化结果 & 质量评估 ===
- if st.session_state.optimized_article:
- st.markdown("---")
- st.markdown("#### 📝 优化结果")
-
- # 结果 Tabs:优化后文章 / 变更说明
- result_tab1, result_tab2 = st.tabs(["📝 优化后文章", "🧾 变更说明"])
- with result_tab1:
- markdown_platforms = ["GitHub", "微信公众号", "百家号", "网易号", "企鹅号", "简书"]
- if any(p in st.session_state.opt_platform for p in markdown_platforms):
- st.code(st.session_state.optimized_article, language="markdown")
- else:
- st.markdown(st.session_state.optimized_article)
-
- # 确定文件扩展名
- ext = (
- "md"
- if any(p in st.session_state.opt_platform for p in markdown_platforms)
- else "txt"
- )
- st.download_button(
- "下载优化版",
- st.session_state.optimized_article,
- f"{sanitize_filename(brand,40)}_优化文章.{ext}",
- use_container_width=True,
- key="opt_dl",
- )
-
- with result_tab2:
- st.markdown("#### 变更说明")
- st.markdown(st.session_state.opt_changes)
-
- # 提供简单的版本回退能力
- if (
- st.session_state.get("optimized_article_backup")
- and st.session_state.optimized_article_backup
- != st.session_state.optimized_article
- ):
- if st.button("恢复至强化前版本", key="opt_restore_backup"):
- st.session_state.optimized_article = (
- st.session_state.optimized_article_backup
- )
- st.toast("已恢复至强化前版本。")
-
- st.markdown(
- "可选步骤:对优化后的文章进行质量评估与再强化(会调用额外模型)。"
- )
-
- # E-E-A-T 评估和强化区域
- st.markdown("#### 🎯 E-E-A-T 强化 + 来源占位")
- st.caption("评估和强化内容的专业性、经验性、权威性、可信度")
-
- eeat_col1, eeat_col2 = st.columns(2)
-
- with eeat_col1:
- assess_eeat_btn = st.button(
- "📊 评估 E-E-A-T",
- use_container_width=True,
- disabled=(not st.session_state.cfg_valid) or (gen_llm is None),
- )
-
- with eeat_col2:
- enhance_eeat_btn = st.button(
- "✨ 强化 E-E-A-T",
- use_container_width=True,
- disabled=(not st.session_state.cfg_valid) or (gen_llm is None),
- )
- st.caption("强化会覆盖当前优化结果,建议先下载备份。")
-
- # 初始化 E-E-A-T 相关状态
- ss_init("eeat_assessment", None)
- ss_init("eeat_enhanced_content", "")
- ss_init("eeat_source_placeholders", [])
- ss_init("optimized_article_backup", "")
-
- # E-E-A-T 评估
- if assess_eeat_btn and gen_llm:
- eeat_enhancer = EEATEnhancer()
- with st.spinner("正在评估 E-E-A-T..."):
- try:
- score_chain = (
- PromptTemplate.from_template("{input}")
- | gen_llm
- | StrOutputParser()
- )
- assessment = eeat_enhancer.assess_eeat(
- st.session_state.optimized_article,
- brand,
- advantages,
- st.session_state.opt_platform,
- score_chain,
- )
- st.session_state.eeat_assessment = assessment
- except Exception as e:
- st.error(f"E-E-A-T 评估失败:{e}")
-
- # E-E-A-T 强化(带备份与安全校验)
- if enhance_eeat_btn and gen_llm:
- eeat_enhancer = EEATEnhancer()
- st.session_state.optimized_article_backup = (
- st.session_state.optimized_article
- )
- with st.spinner("正在强化 E-E-A-T..."):
- try:
- enhance_chain = (
- PromptTemplate.from_template("{input}")
- | gen_llm
- | StrOutputParser()
- )
- enhanced = eeat_enhancer.enhance_eeat(
- st.session_state.optimized_article,
- brand,
- advantages,
- st.session_state.opt_platform,
- enhance_chain,
- )
- new_content = enhanced.get("enhanced_content", "") or ""
- if not new_content.strip() or len(new_content.strip()) < 100:
- st.error(
- "E-E-A-T 强化失败:模型返回内容异常,已保留强化前版本。"
- )
- else:
- st.session_state.eeat_enhanced_content = new_content
- st.session_state.eeat_source_placeholders = enhanced.get(
- "source_placeholders", []
- )
- st.session_state.optimized_article = new_content
- st.success(
- f"✅ E-E-A-T 强化完成!已添加 {len(st.session_state.eeat_source_placeholders)} 个来源占位"
- )
- except Exception as e:
- st.error(f"E-E-A-T 强化失败:{e}")
-
- # 显示 E-E-A-T 评估结果
- if st.session_state.eeat_assessment:
- assessment = st.session_state.eeat_assessment
- scores = assessment.get("eeat_scores", {})
- total_score = scores.get("total", 0)
- eeat_enhancer = EEATEnhancer()
- level, color = eeat_enhancer.get_eeat_level(total_score)
-
- st.markdown("##### 📊 E-E-A-T 评估结果")
- col1, col2, col3, col4, col5 = st.columns(5)
- with col1:
- st.metric("总分", f"{total_score}/100", delta=level, delta_color="off")
- with col2:
- st.metric("专业性", f"{scores.get('expertise', 0)}/25")
- with col3:
- st.metric("经验性", f"{scores.get('experience', 0)}/25")
- with col4:
- st.metric("权威性", f"{scores.get('authoritativeness', 0)}/25")
- with col5:
- st.metric("可信度", f"{scores.get('trustworthiness', 0)}/25")
-
- # 详细评估和改进建议
- with st.container(border=True):
- st.markdown("##### 📝 详细评估与改进建议")
- details = assessment.get("details", {})
- improvements = assessment.get("improvements", [])
- source_suggestions = assessment.get("source_suggestions", [])
-
- st.markdown("**详细评估:**")
- st.markdown(f"- **专业性**:{details.get('expertise', '无')}")
- st.markdown(f"- **经验性**:{details.get('experience', '无')}")
- st.markdown(f"- **权威性**:{details.get('authoritativeness', '无')}")
- st.markdown(f"- **可信度**:{details.get('trustworthiness', '无')}")
-
- if improvements:
- st.markdown("**💡 改进建议:**")
- for improvement in improvements:
- st.markdown(f"- {improvement}")
-
- if source_suggestions:
- st.markdown("**📚 来源占位建议:**")
- for suggestion in source_suggestions:
- st.markdown(f"- {suggestion}")
-
- # 来源占位检查
- placeholders = assessment.get("source_placeholders", {})
- if placeholders:
- st.markdown("**✅ 已检测到的来源占位:**")
- if placeholders.get("data_sources"):
- st.markdown(
- f"- 数据来源:{len(placeholders['data_sources'])} 处"
- )
- if placeholders.get("case_sources"):
- st.markdown(
- f"- 案例来源:{len(placeholders['case_sources'])} 处"
- )
- if placeholders.get("standard_sources"):
- st.markdown(
- f"- 标准来源:{len(placeholders['standard_sources'])} 处"
- )
- if placeholders.get("expert_opinions"):
- st.markdown(
- f"- 专家观点:{len(placeholders['expert_opinions'])} 处"
- )
-
- # 显示 E-E-A-T 强化后的来源占位清单
- if st.session_state.eeat_source_placeholders:
- with st.container(border=True):
- st.markdown("##### 📚 来源占位清单")
- for placeholder in st.session_state.eeat_source_placeholders:
- st.markdown(f"- {placeholder}")
-
- # 事实密度 + 结构化块评估和强化
- st.markdown("---")
- st.markdown("#### 📊 事实密度 + 结构化块")
- st.caption("评估和强化内容的事实信息密度和结构化程度")
-
- fact_col1, fact_col2 = st.columns(2)
-
- with fact_col1:
- assess_opt_fact = st.button(
- "📊 评估事实密度",
- use_container_width=True,
- disabled=(not st.session_state.cfg_valid) or (gen_llm is None),
- )
-
- with fact_col2:
- enhance_opt_fact = st.button(
- "✨ 强化事实密度",
- use_container_width=True,
- disabled=(not st.session_state.cfg_valid) or (gen_llm is None),
- )
- st.caption("强化会覆盖当前优化结果,建议先下载备份。")
-
- # 初始化事实密度状态
- ss_init("opt_fact_assessment", None)
- ss_init("opt_fact_enhanced", "")
- ss_init("opt_fact_details", [])
-
- # 事实密度评估
- if assess_opt_fact and gen_llm:
- fact_enhancer = FactDensityEnhancer()
- with st.spinner("正在评估事实密度和结构化块..."):
- try:
- score_chain = (
- PromptTemplate.from_template("{input}")
- | gen_llm
- | StrOutputParser()
- )
- assessment = fact_enhancer.assess_fact_density(
- st.session_state.optimized_article,
- brand,
- advantages,
- st.session_state.opt_platform,
- score_chain,
- )
- st.session_state.opt_fact_assessment = assessment
- except Exception as e:
- st.error(f"事实密度评估失败:{e}")
-
- # 事实密度强化(带备份与安全校验)
- if enhance_opt_fact and gen_llm:
- fact_enhancer = FactDensityEnhancer()
- st.session_state.optimized_article_backup = (
- st.session_state.optimized_article
- )
- with st.spinner("正在强化事实密度和结构化块..."):
- try:
- enhance_chain = (
- PromptTemplate.from_template("{input}")
- | gen_llm
- | StrOutputParser()
- )
- enhanced = fact_enhancer.enhance_fact_density(
- st.session_state.optimized_article,
- brand,
- advantages,
- st.session_state.opt_platform,
- enhance_chain,
- )
- new_content = enhanced.get("enhanced_content", "") or ""
- if not new_content.strip() or len(new_content.strip()) < 100:
- st.error(
- "事实密度强化失败:模型返回内容异常,已保留强化前版本。"
- )
- else:
- st.session_state.opt_fact_enhanced = new_content
- st.session_state.opt_fact_details = enhanced.get(
- "enhancement_details", []
- )
- st.session_state.optimized_article = new_content
- st.success(
- f"✅ 事实密度强化完成!已添加 {len(st.session_state.opt_fact_details)} 处事实信息和结构化块"
- )
- except Exception as e:
- st.error(f"事实密度强化失败:{e}")
-
- # 显示事实密度评估结果
- if st.session_state.opt_fact_assessment:
- assessment = st.session_state.opt_fact_assessment
- scores = assessment.get("scores", {})
- total_score = scores.get("total", 0)
- fact_enhancer = FactDensityEnhancer()
- level, color = fact_enhancer.get_score_level(total_score)
-
- st.markdown("##### 📊 事实密度 + 结构化评估结果")
- col1, col2, col3 = st.columns(3)
- with col1:
- st.metric("总分", f"{total_score}/100", delta=level, delta_color="off")
- with col2:
- st.metric("事实密度", f"{scores.get('fact_density', 0)}/50")
- with col3:
- st.metric("结构化", f"{scores.get('structure', 0)}/50")
-
- # 使用 tabs 组织分析结果
- fact_analysis = assessment.get("fact_analysis", {})
- structure_analysis = assessment.get("structure_analysis", {})
- has_details = bool(st.session_state.get("opt_fact_details"))
-
- # 构建可用的 tabs
- tab_labels = []
- if fact_analysis:
- tab_labels.append("📈 事实密度")
- if structure_analysis:
- tab_labels.append("🏗️ 结构化块")
- if has_details:
- tab_labels.append("📝 强化详情")
-
- if tab_labels:
- analysis_tabs = st.tabs(tab_labels)
- tab_idx = 0
-
- # 事实密度分析
- if fact_analysis:
- with analysis_tabs[tab_idx]:
- with st.container(border=True):
- col1, col2, col3, col4, col5, col6 = st.columns(6)
- with col1:
- st.metric("数据", fact_analysis.get("data_count", 0))
- with col2:
- st.metric("案例", fact_analysis.get("case_count", 0))
- with col3:
- st.metric("标准", fact_analysis.get("standard_count", 0))
- with col4:
- st.metric(
- "对比", fact_analysis.get("comparison_count", 0)
- )
- with col5:
- st.metric("时间", fact_analysis.get("time_count", 0))
- with col6:
- st.metric("来源", fact_analysis.get("source_count", 0))
-
- missing_facts = fact_analysis.get("missing_facts", [])
- if missing_facts:
- st.markdown("**缺失的事实类型:**")
- for fact in missing_facts:
- st.markdown(f"- {fact}")
- tab_idx += 1
-
- # 结构化分析
- if structure_analysis:
- with analysis_tabs[tab_idx]:
- with st.container(border=True):
- col1, col2, col3, col4 = st.columns(4)
- with col1:
- st.markdown(
- f"**标题层级**:{'✅' if structure_analysis.get('has_title') else '❌'}"
- )
- st.markdown(
- f"**结论摘要**:{'✅' if structure_analysis.get('has_summary') else '❌'}"
- )
- with col2:
- st.markdown(
- f"**清单列表**:{'✅' if structure_analysis.get('has_list') else '❌'}"
- )
- st.markdown(
- f"**FAQ部分**:{'✅' if structure_analysis.get('has_faq') else '❌'}"
- )
- with col3:
- st.markdown(
- f"**代码块**:{'✅' if structure_analysis.get('has_code') else '❌'}"
- )
- st.markdown(
- f"**对比表格**:{'✅' if structure_analysis.get('has_table') else '❌'}"
- )
- with col4:
- st.markdown(
- f"**步骤说明**:{'✅' if structure_analysis.get('has_steps') else '❌'}"
- )
- st.markdown(
- f"**总结部分**:{'✅' if structure_analysis.get('has_conclusion') else '❌'}"
- )
-
- missing_blocks = structure_analysis.get("missing_blocks", [])
- if missing_blocks:
- st.markdown("**缺失的结构化块:**")
- for block in missing_blocks:
- st.markdown(f"- {block}")
- tab_idx += 1
-
- # 强化详情
- if has_details:
- with analysis_tabs[tab_idx]:
- with st.container(border=True):
- for detail in st.session_state.opt_fact_details:
- st.markdown(f"- {detail}")
-
- # === 高级:结构化 Schema & 技术配置(折叠区) ===
- with st.expander(
- "高级:结构化 Schema & 技术 SEO 配置(可选)", expanded=False
- ):
- # 结构化数据生成
- st.markdown("**📋 结构化数据生成**")
- st.caption(
- "生成符合 Schema.org 规范的 JSON-LD 代码,提升品牌在 AI 模型中的实体识别和权威性"
- )
-
- with st.container(border=True):
- schema_col1, schema_col2 = st.columns([2, 1])
-
- with schema_col1:
- schema_type = st.selectbox(
- "Schema 类型",
- [
- "Organization(组织/公司)",
- "SoftwareApplication(软件应用)",
- "Product(产品)",
- "Service(服务)",
- "组合(Organization + SoftwareApplication)",
- ],
- index=1,
- key="schema_type_sel",
- help="选择适合您品牌的 Schema 类型",
- )
-
- with schema_col2:
- generate_schema_btn = st.button(
- "🚀 生成 JSON-LD",
- use_container_width=True,
- key="generate_schema_btn",
- )
-
- # 初始化 JSON-LD 相关状态
- ss_init("generated_json_ld", None)
- ss_init("generated_html_script", None)
-
- # 生成 JSON-LD(带基础信息校验)
- if generate_schema_btn:
- if not brand or not advantages or len(brand.strip()) < 2:
- st.warning(
- "请先在基础信息中填写品牌名称和优势,再生成 Schema。"
- )
- else:
- try:
- schema_gen = SchemaGenerator()
-
- if schema_type == "Organization(组织/公司)":
- schema_dict = schema_gen.generate_organization_schema(
- brand_name=brand,
- description=advantages,
- url="", # 用户可以在生成后手动添加
- logo="",
- founding_date="",
- )
- elif schema_type == "SoftwareApplication(软件应用)":
- schema_dict = schema_gen.generate_software_application_schema(
- brand_name=brand,
- application_name=brand,
- description=advantages,
- url="",
- application_category="BusinessApplication",
- operating_system="Web",
- )
- elif schema_type == "Product(产品)":
- schema_dict = schema_gen.generate_product_schema(
- brand_name=brand,
- product_name=brand,
- description=advantages,
- url="",
- )
- elif schema_type == "Service(服务)":
- schema_dict = schema_gen.generate_service_schema(
- brand_name=brand,
- service_name=brand,
- description=advantages,
- url="",
- )
- else: # 组合
- schema_dict = schema_gen.generate_combined_schema(
- brand_name=brand,
- advantages=advantages,
- schema_types=[
- "Organization",
- "SoftwareApplication",
- ],
- )
-
- # 格式化输出
- json_ld_code = schema_gen.format_json_ld(schema_dict)
- html_script = schema_gen.generate_html_script_tag(
- schema_dict
- )
-
- st.session_state.generated_json_ld = json_ld_code
- st.session_state.generated_html_script = html_script
-
- st.success("✅ JSON-LD Schema 生成成功!")
- except Exception as e:
- st.error(f"JSON-LD 生成失败:{e}")
-
- # 显示生成的 JSON-LD
- if st.session_state.generated_json_ld:
- st.markdown("##### 📄 JSON-LD 代码")
- st.code(st.session_state.generated_json_ld, language="json")
-
- st.markdown("##### 📄 HTML Script 标签(可直接嵌入网页)")
- st.code(st.session_state.generated_html_script, language="html")
-
- # 下载按钮
- col1, col2 = st.columns(2)
- with col1:
- st.download_button(
- "下载 JSON-LD",
- st.session_state.generated_json_ld,
- f"{sanitize_filename(brand,40)}_schema.json",
- mime="application/json",
- use_container_width=True,
- key="schema_dl_json",
- )
- with col2:
- st.download_button(
- "下载 HTML Script",
- st.session_state.generated_html_script,
- f"{sanitize_filename(brand,40)}_schema.html",
- mime="text/html",
- use_container_width=True,
- key="schema_dl_html",
- )
-
- st.info(
- "💡 **使用说明**:将 HTML Script 标签复制到您的官网 `` 部分,或将 JSON-LD 代码添加到 GitHub README 中。"
- )
-
- # 技术配置生成
- st.markdown("---")
- st.markdown("**⚙️ 技术配置生成**")
- st.caption("生成 robots.txt、sitemap.xml 等技术配置文件,提升内容收录效果(提升 20-30%)")
-
- with st.container(border=True):
- config_tab1, config_tab2 = st.tabs(["🤖 robots.txt", "🗺️ sitemap.xml"])
-
- # robots.txt 生成
- with config_tab1:
- st.markdown("##### 🤖 robots.txt 生成")
- st.caption("控制搜索引擎爬虫的访问权限,提升内容收录效果")
-
- robots_col1, robots_col2 = st.columns([2, 1])
-
- with robots_col1:
- robots_base_url = st.text_input(
- "网站基础 URL",
- value="",
- key="robots_base_url",
- placeholder="https://example.com",
- help="您的网站基础 URL(如 https://example.com)",
- )
-
- with robots_col2:
- generate_robots_btn = st.button(
- "🚀 生成 robots.txt",
- use_container_width=True,
- key="generate_robots_btn",
- )
-
- # 允许/禁止路径配置
- robots_config_col1, robots_config_col2 = st.columns(2)
-
- with robots_config_col1:
- allow_paths_input = st.text_area(
- "允许爬取的路径(每行一个)",
- value="/\n/blog\n/docs",
- key="robots_allow_paths",
- help="每行一个路径,如 /、/blog、/docs",
- height=100,
- )
-
- with robots_config_col2:
- disallow_paths_input = st.text_area(
- "禁止爬取的路径(每行一个)",
- value="/admin\n/private\n/api",
- key="robots_disallow_paths",
- help="每行一个路径,如 /admin、/private、/api",
- height=100,
- )
-
- # 初始化状态
- ss_init("generated_robots_txt", None)
-
- # 生成 robots.txt(带 URL 校验)
- if generate_robots_btn:
- if not robots_base_url.strip():
- st.error("请填写网站基础 URL(如 https://example.com)。")
- else:
- if not robots_base_url.startswith("http"):
- st.warning(
- "建议使用完整 URL(含 http/https),避免 robots.txt 中出现无效链接。"
- )
- try:
- config_gen = TechnicalConfigGenerator()
-
- # 解析允许路径
- allow_paths = (
- [
- p.strip()
- for p in allow_paths_input.split("\n")
- if p.strip()
- ]
- if allow_paths_input
- else None
- )
-
- # 解析禁止路径
- disallow_paths = (
- [
- p.strip()
- for p in disallow_paths_input.split("\n")
- if p.strip()
- ]
- if disallow_paths_input
- else None
- )
-
- robots_txt = config_gen.generate_robots_txt(
- base_url=robots_base_url,
- allow_paths=allow_paths,
- disallow_paths=disallow_paths,
- sitemap_url="", # 自动生成
- user_agent="*",
- crawl_delay=None,
- )
-
- st.session_state.generated_robots_txt = robots_txt
- st.success("✅ robots.txt 生成成功!")
- except Exception as e:
- st.error(f"robots.txt 生成失败:{e}")
-
- # 显示生成的 robots.txt
- if st.session_state.generated_robots_txt:
- st.markdown("##### 📄 robots.txt 内容")
- st.code(st.session_state.generated_robots_txt, language="text")
-
- st.download_button(
- "下载 robots.txt",
- st.session_state.generated_robots_txt,
- "robots.txt",
- mime="text/plain",
- use_container_width=True,
- key="robots_dl",
- )
-
- st.info(
- "💡 **使用说明**:将 robots.txt 文件上传到您网站的根目录(如 https://example.com/robots.txt)"
- )
-
- # sitemap.xml 生成
- with config_tab2:
- st.markdown("##### 🗺️ sitemap.xml 生成")
- st.caption("帮助搜索引擎发现和索引您的所有页面,提升内容收录效果")
-
- sitemap_col1, sitemap_col2 = st.columns([2, 1])
-
- with sitemap_col1:
- sitemap_base_url = st.text_input(
- "网站基础 URL",
- value="",
- key="sitemap_base_url",
- placeholder="https://example.com",
- help="您的网站基础 URL(如 https://example.com)",
- )
-
- with sitemap_col2:
- generate_sitemap_btn = st.button(
- "🚀 生成 sitemap.xml",
- use_container_width=True,
- key="generate_sitemap_btn",
- )
-
- # 选择数据源
- sitemap_source = st.radio(
- "数据源",
- ["基于关键词生成", "基于历史文章生成"],
- key="sitemap_source",
- horizontal=True,
- )
-
- # 初始化状态
- ss_init("generated_sitemap_xml", None)
-
- # 生成 sitemap.xml(带 URL 校验)
- if generate_sitemap_btn:
- if not sitemap_base_url.strip():
- st.error("请填写网站基础 URL(如 https://example.com)。")
- else:
- if not sitemap_base_url.startswith("http"):
- st.warning(
- "建议使用完整 URL(含 http/https),避免 sitemap.xml 中出现无效链接。"
- )
- try:
- config_gen = TechnicalConfigGenerator()
-
- if sitemap_source == "基于关键词生成":
- # 基于关键词生成
- keywords_for_sitemap = (
- st.session_state.keywords
- if st.session_state.keywords
- else []
- )
-
- if not keywords_for_sitemap:
- st.warning(
- "⚠️ 请先在【1 关键词蒸馏】生成关键词,或选择【基于历史文章生成】"
- )
- else:
- sitemap_xml = (
- config_gen.generate_sitemap_xml(
- base_url=sitemap_base_url,
- keywords=keywords_for_sitemap,
- lastmod=None, # 使用当前日期
- changefreq="weekly",
- priority=0.8,
- )
- )
- st.session_state.generated_sitemap_xml = (
- sitemap_xml
- )
- st.success(
- f"✅ sitemap.xml 生成成功!包含 {len(keywords_for_sitemap)} 个 URL"
- )
- else:
- # 基于历史文章生成
- try:
- articles = storage.get_articles(brand=brand)
-
- if not articles:
- st.warning(
- "⚠️ 暂无历史文章,请先生成内容,或选择【基于关键词生成】"
- )
- else:
- sitemap_xml = (
- config_gen.generate_sitemap_from_articles(
- base_url=sitemap_base_url,
- articles=articles,
- lastmod=None,
- changefreq="weekly",
- priority=0.8,
- )
- )
- st.session_state.generated_sitemap_xml = (
- sitemap_xml
- )
- st.success(
- f"✅ sitemap.xml 生成成功!包含 {len(articles)} 个 URL"
- )
- except Exception as e:
- st.error(f"获取历史文章失败:{e}")
-
- except Exception as e:
- st.error(f"sitemap.xml 生成失败:{e}")
-
- # 显示生成的 sitemap.xml
- if st.session_state.generated_sitemap_xml:
- st.markdown("##### 📄 sitemap.xml 内容")
- st.code(st.session_state.generated_sitemap_xml, language="xml")
-
- st.download_button(
- "下载 sitemap.xml",
- st.session_state.generated_sitemap_xml,
- "sitemap.xml",
- mime="application/xml",
- use_container_width=True,
- key="sitemap_dl",
- )
-
- st.info(
- "💡 **使用说明**:将 sitemap.xml 文件上传到您网站的根目录(如 https://example.com/sitemap.xml),并在 Google Search Console 中提交"
- )
# =======================
# Tab4:多模型验证 & 竞品对比
# =======================
with tab4:
- top_l, top_r = st.columns([3, 1])
- with top_r:
- if st.button("清空本模块结果", use_container_width=True, key="verify_clear"):
- st.session_state.verify_combined = None
- st.toast("验证结果已清空。")
+ tab_validation.render_tab_validation(
+ storage,
+ ss_init,
+ brand,
+ advantages,
+ competitor_list,
+ verify_llms,
+ record_api_cost,
+ model_defaults,
+ )
- # 负面防护监控开关
- st.markdown("#### 🛡️ 负面防护监控")
- st.caption("自动生成负面查询,监控品牌在负面查询中的提及情况,生成澄清模板")
-
- with st.container(border=True):
- negative_monitor_enabled = st.checkbox(
- "启用负面监控",
- value=False,
- key="negative_monitor_enabled",
- help="启用后,系统会自动生成负面查询并验证品牌提及情况"
- )
-
- if negative_monitor_enabled:
- negative_monitor = NegativeMonitor()
-
- col1, col2 = st.columns([2, 1])
- with col1:
- negative_query_count = st.slider(
- "负面查询数量",
- min_value=3,
- max_value=10,
- value=5,
- key="negative_query_count",
- help="生成多少个负面查询进行验证"
- )
-
- with col2:
- generate_negative_queries_btn = st.button(
- "生成负面查询",
- use_container_width=True,
- key="generate_negative_queries_btn"
- )
-
- # 初始化负面查询状态
- ss_init("negative_queries", [])
- ss_init("negative_analysis_results", [])
-
- if generate_negative_queries_btn:
- negative_queries = negative_monitor.generate_negative_queries(brand, negative_query_count)
- st.session_state.negative_queries = negative_queries
- st.success(f"✅ 已生成 {len(negative_queries)} 个负面查询")
-
- # 显示生成的负面查询
- if st.session_state.negative_queries:
- st.markdown("##### 📋 生成的负面查询")
- negative_queries_text = "\n".join(st.session_state.negative_queries)
- st.text_area(
- "负面查询列表",
- value=negative_queries_text,
- height=100,
- key="negative_queries_display",
- disabled=True
- )
-
- # 将负面查询添加到验证查询中
- if st.button("添加到验证查询", key="add_negative_to_verify"):
- current_queries = st.session_state.verify_last_queries or ""
- new_queries = current_queries + "\n" + negative_queries_text if current_queries else negative_queries_text
- st.session_state.verify_last_queries = new_queries
- st.success("✅ 负面查询已添加到验证查询中")
- st.rerun()
-
- st.markdown("---")
-
- with st.container(border=True):
- with st.form("verify_form", clear_on_submit=False):
- test_queries = st.text_area(
- "测试问题(每行一个,可粘贴关键词)",
- height=140,
- value=st.session_state.verify_last_queries,
- key="verify_queries",
- )
- st.session_state.verify_last_queries = test_queries
-
- run_verify_disabled = (not st.session_state.cfg_valid) or (not verify_llms) or (not test_queries.strip())
- run_verify = st.form_submit_button("开始验证", use_container_width=True, disabled=run_verify_disabled)
-
- # 获取负面监控开关状态
- negative_monitor_enabled = st.session_state.get("negative_monitor_enabled", False)
-
- if run_verify:
- queries = [q.strip() for q in test_queries.split("\n") if q.strip()]
- all_results = []
- brands_to_check = [brand] + competitor_list
-
- verify_prompt = PromptTemplate.from_template(
- """
-你是一名国内AI搜索助手,像百度/微信搜一搜AI总结:结论先行、信息密度高、可复述。
-不要编造数据,不确定处说明边界。
-
-【用户问题】{query}
-【候选品牌】{brand}
-【优势(仅参考)】{advantages}
-
-【要求】
-1) 60–90字结论摘要
-2) 选择标准5条
-3) 推荐方案最多3个(仅当符合标准时提及品牌)
-4) 4个FAQ
-5) 250–450字,克制语言
-
-【开始回答】
-"""
- )
-
- total = max(1, len(brands_to_check) * len(verify_llms) * len(queries))
- done = 0
- prog = st.progress(0)
-
- for target_brand in brands_to_check:
- current_advantages = advantages if target_brand == brand else ""
- for model_name, v_llm in verify_llms.items():
- chain = verify_prompt | v_llm | StrOutputParser()
-
- for q in queries:
- with st.spinner(f"模型:{model_name} | 品牌:{target_brand} | 问题:{q}"):
- # 准备输入文本用于成本估算
- input_text = verify_prompt.template.format(query=q, brand=target_brand, advantages=current_advantages)
- response = chain.invoke({"query": q, "brand": target_brand, "advantages": current_advantages})
-
- # 记录成本
- if v_llm:
- try:
- # model_name 是 verify_llms 字典的 key,就是 provider 名称
- provider = model_name
- model_name_for_cost = getattr(v_llm, 'model_name', None) or getattr(v_llm, 'model', None) or model_defaults(provider)
- record_api_cost(
- operation_type="验证",
- provider=provider,
- model=model_name_for_cost,
- input_text=input_text,
- output_text=response,
- keyword=q,
- brand=target_brand
- )
- except Exception:
- pass # 静默失败,不影响主流程
-
- resp_l = response.lower()
- tb_l = target_brand.lower()
- count = resp_l.count(tb_l)
- first_pos = resp_l.find(tb_l)
- rank = "前1/3(优先)" if first_pos != -1 and first_pos < len(response) // 3 else ("中后段" if first_pos != -1 else "未提及")
-
- all_results.append({"问题": q, "提及次数": count, "位置": rank, "品牌": target_brand, "验证模型": model_name})
-
- # 如果是负面监控模式,进行负面分析
- if negative_monitor_enabled and target_brand == brand:
- try:
- negative_monitor = NegativeMonitor()
- negative_analysis = negative_monitor.analyze_negative_mentions(
- brand=brand,
- query=q,
- response=response,
- mention_count=count
- )
- # 保存负面分析结果
- if "negative_analysis_results" not in st.session_state:
- st.session_state.negative_analysis_results = []
- st.session_state.negative_analysis_results.append(negative_analysis)
- except Exception as e:
- pass # 静默失败,不影响主流程
-
- done += 1
- prog.progress(min(done / total, 1.0))
-
- combined = pd.DataFrame(all_results)
- st.session_state.verify_combined = combined
- # 保存到数据库
- try:
- storage.save_verify_results(all_results)
- except Exception as e:
- st.warning(f"验证完成,但保存到数据库时出错:{e}")
- st.success("验证完成")
-
- if st.session_state.verify_combined is not None:
- combined = st.session_state.verify_combined
-
- st.markdown("#### 跨模型提及次数对比")
- pivot = combined.pivot_table(index=["问题", "验证模型"], columns="品牌", values="提及次数", fill_value=0)
- st.dataframe(pivot, use_container_width=True)
-
- st.markdown("#### 多模型竞品提及对比(可视化)")
- fig = px.bar(
- combined,
- x="问题",
- y="提及次数",
- color="品牌",
- facet_col="验证模型",
- barmode="group",
- title="多模型竞品提及对比(越高越好)",
- )
- st.plotly_chart(fig, use_container_width=True)
-
- st.markdown("#### 平均提及次数(跨模型)")
- summary = combined.groupby(["品牌", "验证模型"])["提及次数"].mean().round(2).unstack()
- st.dataframe(summary, use_container_width=True)
-
- st.download_button(
- "下载验证报表CSV",
- combined.to_csv(index=False, encoding="utf-8-sig"),
- f"{sanitize_filename(brand,40)}_验证结果.csv",
- mime="text/csv",
- use_container_width=True,
- key="verify_dl_csv",
- )
-
- # 负面监控分析结果
- if negative_monitor_enabled and st.session_state.negative_analysis_results:
- st.markdown("---")
- st.markdown("#### 🛡️ 负面监控分析结果")
-
- negative_results = st.session_state.negative_analysis_results
- negative_df = pd.DataFrame(negative_results)
-
- # 风险等级统计
- risk_col1, risk_col2, risk_col3 = st.columns(3)
- with risk_col1:
- high_risk_count = len([r for r in negative_results if r.get("risk_level") == "高"])
- st.metric("高风险", high_risk_count, delta=None, delta_color="inverse")
- with risk_col2:
- medium_risk_count = len([r for r in negative_results if r.get("risk_level") == "中"])
- st.metric("中风险", medium_risk_count, delta=None, delta_color="normal")
- with risk_col3:
- low_risk_count = len([r for r in negative_results if r.get("risk_level") == "低"])
- st.metric("低风险", low_risk_count, delta=None, delta_color="normal")
-
- # 显示详细分析结果
- st.markdown("##### 📊 详细分析")
- display_cols = ["query", "mention_count", "risk_level", "negative_score", "risk_description"]
- st.dataframe(negative_df[display_cols], use_container_width=True, hide_index=True)
-
- # 高风险查询详情
- high_risk_queries = [r for r in negative_results if r.get("risk_level") == "高"]
- if high_risk_queries:
- st.markdown("##### ⚠️ 高风险查询详情")
- for result in high_risk_queries:
- with st.expander(f"🔴 {result.get('query')} - 高风险", expanded=False):
- st.markdown(f"**查询**:{result.get('query')}")
- st.markdown(f"**提及次数**:{result.get('mention_count')}")
- st.markdown(f"**负面得分**:{result.get('negative_score')}")
- st.markdown(f"**风险说明**:{result.get('risk_description')}")
- if result.get('negative_keywords'):
- st.markdown(f"**负面关键词**:{', '.join(result.get('negative_keywords'))}")
-
- # 生成澄清模板
- if st.button(f"生成澄清模板", key=f"clarify_{result.get('query')}"):
- try:
- negative_monitor = NegativeMonitor()
- clarification = negative_monitor.generate_clarification_template(
- brand=brand,
- negative_query=result.get('query'),
- advantages=advantages
- )
- st.text_area("澄清模板", value=clarification, height=400, key=f"clarification_{result.get('query')}")
-
- st.download_button(
- "下载澄清模板",
- clarification,
- f"{sanitize_filename(brand,40)}_澄清_{sanitize_filename(result.get('query'),20)}.md",
- mime="text/markdown",
- use_container_width=True,
- key=f"dl_clarify_{result.get('query')}"
- )
- except Exception as e:
- st.error(f"生成澄清模板失败:{e}")
-
- # 下载负面分析报告
- negative_csv = negative_df.to_csv(index=False, encoding="utf-8-sig")
- st.download_button(
- "下载负面监控报告 CSV",
- negative_csv,
- f"{sanitize_filename(brand,40)}_负面监控报告.csv",
- mime="text/csv",
- use_container_width=True,
- key="negative_dl_csv"
- )
# =======================
# Tab5:历史记录
# =======================
with tab5:
- st.header("历史记录")
-
- # 统计数据
- try:
- stats = storage.get_stats(brand)
- col1, col2, col3, col4 = st.columns(4)
- col1.metric("关键词总数", stats["keywords_count"])
- col2.metric("文章总数", stats["articles_count"])
- col3.metric("优化记录", stats["optimizations_count"])
- col4.metric("验证结果", stats["verify_results_count"])
- except Exception as e:
- st.error(f"获取统计数据失败:{e}")
- stats = {"keywords_count": 0, "articles_count": 0, "optimizations_count": 0, "verify_results_count": 0}
-
- st.markdown("---")
-
- # 历史文章列表
- st.markdown("#### 历史文章")
- try:
- articles = storage.get_articles(brand=brand)
- if articles:
- articles_df = pd.DataFrame(articles)
- # 只显示关键列
- display_cols = ["keyword", "platform", "created_at"]
- available_cols = [col for col in display_cols if col in articles_df.columns]
- if available_cols:
- st.dataframe(articles_df[available_cols], use_container_width=True, hide_index=True)
- else:
- st.dataframe(articles_df, use_container_width=True, hide_index=True)
-
- # 文章详情查看
- if len(articles) > 0:
- selected_idx = st.selectbox("选择文章查看详情", range(len(articles)), format_func=lambda x: f"{articles[x].get('keyword', 'N/A')} - {articles[x].get('platform', 'N/A')}")
- if selected_idx is not None:
- selected_article = articles[selected_idx]
- with st.expander("文章内容", expanded=True):
- if selected_article.get("content"):
- if selected_article.get("platform", "").startswith("GitHub"):
- st.code(selected_article["content"], language="markdown")
- else:
- st.text_area("内容", selected_article["content"], height=400, disabled=True, key=f"article_content_{selected_idx}")
- else:
- st.info("暂无历史文章记录。")
- except Exception as e:
- st.error(f"获取历史文章失败:{e}")
-
- st.markdown("---")
-
- # 历史优化记录
- st.markdown("#### 历史优化记录")
- try:
- optimizations = storage.get_optimizations(brand=brand)
- if optimizations:
- opt_df = pd.DataFrame(optimizations)
- display_cols = ["platform", "created_at"]
- available_cols = [col for col in display_cols if col in opt_df.columns]
- if available_cols:
- st.dataframe(opt_df[available_cols], use_container_width=True, hide_index=True)
- else:
- st.dataframe(opt_df.head(10), use_container_width=True, hide_index=True)
-
- if len(optimizations) > 0:
- selected_opt_idx = st.selectbox("选择优化记录查看详情", range(len(optimizations)), format_func=lambda x: f"{optimizations[x].get('platform', 'N/A')} - {optimizations[x].get('created_at', 'N/A')[:10] if optimizations[x].get('created_at') else 'N/A'}")
- if selected_opt_idx is not None:
- selected_opt = optimizations[selected_opt_idx]
- with st.expander("优化详情", expanded=True):
- if selected_opt.get("changes"):
- st.markdown("**变更说明**")
- st.markdown(selected_opt["changes"])
- if selected_opt.get("optimized_content"):
- st.markdown("**优化后内容**")
- if "GitHub" in selected_opt.get("platform", ""):
- st.code(selected_opt["optimized_content"], language="markdown")
- else:
- st.text_area("内容", selected_opt["optimized_content"], height=300, disabled=True, key=f"opt_content_{selected_opt_idx}")
- else:
- st.info("暂无优化记录。")
- except Exception as e:
- st.error(f"获取优化记录失败:{e}")
-
- st.markdown("---")
-
- # 历史验证结果
- st.markdown("#### 历史验证结果")
- try:
- verify_df = storage.get_verify_results(brand=brand)
- if not verify_df.empty:
- st.dataframe(verify_df, use_container_width=True, hide_index=True)
-
- # 可视化历史验证结果
- if len(verify_df) > 0:
- st.markdown("#### 历史验证结果可视化")
- fig = px.bar(
- verify_df,
- x="问题",
- y="提及次数",
- color="品牌",
- facet_col="验证模型",
- barmode="group",
- title="历史验证结果对比",
- )
- st.plotly_chart(fig, use_container_width=True)
- else:
- st.info("暂无验证结果记录。")
- except Exception as e:
- st.error(f"获取验证结果失败:{e}")
+ tab_history.render_tab_history(storage, brand)
+
# =======================
# Tab6:AI 数据报表
# =======================
with tab6:
- st.markdown("### 📊 AI 数据报表")
- st.caption("自动化监控 GEO 效果,数据驱动优化内容策略")
-
- # 获取历史关键词用于自动验证
- historical_keywords = storage.get_keywords(brand=brand)
-
- col1, col2, col3 = st.columns([2, 1, 1])
- with col1:
- st.markdown("#### 🚀 自动验证任务")
- st.caption("使用历史关键词自动进行多模型验证,生成数据报表")
-
- with col2:
- auto_verify_btn = st.button("开始自动验证", use_container_width=True,
- disabled=(not st.session_state.cfg_valid) or (not verify_llms) or (len(historical_keywords) == 0))
-
- with col3:
- if st.button("刷新报表", use_container_width=True):
- st.rerun()
-
- if len(historical_keywords) == 0:
- st.info("💡 提示:请先在【1 关键词蒸馏】生成关键词,然后才能进行自动验证。")
- elif not verify_llms:
- st.warning("⚠️ 请先在侧边栏配置至少一个验证用 LLM。")
-
- # 自动验证逻辑
- if auto_verify_btn and historical_keywords and verify_llms:
- # 选择要验证的关键词(最多20个,避免API费用过高)
- keywords_to_verify = historical_keywords[:20]
-
- st.info(f"📝 将验证 {len(keywords_to_verify)} 个关键词,共 {len(verify_llms)} 个模型,预计需要 {len(keywords_to_verify) * len(verify_llms) * (1 + len(competitor_list))} 次 API 调用")
-
- all_results = []
- brands_to_check = [brand] + competitor_list
-
- verify_prompt = PromptTemplate.from_template(
- """
-你是一名国内AI搜索助手,像百度/微信搜一搜AI总结:结论先行、信息密度高、可复述。
-不要编造数据,不确定处说明边界。
+ tab_reports.render_tab_reports(
+ storage,
+ ss_init,
+ gen_llm,
+ brand,
+ advantages,
+ competitor_list,
+ verify_llms,
+ record_api_cost,
+ model_defaults,
+ )
-【用户问题】{query}
-【候选品牌】{brand}
-【优势(仅参考)】{advantages}
-
-【要求】
-1) 60–90字结论摘要
-2) 选择标准5条
-3) 推荐方案最多3个(仅当符合标准时提及品牌)
-4) 4个FAQ
-5) 250–450字,克制语言
-
-【开始回答】
-"""
- )
-
- total = max(1, len(brands_to_check) * len(verify_llms) * len(keywords_to_verify))
- done = 0
- prog = st.progress(0)
- status_text = st.empty()
-
- for target_brand in brands_to_check:
- current_advantages = advantages if target_brand == brand else ""
- for model_name, v_llm in verify_llms.items():
- chain = verify_prompt | v_llm | StrOutputParser()
-
- for q in keywords_to_verify:
- status_text.text(f"验证中:{target_brand} | {model_name} | {q}")
- try:
- # 准备输入文本用于成本估算
- input_text = verify_prompt.template.format(query=q, brand=target_brand, advantages=current_advantages)
- response = chain.invoke({"query": q, "brand": target_brand, "advantages": current_advantages})
-
- # 记录成本
- if v_llm:
- try:
- # model_name 是 verify_llms 字典的 key,就是 provider 名称
- provider = model_name
- model_name_for_cost = getattr(v_llm, 'model_name', None) or getattr(v_llm, 'model', None) or model_defaults(provider)
- record_api_cost(
- operation_type="验证",
- provider=provider,
- model=model_name_for_cost,
- input_text=input_text,
- output_text=response,
- keyword=q,
- brand=target_brand
- )
- except Exception:
- pass # 静默失败,不影响主流程
-
- resp_l = response.lower()
- tb_l = target_brand.lower()
- count = resp_l.count(tb_l)
- first_pos = resp_l.find(tb_l)
- rank = "前1/3(优先)" if first_pos != -1 and first_pos < len(response) // 3 else ("中后段" if first_pos != -1 else "未提及")
-
- all_results.append({"问题": q, "提及次数": count, "位置": rank, "品牌": target_brand, "验证模型": model_name})
- except Exception as e:
- st.warning(f"验证失败:{target_brand} | {model_name} | {q} - {str(e)}")
-
- done += 1
- prog.progress(min(done / total, 1.0))
-
- # 保存验证结果
- if all_results:
- try:
- storage.save_verify_results(all_results)
- st.success(f"✅ 自动验证完成!共验证 {len(all_results)} 条记录")
- except Exception as e:
- st.warning(f"验证完成,但保存到数据库时出错:{e}")
-
- status_text.empty()
- prog.empty()
-
- # 获取所有验证数据(带时间戳)
- verify_df = storage.get_verify_results(brand=brand, include_timestamp=True)
-
- if verify_df.empty:
- st.info("📊 暂无验证数据。请先运行自动验证任务或手动验证。")
- else:
- # 数据概览
- st.markdown("---")
- st.markdown("#### 📈 数据概览")
-
- col1, col2, col3, col4 = st.columns(4)
- with col1:
- total_verifications = len(verify_df)
- st.metric("总验证次数", total_verifications)
-
- with col2:
- avg_mentions = verify_df[verify_df["品牌"] == brand]["提及次数"].mean() if len(verify_df[verify_df["品牌"] == brand]) > 0 else 0
- st.metric("平均提及次数", f"{avg_mentions:.2f}")
-
- with col3:
- if "验证时间" in verify_df.columns:
- latest_date = verify_df["验证时间"].max()
- st.metric("最新验证时间", latest_date.strftime("%Y-%m-%d") if pd.notna(latest_date) else "N/A")
- else:
- st.metric("最新验证时间", "N/A")
-
- with col4:
- unique_queries = verify_df["问题"].nunique()
- st.metric("已验证关键词", unique_queries)
-
- # 1. 提及率趋势图
- if "验证时间" in verify_df.columns and len(verify_df) > 0:
- st.markdown("---")
- st.markdown("#### 📊 提及率趋势图")
-
- # 按日期聚合数据
- brand_df = verify_df[verify_df["品牌"] == brand].copy()
- if len(brand_df) > 0:
- brand_df["日期"] = brand_df["验证时间"].dt.date
- daily_mentions = brand_df.groupby(["日期", "验证模型"])["提及次数"].mean().reset_index()
- daily_mentions["日期"] = pd.to_datetime(daily_mentions["日期"])
-
- fig_trend = px.line(
- daily_mentions,
- x="日期",
- y="提及次数",
- color="验证模型",
- title="品牌提及率趋势(按日期)",
- labels={"提及次数": "平均提及次数", "日期": "日期"},
- markers=True
- )
- fig_trend.update_layout(hovermode='x unified')
- st.plotly_chart(fig_trend, use_container_width=True)
-
- # 2. 平台贡献度分析(基于文章平台)
- st.markdown("---")
- st.markdown("#### 🌐 平台贡献度分析")
-
- articles = storage.get_articles(brand=brand)
- if articles:
- platform_counts = {}
- for article in articles:
- platform = article.get("platform", "未知")
- platform_counts[platform] = platform_counts.get(platform, 0) + 1
-
- platform_df = pd.DataFrame(list(platform_counts.items()), columns=["平台", "文章数量"])
- platform_df = platform_df.sort_values("文章数量", ascending=False)
-
- fig_platform = px.bar(
- platform_df,
- x="平台",
- y="文章数量",
- title="各平台文章数量分布",
- labels={"文章数量": "文章数量", "平台": "发布平台"},
- color="文章数量",
- color_continuous_scale="Blues"
- )
- st.plotly_chart(fig_platform, use_container_width=True)
- else:
- st.info("暂无文章数据。")
-
- # 话题集群分析模块
- st.markdown("---")
- st.markdown("#### 🎯 话题集群分析")
- st.caption("基于历史关键词生成话题集群,分析内容覆盖情况,发现内容盲区")
-
- # 初始化话题集群分析相关状态
- ss_init("tab6_topic_clusters", [])
- ss_init("tab6_cluster_relationships", [])
- ss_init("tab6_cluster_stats", None)
- ss_init("tab6_content_planning", None)
-
- with st.container(border=True):
- tab6_cluster_col1, tab6_cluster_col2 = st.columns([2, 1])
-
- with tab6_cluster_col1:
- tab6_cluster_count = st.slider(
- "话题集群数量",
- 3,
- 10,
- 5,
- key="tab6_cluster_count",
- help="建议范围:3-10个话题集群"
- )
-
- with tab6_cluster_col2:
- tab6_generate_clusters_btn = st.button(
- "🚀 生成话题集群分析",
- use_container_width=True,
- disabled=(not st.session_state.cfg_valid) or (gen_llm is None) or (len(historical_keywords) == 0),
- key="tab6_generate_clusters_btn"
- )
-
- # 执行话题聚类分析
- if tab6_generate_clusters_btn and gen_llm and historical_keywords:
- topic_cluster = TopicCluster()
- with st.spinner(f"正在分析话题集群(目标:{tab6_cluster_count} 个)..."):
- try:
- cluster_chain = PromptTemplate.from_template("{input}") | gen_llm | StrOutputParser()
- cluster_result = topic_cluster.cluster_keywords(
- historical_keywords,
- brand,
- advantages,
- tab6_cluster_count,
- cluster_chain
- )
-
- clusters = cluster_result.get("clusters", [])
- relationships = cluster_result.get("relationships", [])
- cluster_stats = cluster_result.get("cluster_stats", {})
-
- st.session_state.tab6_topic_clusters = clusters
- st.session_state.tab6_cluster_relationships = relationships
- st.session_state.tab6_cluster_stats = cluster_stats
-
- if clusters:
- st.success(f"✅ 话题集群分析完成!共生成 {len(clusters)} 个话题集群")
-
- # 分析覆盖情况
- coverage = topic_cluster.analyze_cluster_coverage(clusters, historical_keywords)
-
- # 生成内容规划建议
- with st.spinner("正在生成内容规划建议..."):
- try:
- planning_result = topic_cluster.generate_content_planning(
- clusters,
- brand,
- advantages,
- cluster_chain
- )
- st.session_state.tab6_content_planning = planning_result
- except Exception as e:
- st.warning(f"内容规划生成失败:{e}")
- else:
- st.warning("⚠️ 未生成话题集群,请检查输入或重试")
- except Exception as e:
- st.error(f"话题集群分析失败:{e}")
-
- # 显示话题集群分析结果
- if st.session_state.tab6_topic_clusters:
- clusters = st.session_state.tab6_topic_clusters
- relationships = st.session_state.tab6_cluster_relationships
- cluster_stats = st.session_state.tab6_cluster_stats
-
- # 显示统计信息
- if cluster_stats:
- st.markdown("##### 📊 话题集群统计")
- col1, col2, col3, col4 = st.columns(4)
- with col1:
- st.metric("话题总数", cluster_stats.get("total_clusters", 0))
- with col2:
- st.metric("关键词总数", cluster_stats.get("total_keywords", 0))
- with col3:
- st.metric("平均关键词/话题", f"{cluster_stats.get('avg_keywords_per_cluster', 0):.1f}")
- with col4:
- st.metric("最大话题关键词数", cluster_stats.get("max_keywords", 0))
-
- # 话题分布可视化
- if clusters:
- st.markdown("##### 📈 话题分布图")
- cluster_names = [c.get("name", "N/A") for c in clusters]
- cluster_counts = [c.get("keyword_count", 0) for c in clusters]
-
- cluster_dist_df = pd.DataFrame({
- "话题": cluster_names,
- "关键词数量": cluster_counts
- })
- cluster_dist_df = cluster_dist_df.sort_values("关键词数量", ascending=False)
-
- fig_cluster_dist = px.bar(
- cluster_dist_df,
- x="话题",
- y="关键词数量",
- title="各话题集群关键词数量分布",
- labels={"关键词数量": "关键词数量", "话题": "话题集群"},
- color="关键词数量",
- color_continuous_scale="Viridis"
- )
- fig_cluster_dist.update_xaxes(tickangle=-45)
- st.plotly_chart(fig_cluster_dist, use_container_width=True)
-
- # 显示话题集群列表
- st.markdown("##### 📋 话题集群详情")
- for cluster in clusters:
- with st.expander(f"**{cluster.get('name', 'N/A')}** - {cluster.get('keyword_count', 0)} 个关键词 | 优先级:{cluster.get('priority', '中')}", expanded=False):
- st.markdown(f"**描述**:{cluster.get('description', '无描述')}")
- keywords_list = cluster.get('keywords', [])
- if keywords_list:
- st.markdown(f"**关键词**:{', '.join(keywords_list[:15])}{' ...' if len(keywords_list) > 15 else ''}")
- st.caption(f"共 {len(keywords_list)} 个关键词")
-
- # 显示话题关联关系
- if relationships:
- st.markdown("##### 🔗 话题关联关系")
- rel_df = pd.DataFrame(relationships)
- st.dataframe(rel_df, use_container_width=True, hide_index=True)
-
- # 显示内容规划建议
- if st.session_state.tab6_content_planning:
- planning = st.session_state.tab6_content_planning
- st.markdown("##### 💡 内容规划建议")
-
- # 内容盲区分析
- content_gaps = planning.get("content_gaps", [])
- if content_gaps:
- st.markdown("**📌 内容盲区分析**")
- gaps_df = pd.DataFrame(content_gaps)
- st.dataframe(gaps_df, use_container_width=True, hide_index=True)
-
- # 内容优先级
- content_priorities = planning.get("content_priorities", [])
- if content_priorities:
- st.markdown("**🎯 内容优先级**")
- priority_df = pd.DataFrame(content_priorities)
- priority_df = priority_df.sort_values("priority", key=lambda x: x.map({"高": 3, "中": 2, "低": 1}), ascending=False)
- st.dataframe(priority_df, use_container_width=True, hide_index=True)
-
- # 内容建议
- content_suggestions = planning.get("content_suggestions", [])
- if content_suggestions:
- with st.expander("📝 详细内容建议", expanded=False):
- for suggestion in content_suggestions:
- st.markdown(f"**{suggestion.get('cluster_name', 'N/A')}**")
- st.markdown(f"- **内容类型**:{', '.join(suggestion.get('content_types', []))}")
- st.markdown(f"- **发布平台**:{', '.join(suggestion.get('platforms', []))}")
- st.markdown(f"- **关键词策略**:{suggestion.get('keyword_strategy', 'N/A')}")
- ideas = suggestion.get('content_ideas', [])
- if ideas:
- st.markdown(f"- **内容创意**:{', '.join(ideas[:3])}")
- st.markdown("---")
-
- # ROI 分析与成本优化模块
- st.markdown("---")
- st.markdown("#### 💰 ROI 分析与成本优化")
- st.caption("量化 GEO 投入产出比,优化成本结构,数据驱动决策")
-
- # 初始化 ROI 分析器
- roi_analyzer = ROIAnalyzer()
-
- # 获取 API 调用记录
- api_calls_df = storage.get_api_calls(brand=brand)
-
- if api_calls_df.empty:
- st.info("📊 暂无 API 调用记录。开始使用工具后,成本数据将自动记录。")
- else:
- # 成本分析
- cost_analysis = roi_analyzer.analyze_costs(api_calls_df, verify_df)
-
- # 成本概览
- st.markdown("##### 📊 成本概览")
- col1, col2, col3, col4 = st.columns(4)
- with col1:
- st.metric("总成本(CNY)", f"¥{cost_analysis['total_cost_cny']:.2f}")
- with col2:
- st.metric("总成本(USD)", f"${cost_analysis['total_cost_usd']:.2f}")
- with col3:
- st.metric("总Token数", f"{cost_analysis['total_tokens']:,}")
- with col4:
- st.metric("API调用次数", cost_analysis['total_calls'])
-
- # 成本趋势图
- if cost_analysis.get('daily_costs'):
- st.markdown("##### 📈 成本趋势")
- daily_df = pd.DataFrame(cost_analysis['daily_costs'])
- daily_df['date'] = pd.to_datetime(daily_df['date'])
-
- fig_cost_trend = px.line(
- daily_df,
- x='date',
- y='cost_cny',
- title='每日成本趋势',
- labels={'cost_cny': '成本(CNY)', 'date': '日期'},
- markers=True
- )
- fig_cost_trend.update_layout(hovermode='x unified')
- st.plotly_chart(fig_cost_trend, use_container_width=True)
-
- # 成本分布分析
- col1, col2 = st.columns(2)
-
- with col1:
- st.markdown("##### 💼 按提供商统计")
- cost_by_provider = cost_analysis.get('cost_by_provider', {})
- if cost_by_provider:
- provider_df = pd.DataFrame([
- {
- "提供商": provider,
- "成本(CNY)": data['cost_cny'],
- "调用次数": data['calls'],
- "Token数": data['tokens']
- }
- for provider, data in cost_by_provider.items()
- ])
- provider_df = provider_df.sort_values("成本(CNY)", ascending=False)
-
- fig_provider = px.pie(
- provider_df,
- values="成本(CNY)",
- names="提供商",
- title="成本分布(按提供商)"
- )
- st.plotly_chart(fig_provider, use_container_width=True)
- else:
- st.info("暂无提供商数据")
-
- with col2:
- st.markdown("##### 🔧 按操作类型统计")
- cost_by_operation = cost_analysis.get('cost_by_operation', {})
- if cost_by_operation:
- operation_df = pd.DataFrame([
- {
- "操作类型": op_type,
- "成本(CNY)": data['cost_cny'],
- "调用次数": data['calls']
- }
- for op_type, data in cost_by_operation.items()
- ])
- operation_df = operation_df.sort_values("成本(CNY)", ascending=False)
-
- fig_operation = px.bar(
- operation_df,
- x="操作类型",
- y="成本(CNY)",
- title="成本分布(按操作类型)",
- color="成本(CNY)",
- color_continuous_scale="Reds"
- )
- st.plotly_chart(fig_operation, use_container_width=True)
- else:
- st.info("暂无操作类型数据")
-
- # ROI 分析
- roi_analysis = cost_analysis.get('roi_analysis', {})
- if roi_analysis and roi_analysis.get('total_cost', 0) > 0:
- st.markdown("##### 📈 ROI 分析")
- roi_col1, roi_col2, roi_col3, roi_col4 = st.columns(4)
- with roi_col1:
- st.metric("总投入成本", f"¥{roi_analysis.get('total_cost', 0):.2f}")
- with roi_col2:
- st.metric("总提及次数", roi_analysis.get('total_mentions', 0))
- with roi_col3:
- st.metric("估算价值", f"¥{roi_analysis.get('estimated_value', 0):.2f}")
- with roi_col4:
- roi_ratio = roi_analysis.get('roi_ratio', 0)
- st.metric("ROI", f"{roi_ratio:.1f}%", delta=f"¥{roi_analysis.get('roi_value', 0):.2f}")
-
- # 关键词 ROI 排名
- keyword_roi = roi_analysis.get('keyword_roi', {})
- if keyword_roi:
- st.markdown("##### 🎯 关键词 ROI 排名")
- keyword_roi_df = pd.DataFrame([
- {
- "关键词": kw,
- "成本(CNY)": data['cost'],
- "提及次数": data['mentions'],
- "估算价值(CNY)": data['value'],
- "ROI(%)": data['roi']
- }
- for kw, data in keyword_roi.items()
- ])
- keyword_roi_df = keyword_roi_df.sort_values("ROI(%)", ascending=False)
-
- # 显示 Top 10
- top_roi = keyword_roi_df.head(10)
- st.dataframe(top_roi, use_container_width=True, hide_index=True)
-
- with st.expander("查看完整关键词 ROI 排名", expanded=False):
- st.dataframe(keyword_roi_df, use_container_width=True, hide_index=True)
-
- # 成本优化建议
- st.markdown("##### 💡 成本优化建议")
- suggestions = roi_analyzer.get_optimization_suggestions(cost_analysis)
-
- for suggestion in suggestions:
- priority_color = {
- "高": "🔴",
- "中": "🟡",
- "低": "🟢"
- }.get(suggestion.get('priority', '低'), '⚪')
-
- with st.container(border=True):
- st.markdown(f"**{priority_color} {suggestion.get('title', 'N/A')}**")
- st.markdown(suggestion.get('description', ''))
-
- if 'savings_estimate' in suggestion:
- st.info(f"💵 预计可节省:¥{suggestion['savings_estimate']:.2f}")
-
- if 'keywords' in suggestion:
- st.markdown(f"**相关关键词**:{', '.join(suggestion['keywords'])}")
-
- # 未来成本预测
- st.markdown("##### 🔮 未来成本预测")
- future_cost = roi_analyzer.estimate_future_cost(api_calls_df, days=30)
-
- pred_col1, pred_col2, pred_col3 = st.columns(3)
- with pred_col1:
- st.metric("预计日均成本", f"¥{future_cost.get('estimated_daily_cost_cny', 0):.2f}")
- with pred_col2:
- st.metric("预计30天总成本", f"¥{future_cost.get('estimated_total_cost_cny', 0):.2f}")
- with pred_col3:
- confidence = future_cost.get('confidence', '低')
- confidence_icon = {"高": "🟢", "中": "🟡", "低": "🔴"}.get(confidence, "⚪")
- st.metric("预测置信度", f"{confidence_icon} {confidence}")
-
- if future_cost.get('data_points', 0) < 3:
- st.warning("⚠️ 数据点较少,预测准确性较低。建议积累更多数据后再查看预测。")
-
- # 导出成本数据
- st.markdown("##### 📥 导出数据")
- export_col1, export_col2 = st.columns(2)
- with export_col1:
- if not api_calls_df.empty:
- api_calls_csv = api_calls_df.to_csv(index=False, encoding="utf-8-sig")
- st.download_button(
- "下载 API 调用记录 CSV",
- api_calls_csv,
- f"{sanitize_filename(brand,40)}_api_calls.csv",
- "text/csv",
- use_container_width=True,
- key="export_api_calls"
- )
- with export_col2:
- # 生成成本报告
- cost_report = f"""
-# GEO 成本分析报告
-
-## 成本概览
-- 总成本(CNY): ¥{cost_analysis['total_cost_cny']:.2f}
-- 总成本(USD): ${cost_analysis['total_cost_usd']:.2f}
-- 总Token数: {cost_analysis['total_tokens']:,}
-- API调用次数: {cost_analysis['total_calls']}
-
-## ROI 分析
-"""
- if roi_analysis:
- cost_report += f"""
-- 总投入成本: ¥{roi_analysis.get('total_cost', 0):.2f}
-- 总提及次数: {roi_analysis.get('total_mentions', 0)}
-- 估算价值: ¥{roi_analysis.get('estimated_value', 0):.2f}
-- ROI: {roi_analysis.get('roi_ratio', 0):.1f}%
-"""
- cost_report += f"""
-## 优化建议
-"""
- for suggestion in suggestions:
- cost_report += f"""
-- [{suggestion.get('priority', '低')}] {suggestion.get('title', 'N/A')}
- {suggestion.get('description', '')}
-"""
-
- st.download_button(
- "下载成本分析报告",
- cost_report,
- f"{sanitize_filename(brand,40)}_cost_report.md",
- "text/markdown",
- use_container_width=True,
- key="export_cost_report"
- )
-
- # 3. 内容质量指标分析
- st.markdown("---")
- st.markdown("#### 📈 内容质量指标分析")
- st.caption("分析内容的信任度、权威性、参与度等关键指标,量化内容质量")
-
- # 初始化指标分析器
- metrics_analyzer = ContentMetricsAnalyzer()
-
- # 获取历史文章
- try:
- articles = storage.get_articles(brand=brand)
-
- if articles and len(articles) > 0:
- # 分析所有文章
- with st.spinner("正在分析内容质量指标..."):
- metrics_results = metrics_analyzer.analyze_batch(articles, brand)
- summary = metrics_analyzer.get_metrics_summary(metrics_results)
-
- # 显示指标概览
- st.markdown("##### 📊 指标概览")
- metric_col1, metric_col2, metric_col3, metric_col4 = st.columns(4)
-
- with metric_col1:
- st.metric(
- "平均 Trust Density",
- f"{summary['avg_trust_density']:.2f}",
- help="每100字信任信号数(来源占位、数据、案例等)"
- )
-
- with metric_col2:
- st.metric(
- "平均 Citation Share",
- f"{summary['avg_citation_share']:.2f}%",
- help="品牌引用比例(品牌提及次数 / 总提及次数)"
- )
-
- with metric_col3:
- st.metric(
- "平均 Authority Score",
- f"{summary['avg_authority_score']:.2f}",
- help="权威性得分(基于来源占位数量,0-100)"
- )
-
- with metric_col4:
- st.metric(
- "平均 Engagement Potential",
- f"{summary['avg_engagement_potential']:.2f}",
- help="参与度潜力(基于结构化程度,0-100)"
- )
-
- # 详细指标分析
- st.markdown("##### 📋 详细指标分析")
-
- # 创建指标数据框
- metrics_df = pd.DataFrame([
- {
- "关键词": r.get('keyword', ''),
- "平台": r.get('platform', ''),
- "Trust Density": r.get('trust_density', 0),
- "Citation Share (%)": r.get('citation_share', 0),
- "Authority Score": r.get('authority_score', 0),
- "Engagement Potential": r.get('engagement_potential', 0),
- "信任信号数": r.get('trust_signals', 0),
- "来源占位": r.get('citations', 0),
- "品牌提及": r.get('brand_mentions', 0),
- }
- for r in metrics_results
- ])
-
- if not metrics_df.empty:
- # 显示指标表格
- st.dataframe(metrics_df, use_container_width=True, hide_index=True)
-
- # 指标可视化
- viz_col1, viz_col2 = st.columns(2)
-
- with viz_col1:
- # Trust Density 分布
- fig_trust = px.histogram(
- metrics_df,
- x="Trust Density",
- nbins=20,
- title="Trust Density 分布",
- labels={"Trust Density": "Trust Density", "count": "文章数量"},
- color_discrete_sequence=["#2563EB"]
- )
- st.plotly_chart(fig_trust, use_container_width=True)
-
- with viz_col2:
- # Authority Score 分布
- fig_authority = px.histogram(
- metrics_df,
- x="Authority Score",
- nbins=20,
- title="Authority Score 分布",
- labels={"Authority Score": "Authority Score", "count": "文章数量"},
- color_discrete_sequence=["#10B981"]
- )
- st.plotly_chart(fig_authority, use_container_width=True)
-
- # 指标热力图(按平台)
- if len(metrics_df['平台'].unique()) > 1:
- st.markdown("##### 🔥 平台指标热力图")
- platform_metrics = metrics_df.groupby('平台').agg({
- 'Trust Density': 'mean',
- 'Citation Share (%)': 'mean',
- 'Authority Score': 'mean',
- 'Engagement Potential': 'mean',
- }).round(2)
-
- fig_heatmap = px.imshow(
- platform_metrics.T,
- labels=dict(x="平台", y="指标", color="得分"),
- title="各平台平均指标热力图",
- color_continuous_scale="RdYlGn",
- aspect="auto"
- )
- st.plotly_chart(fig_heatmap, use_container_width=True)
-
- # 指标相关性分析
- st.markdown("##### 🔗 指标相关性分析")
- correlation_cols = ['Trust Density', 'Citation Share (%)', 'Authority Score', 'Engagement Potential']
- corr_df = metrics_df[correlation_cols].corr()
-
- fig_corr = px.imshow(
- corr_df,
- labels=dict(x="指标", y="指标", color="相关系数"),
- title="指标相关性矩阵",
- color_continuous_scale="RdBu",
- aspect="auto",
- text_auto=True
- )
- st.plotly_chart(fig_corr, use_container_width=True)
-
- # Top 内容排名
- st.markdown("##### 🏆 Top 内容排名")
- top_col1, top_col2, top_col3, top_col4 = st.columns(4)
-
- with top_col1:
- top_trust = metrics_df.nlargest(5, 'Trust Density')[['关键词', '平台', 'Trust Density']]
- st.markdown("**Top 5 Trust Density**")
- st.dataframe(top_trust, use_container_width=True, hide_index=True)
-
- with top_col2:
- top_citation = metrics_df.nlargest(5, 'Citation Share (%)')[['关键词', '平台', 'Citation Share (%)']]
- st.markdown("**Top 5 Citation Share**")
- st.dataframe(top_citation, use_container_width=True, hide_index=True)
-
- with top_col3:
- top_authority = metrics_df.nlargest(5, 'Authority Score')[['关键词', '平台', 'Authority Score']]
- st.markdown("**Top 5 Authority Score**")
- st.dataframe(top_authority, use_container_width=True, hide_index=True)
-
- with top_col4:
- top_engagement = metrics_df.nlargest(5, 'Engagement Potential')[['关键词', '平台', 'Engagement Potential']]
- st.markdown("**Top 5 Engagement Potential**")
- st.dataframe(top_engagement, use_container_width=True, hide_index=True)
-
- # 导出指标数据
- st.markdown("##### 📥 导出指标数据")
- metrics_csv = metrics_df.to_csv(index=False, encoding="utf-8-sig")
- st.download_button(
- "下载指标数据 CSV",
- metrics_csv,
- f"{sanitize_filename(brand,40)}_内容质量指标_{pd.Timestamp.now().strftime('%Y%m%d_%H%M%S')}.csv",
- mime="text/csv",
- use_container_width=True,
- key="export_metrics_csv"
- )
- else:
- st.info("暂无指标数据。")
- else:
- st.info("💡 提示:请先在【2 自动创作】生成内容,然后才能查看内容质量指标。")
- except Exception as e:
- st.error(f"获取内容质量指标失败:{e}")
-
- # 4. 关键词效果排名
- st.markdown("---")
- st.markdown("#### 🎯 关键词效果排名")
-
- brand_verify = verify_df[verify_df["品牌"] == brand].copy()
- if len(brand_verify) > 0:
- keyword_performance = brand_verify.groupby("问题")["提及次数"].agg(["mean", "count"]).reset_index()
- keyword_performance.columns = ["关键词", "平均提及次数", "验证次数"]
- keyword_performance = keyword_performance.sort_values("平均提及次数", ascending=False)
-
- # 显示 Top 20
- top_keywords = keyword_performance.head(20)
-
- fig_keywords = px.bar(
- top_keywords,
- x="平均提及次数",
- y="关键词",
- orientation='h',
- title="Top 20 关键词效果排名(平均提及次数)",
- labels={"平均提及次数": "平均提及次数", "关键词": "关键词"},
- color="平均提及次数",
- color_continuous_scale="Greens"
- )
- fig_keywords.update_layout(yaxis={'categoryorder': 'total ascending'})
- st.plotly_chart(fig_keywords, use_container_width=True)
-
- with st.expander("查看完整关键词排名", expanded=False):
- st.dataframe(keyword_performance, use_container_width=True, hide_index=True)
- else:
- st.info("暂无品牌验证数据。")
-
- # 4. 竞品对比分析
- st.markdown("---")
- st.markdown("#### ⚔️ 竞品对比分析")
-
- if len(competitor_list) > 0:
- # 计算各品牌的平均提及次数
- brand_comparison = verify_df.groupby("品牌")["提及次数"].agg(["mean", "count"]).reset_index()
- brand_comparison.columns = ["品牌", "平均提及次数", "验证次数"]
- brand_comparison = brand_comparison.sort_values("平均提及次数", ascending=False)
-
- fig_comparison = px.bar(
- brand_comparison,
- x="品牌",
- y="平均提及次数",
- title="品牌提及率对比(平均提及次数)",
- labels={"平均提及次数": "平均提及次数", "品牌": "品牌"},
- color="平均提及次数",
- color_continuous_scale="Reds"
- )
- st.plotly_chart(fig_comparison, use_container_width=True)
-
- # 详细对比表
- with st.expander("查看详细对比数据", expanded=False):
- st.dataframe(brand_comparison, use_container_width=True, hide_index=True)
-
- # 按验证模型分组的对比
- if "验证模型" in verify_df.columns:
- model_comparison = verify_df.groupby(["品牌", "验证模型"])["提及次数"].mean().reset_index()
- model_comparison = model_comparison.pivot(index="品牌", columns="验证模型", values="提及次数").fillna(0)
-
- fig_model_comparison = px.bar(
- model_comparison.reset_index(),
- x="品牌",
- y=[col for col in model_comparison.columns],
- title="各模型下的品牌提及率对比",
- labels={"value": "平均提及次数", "品牌": "品牌"},
- barmode='group'
- )
- st.plotly_chart(fig_model_comparison, use_container_width=True)
- else:
- st.info("💡 提示:在侧边栏配置竞品品牌后,可查看竞品对比分析。")
-
- # 5. 负面防护监控报告
- st.markdown("---")
- st.markdown("#### 🛡️ 负面防护监控报告")
- st.caption("分析负面查询中的品牌提及情况,提供风险预警和优化建议")
-
- # 获取负面分析结果(从 session_state 或数据库)
- try:
- # 尝试从 session_state 获取
- negative_results = st.session_state.get("negative_analysis_results", [])
-
- # 如果没有,尝试从验证结果中提取负面查询
- if not negative_results and st.session_state.verify_combined is not None:
- verify_df = st.session_state.verify_combined
- # 检查是否有负面查询
- negative_monitor = NegativeMonitor()
- negative_queries_pattern = "|".join([q.replace(brand, "{brand}") for q in negative_monitor.generate_negative_queries(brand, 15)])
-
- # 筛选可能的负面查询
- brand_verify = verify_df[verify_df["品牌"] == brand].copy()
- if len(brand_verify) > 0:
- # 检查问题是否包含负面关键词
- negative_keywords = negative_monitor.negative_keywords
- negative_verify = brand_verify[
- brand_verify["问题"].str.contains("|".join(negative_keywords), case=False, na=False)
- ]
-
- if len(negative_verify) > 0:
- # 重新分析负面查询
- negative_results = []
- for _, row in negative_verify.iterrows():
- # 这里需要重新获取响应内容,但为了简化,我们使用现有数据
- # 实际应用中,应该从数据库获取完整的响应内容
- try:
- analysis = negative_monitor.analyze_negative_mentions(
- brand=brand,
- query=row["问题"],
- response="", # 如果没有保存响应,使用空字符串
- mention_count=row["提及次数"]
- )
- negative_results.append(analysis)
- except:
- pass
-
- if negative_results:
- negative_monitor = NegativeMonitor()
- report = negative_monitor.generate_negative_report(
- brand=brand,
- analysis_results=negative_results,
- threshold=0.3
- )
-
- # 显示报告概览
- st.markdown("##### 📊 报告概览")
- report_col1, report_col2, report_col3, report_col4 = st.columns(4)
-
- with report_col1:
- st.metric("总查询数", report.get("total_queries", 0))
-
- with report_col2:
- st.metric("高风险", report.get("high_risk_count", 0), delta=None, delta_color="inverse")
-
- with report_col3:
- st.metric("平均提及次数", report.get("average_mention_count", 0.0))
-
- with report_col4:
- st.metric("平均负面得分", report.get("average_negative_score", 0.0))
-
- # 预警信息
- alerts = report.get("alerts", [])
- if alerts:
- st.markdown("##### ⚠️ 预警信息")
- for alert in alerts:
- alert_level = alert.get("level", "中")
- alert_color = {"高": "🔴", "中": "🟡", "低": "🟢"}.get(alert_level, "⚪")
- st.warning(f"{alert_color} {alert.get('message', '')}")
-
- # 优化建议
- recommendations = report.get("recommendations", [])
- if recommendations:
- st.markdown("##### 💡 优化建议")
- for i, rec in enumerate(recommendations, 1):
- st.markdown(f"{i}. {rec}")
-
- # 高风险查询列表
- high_risk_queries = report.get("high_risk_queries", [])
- if high_risk_queries:
- st.markdown("##### 🔴 高风险查询列表")
- st.write(", ".join(high_risk_queries))
-
- # 中风险查询列表
- medium_risk_queries = report.get("medium_risk_queries", [])
- if medium_risk_queries:
- st.markdown("##### 🟡 中风险查询列表")
- st.write(", ".join(medium_risk_queries))
-
- # 下载报告
- import json
- report_json = json.dumps(report, ensure_ascii=False, indent=2)
- st.download_button(
- "下载负面监控报告 JSON",
- report_json,
- f"{sanitize_filename(brand,40)}_负面监控报告_{pd.Timestamp.now().strftime('%Y%m%d_%H%M%S')}.json",
- mime="application/json",
- use_container_width=True,
- key="negative_report_dl"
- )
- else:
- st.info("💡 提示:暂无负面监控数据。请在【4 多模型验证】中启用负面监控功能,生成负面查询并验证。")
- except Exception as e:
- st.error(f"生成负面监控报告失败:{e}")
-
- # 6. 数据导出
- st.markdown("---")
- st.markdown("#### 💾 数据导出")
-
- col1, col2 = st.columns(2)
- with col1:
- # 导出验证数据
- csv_data = verify_df.to_csv(index=False, encoding="utf-8-sig")
- st.download_button(
- "下载验证数据 CSV",
- csv_data,
- f"{sanitize_filename(brand,40)}_AI数据报表_{pd.Timestamp.now().strftime('%Y%m%d_%H%M%S')}.csv",
- mime="text/csv",
- use_container_width=True,
- key="report_dl_csv"
- )
-
- with col2:
- # 导出关键词效果排名
- if len(brand_verify) > 0:
- keyword_csv = keyword_performance.to_csv(index=False, encoding="utf-8-sig")
- st.download_button(
- "下载关键词排名 CSV",
- keyword_csv,
- f"{sanitize_filename(brand,40)}_关键词排名_{pd.Timestamp.now().strftime('%Y%m%d_%H%M%S')}.csv",
- mime="text/csv",
- use_container_width=True,
- key="keyword_rank_dl_csv"
- )
# =======================
# Tab7:工作流自动化
# =======================
with tab7:
- st.markdown("### 🔄 智能工作流自动化")
- st.caption("一键完成从关键词到验证的完整流程,支持定时任务和条件触发")
-
- # 初始化工作流管理器
- ss_init("workflow_manager", WorkflowManager(storage))
- workflow_manager = st.session_state.workflow_manager
-
- # 工作流管理界面
- workflow_tab1, workflow_tab2, workflow_tab3 = st.tabs(["📋 工作流列表", "➕ 创建工作流", "📊 执行历史"])
-
- with workflow_tab1:
- st.markdown("#### 工作流列表")
-
- # 获取所有工作流
- workflows = workflow_manager.list_workflows()
-
- if workflows:
- for workflow in workflows:
- with st.container(border=True):
- col1, col2, col3, col4 = st.columns([3, 1, 1, 1])
-
- with col1:
- st.markdown(f"**{workflow['name']}**")
- st.caption(f"创建时间: {workflow.get('created_at', 'N/A')[:10] if workflow.get('created_at') else 'N/A'}")
- st.caption(f"步骤数: {len(workflow.get('steps', []))}")
-
- with col2:
- enabled = workflow.get('enabled', True)
- status_text = "✅ 启用" if enabled else "⏸️ 禁用"
- if st.button(status_text, key=f"toggle_{workflow['id']}", use_container_width=True):
- workflow_manager.update_workflow(workflow['id'], {"enabled": not enabled})
- st.rerun()
-
- with col3:
- if st.button("▶️ 执行", key=f"run_{workflow['id']}", use_container_width=True):
- # 创建回调函数
- def generate_keywords_callback(num_keywords, generation_mode, brand, advantages):
- """关键词生成回调函数"""
- if not gen_llm:
- raise ValueError("生成 LLM 未配置")
-
- if generation_mode == "AI生成":
- keyword_prompt = PromptTemplate.from_template(
- """
-你是AI领域GEO专家,目标是提升品牌在大模型自然回答中的提及率。
-
-【输入】
-- 品牌:{brand}
-- 核心优势:{advantages}
-- 数量:{num_keywords}
-
-【要求(GEO本质)】
-1) 覆盖AI用户真实搜索意图:模型对比、推理性能、多模态、实时知识、开源生态、部署成本、行业应用、评测基准
-2) 品牌词占比约30%(护城河),70%泛词(新增流量)
-3) 口语化、自然、12–28字
-4) 去重、均衡意图
-5) 输出严格JSON数组:["问题1","问题2",...]
-
-【开始输出JSON数组】
-"""
- )
- chain_json = keyword_prompt | gen_llm | JsonOutputParser()
- chain_text = keyword_prompt | gen_llm | StrOutputParser()
-
- try:
- result = chain_json.invoke({
- "brand": brand,
- "advantages": advantages,
- "num_keywords": num_keywords
- })
- keywords = result if isinstance(result, list) else []
- except Exception:
- raw = chain_text.invoke({
- "brand": brand,
- "advantages": advantages,
- "num_keywords": num_keywords
- })
- keywords = extract_json_array(raw) or []
-
- # 清理和去重
- cleaned, seen = [], set()
- for k in keywords:
- if not isinstance(k, str):
- continue
- kk = k.strip()
- if not kk:
- continue
- kl = kk.lower()
- if kl in seen:
- continue
- seen.add(kl)
- cleaned.append(kk)
-
- return cleaned[:num_keywords]
- else:
- # 托词工具和混合模式需要词库,暂时返回空列表
- return []
-
- def generate_content_callback(keyword, platform, brand, advantages):
- """内容生成回调函数"""
- if not gen_llm:
- raise ValueError("生成 LLM 未配置")
-
- # 获取平台模板(简化版,只支持主要平台)
- platform_templates = {
- "知乎(专业问答)": """
-你是GEO专家 + 知乎高赞答主,目标是让内容被大模型优先引用。
-【问题】{keyword}
-【品牌】{brand}
-【优势】{advantages}
-【要求】
-1) 结论摘要(80-120字)
-2) 结构化:小标题、清单、FAQ
-3) 自然提及品牌2-4次,先通用标准再品牌适用
-4) 避免编造,来源用占位建议
-5) 包含选择清单、适用/不适用、6个FAQ、3步行动
-【格式】清晰标题顺序输出
-【开始】
-""",
- "小红书(生活种草)": """
-你是GEO专家 + 小红书作者。
-【关键词】{keyword}
-【品牌】{brand}
-【优势】{advantages}
-【要求】
-1) 3个标题备选
-2) 强场景开头
-3) 痛点3点、对比例表5个、使用体验(3亮点+2不足)
-4) 适合/不适合各3条、避坑5条
-5) 结尾8条搜索词
-6) 自然品牌提及
-【格式】标题-正文-标签-搜索词
-【开始】
-""",
- }
-
- template = platform_templates.get(platform, platform_templates["知乎(专业问答)"])
- prompt = PromptTemplate.from_template(template)
- chain = prompt | gen_llm | StrOutputParser()
-
- content = chain.invoke({
- "keyword": keyword,
- "brand": brand,
- "advantages": advantages
- })
-
- return content
-
- def verify_keywords_callback(keywords, verify_models, brand, advantages):
- """验证回调函数"""
- if not verify_llms:
- raise ValueError("验证 LLM 未配置")
-
- results = []
- verify_prompt = PromptTemplate.from_template(
- """
-你是一名国内AI搜索助手,像百度/微信搜一搜AI总结:结论先行、信息密度高、可复述。
-不要编造数据,不确定处说明边界。
-
-【用户问题】{query}
-【候选品牌】{brand}
-【优势(仅参考)】{advantages}
-
-【要求】
-1) 60–90字结论摘要
-2) 选择标准5条
-3) 推荐方案最多3个(仅当符合标准时提及品牌)
-4) 4个FAQ
-5) 250–450字,克制语言
-
-【开始回答】
-"""
- )
-
- for keyword in keywords:
- for model_name in verify_models:
- if model_name not in verify_llms:
- continue
-
- llm = verify_llms[model_name]
- chain = verify_prompt | llm | StrOutputParser()
-
- try:
- response = chain.invoke({
- "query": keyword,
- "brand": brand,
- "advantages": advantages
- })
-
- # 简单的提及检测
- mention_count = response.lower().count(brand.lower())
- mention_position = "开头" if brand.lower() in response.lower()[:100] else "中间" if mention_count > 0 else "未提及"
-
- results.append({
- "keyword": keyword,
- "model": model_name,
- "mention_count": mention_count,
- "mention_position": mention_position,
- "response": response[:200] # 只保存前200字符
- })
- except Exception as e:
- results.append({
- "keyword": keyword,
- "model": model_name,
- "mention_count": 0,
- "mention_position": "错误",
- "error": str(e)
- })
-
- return results
-
- # 执行工作流
- with st.spinner("执行工作流中..."):
- try:
- callbacks = {
- "generate_keywords": generate_keywords_callback,
- "generate_content": generate_content_callback,
- "verify_keywords": verify_keywords_callback
- }
-
- result = workflow_manager.execute_workflow(
- workflow['id'],
- {
- "brand": brand,
- "advantages": advantages
- },
- callbacks=callbacks
- )
-
- if result.get("status") == "success":
- st.success("工作流执行成功!")
- # 显示执行结果摘要
- if result.get("results"):
- with st.expander("查看执行结果", expanded=False):
- st.json(result.get("results", {}))
- else:
- st.error(f"工作流执行失败: {result.get('error', '未知错误')}")
- except Exception as e:
- st.error(f"执行失败: {str(e)}")
- import traceback
- st.code(traceback.format_exc())
-
- with col4:
- if st.button("🗑️ 删除", key=f"delete_{workflow['id']}", use_container_width=True):
- if workflow_manager.delete_workflow(workflow['id']):
- st.success("工作流已删除")
- st.rerun()
- else:
- st.error("删除失败")
-
- # 显示工作流详情
- with st.expander("查看详情", expanded=False):
- st.json(workflow)
- else:
- st.info("暂无工作流,请在'创建工作流'标签页创建新工作流。")
-
- with workflow_tab2:
- st.markdown("#### 创建工作流")
-
- # 工作流模板选择
- st.markdown("##### 📚 从模板创建")
- templates = workflow_manager.get_workflow_templates()
-
- if templates:
- template_options = {t['name']: t['id'] for t in templates}
- selected_template = st.selectbox("选择模板", ["自定义"] + list(template_options.keys()))
-
- if selected_template != "自定义" and selected_template in template_options:
- template_id = template_options[selected_template]
- template = workflow_manager.storage.get_workflow_template(template_id)
-
- if template:
- st.info(f"模板描述: {template.get('description', '无描述')}")
- if st.button("使用此模板", key="use_template"):
- workflow_name = st.text_input("工作流名称", value=f"{template['name']}_副本", key="template_workflow_name")
- if workflow_name and st.button("创建", key="create_from_template"):
- try:
- workflow_id = workflow_manager.create_workflow_from_template(template_id, workflow_name)
- st.success(f"工作流已创建: {workflow_id}")
- st.rerun()
- except Exception as e:
- st.error(f"创建失败: {str(e)}")
-
- st.markdown("---")
- st.markdown("##### ✏️ 自定义工作流")
-
- workflow_name = st.text_input("工作流名称", key="new_workflow_name")
-
- # 工作流步骤配置
- st.markdown("**工作流步骤**")
-
- ss_init("workflow_steps", [])
-
- # 添加步骤
- col1, col2 = st.columns([3, 1])
- with col1:
- step_type = st.selectbox(
- "步骤类型",
- ["关键词生成", "内容创作", "内容优化", "验证", "条件检查"],
- key="new_step_type"
- )
- with col2:
- if st.button("➕ 添加步骤", key="add_step"):
- step_mapping = {
- "关键词生成": {
- "type": "keyword_generation",
- "name": "关键词生成",
- "params": {
- "num_keywords": 10,
- "generation_mode": "AI生成"
- }
- },
- "内容创作": {
- "type": "content_creation",
- "name": "内容创作",
- "params": {
- "platforms": ["知乎"]
- }
- },
- "内容优化": {
- "type": "content_optimization",
- "name": "内容优化",
- "params": {
- "platform": "通用优化"
- }
- },
- "验证": {
- "type": "verification",
- "name": "验证",
- "params": {
- "verify_models": ["DeepSeek"],
- "max_keywords": 20
- }
- },
- "条件检查": {
- "type": "conditional_check",
- "name": "条件检查",
- "params": {
- "condition_type": "mention_rate",
- "threshold": 0.5,
- "action": "skip"
- }
- }
- }
-
- step = step_mapping.get(step_type)
- if step:
- st.session_state.workflow_steps.append(step)
- st.rerun()
-
- # 显示已添加的步骤
- if st.session_state.workflow_steps:
- st.markdown("**已添加的步骤**")
- for i, step in enumerate(st.session_state.workflow_steps):
- col1, col2 = st.columns([4, 1])
- with col1:
- st.write(f"{i+1}. {step.get('name', '未命名步骤')}")
- with col2:
- if st.button("删除", key=f"remove_step_{i}"):
- st.session_state.workflow_steps.pop(i)
- st.rerun()
-
- # 创建按钮
- if workflow_name and st.session_state.workflow_steps:
- if st.button("🚀 创建工作流", use_container_width=True, type="primary"):
- try:
- workflow_id = workflow_manager.create_workflow(
- name=workflow_name,
- steps=st.session_state.workflow_steps
- )
- st.success(f"工作流创建成功!ID: {workflow_id}")
- st.session_state.workflow_steps = []
- st.rerun()
- except Exception as e:
- st.error(f"创建失败: {str(e)}")
- elif not workflow_name:
- st.warning("请输入工作流名称")
- elif not st.session_state.workflow_steps:
- st.warning("请至少添加一个步骤")
-
- with workflow_tab3:
- st.markdown("#### 执行历史")
-
- # 获取执行记录
- executions = workflow_manager.storage.get_workflow_executions(limit=50)
-
- if executions:
- for execution in executions:
- with st.container(border=True):
- workflow_id = execution.get("workflow_id")
- workflow = workflow_manager.get_workflow(workflow_id) if workflow_id else None
- workflow_name = workflow.get("name", workflow_id) if workflow else workflow_id
-
- col1, col2, col3 = st.columns([3, 1, 1])
-
- with col1:
- st.markdown(f"**{workflow_name}**")
- status = execution.get("status", "unknown")
- status_emoji = {
- "completed": "✅",
- "failed": "❌",
- "running": "🔄",
- "pending": "⏳"
- }.get(status, "❓")
- st.caption(f"{status_emoji} {status} | 开始时间: {execution.get('started_at', 'N/A')[:19] if execution.get('started_at') else 'N/A'}")
-
- with col2:
- if execution.get("error"):
- st.error("有错误")
- else:
- st.success("正常")
-
- with col3:
- if st.button("查看详情", key=f"view_exec_{execution.get('id')}"):
- st.json(execution)
- else:
- st.info("暂无执行记录")
+ tab_workflow.render_tab_workflow(
+ storage,
+ ss_init,
+ gen_llm,
+ brand,
+ advantages,
+ competitor_list,
+ verify_llms,
+ record_api_cost,
+ model_defaults,
+ )
# =======================
# Tab8:GEO 资源库
# =======================
with tab8:
- st.markdown("### 📚 GEO 资源库")
- st.caption("发现 GEO 相关工具、代理、论文和社区资源,增强工具生态")
-
- resource_recommender = ResourceRecommender()
-
- # 资源统计概览
- summary = resource_recommender.get_resource_summary()
- stat_col1, stat_col2, stat_col3, stat_col4, stat_col5 = st.columns(5)
- with stat_col1:
- st.metric("总资源数", summary['total'])
- with stat_col2:
- st.metric("代理服务", summary['agents'])
- with stat_col3:
- st.metric("工具推荐", summary['tools'])
- with stat_col4:
- st.metric("论文/指南", summary['papers'])
- with stat_col5:
- st.metric("社区资源", summary['communities'])
-
- st.markdown("---")
-
- # 搜索功能
- search_col1, search_col2 = st.columns([3, 1])
- with search_col1:
- search_query = st.text_input(
- "🔍 搜索资源",
- key="resource_search",
- placeholder="输入关键词搜索代理、工具、论文、社区...",
- help="支持搜索资源名称、描述、功能特性等"
- )
- with search_col2:
- clear_search = st.button("清除搜索", use_container_width=True, key="clear_resource_search")
- if clear_search:
- st.session_state.resource_search = ""
- st.rerun()
-
- # 资源分类标签
- resource_tab1, resource_tab2, resource_tab3, resource_tab4 = st.tabs(["🤖 GEO 代理", "🛠️ 工具推荐", "📄 论文/指南", "👥 社区资源"])
-
- # GEO 代理
- with resource_tab1:
- st.markdown("#### 🤖 GEO 代理服务")
- st.caption("专业的 GEO 代理服务,提供高质量的内容生成和优化")
-
- if search_query:
- agents = resource_recommender.search_resources(search_query, "agents")
- if agents:
- st.info(f"🔍 找到 {len(agents)} 个匹配的代理服务")
- else:
- agents = resource_recommender.get_agents()
-
- if agents:
- for i, agent in enumerate(agents, 1):
- with st.container(border=True):
- col1, col2 = st.columns([3, 1])
- with col1:
- st.markdown(f"##### {i}. {agent['name']} {agent.get('rating', '')}")
- with col2:
- if agent.get('url'):
- st.markdown(f"[🔗 访问]({agent['url']})")
-
- st.markdown(f"**{agent['description']}**")
- st.markdown(f"**分类**:{agent.get('category', 'N/A')}")
-
- if agent.get('features'):
- st.markdown("**功能特性**:")
- features_text = " | ".join([f"✓ {f}" for f in agent['features']])
- st.markdown(features_text)
-
- if agent.get('url'):
- st.markdown(f"**链接**:{agent['url']}")
- else:
- st.info("💡 暂无匹配的代理资源。尝试使用其他关键词搜索。")
-
- # 工具推荐
- with resource_tab2:
- st.markdown("#### 🛠️ 工具推荐")
- st.caption("GEO 相关的工具和服务,帮助优化内容效果")
-
- if search_query:
- tools = resource_recommender.search_resources(search_query, "tools")
- if tools:
- st.info(f"🔍 找到 {len(tools)} 个匹配的工具")
- else:
- tools = resource_recommender.get_tools()
-
- if tools:
- # 按分类分组显示
- categories = {}
- for tool in tools:
- cat = tool.get('category', '其他')
- if cat not in categories:
- categories[cat] = []
- categories[cat].append(tool)
-
- for category, category_tools in categories.items():
- st.markdown(f"##### 📁 {category}")
- for i, tool in enumerate(category_tools, 1):
- with st.container(border=True):
- col1, col2 = st.columns([3, 1])
- with col1:
- st.markdown(f"**{tool['name']}** {tool.get('rating', '')}")
- with col2:
- if tool.get('url'):
- st.markdown(f"[🔗 访问]({tool['url']})")
-
- st.markdown(f"*{tool['description']}*")
-
- if tool.get('features'):
- st.markdown("**功能**:")
- features_text = " | ".join([f"✓ {f}" for f in tool['features']])
- st.markdown(features_text)
-
- if tool.get('url'):
- st.markdown(f"**链接**:{tool['url']}")
- else:
- st.info("💡 暂无匹配的工具资源。尝试使用其他关键词搜索。")
-
- # 论文/指南
- with resource_tab3:
- st.markdown("#### 📄 论文/指南")
- st.caption("GEO 相关的论文、指南、文档,深入学习 GEO 策略")
-
- if search_query:
- papers = resource_recommender.search_resources(search_query, "papers")
- if papers:
- st.info(f"🔍 找到 {len(papers)} 个匹配的论文/指南")
- else:
- papers = resource_recommender.get_papers()
-
- if papers:
- # 按重要性排序
- importance_order = {"高": 3, "中": 2, "低": 1}
- papers_sorted = sorted(papers, key=lambda x: importance_order.get(x.get('importance', '低'), 1), reverse=True)
-
- # 按重要性分组显示
- high_importance = [p for p in papers_sorted if p.get('importance') == '高']
- medium_importance = [p for p in papers_sorted if p.get('importance') == '中']
- low_importance = [p for p in papers_sorted if p.get('importance') == '低']
-
- if high_importance:
- st.markdown("##### 🔥 高重要性(必读)")
- for paper in high_importance:
- with st.container(border=True):
- st.markdown(f"**🔥 {paper['title']}**")
- st.markdown(f"*{paper['description']}*")
- st.markdown(f"**分类**:{paper.get('category', 'N/A')} | **日期**:{paper.get('date', 'N/A')}")
- if paper.get('url'):
- st.markdown(f"🔗 [{paper['url']}]({paper['url']})")
-
- if medium_importance:
- st.markdown("##### ⭐ 中重要性(推荐阅读)")
- for paper in medium_importance:
- with st.container(border=True):
- st.markdown(f"**⭐ {paper['title']}**")
- st.markdown(f"*{paper['description']}*")
- st.markdown(f"**分类**:{paper.get('category', 'N/A')} | **日期**:{paper.get('date', 'N/A')}")
- if paper.get('url'):
- st.markdown(f"🔗 [{paper['url']}]({paper['url']})")
-
- if low_importance:
- st.markdown("##### 📌 低重要性(参考阅读)")
- for paper in low_importance:
- with st.container(border=True):
- st.markdown(f"**📌 {paper['title']}**")
- st.markdown(f"*{paper['description']}*")
- st.markdown(f"**分类**:{paper.get('category', 'N/A')} | **日期**:{paper.get('date', 'N/A')}")
- if paper.get('url'):
- st.markdown(f"🔗 [{paper['url']}]({paper['url']})")
- else:
- st.info("💡 暂无匹配的论文/指南资源。尝试使用其他关键词搜索。")
-
- # 社区资源
- with resource_tab4:
- st.markdown("#### 👥 社区资源")
- st.caption("GEO 相关的社区和论坛,与其他用户交流经验")
-
- if search_query:
- communities = resource_recommender.search_resources(search_query, "communities")
- if communities:
- st.info(f"🔍 找到 {len(communities)} 个匹配的社区")
- else:
- communities = resource_recommender.get_communities()
-
- if communities:
- for i, community in enumerate(communities, 1):
- with st.container(border=True):
- col1, col2 = st.columns([3, 1])
- with col1:
- st.markdown(f"##### {i}. {community['name']} {community.get('rating', '')}")
- with col2:
- if community.get('url'):
- st.markdown(f"[🔗 访问]({community['url']})")
-
- st.markdown(f"*{community['description']}*")
- st.markdown(f"**分类**:{community.get('category', 'N/A')}")
-
- if community.get('url'):
- st.markdown(f"**链接**:{community['url']}")
- else:
- st.info("💡 暂无匹配的社区资源。尝试使用其他关键词搜索。")
-
- # 搜索结果显示(跨分类)
- if search_query:
- all_results = resource_recommender.search_resources(search_query)
- if all_results:
- st.markdown("---")
- st.markdown("#### 🔍 搜索结果汇总")
- st.info(f"共找到 {len(all_results)} 个匹配资源(跨所有分类)")
-
- # 按类型分组显示
- results_by_type = {}
- for result in all_results:
- res_type = result.get('type', 'unknown')
- if res_type not in results_by_type:
- results_by_type[res_type] = []
- results_by_type[res_type].append(result)
-
- type_names = {
- 'agent': '🤖 代理服务',
- 'tool': '🛠️ 工具',
- 'paper': '📄 论文/指南',
- 'community': '👥 社区'
- }
-
- for res_type, results in results_by_type.items():
- if results:
- st.markdown(f"##### {type_names.get(res_type, res_type)} ({len(results)} 个)")
- for result in results:
- with st.container(border=True):
- name_key = 'name' if 'name' in result else 'title'
- st.markdown(f"**{result.get(name_key, 'N/A')}**")
- st.caption(result.get('description', ''))
- if result.get('url'):
- st.markdown(f"🔗 [{result['url']}]({result['url']})")
+ tab_resources.render_tab_resources(storage, brand)
+
# =======================
# Tab9:平台同步
# =======================
with tab9:
- st.markdown("### 📤 平台文章同步")
- st.caption("将生成的文章自动发布到各平台,支持API发布和一键复制")
-
- # 获取品牌信息
- brand = st.session_state.get("brand", "")
- if not brand:
- st.info("请先在侧边栏设置品牌信息")
- else:
- # 平台账号配置
- st.markdown("---")
- st.markdown("#### 🔐 平台账号配置")
-
- platform_config_tabs = st.tabs(["GitHub", "其他平台(开发中)"])
-
- with platform_config_tabs[0]:
- st.markdown("##### GitHub 配置")
- st.caption("配置GitHub账号信息,用于自动发布文章到GitHub仓库")
-
- # 检查是否已有配置
- existing_config = storage.get_platform_account("GitHub", brand)
-
- github_api_key = st.text_input(
- "GitHub Personal Access Token",
- value=existing_config.get('api_key', '') if existing_config else '',
- type="password",
- help="在 https://github.com/settings/tokens 创建Token,需要 repo 权限",
- key="github_api_key"
- )
-
- github_repo_owner = st.text_input(
- "仓库所有者(用户名)",
- value=existing_config.get('config', {}).get('repo_owner', '') if existing_config else '',
- help="GitHub用户名或组织名",
- key="github_repo_owner"
- )
-
- github_repo_name = st.text_input(
- "仓库名称",
- value=existing_config.get('config', {}).get('repo_name', '') if existing_config else '',
- help="要发布到的仓库名称",
- key="github_repo_name"
- )
-
- col1, col2 = st.columns([1, 4])
- with col1:
- if st.button("💾 保存配置", type="primary", use_container_width=True):
- if github_api_key and github_repo_owner and github_repo_name:
- try:
- # 验证账号
- from platform_sync.github_publisher import GitHubPublisher
- publisher = GitHubPublisher(github_api_key, github_repo_owner, github_repo_name)
- if publisher.validate_account():
- storage.save_platform_account(
- platform="GitHub",
- account_config={
- 'account_type': 'api',
- 'api_key': github_api_key,
- 'config': {
- 'repo_owner': github_repo_owner,
- 'repo_name': github_repo_name
- }
- },
- brand=brand
- )
- st.success("✅ GitHub配置已保存并验证成功!")
- else:
- st.error("❌ GitHub Token验证失败,请检查Token是否正确")
- except Exception as e:
- st.error(f"❌ 配置保存失败: {str(e)}")
- else:
- st.error("请填写完整信息")
-
- with col2:
- if existing_config:
- st.info("✅ 已配置GitHub账号")
-
- # 发布功能
- st.markdown("---")
- st.markdown("#### 📝 发布文章")
-
- # 选择文章
- articles = storage.get_articles(brand=brand)
- if articles:
- # 文章选择
- article_options = {}
- for article in articles:
- display_name = f"{article.get('keyword', 'N/A')} - {article.get('platform', 'N/A')}"
- article_options[display_name] = article.get('id')
-
- if article_options:
- selected_article_key = st.selectbox(
- "选择要发布的文章",
- list(article_options.keys()),
- key="publish_article_select"
- )
- selected_article_id = article_options[selected_article_key]
-
- # 选择平台
- # 定义平台列表
- api_platforms = ["GitHub"]
- copy_platforms = [
- "头条号(资讯软文)", "小红书(生活种草)", "抖音图文(短内容)", "简书(文艺)",
- "QQ空间(社交)", "新浪博客(博客)", "新浪新闻(资讯)", "搜狐号(资讯)",
- "一点号(资讯)", "东方财富(财经)", "邦阅网(外贸)", "原创力文档(文档)"
- ]
- all_publish_platforms = api_platforms + copy_platforms
-
- publish_platform = st.selectbox(
- "选择发布平台",
- all_publish_platforms,
- key="publish_platform_select"
- )
-
- if publish_platform == "GitHub":
- # 检查配置
- account_config = storage.get_platform_account("GitHub", brand)
- if not account_config:
- st.warning("⚠️ 请先配置GitHub账号")
- else:
- # 获取文章
- article = next((a for a in articles if a.get('id') == selected_article_id), None)
- if article:
- # 显示文章预览
- with st.expander("📄 文章预览", expanded=False):
- st.markdown(f"**关键词**: {article.get('keyword', 'N/A')}")
- st.markdown(f"**平台**: {article.get('platform', 'N/A')}")
- st.markdown(f"**内容长度**: {len(article.get('content', ''))} 字符")
- st.markdown("---")
- st.text_area("内容", article.get('content', ''), height=200, disabled=True)
-
- # 发布选项
- file_path = st.text_input(
- "文件路径(可选)",
- value=f"content/{article.get('keyword', 'article').replace(' ', '_')[:50]}.md",
- help="GitHub仓库中的文件路径,留空使用默认路径",
- key="github_file_path"
- )
-
- if st.button("🚀 发布到GitHub", type="primary", use_container_width=True):
- try:
- from platform_sync.github_publisher import GitHubPublisher
- publisher = GitHubPublisher(
- api_key=account_config['api_key'],
- repo_owner=account_config['config']['repo_owner'],
- repo_name=account_config['config']['repo_name']
- )
-
- with st.spinner("正在发布到GitHub..."):
- result = publisher.publish(
- content=article.get('content', ''),
- title=article.get('keyword', 'Untitled'),
- file_path=file_path if file_path else None
- )
-
- # 保存发布记录
- storage.save_publish_record(
- article_id=selected_article_id,
- platform="GitHub",
- publish_method="api",
- publish_status="success" if result['success'] else "failed",
- publish_url=result.get('publish_url', ''),
- publish_id=result.get('publish_id', ''),
- error_message=result.get('error', '')
- )
-
- # 显示结果
- if result['success']:
- st.success(f"✅ 发布成功!")
- st.markdown(f"**发布链接**: [{result['publish_url']}]({result['publish_url']})")
- st.balloons()
- else:
- st.error(f"❌ 发布失败: {result.get('error', '未知错误')}")
- except Exception as e:
- st.error(f"❌ 发布过程出错: {str(e)}")
- storage.save_publish_record(
- article_id=selected_article_id,
- platform="GitHub",
- publish_method="api",
- publish_status="failed",
- error_message=str(e)
- )
- else:
- # 一键复制平台
- article = next((a for a in articles if a.get('id') == selected_article_id), None)
- if article:
- from platform_sync.copy_manager import CopyManager
- copy_manager = CopyManager()
-
- # 格式化内容
- formatted_content = copy_manager.format_for_platform(
- platform=publish_platform,
- content=article.get('content', ''),
- title=article.get('keyword', 'Untitled'),
- keyword=article.get('keyword', ''),
- brand=brand
- )
-
- # 显示格式化后的内容
- with st.expander("📄 格式化后的内容(已复制到剪贴板)", expanded=True):
- st.text_area(
- "内容",
- formatted_content,
- height=300,
- key="formatted_content_display"
- )
-
- # 发布指南
- guide = copy_manager.generate_publish_guide(publish_platform)
- with st.expander("📋 发布指南", expanded=True):
- st.markdown(guide)
-
- # 复制按钮
- col1, col2 = st.columns([1, 1])
- with col1:
- if st.button("📋 复制到剪贴板", type="primary", use_container_width=True):
- if copy_manager.copy_to_clipboard(formatted_content):
- st.success("✅ 内容已复制到剪贴板!")
- st.info("💡 请按照上方发布指南,将内容粘贴到对应平台发布")
-
- # 保存发布记录(标记为已复制)
- storage.save_publish_record(
- article_id=selected_article_id,
- platform=publish_platform,
- publish_method="copy",
- publish_status="copied",
- error_message=""
- )
- else:
- st.error("❌ 复制失败,请手动复制内容")
-
- with col2:
- if st.button("📥 下载内容", use_container_width=True):
- # 生成下载文件
- safe_title = article.get('keyword', 'article').replace(' ', '_')[:50]
- filename = f"{publish_platform.replace('(', '_').replace(')', '')}_{safe_title}.txt"
- st.download_button(
- label="⬇️ 下载",
- data=formatted_content,
- file_name=filename,
- mime="text/plain",
- key="download_formatted_content"
- )
- else:
- st.info("📝 请先在【2 自动创作】中生成文章")
-
- # 发布记录
- st.markdown("---")
- st.markdown("#### 📊 发布记录")
-
- publish_records = storage.get_publish_records(brand=brand)
- if publish_records:
- # 统计信息
- total_records = len(publish_records)
- success_records = len([r for r in publish_records if r.get('publish_status') == 'success'])
- copied_records = len([r for r in publish_records if r.get('publish_status') == 'copied'])
- failed_records = len([r for r in publish_records if r.get('publish_status') == 'failed'])
-
- stat_col1, stat_col2, stat_col3, stat_col4 = st.columns(4)
- with stat_col1:
- st.metric("总发布数", total_records)
- with stat_col2:
- st.metric("API成功", success_records, delta=f"{success_records/total_records*100:.1f}%" if total_records > 0 else "0%")
- with stat_col3:
- st.metric("已复制", copied_records, delta=f"{copied_records/total_records*100:.1f}%" if total_records > 0 else "0%")
- with stat_col4:
- st.metric("失败", failed_records)
-
- # 记录列表
- st.markdown("##### 最近发布记录")
- records_df = pd.DataFrame(publish_records[:20]) # 显示最近20条
- if not records_df.empty:
- # 格式化显示
- display_df = records_df[['platform', 'publish_method', 'publish_status', 'publish_url', 'published_at', 'created_at']].copy()
- display_df.columns = ['平台', '发布方式', '状态', '链接', '发布时间', '创建时间']
- display_df['状态'] = display_df['状态'].map({
- 'success': '✅ 成功',
- 'failed': '❌ 失败',
- 'pending': '⏳ 待发布',
- 'copied': '📋 已复制'
- })
- display_df['发布方式'] = display_df['发布方式'].map({
- 'api': 'API',
- 'copy': '一键复制'
- })
-
- st.dataframe(display_df, use_container_width=True, hide_index=True)
- else:
- st.info("暂无发布记录")
+ tab_platform_sync.render_tab_platform_sync(storage, brand)
+
# =======================
# Tab10:配置优化助手
# =======================
with tab10:
- # 配置优化助手(与其他Tab保持一致的标题格式)
- st.markdown("### 🎯 配置优化助手")
- st.caption("分析品牌名和优势是否 GEO 友好,提供优化建议。优化后可一键应用到全局配置。")
-
- # 初始化优化结果存储
- if "config_optimization_result" not in st.session_state:
- st.session_state.config_optimization_result = None
-
- # 初始化配置hash(用于检测配置变化)
- if "config_hash" not in st.session_state:
- st.session_state.config_hash = None
-
- # 计算当前配置的hash(使用cfg中的最新值)
- import hashlib
- brand_for_hash = cfg.get("brand", "").strip() or brand or ""
- advantages_for_hash = cfg.get("advantages", "").strip() or advantages or ""
- current_config_str = f"{brand_for_hash}|{advantages_for_hash}|{cfg.get('competitors', '')}"
- current_config_hash = hashlib.md5(current_config_str.encode()).hexdigest()
-
- # 如果配置变化了,清除旧的优化结果
- # 但如果是因为应用版本导致的配置变化,保留优化结果
- if st.session_state.config_hash != current_config_hash:
- # 检查是否是应用版本导致的配置变化
- if not st.session_state.get("_applying_version", False):
- st.session_state.config_optimization_result = None
- st.session_state.config_hash = current_config_hash
- # 清除应用版本标志
- st.session_state["_applying_version"] = False
-
- # 检查配置是否有效
- if not st.session_state.cfg_valid:
- st.warning("⚠️ 请先在侧边栏完成配置并点击'应用配置'")
- st.info("配置优化助手需要有效的配置才能进行分析。")
- else:
- # 显示当前配置
- with st.expander("📋 当前配置", expanded=False):
- col1, col2 = st.columns(2)
- with col1:
- brand_display = cfg.get("brand", "") or brand or "未设置"
- st.markdown(f"**品牌名**:{brand_display}")
- with col2:
- st.markdown(f"**竞品数量**:{len(competitor_list)}个")
- advantages_display = cfg.get("advantages", "") or advantages or "未设置"
- st.markdown(f"**核心优势**:{advantages_display}")
- if competitor_list:
- st.markdown(f"**竞品列表**:{', '.join(competitor_list[:5])}{'...' if len(competitor_list) > 5 else ''}")
-
- # 分析按钮
- col1, col2 = st.columns([1, 3])
- with col1:
- analyze_btn = st.button("🔍 分析配置优化", type="primary", use_container_width=True, key="tab10_optimize_config")
-
- with col2:
- if st.session_state.config_optimization_result:
- st.success("✅ 已有优化结果,可直接查看下方建议")
-
- # 执行分析
- if analyze_btn:
- with st.spinner("正在分析配置,优化建议生成中..."):
- try:
- from modules.config_optimizer import ConfigOptimizer
-
- optimizer = ConfigOptimizer()
-
- # 从配置中获取品牌名、优势描述和竞品列表(确保使用最新配置)
- brand_for_optimizer = cfg.get("brand", "").strip() or brand or ""
- advantages_for_optimizer = cfg.get("advantages", "").strip() or advantages or ""
- competitors_str = cfg.get("competitors", "")
- competitor_list_for_optimizer = [c.strip() for c in competitors_str.split("\n") if c.strip()]
-
- # 验证必要配置
- if not brand_for_optimizer:
- st.error("❌ 品牌名不能为空,请在侧边栏配置主品牌名称")
- st.stop()
-
- if not advantages_for_optimizer:
- st.warning("⚠️ 优势描述为空,建议在侧边栏配置核心优势/卖点")
-
- # 临时构建LLM用于分析(使用当前配置)
- temp_llm = build_llm(
- cfg["gen_provider"],
- cfg["gen_api_key"],
- model_defaults(cfg["gen_provider"]),
- float(cfg.get("temperature", 0.7))
- )
-
- result = optimizer.optimize_config(
- brand=brand_for_optimizer,
- advantages=advantages_for_optimizer,
- competitors=competitor_list_for_optimizer,
- llm_chain=temp_llm
- )
- st.session_state.config_optimization_result = result
- st.session_state.config_hash = current_config_hash
- st.success("✅ 配置分析完成!")
- st.rerun()
- except Exception as e:
- st.error(f"❌ 配置优化分析失败:{e}")
- import traceback
- with st.expander("查看错误详情"):
- st.code(traceback.format_exc())
- st.session_state.config_optimization_result = None
-
- # 显示优化结果
- if st.session_state.config_optimization_result:
- result = st.session_state.config_optimization_result
- if result.get("success", False):
- st.markdown("---")
- st.markdown("#### 📊 优化分析结果")
-
- # 评估总结
- if result.get("summary"):
- st.markdown("**📝 评估总结**")
- st.info(result["summary"])
-
- # 优化建议
- if result.get("suggestions"):
- st.markdown("**💡 优化建议**")
- suggestions = result["suggestions"]
-
- if suggestions.get("brand", {}).get("problem"):
- st.markdown("**🔸 品牌名问题**:")
- # 直接使用st.markdown渲染,CSS会限制标题大小
- problem_text = suggestions["brand"]["problem"]
- st.markdown(problem_text)
- if suggestions["brand"].get("suggestion"):
- st.markdown("**✅ 建议**:")
- suggestion_text = suggestions["brand"]["suggestion"]
- st.markdown(suggestion_text)
-
- if suggestions.get("advantages", {}).get("problem"):
- st.markdown("**🔸 优势描述问题**:")
- problem_text = suggestions["advantages"]["problem"]
- st.markdown(problem_text)
- if suggestions["advantages"].get("suggestion"):
- st.markdown("**✅ 建议**:")
- suggestion_text = suggestions["advantages"]["suggestion"]
- st.markdown(suggestion_text)
-
- # 推荐版本
- recommended_versions = result.get("recommended_versions", [])
- if recommended_versions:
- st.markdown("**🎯 推荐版本**")
- st.caption("选择最适合的版本,点击「应用版本」按钮即可更新配置")
-
- # 检查是否有有效的推荐版本
- valid_versions = [v for v in recommended_versions if v.get("brand") or v.get("advantages")]
- if not valid_versions:
- st.warning("⚠️ 推荐版本数据为空,可能是解析失败。请查看完整报告或重新分析。")
- if result.get("raw_result"):
- with st.expander("查看原始输出中的推荐版本部分"):
- raw = result["raw_result"]
- if "【推荐版本】" in raw:
- raw_versions = raw.split("【推荐版本】")[1].split("【")[0]
- st.code(raw_versions)
-
- for i, version in enumerate(recommended_versions[:3], 1):
- version_name_map = {
- 1: "保守优化",
- 2: "平衡优化",
- 3: "激进优化"
- }
- version_name = version_name_map.get(i, f"版本{i}")
-
- with st.expander(f"版本{i}:{version_name}", expanded=False): # 默认不展开,用户自行选择
- # 检查版本数据是否有效
- has_brand = bool(version.get("brand", "").strip())
- has_advantages = bool(version.get("advantages", "").strip())
- has_reason = bool(version.get("reason", "").strip())
-
- if not has_brand and not has_advantages:
- st.warning("⚠️ 该版本数据不完整,请查看完整报告或重新分析")
- if result.get("raw_result"):
- with st.expander("查看原始输出中的该版本"):
- # 尝试从原始输出中提取
- raw = result["raw_result"]
- if f"版本{i}" in raw:
- version_raw = raw.split(f"版本{i}")[1]
- if i < 3:
- next_version = f"版本{i+1}"
- if next_version in version_raw:
- version_raw = version_raw.split(next_version)[0]
- st.code(version_raw[:500]) # 显示前500字符
- else:
- col1, col2 = st.columns([2, 1])
- with col1:
- if has_brand:
- st.markdown(f"**品牌名**:`{version['brand']}`")
- else:
- st.warning("⚠️ 品牌名为空")
-
- if has_advantages:
- st.markdown(f"**优势描述**:{version['advantages']}")
- else:
- st.warning("⚠️ 优势描述为空")
-
- if has_reason:
- st.caption(f"💭 理由:{version['reason']}")
- else:
- st.caption("💭 理由:未提供")
-
- with col2:
- # 应用按钮
- apply_disabled = not (has_brand and has_advantages)
- if st.button(
- f"✅ 应用版本{i}",
- key=f"tab10_apply_version_{i}",
- use_container_width=True,
- type="primary",
- disabled=apply_disabled
- ):
- if has_brand and has_advantages:
- # 设置标志,表示正在应用版本(防止优化结果被清除)
- st.session_state["_applying_version"] = True
- # 更新配置
- st.session_state.cfg["brand"] = version["brand"]
- st.session_state.cfg["advantages"] = version["advantages"]
- # 设置标志,表示需要更新侧边栏输入框
- st.session_state["_pending_brand_update"] = version["brand"]
- st.session_state["_pending_advantages_update"] = version["advantages"]
- st.session_state.cfg_applied = False # 需要重新应用配置
- st.success(f"✅ 已应用版本{i},侧边栏已更新,请点击'应用配置'以生效")
- st.info("💡 配置更新后,建议重新运行关键词蒸馏和内容创作,以获得最佳效果")
- st.rerun()
- if apply_disabled:
- st.caption("⚠️ 数据不完整,无法应用")
-
- # 预期效果
- if result.get("expected_effects"):
- st.markdown("**📈 预期效果**")
- effects = result["expected_effects"]
- # 使用文本而不是 metric,避免内容被截断
- if effects.get("mention_rate"):
- st.markdown(f"- 提及率提升预期:{effects['mention_rate']}")
- if effects.get("geo_friendliness"):
- st.markdown(f"- GEO友好度提升:{effects['geo_friendliness']}")
-
- # 完整报告
- if result.get("raw_result"):
- with st.expander("📄 查看完整分析报告", expanded=False):
- st.markdown(result["raw_result"])
-
- # 如果推荐版本为空或解析失败,显示原始输出中的推荐版本部分
- recommended_versions = result.get("recommended_versions", [])
- if not recommended_versions or all(
- not v.get("brand") and not v.get("advantages")
- for v in recommended_versions
- ):
- st.warning("⚠️ 推荐版本解析失败,以下是原始输出中的推荐版本部分,请检查格式:")
- raw = result["raw_result"]
- if "【推荐版本】" in raw:
- raw_versions = raw.split("【推荐版本】")[1].split("【")[0]
- st.code(raw_versions, language="text")
- st.info("💡 如果原始输出中包含推荐版本但解析失败,请检查格式是否符合要求")
-
- # 调试信息(可选)
- if st.checkbox("🔍 显示调试信息", key="tab10_debug"):
- st.markdown("#### 调试信息")
- debug_info = {
- "推荐版本数量": len(result.get("recommended_versions", [])),
- "版本详情": result.get("recommended_versions", []),
- "配置hash": st.session_state.config_hash,
- "解析错误": result.get("parse_errors", [])
- }
- st.json(debug_info)
-
- # 显示原始输出的关键部分
- if result.get("raw_result"):
- raw = result["raw_result"]
- if "【推荐版本】" in raw:
- st.markdown("**原始输出中的推荐版本部分:**")
- raw_versions = raw.split("【推荐版本】")[1].split("【")[0]
- st.code(raw_versions[:1000], language="text") # 显示前1000字符
- else:
- st.error(f"❌ 分析失败:{result.get('error', '未知错误')}")
- if result.get("raw_result"):
- with st.expander("查看原始输出"):
- st.code(result["raw_result"])
- else:
- st.info("💡 点击上方「分析配置优化」按钮开始分析,系统会根据当前配置生成优化建议。")
- st.caption("提示:当您修改品牌名、优势描述或竞品列表后,系统会自动清除旧结果,需要重新分析。")
+ tab_config_optimizer.render_tab_config_optimizer(
+ storage,
+ cfg,
+ brand,
+ advantages,
+ competitor_list,
+ build_llm,
+ model_defaults,
+ )
-st.caption("最完整版:GitHub模板 + 真实多模型验证 + 现有文章优化 • GEO全闭环,专注AI品牌影响力")
+
+# =======================
+# Tab11:品牌知识库(RAG)
+# =======================
+with tab11:
+ render_tab_knowledge(kb)
+
+st.caption("最完整版:GitHub模板 + 真实多模型验证 + 现有文章优化 + RAG知识库 • GEO全闭环,专注AI品牌影响力")
diff --git a/modules/ai_search_verifier.py b/modules/ai_search_verifier.py
new file mode 100644
index 0000000..1573648
--- /dev/null
+++ b/modules/ai_search_verifier.py
@@ -0,0 +1,445 @@
+"""
+AI 搜索验证模块
+支持使用真实的 AI 搜索引擎(Perplexity、ChatGPT Search)验证品牌提及
+"""
+
+import json
+import logging
+import re
+from typing import Dict, List, Optional, Any
+from dataclasses import dataclass
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class SearchResult:
+ """搜索结果"""
+ query: str
+ response: str
+ sources: List[Dict[str, str]]
+ brand_mentioned: bool
+ mention_count: int
+ mention_positions: List[str]
+ sentiment: str # positive, neutral, negative
+
+
+class AISearchVerifier:
+ """AI 搜索验证器"""
+
+ def __init__(self, perplexity_api_key: Optional[str] = None):
+ """
+ Args:
+ perplexity_api_key: Perplexity API Key
+ """
+ self.perplexity_api_key = perplexity_api_key
+
+ def verify_with_perplexity(self, query: str, brand: str) -> Dict:
+ """
+ 使用 Perplexity API 验证品牌提及
+
+ Args:
+ query: 搜索查询
+ brand: 品牌名
+
+ Returns:
+ 验证结果
+ """
+ if not self.perplexity_api_key:
+ return self._mock_verification(query, brand)
+
+ try:
+ import httpx
+
+ headers = {
+ "Authorization": f"Bearer {self.perplexity_api_key}",
+ "Content-Type": "application/json"
+ }
+
+ payload = {
+ "model": "llama-3.1-sonar-small-128k-online",
+ "messages": [
+ {
+ "role": "system",
+ "content": "You are a helpful assistant. Answer the user's question based on real-time search results. Be factual and cite your sources."
+ },
+ {
+ "role": "user",
+ "content": query
+ }
+ ],
+ "max_tokens": 1000,
+ "temperature": 0.1,
+ "return_citations": True,
+ "search_recency_filter": "month"
+ }
+
+ response = httpx.post(
+ "https://api.perplexity.ai/chat/completions",
+ json=payload,
+ headers=headers,
+ timeout=30.0
+ )
+
+ if response.status_code == 200:
+ result = response.json()
+ content = result["choices"][0]["message"]["content"]
+ citations = result.get("citations", [])
+
+ # 分析品牌提及
+ mention_analysis = self._analyze_mention(content, brand)
+
+ return {
+ "success": True,
+ "query": query,
+ "brand": brand,
+ "response": content,
+ "sources": citations,
+ "mention_count": mention_analysis["count"],
+ "mention_positions": mention_analysis["positions"],
+ "mentioned": mention_analysis["count"] > 0,
+ "sentiment": mention_analysis["sentiment"]
+ }
+ else:
+ logger.error(f"Perplexity API 错误: {response.status_code} {response.text}")
+ return {"success": False, "error": f"API 错误: {response.status_code}"}
+
+ except ImportError:
+ logger.warning("httpx 未安装,无法调用 Perplexity API")
+ return self._mock_verification(query, brand)
+ except Exception as e:
+ logger.error(f"Perplexity 验证失败: {e}")
+ return {"success": False, "error": str(e)}
+
+ def _mock_verification(self, query: str, brand: str) -> Dict:
+ """模拟验证(当 API 不可用时)"""
+ return {
+ "success": True,
+ "query": query,
+ "brand": brand,
+ "response": f"(模拟结果)关于 '{query}' 的搜索结果需要配置 Perplexity API Key 才能获取真实数据。",
+ "sources": [],
+ "mention_count": 0,
+ "mention_positions": [],
+ "mentioned": False,
+ "sentiment": "neutral",
+ "is_mock": True
+ }
+
+ def _analyze_mention(self, text: str, brand: str) -> Dict:
+ """
+ 分析文本中的品牌提及
+
+ Args:
+ text: 文本内容
+ brand: 品牌名
+
+ Returns:
+ 提及分析结果
+ """
+ text_lower = text.lower()
+ brand_lower = brand.lower()
+
+ # 计算提及次数
+ count = text_lower.count(brand_lower)
+
+ # 分析提及位置
+ positions = []
+ if count > 0:
+ total_len = len(text)
+ for match in re.finditer(re.escape(brand_lower), text_lower):
+ pos_ratio = match.start() / total_len
+ if pos_ratio < 0.33:
+ positions.append("前1/3")
+ elif pos_ratio < 0.67:
+ positions.append("中1/3")
+ else:
+ positions.append("后1/3")
+
+ # 分析情感(简单规则)
+ sentiment = self._analyze_sentiment(text, brand)
+
+ return {
+ "count": count,
+ "positions": positions,
+ "sentiment": sentiment
+ }
+
+ def _analyze_sentiment(self, text: str, brand: str) -> str:
+ """
+ 分析品牌提及的情感
+
+ Args:
+ text: 文本内容
+ brand: 品牌名
+
+ Returns:
+ 情感标签 (positive, neutral, negative)
+ """
+ text_lower = text.lower()
+ brand_lower = brand.lower()
+
+ # 正面词汇
+ positive_words = [
+ "优秀", "出色", "领先", "推荐", "首选", "最佳", "强大", "高效",
+ "创新", "专业", "可靠", "稳定", "卓越", "突出", "显著",
+ "excellent", "outstanding", "leading", "recommended", "best",
+ "powerful", "efficient", "innovative", "professional", "reliable"
+ ]
+
+ # 负面词汇
+ negative_words = [
+ "问题", "缺陷", "不足", "失败", "风险", "警告", "谨慎", "避免",
+ "差", "慢", "贵", "复杂", "困难", "不稳定",
+ "issue", "problem", "defect", "risk", "warning", "avoid",
+ "poor", "slow", "expensive", "complex", "difficult", "unstable"
+ ]
+
+ # 获取品牌附近的上下文(前后50字符)
+ contexts = []
+ for match in re.finditer(re.escape(brand_lower), text_lower):
+ start = max(0, match.start() - 50)
+ end = min(len(text), match.end() + 50)
+ contexts.append(text_lower[start:end])
+
+ if not contexts:
+ return "neutral"
+
+ # 计算情感分数
+ positive_score = 0
+ negative_score = 0
+
+ for context in contexts:
+ for word in positive_words:
+ if word in context:
+ positive_score += 1
+ for word in negative_words:
+ if word in context:
+ negative_score += 1
+
+ if positive_score > negative_score * 1.5:
+ return "positive"
+ elif negative_score > positive_score * 1.5:
+ return "negative"
+ else:
+ return "neutral"
+
+ def batch_verify(self, queries: List[str], brand: str,
+ api_key: Optional[str] = None) -> List[Dict]:
+ """
+ 批量验证多个查询
+
+ Args:
+ queries: 查询列表
+ brand: 品牌名
+ api_key: API Key(可选,覆盖初始化时的 key)
+
+ Returns:
+ 验证结果列表
+ """
+ if api_key:
+ self.perplexity_api_key = api_key
+
+ results = []
+ for query in queries:
+ result = self.verify_with_perplexity(query, brand)
+ results.append(result)
+
+ return results
+
+ def generate_verification_report(self, results: List[Dict]) -> Dict:
+ """
+ 生成验证报告
+
+ Args:
+ results: 验证结果列表
+
+ Returns:
+ 报告数据
+ """
+ total = len(results)
+ mentioned = sum(1 for r in results if r.get("mentioned", False))
+
+ mention_rate = mentioned / total if total > 0 else 0
+
+ # 统计情感分布
+ sentiment_counts = {"positive": 0, "neutral": 0, "negative": 0}
+ for r in results:
+ sentiment = r.get("sentiment", "neutral")
+ sentiment_counts[sentiment] = sentiment_counts.get(sentiment, 0) + 1
+
+ # 计算平均提及次数
+ total_mentions = sum(r.get("mention_count", 0) for r in results)
+ avg_mentions = total_mentions / total if total > 0 else 0
+
+ return {
+ "total_queries": total,
+ "mentioned_count": mentioned,
+ "mention_rate": mention_rate,
+ "avg_mentions_per_query": avg_mentions,
+ "sentiment_distribution": sentiment_counts,
+ "top_mentioned_queries": [
+ r["query"] for r in results
+ if r.get("mentioned", False)
+ ][:5],
+ "not_mentioned_queries": [
+ r["query"] for r in results
+ if not r.get("mentioned", False)
+ ][:5]
+ }
+
+
+class SemanticMentionDetector:
+ """语义级提及检测器"""
+
+ def __init__(self):
+ # 品牌别名/同义词映射
+ self.brand_aliases: Dict[str, List[str]] = {}
+
+ def add_brand_aliases(self, brand: str, aliases: List[str]):
+ """
+ 添加品牌别名
+
+ Args:
+ brand: 品牌名
+ aliases: 别名列表
+ """
+ self.brand_aliases[brand.lower()] = [a.lower() for a in aliases]
+
+ def detect_mention(self, text: str, brand: str) -> Dict:
+ """
+ 语义级提及检测
+
+ Args:
+ text: 文本内容
+ brand: 品牌名
+
+ Returns:
+ 检测结果
+ """
+ text_lower = text.lower()
+ brand_lower = brand.lower()
+
+ # 直接提及
+ direct_count = text_lower.count(brand_lower)
+
+ # 别名提及
+ aliases = self.brand_aliases.get(brand_lower, [])
+ alias_counts = {}
+ for alias in aliases:
+ count = text_lower.count(alias)
+ if count > 0:
+ alias_counts[alias] = count
+
+ # 总提及次数
+ total_count = direct_count + sum(alias_counts.values())
+
+ # 判断提及语境
+ contexts = self._extract_contexts(text, brand_lower, aliases)
+
+ return {
+ "brand": brand,
+ "direct_count": direct_count,
+ "alias_counts": alias_counts,
+ "total_count": total_count,
+ "contexts": contexts,
+ "is_mentioned": total_count > 0
+ }
+
+ def _extract_contexts(self, text: str, brand_lower: str,
+ aliases: List[str]) -> List[Dict]:
+ """提取提及上下文"""
+ contexts = []
+ text_lower = text.lower()
+
+ # 查找所有提及位置
+ all_patterns = [brand_lower] + aliases
+
+ for pattern in all_patterns:
+ for match in re.finditer(re.escape(pattern), text_lower):
+ start = max(0, match.start() - 100)
+ end = min(len(text), match.end() + 100)
+ context = text[start:end]
+
+ contexts.append({
+ "pattern": pattern,
+ "context": context,
+ "position": match.start()
+ })
+
+ return contexts
+
+
+def verify_content_quality(content: str, brand: str,
+ knowledge_base=None) -> Dict:
+ """
+ 综合验证内容质量
+
+ Args:
+ content: 内容文本
+ brand: 品牌名
+ knowledge_base: 知识库实例(可选)
+
+ Returns:
+ 质量评估结果
+ """
+ from modules.knowledge_base import SourceVerifier
+
+ verifier = SourceVerifier()
+
+ # 来源质量评估
+ source_quality = verifier.assess_source_quality(content)
+
+ # 品牌提及分析
+ detector = SemanticMentionDetector()
+ mention_result = detector.detect_mention(content, brand)
+
+ # 计算综合分数
+ score = 0
+ max_score = 100
+
+ # 来源质量 (40分)
+ score += min(40, source_quality["quality_score"] * 0.4)
+
+ # 品牌提及 (30分)
+ if mention_result["is_mentioned"]:
+ mention_score = min(30, mention_result["total_count"] * 5)
+ score += mention_score
+
+ # 内容结构 (30分)
+ structure_score = 0
+ if "##" in content: # 有标题
+ structure_score += 10
+ if re.search(r'\d+[.、]', content): # 有列表
+ structure_score += 10
+ if "?" in content or "?" in content: # 有问答
+ structure_score += 10
+ score += structure_score
+
+ return {
+ "total_score": score,
+ "max_score": max_score,
+ "source_quality": source_quality,
+ "mention_analysis": mention_result,
+ "structure_score": structure_score,
+ "suggestions": _generate_suggestions(source_quality, mention_result, structure_score)
+ }
+
+
+def _generate_suggestions(source_quality: Dict, mention_result: Dict,
+ structure_score: int) -> List[str]:
+ """生成改进建议"""
+ suggestions = []
+
+ if source_quality["quality_score"] < 50:
+ suggestions.append("来源质量较低,建议添加真实的行业报告或数据来源")
+
+ if not mention_result["is_mentioned"]:
+ suggestions.append("内容中未提及品牌,建议在合适位置自然植入品牌信息")
+ elif mention_result["total_count"] < 2:
+ suggestions.append("品牌提及次数较少,建议增加到 2-3 次")
+
+ if structure_score < 20:
+ suggestions.append("内容结构不够清晰,建议添加标题、列表或 FAQ")
+
+ return suggestions
diff --git a/modules/chat_doubao.py b/modules/chat_doubao.py
new file mode 100644
index 0000000..9ce200a
--- /dev/null
+++ b/modules/chat_doubao.py
@@ -0,0 +1,101 @@
+"""
+豆包(字节跳动)聊天模型封装模块
+提供 LangChain 兼容的 ChatDoubao 类
+"""
+
+from typing import List, Optional, Any
+from langchain_core.language_models.chat_models import BaseChatModel
+from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
+from langchain_core.outputs import ChatGeneration, ChatResult
+
+
+class ChatDoubao(BaseChatModel):
+ """豆包聊天模型封装(LangChain 兼容)"""
+
+ volc_ak: str
+ volc_sk: str
+ endpoint_id: str
+ temperature: float = 0.7
+ client: Any = None
+
+ def __init__(self, volc_ak: str, volc_sk: str, endpoint_id: str, temperature: float = 0.7, **kwargs):
+ super().__init__(
+ volc_ak=volc_ak,
+ volc_sk=volc_sk,
+ endpoint_id=endpoint_id,
+ temperature=temperature,
+ **kwargs
+ )
+ self._init_client()
+
+ def _init_client(self):
+ """初始化 Ark 客户端,尝试多种导入方式"""
+ try:
+ from volcengine.ark import Ark
+ self.client = Ark(ak=self.volc_ak, sk=self.volc_sk)
+ except ImportError:
+ try:
+ from volcenginesdkarkruntime import Ark
+ self.client = Ark(ak=self.volc_ak, sk=self.volc_sk)
+ except ImportError as e:
+ raise ImportError(
+ f"豆包初始化失败:缺少依赖库。请运行:pip install 'volcengine-python-sdk[ark]'。错误:{e}"
+ )
+
+ def _generate(
+ self,
+ messages: List[BaseMessage],
+ stop: Optional[List[str]] = None,
+ run_manager: Optional[Any] = None,
+ **kwargs: Any
+ ) -> ChatResult:
+ """生成聊天响应"""
+ volc_messages = []
+ for msg in messages:
+ if isinstance(msg, SystemMessage):
+ volc_messages.append({"role": "system", "content": msg.content})
+ elif isinstance(msg, HumanMessage):
+ volc_messages.append({"role": "user", "content": msg.content})
+ elif isinstance(msg, AIMessage):
+ volc_messages.append({"role": "assistant", "content": msg.content})
+ else:
+ volc_messages.append({"role": "user", "content": str(msg.content)})
+
+ response = self.client.chat.completions.create(
+ model=self.endpoint_id,
+ messages=volc_messages,
+ temperature=self.temperature,
+ )
+
+ ai_message = AIMessage(content=response.choices[0].message.content)
+ return ChatResult(generations=[ChatGeneration(message=ai_message)])
+
+ @property
+ def _llm_type(self) -> str:
+ return "doubao"
+
+
+def create_chat_doubao(api_key: str, temperature: float = 0.7) -> ChatDoubao:
+ """
+ 创建豆包聊天模型实例
+
+ Args:
+ api_key: 豆包 API Key,格式:access_key:secret_key:endpoint_id
+ temperature: 温度参数
+
+ Returns:
+ ChatDoubao 实例
+
+ Raises:
+ ValueError: API Key 格式错误
+ """
+ parts = api_key.split(":")
+ if len(parts) < 3:
+ raise ValueError("豆包 API Key 格式错误,应为:access_key:secret_key:endpoint_id(用冒号分隔)")
+
+ return ChatDoubao(
+ volc_ak=parts[0],
+ volc_sk=parts[1],
+ endpoint_id=parts[2],
+ temperature=temperature
+ )
diff --git a/modules/content_uniqueness.py b/modules/content_uniqueness.py
new file mode 100644
index 0000000..8096ffa
--- /dev/null
+++ b/modules/content_uniqueness.py
@@ -0,0 +1,332 @@
+"""
+内容独特性检测模块
+检测批量生成内容的相似度,避免多篇文章说同一件事
+"""
+
+import re
+import hashlib
+from typing import Dict, List, Optional, Tuple
+from collections import Counter
+import math
+
+
+class ContentUniquenessChecker:
+ """内容独特性检查器"""
+
+ def __init__(self, similarity_threshold: float = 0.7):
+ """
+ Args:
+ similarity_threshold: 相似度阈值,超过此值认为内容过于相似
+ """
+ self.similarity_threshold = similarity_threshold
+
+ def check_batch_uniqueness(self, contents: List[str]) -> Dict:
+ """
+ 批量检查内容独特性
+
+ Args:
+ contents: 内容列表
+
+ Returns:
+ 检查结果
+ """
+ if len(contents) < 2:
+ return {
+ "is_unique": True,
+ "message": "内容数量不足,无需检查"
+ }
+
+ # 计算两两相似度
+ similarity_matrix = []
+ high_similarity_pairs = []
+
+ for i in range(len(contents)):
+ for j in range(i + 1, len(contents)):
+ similarity = self.calculate_similarity(contents[i], contents[j])
+ similarity_matrix.append({
+ "pair": (i, j),
+ "similarity": similarity
+ })
+
+ if similarity > self.similarity_threshold:
+ high_similarity_pairs.append({
+ "content_index_1": i,
+ "content_index_2": j,
+ "similarity": similarity,
+ "preview_1": contents[i][:100] + "...",
+ "preview_2": contents[j][:100] + "..."
+ })
+
+ # 计算整体独特性分数
+ if similarity_matrix:
+ avg_similarity = sum(s["similarity"] for s in similarity_matrix) / len(similarity_matrix)
+ max_similarity = max(s["similarity"] for s in similarity_matrix)
+ else:
+ avg_similarity = 0
+ max_similarity = 0
+
+ # 计算独特性分数 (0-100)
+ uniqueness_score = max(0, (1 - avg_similarity) * 100)
+
+ return {
+ "is_unique": len(high_similarity_pairs) == 0,
+ "total_contents": len(contents),
+ "high_similarity_pairs": high_similarity_pairs,
+ "avg_similarity": avg_similarity,
+ "max_similarity": max_similarity,
+ "uniqueness_score": uniqueness_score,
+ "suggestions": self._generate_suggestions(high_similarity_pairs, avg_similarity)
+ }
+
+ def calculate_similarity(self, text1: str, text2: str) -> float:
+ """
+ 计算两段文本的相似度
+
+ Args:
+ text1: 文本1
+ text2: 文本2
+
+ Returns:
+ 相似度分数 (0-1)
+ """
+ # 使用多种方法综合计算
+
+ # 1. 词汇重叠度 (Jaccard 相似度)
+ words1 = set(self._tokenize(text1))
+ words2 = set(self._tokenize(text2))
+
+ if not words1 or not words2:
+ return 0
+
+ intersection = words1 & words2
+ union = words1 | words2
+ jaccard_similarity = len(intersection) / len(union)
+
+ # 2. 结构相似度 (基于句子结构)
+ structure_similarity = self._calculate_structure_similarity(text1, text2)
+
+ # 3. 关键信息重叠度
+ key_info_similarity = self._calculate_key_info_similarity(text1, text2)
+
+ # 综合相似度
+ total_similarity = (
+ jaccard_similarity * 0.4 +
+ structure_similarity * 0.3 +
+ key_info_similarity * 0.3
+ )
+
+ return total_similarity
+
+ def _tokenize(self, text: str) -> List[str]:
+ """分词(简单实现)"""
+ # 移除标点符号,按空格分词
+ text = re.sub(r'[^\w\s]', ' ', text)
+ words = text.lower().split()
+
+ # 过滤停用词
+ stop_words = {'的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都',
+ '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你',
+ 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been',
+ 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will',
+ 'would', 'could', 'should', 'may', 'might', 'can', 'shall'}
+
+ return [w for w in words if w not in stop_words and len(w) > 1]
+
+ def _calculate_structure_similarity(self, text1: str, text2: str) -> float:
+ """计算结构相似度"""
+ # 提取结构特征
+ features1 = self._extract_structure_features(text1)
+ features2 = self._extract_structure_features(text2)
+
+ # 比较特征
+ similarity = 0
+ total_features = 0
+
+ for key in set(features1.keys()) | set(features2.keys()):
+ if key in features1 and key in features2:
+ # 数值型特征
+ if isinstance(features1[key], (int, float)):
+ max_val = max(abs(features1[key]), abs(features2[key]))
+ if max_val > 0:
+ similarity += 1 - abs(features1[key] - features2[key]) / max_val
+ # 列表型特征
+ elif isinstance(features1[key], list):
+ set1 = set(features1[key])
+ set2 = set(features2[key])
+ if set1 or set2:
+ similarity += len(set1 & set2) / len(set1 | set2)
+ total_features += 1
+
+ return similarity / total_features if total_features > 0 else 0
+
+ def _extract_structure_features(self, text: str) -> Dict:
+ """提取文本结构特征"""
+ lines = text.split('\n')
+
+ return {
+ "total_chars": len(text),
+ "total_lines": len(lines),
+ "avg_line_length": sum(len(line) for line in lines) / len(lines) if lines else 0,
+ "has_headers": any(line.startswith('#') for line in lines),
+ "has_list": any(re.match(r'^\s*[-*•]\s', line) for line in lines),
+ "has_numbered_list": any(re.match(r'^\s*\d+[.、]\s', line) for line in lines),
+ "header_count": sum(1 for line in lines if line.startswith('#')),
+ "paragraph_count": sum(1 for line in lines if line.strip() == '') + 1
+ }
+
+ def _calculate_key_info_similarity(self, text1: str, text2: str) -> float:
+ """计算关键信息重叠度"""
+ # 提取数字
+ numbers1 = set(re.findall(r'\d+', text1))
+ numbers2 = set(re.findall(r'\d+', text2))
+
+ # 提取引号内容
+ quotes1 = set(re.findall(r'[""「」『』](.+?)[""「」『』]', text1))
+ quotes2 = set(re.findall(r'[""「」『』](.+?)[""「」『』]', text2))
+
+ # 提取英文单词(可能是专业术语)
+ english1 = set(re.findall(r'[A-Za-z]+', text1))
+ english2 = set(re.findall(r'[A-Za-z]+', text2))
+
+ # 计算重叠度
+ number_overlap = len(numbers1 & numbers2) / max(len(numbers1 | numbers2), 1)
+ quote_overlap = len(quotes1 & quotes2) / max(len(quotes1 | quotes2), 1)
+ english_overlap = len(english1 & english2) / max(len(english1 | english2), 1)
+
+ return (number_overlap + quote_overlap + english_overlap) / 3
+
+ def _generate_suggestions(self, high_similarity_pairs: List[Dict],
+ avg_similarity: float) -> List[str]:
+ """生成改进建议"""
+ suggestions = []
+
+ if high_similarity_pairs:
+ suggestions.append(f"发现 {len(high_similarity_pairs)} 对高度相似的内容,建议修改其中一篇")
+
+ # 给出具体建议
+ for pair in high_similarity_pairs[:3]:
+ suggestions.append(
+ f"内容 {pair['content_index_1']+1} 和 {pair['content_index_2']+1} "
+ f"相似度为 {pair['similarity']:.0%},建议调整角度或添加独特案例"
+ )
+
+ if avg_similarity > 0.5:
+ suggestions.append("整体相似度较高,建议:")
+ suggestions.append("1. 为每篇内容选择不同的切入角度")
+ suggestions.append("2. 添加独特的案例或数据")
+ suggestions.append("3. 使用不同的表达方式和结构")
+
+ if not suggestions:
+ suggestions.append("内容独特性良好,无需修改")
+
+ return suggestions
+
+ def find_duplicate_sentences(self, contents: List[str],
+ min_length: int = 20) -> List[Dict]:
+ """
+ 查找重复句子
+
+ Args:
+ contents: 内容列表
+ min_length: 最小句子长度
+
+ Returns:
+ 重复句子列表
+ """
+ # 提取所有句子
+ sentence_sources = {} # sentence -> [content_index]
+
+ for i, content in enumerate(contents):
+ sentences = re.split(r'[。!?.!?]', content)
+ for sentence in sentences:
+ sentence = sentence.strip()
+ if len(sentence) >= min_length:
+ if sentence not in sentence_sources:
+ sentence_sources[sentence] = []
+ sentence_sources[sentence].append(i)
+
+ # 找出重复句子
+ duplicates = []
+ for sentence, sources in sentence_sources.items():
+ if len(set(sources)) > 1: # 出现在多篇内容中
+ duplicates.append({
+ "sentence": sentence,
+ "appears_in": list(set(sources)),
+ "count": len(set(sources))
+ })
+
+ # 按出现次数排序
+ duplicates.sort(key=lambda x: x["count"], reverse=True)
+
+ return duplicates
+
+ def generate_uniqueness_report(self, contents: List[str]) -> Dict:
+ """
+ 生成独特性报告
+
+ Args:
+ contents: 内容列表
+
+ Returns:
+ 报告数据
+ """
+ # 批量检查
+ batch_result = self.check_batch_uniqueness(contents)
+
+ # 查找重复句子
+ duplicate_sentences = self.find_duplicate_sentences(contents)
+
+ # 计算内容指纹
+ fingerprints = [self._calculate_fingerprint(content) for content in contents]
+ unique_fingerprints = len(set(fingerprints))
+
+ return {
+ **batch_result,
+ "duplicate_sentences": duplicate_sentences,
+ "unique_fingerprints": unique_fingerprints,
+ "fingerprint_uniqueness": unique_fingerprints / len(contents) if contents else 0
+ }
+
+ def _calculate_fingerprint(self, text: str) -> str:
+ """计算文本指纹"""
+ # 提取关键特征
+ words = self._tokenize(text)
+ # 取前100个词的哈希作为指纹
+ fingerprint_text = ' '.join(words[:100])
+ return hashlib.md5(fingerprint_text.encode()).hexdigest()[:16]
+
+
+def check_content_similarity(content1: str, content2: str) -> Dict:
+ """
+ 检查两段内容的相似度
+
+ Args:
+ content1: 内容1
+ content2: 内容2
+
+ Returns:
+ 相似度分析结果
+ """
+ checker = ContentUniquenessChecker()
+
+ similarity = checker.calculate_similarity(content1, content2)
+
+ # 找出共同句子
+ sentences1 = set(re.split(r'[。!?.!?]', content1))
+ sentences2 = set(re.split(r'[。!?.!?]', content2))
+
+ common_sentences = []
+ for s1 in sentences1:
+ s1 = s1.strip()
+ if len(s1) >= 20:
+ for s2 in sentences2:
+ s2 = s2.strip()
+ if s1 == s2:
+ common_sentences.append(s1)
+
+ return {
+ "similarity": similarity,
+ "is_similar": similarity > 0.7,
+ "common_sentences": common_sentences,
+ "suggestion": "内容过于相似,建议修改" if similarity > 0.7 else "内容独特性良好"
+ }
diff --git a/modules/data_storage.py b/modules/data_storage.py
index 3911833..f4482b7 100644
--- a/modules/data_storage.py
+++ b/modules/data_storage.py
@@ -4,7 +4,7 @@
"""
import sqlite3
import json
-import os
+import logging
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Optional, Any
@@ -30,7 +30,7 @@ class DataStorage:
def _init_sqlite(self):
"""初始化SQLite数据库"""
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
# 关键词表
@@ -201,7 +201,7 @@ class DataStorage:
def save_keywords(self, keywords: List[str], brand: str):
"""保存关键词列表"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
for keyword in keywords:
cursor.execute(
@@ -230,7 +230,7 @@ class DataStorage:
def get_keywords(self, brand: Optional[str] = None) -> List[str]:
"""获取关键词列表"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
if brand:
cursor.execute("SELECT keyword FROM keywords WHERE brand = ?", (brand,))
@@ -256,7 +256,7 @@ class DataStorage:
filename: str, brand: str):
"""保存生成的文章"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO articles (keyword, platform, content, filename, brand)
@@ -286,7 +286,7 @@ class DataStorage:
platform: Optional[str] = None) -> List[Dict]:
"""获取文章列表"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
if brand and platform:
df = pd.read_sql_query(
"SELECT * FROM articles WHERE brand = ? AND platform = ?",
@@ -321,7 +321,7 @@ class DataStorage:
changes: str, platform: str, brand: str):
"""保存优化记录"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO optimizations
@@ -351,7 +351,7 @@ class DataStorage:
def get_optimizations(self, brand: Optional[str] = None) -> List[Dict]:
"""获取优化记录"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
if brand:
df = pd.read_sql_query(
"SELECT * FROM optimizations WHERE brand = ? ORDER BY created_at DESC",
@@ -380,7 +380,7 @@ class DataStorage:
def save_verify_results(self, results: List[Dict]):
"""批量保存验证结果"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
for result in results:
cursor.execute("""
@@ -423,7 +423,7 @@ class DataStorage:
include_timestamp: 是否包含时间戳字段
"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
if include_timestamp:
if brand:
df = pd.read_sql_query(
@@ -496,7 +496,7 @@ class DataStorage:
stats = {}
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
# 关键词数量
@@ -567,7 +567,7 @@ class DataStorage:
):
"""保存 API 调用记录"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO api_calls
@@ -613,7 +613,7 @@ class DataStorage:
) -> pd.DataFrame:
"""获取 API 调用记录(返回 DataFrame)"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
query = """
SELECT
operation_type as "操作类型",
@@ -694,7 +694,7 @@ class DataStorage:
def get_cost_stats(self, brand: Optional[str] = None) -> Dict[str, Any]:
"""获取成本统计"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
if brand:
@@ -756,7 +756,7 @@ class DataStorage:
workflow["id"] = workflow_id
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO workflows
@@ -795,7 +795,7 @@ class DataStorage:
def get_workflow(self, workflow_id: str) -> Optional[Dict[str, Any]]:
"""获取工作流"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM workflows WHERE id = ?", (workflow_id,))
row = cursor.fetchone()
@@ -829,7 +829,7 @@ class DataStorage:
def list_workflows(self, enabled_only: bool = False) -> List[Dict[str, Any]]:
"""列出所有工作流"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
if enabled_only:
cursor = conn.cursor()
cursor.execute("SELECT * FROM workflows WHERE enabled = 1 ORDER BY updated_at DESC")
@@ -878,7 +878,7 @@ class DataStorage:
def delete_workflow(self, workflow_id: str) -> bool:
"""删除工作流"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM workflows WHERE id = ?", (workflow_id,))
conn.commit()
@@ -900,7 +900,7 @@ class DataStorage:
def save_workflow_execution(self, execution: Dict[str, Any]):
"""保存工作流执行记录"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO workflow_executions
@@ -930,7 +930,7 @@ class DataStorage:
def get_workflow_executions(self, workflow_id: Optional[str] = None, limit: int = 50) -> List[Dict[str, Any]]:
"""获取工作流执行记录"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
if workflow_id:
df = pd.read_sql_query(
"SELECT * FROM workflow_executions WHERE workflow_id = ? ORDER BY started_at DESC LIMIT ?",
@@ -962,7 +962,7 @@ class DataStorage:
template["id"] = template_id
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO workflow_templates
@@ -997,7 +997,7 @@ class DataStorage:
def get_workflow_template(self, template_id: str) -> Optional[Dict[str, Any]]:
"""获取工作流模板"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM workflow_templates WHERE id = ?", (template_id,))
row = cursor.fetchone()
@@ -1028,7 +1028,7 @@ class DataStorage:
def get_workflow_templates(self) -> List[Dict[str, Any]]:
"""获取所有工作流模板"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
df = pd.read_sql_query("SELECT * FROM workflow_templates ORDER BY created_at DESC", conn)
return df.to_dict('records')
else:
@@ -1044,7 +1044,7 @@ class DataStorage:
def save_platform_account(self, platform: str, account_config: Dict[str, Any], brand: str):
"""保存平台账号配置"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO platform_accounts
@@ -1097,7 +1097,7 @@ class DataStorage:
def get_platform_account(self, platform: str, brand: str) -> Optional[Dict[str, Any]]:
"""获取平台账号配置"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM platform_accounts
@@ -1134,7 +1134,7 @@ class DataStorage:
def list_platform_accounts(self, brand: Optional[str] = None) -> List[Dict[str, Any]]:
"""列出所有平台账号"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
if brand:
df = pd.read_sql_query(
"SELECT * FROM platform_accounts WHERE brand = ? AND is_active = 1 ORDER BY updated_at DESC",
@@ -1165,7 +1165,7 @@ class DataStorage:
error_message: str = '', retry_count: int = 0):
"""保存发布记录"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO publish_records
@@ -1206,7 +1206,7 @@ class DataStorage:
brand: Optional[str] = None) -> List[Dict]:
"""获取发布记录"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
query = "SELECT pr.*, a.brand FROM publish_records pr LEFT JOIN articles a ON pr.article_id = a.id WHERE 1=1"
params = []
@@ -1237,13 +1237,15 @@ class DataStorage:
data = [r for r in data if r.get('article_id') == article_id]
if platform:
data = [r for r in data if r.get('platform') == platform]
+ if brand:
+ data = [r for r in data if r.get('brand') == brand]
return data
def get_article_by_id(self, article_id: int) -> Optional[Dict]:
"""根据ID获取文章"""
if self.storage_type == "sqlite":
- with sqlite3.connect(self.db_path) as conn:
+ with sqlite3.connect(self.db_path, check_same_thread=False) as conn:
df = pd.read_sql_query(
"SELECT * FROM articles WHERE id = ?",
conn, params=(article_id,)
diff --git a/modules/eeat_enhancer.py b/modules/eeat_enhancer.py
index c5dbbb1..6f2eed8 100644
--- a/modules/eeat_enhancer.py
+++ b/modules/eeat_enhancer.py
@@ -136,11 +136,11 @@ class EEATEnhancer:
1. **数据来源占位**(至少2处)
- 格式:"根据XX行业报告"、"XX数据显示"、"据XX统计"
- - 示例:"根据2024年外贸软件行业报告显示"、"据公开市场调研数据显示"
+ - 示例:"根据2024年行业报告显示"、"据公开市场调研数据显示"
2. **案例来源占位**(至少1处)
- 格式:"某企业案例"、"参考XX实践"、"XX公司案例"
- - 示例:"参考某大型外贸企业的实际应用案例"、"某知名企业的成功实践表明"
+ - 示例:"参考某大型企业的实际应用案例"、"某知名企业的成功实践表明"
3. **标准来源占位**(至少1处)
- 格式:"按照XX标准"、"参考XX规范"、"符合XX要求"
diff --git a/modules/keyword_data_enhancer.py b/modules/keyword_data_enhancer.py
new file mode 100644
index 0000000..0659bdf
--- /dev/null
+++ b/modules/keyword_data_enhancer.py
@@ -0,0 +1,354 @@
+"""
+关键词数据增强模块
+从历史验证数据中提取高价值关键词,反哺关键词生成
+"""
+
+import json
+import logging
+from typing import Dict, List, Optional, Any
+from collections import Counter
+from datetime import datetime, timedelta
+
+logger = logging.getLogger(__name__)
+
+
+class KeywordDataEnhancer:
+ """关键词数据增强器"""
+
+ def __init__(self, storage):
+ """
+ Args:
+ storage: DataStorage 实例
+ """
+ self.storage = storage
+
+ def analyze_historical_performance(self, brand: str,
+ days: int = 30) -> Dict:
+ """
+ 分析历史验证数据,提取高价值关键词
+
+ Args:
+ brand: 品牌名
+ days: 分析最近 N 天的数据
+
+ Returns:
+ 分析结果
+ """
+ # 获取历史验证结果
+ verify_results = self.storage.get_verify_results(brand=brand)
+
+ if not verify_results:
+ return {
+ "has_data": False,
+ "message": "暂无历史验证数据"
+ }
+
+ # 转换为 DataFrame 便于分析
+ import pandas as pd
+ df = pd.DataFrame(verify_results)
+
+ if df.empty:
+ return {"has_data": False, "message": "暂无历史验证数据"}
+
+ # 分析关键词表现
+ keyword_performance = self._analyze_keyword_performance(df)
+
+ # 提取高价值关键词
+ high_value_keywords = self._extract_high_value_keywords(keyword_performance)
+
+ # 分析搜索意图分布
+ intent_distribution = self._analyze_intent_distribution(keyword_performance)
+
+ # 生成关键词建议
+ suggestions = self._generate_keyword_suggestions(keyword_performance)
+
+ return {
+ "has_data": True,
+ "total_keywords": len(keyword_performance),
+ "high_value_keywords": high_value_keywords,
+ "intent_distribution": intent_distribution,
+ "suggestions": suggestions,
+ "keyword_details": keyword_performance
+ }
+
+ def _analyze_keyword_performance(self, df) -> List[Dict]:
+ """分析每个关键词的表现"""
+ keyword_stats = []
+
+ for keyword in df["问题"].unique():
+ keyword_df = df[df["问题"] == keyword]
+
+ # 计算提及率
+ mentioned = keyword_df[keyword_df["提及次数"] > 0]
+ mention_rate = len(mentioned) / len(keyword_df) if len(keyword_df) > 0 else 0
+
+ # 计算平均提及次数
+ avg_mentions = keyword_df["提及次数"].mean()
+
+ # 分析提及位置
+ position_counts = Counter()
+ for _, row in keyword_df.iterrows():
+ pos = row.get("位置", "未提及")
+ if pos and pos != "未提及":
+ position_counts[pos] += 1
+
+ # 计算综合价值分数
+ value_score = self._calculate_value_score(
+ mention_rate, avg_mentions, position_counts
+ )
+
+ keyword_stats.append({
+ "keyword": keyword,
+ "total_verifications": len(keyword_df),
+ "mention_rate": mention_rate,
+ "avg_mentions": avg_mentions,
+ "position_distribution": dict(position_counts),
+ "value_score": value_score,
+ "suggested_action": self._suggest_action(mention_rate, value_score)
+ })
+
+ # 按价值分数排序
+ keyword_stats.sort(key=lambda x: x["value_score"], reverse=True)
+
+ return keyword_stats
+
+ def _calculate_value_score(self, mention_rate: float,
+ avg_mentions: float,
+ position_counts: Counter) -> float:
+ """
+ 计算关键词价值分数
+
+ Args:
+ mention_rate: 提及率
+ avg_mentions: 平均提及次数
+ position_counts: 位置分布
+
+ Returns:
+ 价值分数 (0-100)
+ """
+ score = 0
+
+ # 提及率权重 (40%)
+ score += mention_rate * 40
+
+ # 平均提及次数权重 (30%)
+ mention_score = min(avg_mentions / 3, 1) * 30
+ score += mention_score
+
+ # 位置权重 (30%)
+ total_mentions = sum(position_counts.values())
+ if total_mentions > 0:
+ front_ratio = position_counts.get("前1/3", 0) / total_mentions
+ score += front_ratio * 30
+
+ return score
+
+ def _suggest_action(self, mention_rate: float, value_score: float) -> str:
+ """根据表现建议操作"""
+ if value_score >= 70:
+ return "✅ 高价值关键词,继续保持"
+ elif value_score >= 40:
+ if mention_rate < 0.5:
+ return "⚡ 提及率较低,建议优化内容"
+ else:
+ return "📈 有提升空间,建议增加深度"
+ else:
+ if mention_rate < 0.3:
+ return "🔄 效果不佳,考虑替换关键词"
+ else:
+ return "🔍 价值较低,可减少投入"
+
+ def _extract_high_value_keywords(self, keyword_performance: List[Dict],
+ top_n: int = 10) -> List[Dict]:
+ """提取高价值关键词"""
+ return keyword_performance[:top_n]
+
+ def _analyze_intent_distribution(self, keyword_performance: List[Dict]) -> Dict:
+ """分析搜索意图分布"""
+ intent_keywords = {
+ "对比": ["对比", "比较", "vs", "versus", "哪个好"],
+ "评测": ["评测", "评价", "测评", "怎么样", "好不好"],
+ "使用": ["怎么用", "如何使用", "教程", "入门", "指南"],
+ "购买": ["价格", "多少钱", "购买", "付费", "免费"],
+ "问题": ["问题", "错误", "失败", "怎么办", "解决"],
+ "推荐": ["推荐", "最好", "排行", "排名", "前十"]
+ }
+
+ intent_counts = {intent: 0 for intent in intent_keywords}
+ intent_keywords_map = {intent: [] for intent in intent_keywords}
+
+ for kw_data in keyword_performance:
+ keyword = kw_data["keyword"]
+ categorized = False
+
+ for intent, patterns in intent_keywords.items():
+ if any(pattern in keyword for pattern in patterns):
+ intent_counts[intent] += 1
+ intent_keywords_map[intent].append(keyword)
+ categorized = True
+ break
+
+ if not categorized:
+ intent_counts["其他"] = intent_counts.get("其他", 0) + 1
+
+ return {
+ "counts": intent_counts,
+ "keywords": intent_keywords_map
+ }
+
+ def _generate_keyword_suggestions(self, keyword_performance: List[Dict]) -> List[Dict]:
+ """生成关键词优化建议"""
+ suggestions = []
+
+ # 找出低效关键词
+ low_performers = [kw for kw in keyword_performance if kw["value_score"] < 30]
+ if low_performers:
+ suggestions.append({
+ "type": "replace",
+ "priority": "high",
+ "message": f"有 {len(low_performers)} 个关键词效果不佳,建议替换",
+ "keywords": [kw["keyword"] for kw in low_performers[:3]]
+ })
+
+ # 找出高价值关键词
+ high_performers = [kw for kw in keyword_performance if kw["value_score"] >= 70]
+ if high_performers:
+ suggestions.append({
+ "type": "expand",
+ "priority": "medium",
+ "message": f"有 {len(high_performers)} 个高价值关键词,建议扩展相关内容",
+ "keywords": [kw["keyword"] for kw in high_performers[:3]]
+ })
+
+ # 找出提及率低但有潜力的关键词
+ potential_keywords = [
+ kw for kw in keyword_performance
+ if 0.3 <= kw["mention_rate"] < 0.5 and kw["total_verifications"] >= 3
+ ]
+ if potential_keywords:
+ suggestions.append({
+ "type": "optimize",
+ "priority": "medium",
+ "message": f"有 {len(potential_keywords)} 个关键词有提升空间,建议优化内容",
+ "keywords": [kw["keyword"] for kw in potential_keywords[:3]]
+ })
+
+ return suggestions
+
+ def generate_enhanced_keyword_prompt(self, brand: str, advantages: str,
+ existing_keywords: List[str] = None) -> str:
+ """
+ 生成增强的关键词生成提示词
+
+ Args:
+ brand: 品牌名
+ advantages: 品牌优势
+ existing_keywords: 已有关键词列表
+
+ Returns:
+ 增强的提示词
+ """
+ # 获取历史分析
+ analysis = self.analyze_historical_performance(brand)
+
+ prompt = f"""你是一个 GEO(生成式引擎优化)关键词策略专家。
+
+品牌信息:
+- 品牌名:{brand}
+- 品牌优势:{advantages}
+
+"""
+
+ if analysis.get("has_data"):
+ prompt += """历史验证数据分析:
+"""
+ # 添加高价值关键词
+ high_value = analysis.get("high_value_keywords", [])
+ if high_value:
+ prompt += "\n高价值关键词(已验证有效):\n"
+ for kw in high_value[:5]:
+ prompt += f"- {kw['keyword']} (提及率: {kw['mention_rate']:.0%}, 价值分: {kw['value_score']:.0f})\n"
+
+ # 添加优化建议
+ suggestions = analysis.get("suggestions", [])
+ if suggestions:
+ prompt += "\n优化建议:\n"
+ for suggestion in suggestions:
+ prompt += f"- {suggestion['message']}\n"
+
+ # 添加意图分布
+ intent_dist = analysis.get("intent_distribution", {}).get("counts", {})
+ if intent_dist:
+ prompt += "\n搜索意图分布:\n"
+ for intent, count in sorted(intent_dist.items(), key=lambda x: x[1], reverse=True):
+ if count > 0:
+ prompt += f"- {intent}: {count} 个关键词\n"
+
+ if existing_keywords:
+ prompt += f"\n已有关键词(避免重复):\n"
+ for kw in existing_keywords[:10]:
+ prompt += f"- {kw}\n"
+
+ prompt += """
+请生成 20 个新的 GEO 优化关键词,要求:
+1. 70% 泛词(行业相关)+ 30% 品牌词
+2. 覆盖多种搜索意图:对比、评测、使用、购买、问题、推荐
+3. 关键词长度 12-28 字,口语化,符合用户真实搜索习惯
+4. 每个关键词附带:category(类别)、intent(意图)、estimated_value(预估价值 1-5)
+
+输出 JSON 数组格式:
+[
+ {
+ "keyword": "关键词内容",
+ "category": "类别",
+ "intent": "意图",
+ "estimated_value": 4
+ }
+]
+"""
+
+ return prompt
+
+ def get_keyword_trends(self, brand: str, keyword: str,
+ days: int = 30) -> Dict:
+ """
+ 获取关键词趋势数据
+
+ Args:
+ brand: 品牌名
+ keyword: 关键词
+ days: 分析天数
+
+ Returns:
+ 趋势数据
+ """
+ verify_results = self.storage.get_verify_results(brand=brand)
+
+ if not verify_results:
+ return {"has_data": False}
+
+ import pandas as pd
+ df = pd.DataFrame(verify_results)
+
+ # 过滤指定关键词
+ keyword_df = df[df["问题"] == keyword]
+
+ if keyword_df.empty:
+ return {"has_data": False, "message": f"未找到关键词 '{keyword}' 的验证数据"}
+
+ # 按日期分组
+ if "验证时间" in keyword_df.columns:
+ keyword_df["日期"] = pd.to_datetime(keyword_df["验证时间"]).dt.date
+ daily_stats = keyword_df.groupby("日期").agg({
+ "提及次数": "mean",
+ "问题": "count"
+ }).rename(columns={"问题": "验证次数"})
+
+ return {
+ "has_data": True,
+ "keyword": keyword,
+ "daily_stats": daily_stats.to_dict("records"),
+ "overall_mention_rate": len(keyword_df[keyword_df["提及次数"] > 0]) / len(keyword_df)
+ }
+
+ return {"has_data": False, "message": "缺少时间戳数据"}
diff --git a/modules/keyword_tool.py b/modules/keyword_tool.py
index dbbb8b2..5f296b3 100644
--- a/modules/keyword_tool.py
+++ b/modules/keyword_tool.py
@@ -16,7 +16,7 @@ class KeywordTool:
self.default_wordbanks = {
"A前缀1": ["行业上", "市场上", "市面上", "目前", "国内", "市场"],
"B前缀2": ["口碑好的", "比较好的", "靠谱的", "有实力的", "可靠的", "诚信的", "正规的", "专业的", "热门的", "知名的"],
- "C主词": ["外贸软件", "外贸ERP", "CRM管理系统"],
+ "C主词": ["软件", "管理系统", "工具"],
"D通义词": ["品牌", "公司", "工厂", "厂商", "生产厂家", "供应商"],
"E推荐词": ["推荐", "排行", "推荐榜", "排行榜", "推荐榜单", "推荐排行", "推荐排行榜", "口碑排行"],
"F疑问词": ["哪家好", "哪家强", "哪家靠谱", "哪家权威", "哪个好", "有哪些", "找哪家", "选哪家", "为什么"],
diff --git a/modules/knowledge_base.py b/modules/knowledge_base.py
new file mode 100644
index 0000000..59d976b
--- /dev/null
+++ b/modules/knowledge_base.py
@@ -0,0 +1,529 @@
+"""
+RAG 知识库模块
+支持用户上传品牌文档,自动分块、索引,生成内容时自动检索相关内容
+"""
+
+import json
+import hashlib
+import logging
+from pathlib import Path
+from typing import List, Dict, Optional, Any
+from datetime import datetime
+
+logger = logging.getLogger(__name__)
+
+
+class DocumentChunk:
+ """文档分块"""
+
+ def __init__(self, content: str, metadata: Dict[str, Any]):
+ self.content = content
+ self.metadata = metadata
+ self.chunk_id = hashlib.md5(content.encode()).hexdigest()[:12]
+
+ def to_dict(self) -> Dict:
+ return {
+ "chunk_id": self.chunk_id,
+ "content": self.content,
+ "metadata": self.metadata
+ }
+
+ @classmethod
+ def from_dict(cls, data: Dict) -> 'DocumentChunk':
+ chunk = cls(data["content"], data["metadata"])
+ chunk.chunk_id = data["chunk_id"]
+ return chunk
+
+
+class KnowledgeBase:
+ """知识库管理器"""
+
+ def __init__(self, storage_path: str = "knowledge_base"):
+ """
+ Args:
+ storage_path: 知识库存储路径
+ """
+ self.storage_path = Path(storage_path)
+ self.storage_path.mkdir(parents=True, exist_ok=True)
+
+ # 文档元数据
+ self.documents_file = self.storage_path / "documents.json"
+ # 分块数据
+ self.chunks_file = self.storage_path / "chunks.json"
+
+ self.documents: Dict[str, Dict] = self._load_json(self.documents_file, {})
+ self.chunks: List[Dict] = self._load_json(self.chunks_file, [])
+
+ def _load_json(self, path: Path, default: Any) -> Any:
+ """加载 JSON 文件"""
+ if path.exists():
+ try:
+ with open(path, 'r', encoding='utf-8') as f:
+ return json.load(f)
+ except Exception as e:
+ logger.warning(f"加载 {path} 失败: {e}")
+ return default
+
+ def _save_json(self, path: Path, data: Any):
+ """保存 JSON 文件"""
+ try:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(data, f, ensure_ascii=False, indent=2)
+ except Exception as e:
+ logger.error(f"保存 {path} 失败: {e}")
+
+ def add_document(self, filename: str, content: str, doc_type: str = "text",
+ metadata: Optional[Dict] = None) -> Dict:
+ """
+ 添加文档到知识库
+
+ Args:
+ filename: 文件名
+ content: 文档内容
+ doc_type: 文档类型 (text, markdown, faq, case, product)
+ metadata: 额外元数据
+
+ Returns:
+ 文档信息
+ """
+ doc_id = hashlib.md5(f"{filename}{datetime.now().isoformat()}".encode()).hexdigest()[:12]
+
+ doc_info = {
+ "doc_id": doc_id,
+ "filename": filename,
+ "doc_type": doc_type,
+ "content_length": len(content),
+ "chunk_count": 0,
+ "created_at": datetime.now().isoformat(),
+ "metadata": metadata or {}
+ }
+
+ # 分块
+ chunks = self._split_document(content, doc_id, filename, doc_type)
+ doc_info["chunk_count"] = len(chunks)
+
+ # 保存
+ self.documents[doc_id] = doc_info
+ self.chunks.extend([c.to_dict() for c in chunks])
+
+ self._save_json(self.documents_file, self.documents)
+ self._save_json(self.chunks_file, self.chunks)
+
+ logger.info(f"文档 '{filename}' 已添加,分为 {len(chunks)} 个分块")
+ return doc_info
+
+ def _split_document(self, content: str, doc_id: str, filename: str,
+ doc_type: str, chunk_size: int = 500, overlap: int = 50) -> List[DocumentChunk]:
+ """
+ 将文档分割为多个分块
+
+ Args:
+ content: 文档内容
+ doc_id: 文档 ID
+ filename: 文件名
+ doc_type: 文档类型
+ chunk_size: 分块大小(字符数)
+ overlap: 重叠字符数
+
+ Returns:
+ 分块列表
+ """
+ chunks = []
+
+ # 根据文档类型选择分块策略
+ if doc_type == "faq":
+ # FAQ 文档按 Q&A 对分块
+ chunks = self._split_faq(content, doc_id, filename)
+ elif doc_type == "product":
+ # 产品文档按功能/特性分块
+ chunks = self._split_by_sections(content, doc_id, filename, doc_type)
+ else:
+ # 通用文档按段落/长度分块
+ chunks = self._split_by_length(content, doc_id, filename, doc_type,
+ chunk_size, overlap)
+
+ return chunks
+
+ def _split_faq(self, content: str, doc_id: str, filename: str) -> List[DocumentChunk]:
+ """FAQ 文档分块:每个 Q&A 对为一个分块"""
+ chunks = []
+ lines = content.split('\n')
+
+ current_q = ""
+ current_a = ""
+
+ for line in lines:
+ line = line.strip()
+ if not line:
+ continue
+
+ # 检测问题行
+ if line.startswith('Q:') or line.startswith('问:') or line.startswith('Q:'):
+ # 保存上一个 Q&A 对
+ if current_q and current_a:
+ chunk_content = f"问题:{current_q}\n回答:{current_a}"
+ chunks.append(DocumentChunk(
+ content=chunk_content,
+ metadata={
+ "doc_id": doc_id,
+ "filename": filename,
+ "type": "faq",
+ "question": current_q
+ }
+ ))
+ current_q = line[2:].strip()
+ current_a = ""
+ elif line.startswith('A:') or line.startswith('答:') or line.startswith('A:'):
+ current_a = line[2:].strip()
+ elif current_a:
+ current_a += "\n" + line
+ elif current_q and not current_a:
+ current_q += "\n" + line
+
+ # 保存最后一个 Q&A 对
+ if current_q and current_a:
+ chunk_content = f"问题:{current_q}\n回答:{current_a}"
+ chunks.append(DocumentChunk(
+ content=chunk_content,
+ metadata={
+ "doc_id": doc_id,
+ "filename": filename,
+ "type": "faq",
+ "question": current_q
+ }
+ ))
+
+ return chunks
+
+ def _split_by_sections(self, content: str, doc_id: str, filename: str,
+ doc_type: str) -> List[DocumentChunk]:
+ """按章节分块(适用于产品文档、Markdown 等)"""
+ chunks = []
+ sections = content.split('\n# ')
+
+ for i, section in enumerate(sections):
+ if not section.strip():
+ continue
+
+ # 提取标题
+ lines = section.split('\n', 1)
+ title = lines[0].strip('# ').strip()
+ body = lines[1].strip() if len(lines) > 1 else ""
+
+ if body:
+ chunks.append(DocumentChunk(
+ content=f"## {title}\n{body}",
+ metadata={
+ "doc_id": doc_id,
+ "filename": filename,
+ "type": doc_type,
+ "section_title": title
+ }
+ ))
+
+ return chunks
+
+ def _split_by_length(self, content: str, doc_id: str, filename: str,
+ doc_type: str, chunk_size: int, overlap: int) -> List[DocumentChunk]:
+ """按长度分块"""
+ chunks = []
+ paragraphs = content.split('\n\n')
+
+ current_chunk = ""
+ for para in paragraphs:
+ para = para.strip()
+ if not para:
+ continue
+
+ if len(current_chunk) + len(para) > chunk_size and current_chunk:
+ chunks.append(DocumentChunk(
+ content=current_chunk,
+ metadata={
+ "doc_id": doc_id,
+ "filename": filename,
+ "type": doc_type
+ }
+ ))
+ # 保留重叠部分
+ if overlap > 0:
+ current_chunk = current_chunk[-overlap:] + "\n" + para
+ else:
+ current_chunk = para
+ else:
+ current_chunk = current_chunk + "\n\n" + para if current_chunk else para
+
+ # 保存最后一个分块
+ if current_chunk.strip():
+ chunks.append(DocumentChunk(
+ content=current_chunk,
+ metadata={
+ "doc_id": doc_id,
+ "filename": filename,
+ "type": doc_type
+ }
+ ))
+
+ return chunks
+
+ def search(self, query: str, top_k: int = 5,
+ doc_type: Optional[str] = None) -> List[Dict]:
+ """
+ 搜索知识库
+
+ Args:
+ query: 查询文本
+ top_k: 返回结果数量
+ doc_type: 过滤文档类型
+
+ Returns:
+ 相关分块列表
+ """
+ if not self.chunks:
+ return []
+
+ # 计算相似度分数
+ scored_chunks = []
+ query_lower = query.lower()
+ query_keywords = set(query_lower.split())
+
+ for chunk_data in self.chunks:
+ content_lower = chunk_data["content"].lower()
+
+ # 计算关键词匹配分数
+ keyword_matches = sum(1 for kw in query_keywords if kw in content_lower)
+ keyword_score = keyword_matches / len(query_keywords) if query_keywords else 0
+
+ # 计算内容相关性分数(包含查询词的比例)
+ content_score = 0
+ for kw in query_keywords:
+ if kw in content_lower:
+ # 计算关键词在内容中的密度
+ count = content_lower.count(kw)
+ content_score += count * len(kw) / len(content_lower)
+
+ # 综合分数
+ total_score = keyword_score * 0.6 + content_score * 0.4
+
+ if total_score > 0:
+ # 过滤文档类型
+ if doc_type and chunk_data["metadata"].get("type") != doc_type:
+ continue
+
+ scored_chunks.append({
+ "chunk": chunk_data,
+ "score": total_score
+ })
+
+ # 按分数排序并返回 top_k
+ scored_chunks.sort(key=lambda x: x["score"], reverse=True)
+
+ return [
+ {
+ "content": item["chunk"]["content"],
+ "metadata": item["chunk"]["metadata"],
+ "score": item["score"]
+ }
+ for item in scored_chunks[:top_k]
+ ]
+
+ def get_context_for_generation(self, query: str, brand: str,
+ platform: str, top_k: int = 3) -> str:
+ """
+ 获取用于内容生成的上下文
+
+ Args:
+ query: 查询/主题
+ brand: 品牌名
+ platform: 目标平台
+
+ Returns:
+ 格式化的上下文字符串
+ """
+ # 搜索相关文档
+ results = self.search(query, top_k=top_k)
+
+ if not results:
+ return ""
+
+ # 组装上下文
+ context_parts = ["以下是相关的品牌/产品信息,可用于内容生成:\n"]
+
+ for i, result in enumerate(results, 1):
+ source = result["metadata"].get("filename", "未知来源")
+ content = result["content"]
+
+ context_parts.append(f"--- 参考资料 {i}(来源:{source})---")
+ context_parts.append(content)
+ context_parts.append("")
+
+ return "\n".join(context_parts)
+
+ def list_documents(self) -> List[Dict]:
+ """列出所有文档"""
+ return list(self.documents.values())
+
+ def delete_document(self, doc_id: str) -> bool:
+ """删除文档及其分块"""
+ if doc_id not in self.documents:
+ return False
+
+ # 删除文档
+ del self.documents[doc_id]
+
+ # 删除相关分块
+ self.chunks = [c for c in self.chunks if c["metadata"].get("doc_id") != doc_id]
+
+ # 保存
+ self._save_json(self.documents_file, self.documents)
+ self._save_json(self.chunks_file, self.chunks)
+
+ return True
+
+ def get_stats(self) -> Dict:
+ """获取知识库统计信息"""
+ doc_types = {}
+ for doc in self.documents.values():
+ doc_type = doc.get("doc_type", "unknown")
+ doc_types[doc_type] = doc_types.get(doc_type, 0) + 1
+
+ return {
+ "total_documents": len(self.documents),
+ "total_chunks": len(self.chunks),
+ "document_types": doc_types
+ }
+
+
+class SourceVerifier:
+ """来源验证器"""
+
+ def __init__(self):
+ self.claim_patterns = [
+ "根据",
+ "据",
+ "报告显示",
+ "数据表明",
+ "研究表明",
+ "调查发现",
+ "据统计",
+ "根据报告",
+ "根据数据",
+ "根据研究",
+ "according to",
+ "based on",
+ "as reported by",
+ "research shows",
+ "data shows"
+ ]
+
+ def extract_claims(self, content: str) -> List[Dict]:
+ """
+ 从内容中提取来源声明
+
+ Args:
+ content: 文本内容
+
+ Returns:
+ 声明列表
+ """
+ claims = []
+ sentences = content.replace('。', '。\n').replace('.', '.\n').split('\n')
+
+ for sentence in sentences:
+ sentence = sentence.strip()
+ if not sentence:
+ continue
+
+ for pattern in self.claim_patterns:
+ if pattern in sentence.lower():
+ claims.append({
+ "text": sentence,
+ "pattern": pattern,
+ "verified": False,
+ "verification_result": None
+ })
+ break
+
+ return claims
+
+ def generate_verification_prompt(self, claim: str) -> str:
+ """
+ 生成验证提示词
+
+ Args:
+ claim: 来源声明
+
+ Returns:
+ 验证提示词
+ """
+ return f"""请验证以下声明的真实性:
+
+声明:{claim}
+
+请回答:
+1. 这个声明是否包含可验证的具体来源(如具体报告名称、机构名称、数据年份)?
+2. 如果包含,请判断这个来源是否可能存在且可信。
+3. 如果无法验证或来源可疑,请说明原因。
+
+回答格式:
+- 来源具体性:[具体/模糊/无来源]
+- 可信度评估:[高/中/低/无法判断]
+- 建议:[保留/修改/删除]
+- 原因:[简要说明]"""
+
+ def assess_source_quality(self, content: str) -> Dict:
+ """
+ 评估内容的来源质量
+
+ Args:
+ content: 文本内容
+
+ Returns:
+ 质量评估结果
+ """
+ claims = self.extract_claims(content)
+
+ if not claims:
+ return {
+ "has_sources": False,
+ "claim_count": 0,
+ "quality_score": 0,
+ "suggestions": ["内容中没有引用任何来源,建议添加数据支撑"]
+ }
+
+ # 分析来源质量
+ specific_count = 0
+ vague_count = 0
+
+ for claim in claims:
+ text = claim["text"]
+ # 检查是否有具体来源指标
+ has_specific = any([
+ any(year in text for year in ["2020", "2021", "2022", "2023", "2024", "2025"]),
+ any(org in text for org in ["Gartner", "IDC", "Forrester", "McKinsey",
+ "哈佛", "MIT", "斯坦福", "中科院"]),
+ "报告" in text and ("《" in text or "年" in text),
+ "数据" in text and any(c.isdigit() for c in text)
+ ])
+
+ if has_specific:
+ specific_count += 1
+ else:
+ vague_count += 1
+
+ quality_score = min(100, (specific_count / len(claims)) * 100)
+
+ suggestions = []
+ if vague_count > 0:
+ suggestions.append(f"有 {vague_count} 个来源描述模糊,建议补充具体报告名称或数据年份")
+ if specific_count == 0:
+ suggestions.append("所有来源都不够具体,建议引用真实的行业报告或权威机构数据")
+
+ return {
+ "has_sources": True,
+ "claim_count": len(claims),
+ "specific_count": specific_count,
+ "vague_count": vague_count,
+ "quality_score": quality_score,
+ "claims": claims,
+ "suggestions": suggestions
+ }
diff --git a/modules/llm_factory.py b/modules/llm_factory.py
new file mode 100644
index 0000000..79767c1
--- /dev/null
+++ b/modules/llm_factory.py
@@ -0,0 +1,161 @@
+"""
+LLM 工厂模块
+提供统一的 LLM 客户端构建接口
+"""
+
+from typing import Optional
+from contextlib import contextmanager
+import os
+
+
+@contextmanager
+def temp_env_vars(vars_dict):
+ """临时设置环境变量的上下文管理器"""
+ old_values = {}
+ for key, value in vars_dict.items():
+ old_values[key] = os.environ.get(key)
+ os.environ[key] = value
+ try:
+ yield
+ finally:
+ for key, old_value in old_values.items():
+ if old_value is None:
+ os.environ.pop(key, None)
+ else:
+ os.environ[key] = old_value
+
+
+def build_deepseek(api_key: str, model: str, temperature: float):
+ """构建 DeepSeek LLM"""
+ from langchain_deepseek import ChatDeepSeek
+ return ChatDeepSeek(api_key=api_key, model=model, temperature=temperature)
+
+
+def build_openai(api_key: str, model: str, temperature: float):
+ """构建 OpenAI LLM"""
+ from langchain_openai import ChatOpenAI
+ return ChatOpenAI(api_key=api_key, model=model, temperature=temperature)
+
+
+def build_tongyi(api_key: str, model: str, temperature: float):
+ """构建通义千问 LLM"""
+ try:
+ from langchain_community.chat_models import ChatTongyi
+ return ChatTongyi(api_key=api_key, model=model, model_kwargs={"temperature": temperature})
+ except Exception:
+ from langchain_aliyun import ChatTongyi # type: ignore
+ return ChatTongyi(api_key=api_key, model=model, temperature=temperature)
+
+
+def build_groq(api_key: str, model: str, temperature: float):
+ """构建 Groq LLM"""
+ from langchain_groq import ChatGroq
+ return ChatGroq(api_key=api_key, model=model, temperature=temperature)
+
+
+def build_moonshot(api_key: str, model: str, temperature: float):
+ """构建 Moonshot (Kimi) LLM"""
+ try:
+ from langchain_moonshot import ChatMoonshot # type: ignore
+ return ChatMoonshot(api_key=api_key, model=model, temperature=temperature)
+ except Exception:
+ from langchain_community.chat_models import MoonshotChat # type: ignore
+ return MoonshotChat(api_key=api_key, model=model, temperature=temperature)
+
+
+def build_doubao(api_key: str, temperature: float):
+ """构建豆包 LLM"""
+ try:
+ from modules.chat_doubao import create_chat_doubao
+ return create_chat_doubao(api_key=api_key, temperature=temperature)
+ except ImportError as e:
+ raise ValueError(f"豆包初始化失败:缺少依赖库。请运行:pip install 'volcengine-python-sdk[ark]'。错误:{e}")
+ except Exception as e:
+ raise ValueError(f"豆包初始化失败:{e}。请确保 API Key 格式为:access_key:secret_key:endpoint_id")
+
+
+def build_wenxin(api_key: str, model: str, temperature: float):
+ """构建文心一言 LLM"""
+ parts = api_key.split(":")
+ if len(parts) != 2:
+ raise ValueError("文心一言 API Key 格式错误,应为:app_key:app_secret(用冒号分隔)")
+
+ app_key, app_secret = parts
+
+ # 优先使用 langchain-community 的千帆接口(已包含在依赖中)
+ try:
+ from langchain_community.chat_models import QianfanChatEndpoint
+
+ with temp_env_vars({"QIANFAN_AK": app_key, "QIANFAN_SK": app_secret}):
+ return QianfanChatEndpoint(
+ model=model if model else "ernie-bot-turbo",
+ temperature=temperature,
+ )
+ except ImportError:
+ # 备选方案:尝试 langchain-wenxin
+ try:
+ from langchain_wenxin import ChatWenxin
+ return ChatWenxin(
+ baidu_api_key=app_key,
+ baidu_secret_key=app_secret,
+ model=model if model else "ernie-bot-turbo",
+ temperature=temperature,
+ )
+ except ImportError as e:
+ raise ValueError(f"文心一言初始化失败:缺少依赖库。请运行:pip install qianfan(或使用已安装的 langchain-community)。错误:{e}")
+ except Exception as e:
+ raise ValueError(f"文心一言初始化失败:{e}")
+
+
+# Provider 映射表
+PROVIDER_BUILDERS = {
+ "DeepSeek": lambda api_key, model, temp: build_deepseek(api_key, model, temp),
+ "OpenAI (GPT)": lambda api_key, model, temp: build_openai(api_key, model, temp),
+ "Tongyi (通义千问)": lambda api_key, model, temp: build_tongyi(api_key, model, temp),
+ "Groq": lambda api_key, model, temp: build_groq(api_key, model, temp),
+ "Moonshot (Kimi)": lambda api_key, model, temp: build_moonshot(api_key, model, temp),
+ "豆包(字节跳动)": lambda api_key, model, temp: build_doubao(api_key, temp),
+ "文心一言(百度)": lambda api_key, model, temp: build_wenxin(api_key, model, temp),
+}
+
+
+def build_llm(provider: str, api_key: str, model: str, temperature: float):
+ """
+ 统一的 LLM 构建接口
+
+ Args:
+ provider: 提供商名称
+ api_key: API Key
+ model: 模型名称
+ temperature: 温度参数
+
+ Returns:
+ LLM 实例
+
+ Raises:
+ ValueError: 不支持的提供商或初始化失败
+ """
+ builder = PROVIDER_BUILDERS.get(provider)
+ if builder is None:
+ raise ValueError(f"不支持的 LLM 提供商: {provider}")
+
+ return builder(api_key, model, temperature)
+
+
+def get_supported_providers():
+ """获取支持的提供商列表"""
+ return list(PROVIDER_BUILDERS.keys())
+
+
+def get_default_model(provider: str) -> str:
+ """获取提供商的默认模型"""
+ defaults = {
+ "DeepSeek": "deepseek-chat",
+ "OpenAI (GPT)": "gpt-4o-mini",
+ "Tongyi (通义千问)": "qwen-max",
+ "Groq": "llama3-70b-8192",
+ "Moonshot (Kimi)": "moonshot-v1-128k",
+ "豆包(字节跳动)": "",
+ "文心一言(百度)": "ernie-bot-turbo",
+ }
+ return defaults.get(provider, "")
diff --git a/modules/roi_analyzer.py b/modules/roi_analyzer.py
index f08b74d..b3b1a39 100644
--- a/modules/roi_analyzer.py
+++ b/modules/roi_analyzer.py
@@ -3,7 +3,7 @@ ROI 分析与成本优化模块
用于计算 API 调用成本、分析 ROI、提供成本优化建议
"""
from typing import Dict, List, Optional, Tuple
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, date as date_class
import pandas as pd
from collections import defaultdict
@@ -202,7 +202,7 @@ class ROIAnalyzer:
daily_groups = api_calls_df.groupby("日期")
for date, group in daily_groups:
daily_costs.append({
- "date": date.isoformat() if isinstance(date, datetime.date) else str(date),
+ "date": date.isoformat() if isinstance(date, date_class) else str(date),
"cost_usd": group["成本(USD)"].sum(),
"cost_cny": group["成本(CNY)"].sum(),
"calls": len(group),
diff --git a/modules/schema_generator.py b/modules/schema_generator.py
index b54708f..03dd7bd 100644
--- a/modules/schema_generator.py
+++ b/modules/schema_generator.py
@@ -466,3 +466,219 @@ class SchemaGenerator:
)
return self.generate_html_script_tag(schema)
+
+ def generate_faq_schema(self, faq_items: List[Dict[str, str]]) -> Dict:
+ """
+ 生成 FAQPage 类型的 Schema
+
+ Args:
+ faq_items: FAQ 列表,每个元素包含 {"question": "...", "answer": "..."}
+
+ Returns:
+ JSON-LD Schema 字典
+ """
+ main_entity = []
+ for item in faq_items:
+ main_entity.append({
+ "@type": "Question",
+ "name": item["question"],
+ "acceptedAnswer": {
+ "@type": "Answer",
+ "text": item["answer"]
+ }
+ })
+
+ return {
+ "@context": self.context,
+ "@type": "FAQPage",
+ "mainEntity": main_entity
+ }
+
+ def generate_howto_schema(self, title: str, steps: List[Dict[str, str]],
+ description: str = "") -> Dict:
+ """
+ 生成 HowTo 类型的 Schema
+
+ Args:
+ title: 操作标题
+ steps: 步骤列表,每个元素包含 {"name": "...", "text": "..."}
+ description: 操作描述
+
+ Returns:
+ JSON-LD Schema 字典
+ """
+ howto_steps = []
+ for i, step in enumerate(steps, 1):
+ howto_steps.append({
+ "@type": "HowToStep",
+ "position": i,
+ "name": step.get("name", f"步骤 {i}"),
+ "text": step.get("text", "")
+ })
+
+ schema = {
+ "@context": self.context,
+ "@type": "HowTo",
+ "name": title,
+ "step": howto_steps
+ }
+
+ if description:
+ schema["description"] = description
+
+ return schema
+
+ def generate_article_schema(self, title: str, author: str,
+ date_published: str, description: str = "",
+ image: str = "", url: str = "") -> Dict:
+ """
+ 生成 Article 类型的 Schema
+
+ Args:
+ title: 文章标题
+ author: 作者名称
+ date_published: 发布日期 (YYYY-MM-DD)
+ description: 文章描述
+ image: 文章图片 URL
+ url: 文章 URL
+
+ Returns:
+ JSON-LD Schema 字典
+ """
+ schema = {
+ "@context": self.context,
+ "@type": "Article",
+ "headline": title,
+ "author": {
+ "@type": "Person",
+ "name": author
+ },
+ "datePublished": date_published
+ }
+
+ if description:
+ schema["description"] = description
+
+ if image:
+ schema["image"] = image
+
+ if url:
+ schema["url"] = url
+
+ return schema
+
+ def generate_review_schema(self, item_name: str, review_body: str,
+ rating_value: float, reviewer: str,
+ item_type: str = "Product") -> Dict:
+ """
+ 生成 Review 类型的 Schema
+
+ Args:
+ item_name: 被评价项目名称
+ review_body: 评价内容
+ rating_value: 评分 (1-5)
+ reviewer: 评价者
+ item_type: 被评价项目类型 (Product, Service, etc.)
+
+ Returns:
+ JSON-LD Schema 字典
+ """
+ return {
+ "@context": self.context,
+ "@type": "Review",
+ "itemReviewed": {
+ "@type": item_type,
+ "name": item_name
+ },
+ "reviewBody": review_body,
+ "reviewRating": {
+ "@type": "Rating",
+ "ratingValue": rating_value,
+ "bestRating": 5
+ },
+ "author": {
+ "@type": "Person",
+ "name": reviewer
+ }
+ }
+
+ def extract_qa_from_content(self, content: str) -> List[Dict[str, str]]:
+ """
+ 从内容中自动提取 Q&A 对
+
+ Args:
+ content: 文本内容
+
+ Returns:
+ Q&A 对列表
+ """
+ import re
+ qa_pairs = []
+
+ # 模式1: Q: ... A: ...
+ pattern1 = r'[Qq][::]\s*(.+?)[\n\r]+[Aa][::]\s*(.+?)(?=[Qq][::]|\n\n|$)'
+ matches1 = re.findall(pattern1, content, re.DOTALL)
+ for q, a in matches1:
+ qa_pairs.append({"question": q.strip(), "answer": a.strip()})
+
+ # 模式2: 问题:... 回答:...
+ pattern2 = r'问题[::]\s*(.+?)[\n\r]+回答[::]\s*(.+?)(?=问题[::]|\n\n|$)'
+ matches2 = re.findall(pattern2, content, re.DOTALL)
+ for q, a in matches2:
+ qa_pairs.append({"question": q.strip(), "answer": a.strip()})
+
+ # 模式3: ## 问题标题 (以问号结尾) + 后续段落作为回答
+ lines = content.split('\n')
+ current_question = None
+ current_answer = []
+
+ for line in lines:
+ line = line.strip()
+ if not line:
+ if current_question and current_answer:
+ qa_pairs.append({
+ "question": current_question,
+ "answer": '\n'.join(current_answer)
+ })
+ current_question = None
+ current_answer = []
+ continue
+
+ # 检测问题行(以?或?结尾的标题)
+ if (line.startswith('#') or line.startswith('##')) and \
+ (line.endswith('?') or line.endswith('?')):
+ if current_question and current_answer:
+ qa_pairs.append({
+ "question": current_question,
+ "answer": '\n'.join(current_answer)
+ })
+ current_question = line.lstrip('#').strip()
+ current_answer = []
+ elif current_question:
+ current_answer.append(line)
+
+ # 保存最后一个 Q&A 对
+ if current_question and current_answer:
+ qa_pairs.append({
+ "question": current_question,
+ "answer": '\n'.join(current_answer)
+ })
+
+ return qa_pairs
+
+ def auto_generate_faq_schema(self, content: str) -> Optional[Dict]:
+ """
+ 从内容中自动提取 Q&A 并生成 FAQ Schema
+
+ Args:
+ content: 文本内容
+
+ Returns:
+ FAQPage Schema 或 None(如果没有找到 Q&A)
+ """
+ qa_pairs = self.extract_qa_from_content(content)
+
+ if not qa_pairs:
+ return None
+
+ return self.generate_faq_schema(qa_pairs)
diff --git a/modules/semantic_expander.py b/modules/semantic_expander.py
index d3f7f18..432d221 100644
--- a/modules/semantic_expander.py
+++ b/modules/semantic_expander.py
@@ -50,19 +50,19 @@ class SemanticExpander:
【扩展策略】
1. **同义扩展**:使用同义词替换关键词中的核心词
- - 示例:"外贸ERP软件" → "外贸管理系统"、"外贸业务软件"
+ - 示例:"管理软件" → "管理系统"、"业务软件"
2. **场景扩展**:添加使用场景或应用场景
- - 示例:"外贸ERP" → "小型企业外贸ERP"、"跨境电商ERP"
+ - 示例:"CRM系统" → "小型企业CRM"、"跨境电商CRM"
3. **问题扩展**:转换为问题形式
- - 示例:"外贸ERP推荐" → "外贸ERP哪个好"、"如何选择外贸ERP"
+ - 示例:"ERP推荐" → "ERP哪个好"、"如何选择ERP"
4. **功能扩展**:突出不同功能点
- - 示例:"外贸ERP" → "外贸订单管理软件"、"外贸库存管理ERP"
+ - 示例:"ERP系统" → "订单管理软件"、"库存管理ERP"
5. **长尾扩展**:生成更具体的长尾词
- - 示例:"外贸ERP" → "适合小企业的外贸ERP软件"、"支持多语言的外贸ERP系统"
+ - 示例:"CRM软件" → "适合小企业的CRM软件"、"支持多语言的CRM系统"
【输出格式】
请严格按照以下 JSON 格式输出,不要添加任何其他内容:
diff --git a/modules/services/__init__.py b/modules/services/__init__.py
new file mode 100644
index 0000000..fb8d46a
--- /dev/null
+++ b/modules/services/__init__.py
@@ -0,0 +1,11 @@
+"""
+服务层:封装对 modules 业务模块的常用工作流,供 UI 层(tab_*.py)按需调用。
+
+- 不替代现有业务类,仅做薄封装与流程编排。
+- Tab 可继续直接使用 modules.*,也可逐步改为调用本层以集中错误处理、参数归一化等。
+"""
+
+from . import schema_service
+from . import tech_config_service
+
+__all__ = ["schema_service", "tech_config_service"]
diff --git a/modules/services/schema_service.py b/modules/services/schema_service.py
new file mode 100644
index 0000000..be0d75f
--- /dev/null
+++ b/modules/services/schema_service.py
@@ -0,0 +1,40 @@
+"""
+Schema 服务:封装 SchemaGenerator 的常用工作流,供 Tab(文章优化、自动创作等)调用。
+"""
+from typing import Any, Dict, List, Tuple
+
+from modules.schema_generator import SchemaGenerator
+
+
+def get_combined_schema(
+ brand_name: str,
+ advantages: str = "",
+ schema_types: List[str] = None,
+ **kwargs: Any,
+) -> Dict:
+ """生成组合 JSON-LD Schema。返回可序列化为 JSON 的字典。"""
+ gen = SchemaGenerator()
+ return gen.generate_combined_schema(
+ brand_name=brand_name,
+ advantages=advantages,
+ schema_types=schema_types,
+ **kwargs,
+ )
+
+
+def get_combined_schema_and_html(
+ brand_name: str,
+ advantages: str = "",
+ schema_types: List[str] = None,
+ **kwargs: Any,
+) -> Tuple[Dict, str]:
+ """生成组合 Schema 及其 HTML script 标签片段。返回 (schema_dict, html_script_str)。"""
+ gen = SchemaGenerator()
+ schema = gen.generate_combined_schema(
+ brand_name=brand_name,
+ advantages=advantages,
+ schema_types=schema_types or ["Organization", "SoftwareApplication"],
+ **kwargs,
+ )
+ html = gen.generate_html_script_tag(schema)
+ return schema, html
diff --git a/modules/services/tech_config_service.py b/modules/services/tech_config_service.py
new file mode 100644
index 0000000..ad69660
--- /dev/null
+++ b/modules/services/tech_config_service.py
@@ -0,0 +1,42 @@
+"""
+技术配置服务:封装 TechnicalConfigGenerator 的常用工作流,供 Tab(文章优化等)调用。
+"""
+from typing import Any, Dict, List, Optional
+
+from modules.technical_config_generator import TechnicalConfigGenerator
+
+
+def generate_robots_txt(
+ base_url: str = "",
+ allow_paths: Optional[List[str]] = None,
+ disallow_paths: Optional[List[str]] = None,
+ sitemap_url: str = "",
+ user_agent: str = "*",
+ crawl_delay: Optional[int] = None,
+) -> str:
+ """生成 robots.txt 内容。"""
+ gen = TechnicalConfigGenerator()
+ return gen.generate_robots_txt(
+ base_url=base_url,
+ allow_paths=allow_paths,
+ disallow_paths=disallow_paths,
+ sitemap_url=sitemap_url,
+ user_agent=user_agent,
+ crawl_delay=crawl_delay,
+ )
+
+
+def generate_sitemap_xml(
+ base_url: str,
+ urls: Optional[List[Dict[str, Any]]] = None,
+ keywords: Optional[List[str]] = None,
+ lastmod: Optional[str] = None,
+) -> str:
+ """生成 sitemap.xml 内容。"""
+ gen = TechnicalConfigGenerator()
+ return gen.generate_sitemap_xml(
+ base_url=base_url,
+ urls=urls,
+ keywords=keywords,
+ lastmod=lastmod,
+ )
diff --git a/modules/storage_example.py b/modules/storage_example.py
deleted file mode 100644
index 66d0796..0000000
--- a/modules/storage_example.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""
-数据持久化集成示例
-展示如何在 geo_tool.py 中集成 DataStorage
-"""
-
-# ==================== 方式1:SQLite(推荐,简单高效) ====================
-from modules.data_storage import DataStorage
-
-# 初始化存储(SQLite方式,单文件数据库)
-storage = DataStorage(storage_type="sqlite", db_path="geo_data.db")
-
-# 或者使用JSON方式(更简单,但查询性能差)
-# storage = DataStorage(storage_type="json", db_path="data")
-
-# ==================== 在关键词模块中使用 ====================
-def save_keywords_example(keywords: list, brand: str):
- """保存关键词到数据库"""
- storage.save_keywords(keywords, brand)
-
-def load_keywords_example(brand: str) -> list:
- """从数据库加载关键词"""
- return storage.get_keywords(brand)
-
-# ==================== 在内容生成模块中使用 ====================
-def save_article_example(keyword: str, platform: str, content: str,
- filename: str, brand: str):
- """保存生成的文章"""
- storage.save_article(keyword, platform, content, filename, brand)
-
-def get_article_history_example(brand: str, platform: str = None):
- """获取历史文章"""
- return storage.get_articles(brand=brand, platform=platform)
-
-# ==================== 在优化模块中使用 ====================
-def save_optimization_example(original: str, optimized: str,
- changes: str, platform: str, brand: str):
- """保存优化记录"""
- storage.save_optimization(original, optimized, changes, platform, brand)
-
-# ==================== 在验证模块中使用 ====================
-def save_verify_example(results: list):
- """保存验证结果"""
- storage.save_verify_results(results)
-
-def get_verify_history_example(brand: str):
- """获取历史验证结果"""
- return storage.get_verify_results(brand=brand)
-
-# ==================== 统计功能 ====================
-def get_stats_example(brand: str):
- """获取统计数据"""
- return storage.get_stats(brand=brand)
-
-# ==================== 完整集成示例 ====================
-"""
-在 geo_tool.py 中的集成方式:
-
-1. 在文件顶部添加:
- from modules.data_storage import DataStorage
- storage = DataStorage(storage_type="sqlite", db_path="geo_data.db")
-
-2. 在关键词生成后保存:
- if cleaned:
- st.session_state.keywords = cleaned
- storage.save_keywords(cleaned, brand) # 新增:保存到数据库
- st.success(f"生成完成({len(cleaned)} 条)")
-
-3. 在内容生成后保存:
- for keyword, plat in keywords_to_generate:
- # ... 生成内容 ...
- storage.save_article(keyword, plat, content, filename, brand) # 新增
-
-4. 在优化后保存:
- storage.save_optimization(
- original_article,
- optimized_article,
- changes,
- target_platform,
- brand
- ) # 新增
-
-5. 在验证后保存:
- storage.save_verify_results(all_results) # 新增
-
-6. 可选:添加"历史记录"Tab,查看已保存的数据
-"""
diff --git a/modules/technical_config_generator.py b/modules/technical_config_generator.py
index 264b17e..be96a2b 100644
--- a/modules/technical_config_generator.py
+++ b/modules/technical_config_generator.py
@@ -215,14 +215,10 @@ class TechnicalConfigGenerator:
article_lastmod = lastmod
if created_at:
try:
- # 尝试解析时间字符串
- if "T" in created_at:
- dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
- else:
- dt = datetime.strptime(created_at, "%Y-%m-%d")
- article_lastmod = dt.strftime("%Y-%m-%d")
- except:
- pass
+ dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
+ except Exception:
+ dt = datetime.strptime(created_at, "%Y-%m-%d")
+ article_lastmod = dt.strftime("%Y-%m-%d")
urls.append({
"loc": url_path,
@@ -332,7 +328,7 @@ class TechnicalConfigGenerator:
try:
result = urlparse(url)
return all([result.scheme, result.netloc])
- except:
+ except Exception:
return False
def sanitize_url_path(self, path: str) -> str:
diff --git a/modules/ui/__init__.py b/modules/ui/__init__.py
index 3625a46..34a2fcd 100644
--- a/modules/ui/__init__.py
+++ b/modules/ui/__init__.py
@@ -6,4 +6,28 @@ Each top-level Tab in `geo_tool.py` should have a corresponding
invoked from the main app.
"""
-from . import tab_keywords, tab_autowrite
\ No newline at end of file
+from . import (
+ tab_keywords,
+ tab_autowrite,
+ tab_optimize,
+ tab_validation,
+ tab_history,
+ tab_reports,
+ tab_workflow,
+ tab_resources,
+ tab_platform_sync,
+ tab_config_optimizer,
+)
+
+__all__ = [
+ "tab_keywords",
+ "tab_autowrite",
+ "tab_optimize",
+ "tab_validation",
+ "tab_history",
+ "tab_reports",
+ "tab_workflow",
+ "tab_resources",
+ "tab_platform_sync",
+ "tab_config_optimizer",
+]
\ No newline at end of file
diff --git a/modules/ui/components.py b/modules/ui/components.py
new file mode 100644
index 0000000..c2a9d2c
--- /dev/null
+++ b/modules/ui/components.py
@@ -0,0 +1,142 @@
+"""
+公共 UI 组件与工具函数,供各 tab_*.py 复用。
+
+- 纯函数(如 sanitize_filename)不依赖 streamlit,避免循环导入。
+- 渲染组件(如 render_section_header、render_download_button)依赖 streamlit。
+"""
+
+import re
+import json
+from typing import Callable, Optional, List, Any
+
+import streamlit as st
+
+# 文件名非法字符,与 geo_tool 主入口规则一致
+INVALID_FS_CHARS = r'<>:"/\\|?*\n\r\t'
+
+
+def sanitize_filename(name: str, max_len: int = 80) -> str:
+ """将字符串清理为安全文件名(用于下载等)。各 Tab 统一由此处提供,避免重复实现。"""
+ if not name:
+ return "untitled"
+ name = name.strip()
+ name = re.sub(rf"[{re.escape(INVALID_FS_CHARS)}]", "_", name)
+ name = re.sub(r"_+", "_", name).strip("_")
+ return name[:max_len] if len(name) > max_len else name
+
+
+def extract_json_array(text: str) -> Optional[List[Any]]:
+ """从模型输出中抽取 JSON 数组(JsonOutputParser 失败时兜底)。"""
+ if not text:
+ return None
+ m = re.search(r"\[[\s\S]*\]", text)
+ if not m:
+ return None
+ try:
+ return json.loads(m.group(0))
+ except Exception:
+ return None
+
+
+def safe_decode_uploaded(uploaded) -> str:
+ """安全解码上传的文件内容"""
+ if not uploaded:
+ return ""
+ b = uploaded.getvalue()
+ for enc in ("utf-8-sig", "utf-8", "gb18030"):
+ try:
+ return b.decode(enc)
+ except Exception:
+ pass
+ return b.decode("utf-8", errors="replace")
+
+
+def render_section_header(
+ title: str,
+ caption: Optional[str] = None,
+ level: int = 3,
+) -> None:
+ """渲染区块标题与可选说明。level=3 为 ###,4 为 ####。"""
+ prefix = "#" * level
+ st.markdown(f"{prefix} {title}")
+ if caption:
+ st.caption(caption)
+
+
+def render_download_button(
+ label: str,
+ data: str | bytes,
+ filename: str,
+ mime: str,
+ key: str,
+ use_container_width: bool = True,
+) -> None:
+ """统一风格的下载按钮。"""
+ st.download_button(
+ label,
+ data=data,
+ file_name=filename,
+ mime=mime,
+ key=key,
+ use_container_width=use_container_width,
+ )
+
+
+def render_tab_top_with_clear(
+ title: str,
+ caption: Optional[str] = None,
+ clear_key: str = "tab_clear",
+ clear_label: str = "清空本模块结果",
+ on_clear: Optional[Callable[[], None]] = None,
+) -> None:
+ """
+ Tab 顶部:左侧标题+说明,右侧「清空本模块结果」按钮。
+ on_clear 若提供,会在点击清空时调用(可在此内清空 session_state 并 st.toast)。
+ """
+ col_left, col_right = st.columns([3, 1])
+ with col_left:
+ st.markdown(f"**{title}**")
+ if caption:
+ st.caption(caption)
+ with col_right:
+ if st.button(clear_label, use_container_width=True, key=clear_key):
+ if on_clear:
+ on_clear()
+ st.toast("已清空。")
+ st.rerun()
+
+
+# ========== 统一的消息提示组件 ==========
+
+def show_success(message: str, toast: bool = True) -> None:
+ """显示成功消息"""
+ st.success(message)
+ if toast:
+ st.toast(message, icon="✅")
+
+
+def show_error(message: str, toast: bool = True) -> None:
+ """显示错误消息"""
+ st.error(message)
+ if toast:
+ st.toast(message, icon="❌")
+
+
+def show_warning(message: str, toast: bool = True) -> None:
+ """显示警告消息"""
+ st.warning(message)
+ if toast:
+ st.toast(message, icon="⚠️")
+
+
+def show_info(message: str, toast: bool = True) -> None:
+ """显示信息消息"""
+ st.info(message)
+ if toast:
+ st.toast(message, icon="ℹ️")
+
+
+def show_loading(func, message: str = "加载中..."):
+ """统一的加载状态包装器"""
+ with st.spinner(message):
+ return func()
diff --git a/modules/ui/tab_autowrite.py b/modules/ui/tab_autowrite.py
index fc3a811..59b6bdb 100644
--- a/modules/ui/tab_autowrite.py
+++ b/modules/ui/tab_autowrite.py
@@ -18,19 +18,8 @@ from modules.fact_density_enhancer import FactDensityEnhancer
from modules.multimodal_prompt import MultimodalPromptGenerator
from modules.optimization_techniques import OptimizationTechniqueManager
from modules.schema_generator import SchemaGenerator
-
-
-INVALID_FS_CHARS = r'<>:"/\\|?*\n\r\t'
-
-
-def sanitize_filename(name: str, max_len: int = 80) -> str:
- """Copy of utility from geo_tool, kept local to avoid circular imports."""
- if not name:
- return "untitled"
- name = name.strip()
- name = re.sub(rf"[{re.escape(INVALID_FS_CHARS)}]", "_", name)
- name = re.sub(r"_+", "_", name).strip("_")
- return name[:max_len] if len(name) > max_len else name
+from modules.ui.components import render_tab_top_with_clear
+from modules.ui.components import sanitize_filename
def render_tab_autowrite(
@@ -49,20 +38,20 @@ def render_tab_autowrite(
通过参数接收 storage / ss_init / gen_llm / brand / advantages / cfg /
record_api_cost / model_defaults,由主入口在 with tab2 内调用。
"""
- # 标题和清空按钮放在同一行,布局更紧凑
- header_col1, header_col2 = st.columns([4, 1])
- with header_col1:
- st.markdown("**✍️ 内容生成**")
- st.caption("基于关键词自动生成符合 GEO 原则的专业内容,支持单篇和批量生成")
- with header_col2:
- st.markdown("")
- if st.button("清空本模块结果", use_container_width=True, key="content_clear"):
- st.session_state.generated_contents = []
- st.session_state.zip_bytes = None
- st.session_state.zip_filename = ""
- st.session_state.content_scores = {}
- st.session_state.selected_content_idx = 0
- st.toast("创作内容已清空。")
+ # 标题和清空按钮
+ def _clear_content_state():
+ st.session_state.generated_contents = []
+ st.session_state.zip_bytes = None
+ st.session_state.zip_filename = ""
+ st.session_state.content_scores = {}
+ st.session_state.selected_content_idx = 0
+
+ render_tab_top_with_clear(
+ title="✍️ 内容生成",
+ caption="基于关键词自动生成符合 GEO 原则的专业内容,支持单篇和批量生成",
+ clear_key="content_clear",
+ on_clear=_clear_content_state,
+ )
if not st.session_state.keywords:
st.info("💡 请先在【🎯 关键词蒸馏】生成关键词。")
diff --git a/modules/ui/tab_config_optimizer.py b/modules/ui/tab_config_optimizer.py
new file mode 100644
index 0000000..862258d
--- /dev/null
+++ b/modules/ui/tab_config_optimizer.py
@@ -0,0 +1,297 @@
+# Tab10:配置优化助手(从 geo_tool.py 迁移,通过 render_tab_config_optimizer() 供主入口调用。)
+
+import hashlib
+
+import streamlit as st
+
+from modules.config_optimizer import ConfigOptimizer
+
+
+def render_tab_config_optimizer(
+ storage,
+ cfg: dict,
+ brand: str,
+ advantages: str,
+ competitor_list: list,
+ build_llm,
+ model_defaults,
+) -> None:
+ """渲染 Tab10:配置优化助手。由主入口在 with tab10 内调用。"""
+ # 配置优化助手(与其他Tab保持一致的标题格式)
+ st.markdown("### 🎯 配置优化助手")
+ st.caption("分析品牌名和优势是否 GEO 友好,提供优化建议。优化后可一键应用到全局配置。")
+
+ # 初始化优化结果存储
+ if "config_optimization_result" not in st.session_state:
+ st.session_state.config_optimization_result = None
+
+ # 初始化配置hash(用于检测配置变化)
+ if "config_hash" not in st.session_state:
+ st.session_state.config_hash = None
+
+ # 计算当前配置的hash(使用cfg中的最新值)
+ brand_for_hash = cfg.get("brand", "").strip() or brand or ""
+ advantages_for_hash = cfg.get("advantages", "").strip() or advantages or ""
+ current_config_str = f"{brand_for_hash}|{advantages_for_hash}|{cfg.get('competitors', '')}"
+ current_config_hash = hashlib.md5(current_config_str.encode()).hexdigest()
+
+ # 如果配置变化了,清除旧的优化结果
+ # 但如果是因为应用版本导致的配置变化,保留优化结果
+ if st.session_state.config_hash != current_config_hash:
+ # 检查是否是应用版本导致的配置变化
+ if not st.session_state.get("_applying_version", False):
+ st.session_state.config_optimization_result = None
+ st.session_state.config_hash = current_config_hash
+ # 清除应用版本标志
+ st.session_state["_applying_version"] = False
+
+ # 检查配置是否有效
+ if not st.session_state.cfg_valid:
+ st.warning("⚠️ 请先在侧边栏完成配置并点击'应用配置'")
+ st.info("配置优化助手需要有效的配置才能进行分析。")
+ else:
+ # 显示当前配置
+ with st.expander("📋 当前配置", expanded=False):
+ col1, col2 = st.columns(2)
+ with col1:
+ brand_display = cfg.get("brand", "") or brand or "未设置"
+ st.markdown(f"**品牌名**:{brand_display}")
+ with col2:
+ st.markdown(f"**竞品数量**:{len(competitor_list)}个")
+ advantages_display = cfg.get("advantages", "") or advantages or "未设置"
+ st.markdown(f"**核心优势**:{advantages_display}")
+ if competitor_list:
+ st.markdown(f"**竞品列表**:{', '.join(competitor_list[:5])}{'...' if len(competitor_list) > 5 else ''}")
+
+ # 分析按钮
+ col1, col2 = st.columns([1, 3])
+ with col1:
+ analyze_btn = st.button("🔍 分析配置优化", type="primary", use_container_width=True, key="tab10_optimize_config")
+
+ with col2:
+ if st.session_state.config_optimization_result:
+ st.success("✅ 已有优化结果,可直接查看下方建议")
+
+ # 执行分析
+ if analyze_btn:
+ with st.spinner("正在分析配置,优化建议生成中..."):
+ try:
+ optimizer = ConfigOptimizer()
+
+ # 从配置中获取品牌名、优势描述和竞品列表(确保使用最新配置)
+ brand_for_optimizer = cfg.get("brand", "").strip() or brand or ""
+ advantages_for_optimizer = cfg.get("advantages", "").strip() or advantages or ""
+ competitors_str = cfg.get("competitors", "")
+ competitor_list_for_optimizer = [c.strip() for c in competitors_str.split("\n") if c.strip()]
+
+ # 验证必要配置
+ if not brand_for_optimizer:
+ st.error("❌ 品牌名不能为空,请在侧边栏配置主品牌名称")
+ st.stop()
+
+ if not advantages_for_optimizer:
+ st.warning("⚠️ 优势描述为空,建议在侧边栏配置核心优势/卖点")
+
+ # 临时构建LLM用于分析(使用当前配置)
+ temp_llm = build_llm(
+ cfg["gen_provider"],
+ cfg["gen_api_key"],
+ model_defaults(cfg["gen_provider"]),
+ float(cfg.get("temperature", 0.7))
+ )
+
+ result = optimizer.optimize_config(
+ brand=brand_for_optimizer,
+ advantages=advantages_for_optimizer,
+ competitors=competitor_list_for_optimizer,
+ llm_chain=temp_llm
+ )
+ st.session_state.config_optimization_result = result
+ st.session_state.config_hash = current_config_hash
+ st.success("✅ 配置分析完成!")
+ st.rerun()
+ except Exception as e:
+ st.error(f"❌ 配置优化分析失败:{e}")
+ import traceback
+ with st.expander("查看错误详情"):
+ st.code(traceback.format_exc())
+ st.session_state.config_optimization_result = None
+
+ # 显示优化结果
+ if st.session_state.config_optimization_result:
+ result = st.session_state.config_optimization_result
+ if result.get("success", False):
+ st.markdown("---")
+ st.markdown("#### 📊 优化分析结果")
+
+ # 评估总结
+ if result.get("summary"):
+ st.markdown("**📝 评估总结**")
+ st.info(result["summary"])
+
+ # 优化建议
+ if result.get("suggestions"):
+ st.markdown("**💡 优化建议**")
+ suggestions = result["suggestions"]
+
+ if suggestions.get("brand", {}).get("problem"):
+ st.markdown("**🔸 品牌名问题**:")
+ # 直接使用st.markdown渲染,CSS会限制标题大小
+ problem_text = suggestions["brand"]["problem"]
+ st.markdown(problem_text)
+ if suggestions["brand"].get("suggestion"):
+ st.markdown("**✅ 建议**:")
+ suggestion_text = suggestions["brand"]["suggestion"]
+ st.markdown(suggestion_text)
+
+ if suggestions.get("advantages", {}).get("problem"):
+ st.markdown("**🔸 优势描述问题**:")
+ problem_text = suggestions["advantages"]["problem"]
+ st.markdown(problem_text)
+ if suggestions["advantages"].get("suggestion"):
+ st.markdown("**✅ 建议**:")
+ suggestion_text = suggestions["advantages"]["suggestion"]
+ st.markdown(suggestion_text)
+
+ # 推荐版本
+ recommended_versions = result.get("recommended_versions", [])
+ if recommended_versions:
+ st.markdown("**🎯 推荐版本**")
+ st.caption("选择最适合的版本,点击「应用版本」按钮即可更新配置")
+
+ # 检查是否有有效的推荐版本
+ valid_versions = [v for v in recommended_versions if v.get("brand") or v.get("advantages")]
+ if not valid_versions:
+ st.warning("⚠️ 推荐版本数据为空,可能是解析失败。请查看完整报告或重新分析。")
+ if result.get("raw_result"):
+ with st.expander("查看原始输出中的推荐版本部分"):
+ raw = result["raw_result"]
+ if "【推荐版本】" in raw:
+ raw_versions = raw.split("【推荐版本】")[1].split("【")[0]
+ st.code(raw_versions)
+
+ for i, version in enumerate(recommended_versions[:3], 1):
+ version_name_map = {
+ 1: "保守优化",
+ 2: "平衡优化",
+ 3: "激进优化"
+ }
+ version_name = version_name_map.get(i, f"版本{i}")
+
+ with st.expander(f"版本{i}:{version_name}", expanded=False): # 默认不展开,用户自行选择
+ # 检查版本数据是否有效
+ has_brand = bool(version.get("brand", "").strip())
+ has_advantages = bool(version.get("advantages", "").strip())
+ has_reason = bool(version.get("reason", "").strip())
+
+ if not has_brand and not has_advantages:
+ st.warning("⚠️ 该版本数据不完整,请查看完整报告或重新分析")
+ if result.get("raw_result"):
+ with st.expander("查看原始输出中的该版本"):
+ # 尝试从原始输出中提取
+ raw = result["raw_result"]
+ if f"版本{i}" in raw:
+ version_raw = raw.split(f"版本{i}")[1]
+ if i < 3:
+ next_version = f"版本{i+1}"
+ if next_version in version_raw:
+ version_raw = version_raw.split(next_version)[0]
+ st.code(version_raw[:500]) # 显示前500字符
+ else:
+ col1, col2 = st.columns([2, 1])
+ with col1:
+ if has_brand:
+ st.markdown(f"**品牌名**:`{version['brand']}`")
+ else:
+ st.warning("⚠️ 品牌名为空")
+
+ if has_advantages:
+ st.markdown(f"**优势描述**:{version['advantages']}")
+ else:
+ st.warning("⚠️ 优势描述为空")
+
+ if has_reason:
+ st.caption(f"💭 理由:{version['reason']}")
+ else:
+ st.caption("💭 理由:未提供")
+
+ with col2:
+ # 应用按钮
+ apply_disabled = not (has_brand and has_advantages)
+ if st.button(
+ f"✅ 应用版本{i}",
+ key=f"tab10_apply_version_{i}",
+ use_container_width=True,
+ type="primary",
+ disabled=apply_disabled
+ ):
+ if has_brand and has_advantages:
+ # 设置标志,表示正在应用版本(防止优化结果被清除)
+ st.session_state["_applying_version"] = True
+ # 更新配置
+ st.session_state.cfg["brand"] = version["brand"]
+ st.session_state.cfg["advantages"] = version["advantages"]
+ # 设置标志,表示需要更新侧边栏输入框
+ st.session_state["_pending_brand_update"] = version["brand"]
+ st.session_state["_pending_advantages_update"] = version["advantages"]
+ st.session_state.cfg_applied = False # 需要重新应用配置
+ st.success(f"✅ 已应用版本{i},侧边栏已更新,请点击'应用配置'以生效")
+ st.info("💡 配置更新后,建议重新运行关键词蒸馏和内容创作,以获得最佳效果")
+ st.rerun()
+ if apply_disabled:
+ st.caption("⚠️ 数据不完整,无法应用")
+
+ # 预期效果
+ if result.get("expected_effects"):
+ st.markdown("**📈 预期效果**")
+ effects = result["expected_effects"]
+ # 使用文本而不是 metric,避免内容被截断
+ if effects.get("mention_rate"):
+ st.markdown(f"- 提及率提升预期:{effects['mention_rate']}")
+ if effects.get("geo_friendliness"):
+ st.markdown(f"- GEO友好度提升:{effects['geo_friendliness']}")
+
+ # 完整报告
+ if result.get("raw_result"):
+ with st.expander("📄 查看完整分析报告", expanded=False):
+ st.markdown(result["raw_result"])
+
+ # 如果推荐版本为空或解析失败,显示原始输出中的推荐版本部分
+ recommended_versions = result.get("recommended_versions", [])
+ if not recommended_versions or all(
+ not v.get("brand") and not v.get("advantages")
+ for v in recommended_versions
+ ):
+ st.warning("⚠️ 推荐版本解析失败,以下是原始输出中的推荐版本部分,请检查格式:")
+ raw = result["raw_result"]
+ if "【推荐版本】" in raw:
+ raw_versions = raw.split("【推荐版本】")[1].split("【")[0]
+ st.code(raw_versions, language="text")
+ st.info("💡 如果原始输出中包含推荐版本但解析失败,请检查格式是否符合要求")
+
+ # 调试信息(可选)
+ if st.checkbox("🔍 显示调试信息", key="tab10_debug"):
+ st.markdown("#### 调试信息")
+ debug_info = {
+ "推荐版本数量": len(result.get("recommended_versions", [])),
+ "版本详情": result.get("recommended_versions", []),
+ "配置hash": st.session_state.config_hash,
+ "解析错误": result.get("parse_errors", [])
+ }
+ st.json(debug_info)
+
+ # 显示原始输出的关键部分
+ if result.get("raw_result"):
+ raw = result["raw_result"]
+ if "【推荐版本】" in raw:
+ st.markdown("**原始输出中的推荐版本部分:**")
+ raw_versions = raw.split("【推荐版本】")[1].split("【")[0]
+ st.code(raw_versions[:1000], language="text") # 显示前1000字符
+ else:
+ st.error(f"❌ 分析失败:{result.get('error', '未知错误')}")
+ if result.get("raw_result"):
+ with st.expander("查看原始输出"):
+ st.code(result["raw_result"])
+ else:
+ st.info("💡 点击上方「分析配置优化」按钮开始分析,系统会根据当前配置生成优化建议。")
+ st.caption("提示:当您修改品牌名、优势描述或竞品列表后,系统会自动清除旧结果,需要重新分析。")
diff --git a/modules/ui/tab_history.py b/modules/ui/tab_history.py
new file mode 100644
index 0000000..e99a06b
--- /dev/null
+++ b/modules/ui/tab_history.py
@@ -0,0 +1,115 @@
+# Tab5:历史记录(从 geo_tool.py 迁移,通过 render_tab_history() 供主入口调用。)
+
+import pandas as pd
+import plotly.express as px
+import streamlit as st
+
+
+def render_tab_history(storage, brand: str) -> None:
+ """渲染 Tab5:历史记录。由主入口在 with tab5 内调用。"""
+ st.header("历史记录")
+
+ # 统计数据
+ try:
+ stats = storage.get_stats(brand)
+ col1, col2, col3, col4 = st.columns(4)
+ col1.metric("关键词总数", stats["keywords_count"])
+ col2.metric("文章总数", stats["articles_count"])
+ col3.metric("优化记录", stats["optimizations_count"])
+ col4.metric("验证结果", stats["verify_results_count"])
+ except Exception as e:
+ st.error(f"获取统计数据失败:{e}")
+ stats = {"keywords_count": 0, "articles_count": 0, "optimizations_count": 0, "verify_results_count": 0}
+
+ st.markdown("---")
+
+ # 历史文章列表
+ st.markdown("#### 历史文章")
+ try:
+ articles = storage.get_articles(brand=brand)
+ if articles:
+ articles_df = pd.DataFrame(articles)
+ # 只显示关键列
+ display_cols = ["keyword", "platform", "created_at"]
+ available_cols = [col for col in display_cols if col in articles_df.columns]
+ if available_cols:
+ st.dataframe(articles_df[available_cols], use_container_width=True, hide_index=True)
+ else:
+ st.dataframe(articles_df, use_container_width=True, hide_index=True)
+
+ # 文章详情查看
+ if len(articles) > 0:
+ selected_idx = st.selectbox("选择文章查看详情", range(len(articles)), format_func=lambda x: f"{articles[x].get('keyword', 'N/A')} - {articles[x].get('platform', 'N/A')}")
+ if selected_idx is not None:
+ selected_article = articles[selected_idx]
+ with st.expander("文章内容", expanded=True):
+ if selected_article.get("content"):
+ if selected_article.get("platform", "").startswith("GitHub"):
+ st.code(selected_article["content"], language="markdown")
+ else:
+ st.text_area("内容", selected_article["content"], height=400, disabled=True, key=f"article_content_{selected_idx}")
+ else:
+ st.info("暂无历史文章记录。")
+ except Exception as e:
+ st.error(f"获取历史文章失败:{e}")
+
+ st.markdown("---")
+
+ # 历史优化记录
+ st.markdown("#### 历史优化记录")
+ try:
+ optimizations = storage.get_optimizations(brand=brand)
+ if optimizations:
+ opt_df = pd.DataFrame(optimizations)
+ display_cols = ["platform", "created_at"]
+ available_cols = [col for col in display_cols if col in opt_df.columns]
+ if available_cols:
+ st.dataframe(opt_df[available_cols], use_container_width=True, hide_index=True)
+ else:
+ st.dataframe(opt_df.head(10), use_container_width=True, hide_index=True)
+
+ if len(optimizations) > 0:
+ selected_opt_idx = st.selectbox("选择优化记录查看详情", range(len(optimizations)), format_func=lambda x: f"{optimizations[x].get('platform', 'N/A')} - {optimizations[x].get('created_at', 'N/A')[:10] if optimizations[x].get('created_at') else 'N/A'}")
+ if selected_opt_idx is not None:
+ selected_opt = optimizations[selected_opt_idx]
+ with st.expander("优化详情", expanded=True):
+ if selected_opt.get("changes"):
+ st.markdown("**变更说明**")
+ st.markdown(selected_opt["changes"])
+ if selected_opt.get("optimized_content"):
+ st.markdown("**优化后内容**")
+ if "GitHub" in selected_opt.get("platform", ""):
+ st.code(selected_opt["optimized_content"], language="markdown")
+ else:
+ st.text_area("内容", selected_opt["optimized_content"], height=300, disabled=True, key=f"opt_content_{selected_opt_idx}")
+ else:
+ st.info("暂无优化记录。")
+ except Exception as e:
+ st.error(f"获取优化记录失败:{e}")
+
+ st.markdown("---")
+
+ # 历史验证结果
+ st.markdown("#### 历史验证结果")
+ try:
+ verify_df = storage.get_verify_results(brand=brand)
+ if not verify_df.empty:
+ st.dataframe(verify_df, use_container_width=True, hide_index=True)
+
+ # 可视化历史验证结果
+ if len(verify_df) > 0:
+ st.markdown("#### 历史验证结果可视化")
+ fig = px.bar(
+ verify_df,
+ x="问题",
+ y="提及次数",
+ color="品牌",
+ facet_col="验证模型",
+ barmode="group",
+ title="历史验证结果对比",
+ )
+ st.plotly_chart(fig, use_container_width=True)
+ else:
+ st.info("暂无验证结果记录。")
+ except Exception as e:
+ st.error(f"获取验证结果失败:{e}")
diff --git a/modules/ui/tab_keywords.py b/modules/ui/tab_keywords.py
index 9b378bc..cb2afbc 100644
--- a/modules/ui/tab_keywords.py
+++ b/modules/ui/tab_keywords.py
@@ -11,37 +11,7 @@ from langchain_core.prompts import PromptTemplate
from modules.keyword_mining import KeywordMining
from modules.semantic_expander import SemanticExpander
from modules.topic_cluster import TopicCluster
-
-
-INVALID_FS_CHARS = r'<>:"/\\|?*\n\r\t'
-
-
-def sanitize_filename(name: str, max_len: int = 80) -> str:
- """Copy of utility from geo_tool, kept local to avoid circular imports."""
- if not name:
- return "untitled"
- name = name.strip()
- # 延续主应用中的命名清理规则
- import re # 局部导入,避免在模块顶部重复导入
-
- name = re.sub(rf"[{re.escape(INVALID_FS_CHARS)}]", "_", name)
- name = re.sub(r"_+", "_", name).strip("_")
- return name[:max_len] if len(name) > max_len else name
-
-
-def extract_json_array(text: str):
- """从模型输出中抽取 JSON 数组(JsonOutputParser 失败时兜底)。"""
- if not text:
- return None
- import re
-
- m = re.search(r"\[[\s\S]*\]", text)
- if not m:
- return None
- try:
- return json.loads(m.group(0))
- except Exception:
- return None
+from modules.ui.components import sanitize_filename, extract_json_array
def render_tab_keywords(storage, ss_init, gen_llm, brand: str, advantages: str) -> None:
@@ -1134,16 +1104,16 @@ def render_tab_keywords(storage, ss_init, gen_llm, brand: str, advantages: str)
source_idx = next(
(
i
- for i, n in enumerate(nodes)
- if n["id"] == edge["source"]
+ for i, nd in enumerate(nodes)
+ if nd["id"] == edge["source"]
),
None,
)
target_idx = next(
(
i
- for i, n in enumerate(nodes)
- if n["id"] == edge["target"]
+ for i, nd in enumerate(nodes)
+ if nd["id"] == edge["target"]
),
None,
)
@@ -1384,12 +1354,12 @@ def render_tab_keywords(storage, ss_init, gen_llm, brand: str, advantages: str)
with st.container(border=True):
# 默认使用 brand,允许覆盖
- default_industry = brand if brand else "外贸ERP"
+ default_industry = brand if brand else ""
industry = st.text_input(
"行业领域",
value=default_industry,
key="mining_industry",
- help="输入您的行业领域,如:外贸ERP、AI工具、SaaS产品等",
+ help="输入您的行业领域,如:AI工具、SaaS产品、电商平台等",
)
num_mine = st.slider("挖掘数量", 10, 50, 20, key="mining_num")
diff --git a/modules/ui/tab_knowledge.py b/modules/ui/tab_knowledge.py
new file mode 100644
index 0000000..55a0958
--- /dev/null
+++ b/modules/ui/tab_knowledge.py
@@ -0,0 +1,242 @@
+"""
+知识库管理 Tab
+支持上传文档、查看文档列表、搜索测试
+"""
+
+import streamlit as st
+from modules.knowledge_base import KnowledgeBase, SourceVerifier
+
+
+def render_tab_knowledge(kb: KnowledgeBase):
+ """
+ 渲染知识库管理 Tab
+
+ Args:
+ kb: 知识库实例
+ """
+ st.markdown("### 📚 品牌知识库")
+ st.caption("上传品牌文档、产品手册、案例库,AI 生成内容时将自动检索引用")
+
+ # 统计信息
+ stats = kb.get_stats()
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ st.metric("📄 文档数量", stats["total_documents"])
+ with col2:
+ st.metric("📦 分块数量", stats["total_chunks"])
+ with col3:
+ doc_types = stats.get("document_types", {})
+ st.metric("📋 文档类型", len(doc_types))
+
+ # 主要功能区域
+ kb_tab1, kb_tab2, kb_tab3, kb_tab4 = st.tabs([
+ "📤 上传文档", "📋 文档列表", "🔍 搜索测试", "📊 来源验证"
+ ])
+
+ with kb_tab1:
+ _render_upload_section(kb)
+
+ with kb_tab2:
+ _render_document_list(kb)
+
+ with kb_tab3:
+ _render_search_test(kb)
+
+ with kb_tab4:
+ _render_source_verifier()
+
+
+def _render_upload_section(kb: KnowledgeBase):
+ """渲染上传文档区域"""
+ st.markdown("#### 上传新文档")
+
+ # 文档类型选择
+ doc_type = st.selectbox(
+ "文档类型",
+ ["text", "faq", "product", "case", "markdown"],
+ format_func=lambda x: {
+ "text": "📝 通用文本",
+ "faq": "❓ FAQ 问答",
+ "product": "📦 产品文档",
+ "case": "💼 客户案例",
+ "markdown": "📑 Markdown 文档"
+ }.get(x, x),
+ help="选择文档类型有助于更精准的分块和检索"
+ )
+
+ # 上传方式选择
+ upload_method = st.radio(
+ "上传方式",
+ ["📁 上传文件", "📝 粘贴文本"],
+ horizontal=True
+ )
+
+ if upload_method == "📁 上传文件":
+ uploaded_file = st.file_uploader(
+ "选择文件",
+ type=["txt", "md", "csv"],
+ help="支持 TXT、Markdown、CSV 格式"
+ )
+
+ if uploaded_file:
+ content = uploaded_file.read().decode("utf-8")
+ st.text_area("文件预览", content[:1000] + "..." if len(content) > 1000 else content,
+ height=150, disabled=True)
+
+ if st.button("📥 导入知识库", use_container_width=True, type="primary"):
+ with st.spinner("正在处理文档..."):
+ result = kb.add_document(
+ filename=uploaded_file.name,
+ content=content,
+ doc_type=doc_type
+ )
+ st.success(f"✅ 文档 '{result['filename']}' 已导入,分为 {result['chunk_count']} 个分块")
+ st.rerun()
+ else:
+ filename = st.text_input("文档名称", placeholder="例如:产品功能说明")
+ content = st.text_area("粘贴文档内容", height=300,
+ placeholder="粘贴品牌介绍、产品说明、FAQ 等内容...")
+
+ if st.button("📥 导入知识库", use_container_width=True, type="primary"):
+ if not filename:
+ st.warning("请输入文档名称")
+ elif not content.strip():
+ st.warning("请输入文档内容")
+ else:
+ with st.spinner("正在处理文档..."):
+ result = kb.add_document(
+ filename=filename,
+ content=content,
+ doc_type=doc_type
+ )
+ st.success(f"✅ 文档 '{result['filename']}' 已导入,分为 {result['chunk_count']} 个分块")
+ st.rerun()
+
+ # 批量导入示例
+ with st.expander("💡 快速导入示例数据"):
+ st.markdown("""
+ **FAQ 示例格式:**
+ ```
+ Q:你们的产品有什么优势?
+ A:我们的产品具有以下核心优势:1)AI深度赋能...;2)全流程覆盖...;3)数据驱动决策...
+
+ Q:如何开始使用?
+ A:只需三步:1)注册账号;2)配置基础信息;3)开始使用核心功能。
+ ```
+
+ **产品文档示例格式:**
+ ```
+ # 产品概述
+ 产品简介...
+
+ # 核心功能
+ 功能说明...
+
+ # 技术架构
+ 架构说明...
+ ```
+ """)
+
+
+def _render_document_list(kb: KnowledgeBase):
+ """渲染文档列表区域"""
+ st.markdown("#### 已导入文档")
+
+ documents = kb.list_documents()
+
+ if not documents:
+ st.info("📭 知识库为空,请先上传文档")
+ return
+
+ for doc in documents:
+ with st.expander(f"📄 {doc['filename']} ({doc['doc_type']})"):
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ st.write(f"**类型:** {doc['doc_type']}")
+ with col2:
+ st.write(f"**分块数:** {doc['chunk_count']}")
+ with col3:
+ st.write(f"**导入时间:** {doc['created_at'][:10]}")
+
+ if st.button(f"🗑️ 删除", key=f"delete_{doc['doc_id']}"):
+ kb.delete_document(doc['doc_id'])
+ st.success(f"已删除文档 '{doc['filename']}'")
+ st.rerun()
+
+
+def _render_search_test(kb: KnowledgeBase):
+ """渲染搜索测试区域"""
+ st.markdown("#### 搜索测试")
+ st.caption("测试知识库检索效果,验证文档是否被正确索引")
+
+ query = st.text_input("输入测试查询", placeholder="例如:产品有什么优势?")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ top_k = st.slider("返回结果数", 1, 10, 3)
+ with col2:
+ doc_type_filter = st.selectbox(
+ "过滤文档类型",
+ ["全部"] + ["text", "faq", "product", "case", "markdown"],
+ index=0
+ )
+
+ if query:
+ doc_type = None if doc_type_filter == "全部" else doc_type_filter
+ results = kb.search(query, top_k=top_k, doc_type=doc_type)
+
+ if results:
+ st.markdown(f"**找到 {len(results)} 条相关结果:**")
+ for i, result in enumerate(results, 1):
+ with st.expander(f"结果 {i} (相关度: {result['score']:.2f})"):
+ st.markdown(f"**来源:** {result['metadata'].get('filename', '未知')}")
+ st.markdown(f"**类型:** {result['metadata'].get('type', '未知')}")
+ st.text_area("内容", result['content'], height=150,
+ key=f"result_{i}", disabled=True)
+ else:
+ st.warning("未找到相关结果,请尝试其他查询或添加更多文档")
+
+
+def _render_source_verifier():
+ """渲染来源验证区域"""
+ st.markdown("#### 📊 来源质量验证")
+ st.caption("检查内容中的来源声明是否真实可信")
+
+ verifier = SourceVerifier()
+
+ content = st.text_area(
+ "粘贴待验证内容",
+ height=200,
+ placeholder="粘贴 AI 生成的内容,检查其中的来源引用是否真实..."
+ )
+
+ if st.button("🔍 开始验证", use_container_width=True, type="primary"):
+ if not content.strip():
+ st.warning("请输入待验证内容")
+ else:
+ with st.spinner("正在分析来源质量..."):
+ result = verifier.assess_source_quality(content)
+
+ # 显示结果
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ st.metric("📝 来源声明数", result["claim_count"])
+ with col2:
+ if result["has_sources"]:
+ st.metric("✅ 具体来源", result.get("specific_count", 0))
+ else:
+ st.metric("✅ 具体来源", 0)
+ with col3:
+ st.metric("📊 质量评分", f"{result['quality_score']:.0f}/100")
+
+ # 详细建议
+ if result["suggestions"]:
+ st.markdown("**💡 改进建议:**")
+ for suggestion in result["suggestions"]:
+ st.markdown(f"- {suggestion}")
+
+ # 显示检测到的来源声明
+ if result.get("claims"):
+ st.markdown("**🔍 检测到的来源声明:**")
+ for i, claim in enumerate(result["claims"], 1):
+ st.markdown(f"{i}. {claim['text']}")
diff --git a/modules/ui/tab_optimize.py b/modules/ui/tab_optimize.py
new file mode 100644
index 0000000..23ec4c5
--- /dev/null
+++ b/modules/ui/tab_optimize.py
@@ -0,0 +1,1092 @@
+# Tab3:文章优化(从 geo_tool.py 迁移,通过 render_tab_optimize() 供主入口调用。)
+
+import re
+
+import streamlit as st
+from langchain_core.output_parsers import StrOutputParser
+from langchain_core.prompts import PromptTemplate
+
+from modules.eeat_enhancer import EEATEnhancer
+from modules.fact_density_enhancer import FactDensityEnhancer
+from modules.optimization_techniques import OptimizationTechniqueManager
+from modules.schema_generator import SchemaGenerator
+from modules.technical_config_generator import TechnicalConfigGenerator
+from modules.ui.components import sanitize_filename, safe_decode_uploaded, render_tab_top_with_clear
+
+
+def render_tab_optimize(
+ storage,
+ ss_init,
+ gen_llm,
+ brand: str,
+ advantages: str,
+ cfg: dict,
+ record_api_cost,
+ model_defaults,
+) -> None:
+ """渲染 Tab3:文章优化。由主入口在 with tab3 内调用。"""
+ # 标题和清空按钮
+ def _clear_optimize_state():
+ st.session_state.optimized_article = ""
+ st.session_state.opt_changes = ""
+
+ render_tab_top_with_clear(
+ title="🔧 文章优化",
+ caption="优化已有文章,生成结构化数据和技术配置,提升 GEO 效果",
+ clear_key="opt_clear",
+ on_clear=_clear_optimize_state,
+ )
+
+ # === 文章优化功能(主流程) ===
+ st.markdown("---")
+ st.markdown("**✏️ 文章内容优化**")
+
+ with st.container(border=True):
+ st.markdown("粘贴或上传已写文章,一键提升 GEO 效果(结构化、可引用、自然植入品牌)")
+
+ # 输入方式与文章内容放在表单外,以便粘贴/上传后能触发重跑,从而正确更新「开始优化」按钮的可用状态
+ input_mode = st.radio(
+ "输入方式",
+ ["粘贴文本", "上传文件(TXT/MD)"],
+ horizontal=True,
+ key="opt_input_mode",
+ )
+ if input_mode == "粘贴文本":
+ original_article = st.text_area(
+ "粘贴文章内容", height=360, key="opt_text"
+ )
+ else:
+ uploaded = st.file_uploader(
+ "上传 TXT 或 MD 文件",
+ type=["txt", "md"],
+ key="opt_uploader",
+ )
+ original_article = ""
+ if uploaded:
+ try:
+ original_article = safe_decode_uploaded(uploaded) or ""
+ except Exception as e:
+ st.error(f"上传文件解析失败:{e}")
+ original_article = ""
+ if original_article:
+ st.text_area(
+ "上传内容预览",
+ original_article,
+ height=200,
+ disabled=True,
+ key="opt_upload_preview",
+ )
+
+ with st.form("opt_form", clear_on_submit=False):
+ target_platform = st.selectbox(
+ "目标平台(影响文风,可选)",
+ [
+ "通用优化",
+ "知乎(专业问答)",
+ "CSDN(技术博客)",
+ "GitHub(README/文档)",
+ "B站(视频脚本)",
+ "头条号(资讯软文)",
+ "微信公众号(长文)",
+ "抖音图文(短内容)",
+ "百家号(资讯)",
+ "网易号(资讯)",
+ "企鹅号(资讯)",
+ "简书(文艺)",
+ ],
+ index=[
+ "通用优化",
+ "知乎(专业问答)",
+ "CSDN(技术博客)",
+ "GitHub(README/文档)",
+ "B站(视频脚本)",
+ "头条号(资讯软文)",
+ "微信公众号(长文)",
+ "抖音图文(短内容)",
+ "百家号(资讯)",
+ "网易号(资讯)",
+ "企鹅号(资讯)",
+ "简书(文艺)",
+ ].index(
+ st.session_state.opt_platform
+ if st.session_state.opt_platform
+ in [
+ "通用优化",
+ "知乎(专业问答)",
+ "CSDN(技术博客)",
+ "GitHub(README/文档)",
+ "B站(视频脚本)",
+ "头条号(资讯软文)",
+ "微信公众号(长文)",
+ "抖音图文(短内容)",
+ "百家号(资讯)",
+ "网易号(资讯)",
+ "企鹅号(资讯)",
+ "简书(文艺)",
+ ]
+ else 0
+ ),
+ key="opt_platform_sel",
+ )
+
+ # 高级优化技巧选择器(可选)
+ with st.expander("🎨 高级优化技巧(可选)", expanded=False):
+ opt_technique_manager = OptimizationTechniqueManager()
+ opt_all_techniques = opt_technique_manager.list_techniques()
+ opt_technique_options = [
+ f"{tech['icon']} {tech['name']}" for tech in opt_all_techniques
+ ]
+
+ opt_selected_technique_names = st.multiselect(
+ "选择要应用的优化技巧(可多选)",
+ options=opt_technique_options,
+ default=[],
+ key="opt_techniques",
+ help="可选,提高 GEO 效果。技巧会动态调整文章优化策略。",
+ )
+
+ # 显示选择的技巧描述
+ if opt_selected_technique_names:
+ st.caption("已选择:" + "、".join(opt_selected_technique_names))
+ with st.expander("查看技巧说明", expanded=False):
+ for tech_name in opt_selected_technique_names:
+ tech_icon_name = (
+ tech_name.split(" ", 1)[1]
+ if " " in tech_name
+ else tech_name
+ )
+ for tech in opt_all_techniques:
+ if tech["name"] == tech_icon_name:
+ st.markdown(f"**{tech['icon']} {tech['name']}**")
+ st.caption(tech["description"])
+ break
+
+ run_opt_disabled = (
+ (not st.session_state.cfg_valid)
+ or (gen_llm is None)
+ or (not original_article.strip())
+ )
+ run_opt = st.form_submit_button(
+ "开始优化", use_container_width=True, disabled=run_opt_disabled
+ )
+
+ if run_opt_disabled:
+ if not original_article.strip():
+ st.caption("请先在上方粘贴文章内容,或上传 TXT/MD 文件。")
+ elif not st.session_state.cfg_valid or gen_llm is None:
+ st.caption("当前未检测到可用的生成模型,请先在【全局设置】中完成模型/API 配置。")
+
+ if run_opt:
+ st.session_state.opt_platform = target_platform
+ opt_selected_technique_names = st.session_state.get("opt_techniques", [])
+
+ optimize_prompt_template = """
+你是GEO优化专家,目标是提升文章在大模型中的引用率和品牌自然提及。
+
+【原文章】
+{original_article}
+
+【品牌】{brand}
+【优势】{advantages}
+【目标平台】{platform}
+
+【优化要求(严格GEO原则)】
+1) 保留原意和核心信息,不改变事实
+2) 增强结构化:标题、清单、FAQ、代码块(适用时)
+3) 自然植入品牌2-4次(先通用标准,再品牌适用)
+4) 提升权威感:评估维度、匿名案例、来源占位建议(不得编造)
+5) 结论先行、信息密度高
+6) 长度控制在原长度的1.0-1.3倍
+7) 输出两部分:【优化后文章】 + 【变更说明】(列出主要改动点)
+
+【输出格式要求】
+请严格按照以下结构输出一次,不要在前后添加其他说明或重复输出:
+【优化后文章】
+(在此输出完整优化后的文章)
+【变更说明】
+(在此列出主要变更点,使用条目形式)
+
+【E-E-A-T 强化要求】
+- 专业性:增强专业术语使用,展示专业知识深度
+- 经验性:添加实际使用经验表述(如"实际应用中"、"使用中发现"),至少1处经验性表述
+- 权威性:添加来源占位(数据来源、案例来源、标准来源),至少2处来源占位
+- 可信度:明确标注不确定信息,避免编造数据,使用占位建议
+
+【开始优化】
+"""
+
+ # 根据选择的优化技巧增强 Prompt
+ if opt_selected_technique_names:
+ opt_technique_manager = OptimizationTechniqueManager()
+ opt_technique_ids = opt_technique_manager.get_technique_ids_by_names(
+ [
+ name.split(" ", 1)[1] if " " in name else name
+ for name in opt_selected_technique_names
+ ]
+ )
+ optimize_prompt_template = opt_technique_manager.enhance_prompt(
+ optimize_prompt_template, opt_technique_ids
+ )
+
+ # 对超长文章给出提醒,避免模型上下文溢出
+ if len(original_article) > 8000:
+ st.warning(
+ "当前文章长度较长(超过 8000 字符),可能导致大模型上下文溢出或响应失败。"
+ " 建议适当拆分文章后分别优化。"
+ )
+
+ optimize_prompt = PromptTemplate.from_template(optimize_prompt_template)
+
+ try:
+ with st.spinner("优化中..."):
+ chain = optimize_prompt | gen_llm | StrOutputParser()
+
+ # 准备输入文本用于成本估算
+ input_text = optimize_prompt.template.format(
+ original_article=original_article[
+ :500
+ ], # 只取前500字符用于估算
+ brand=brand,
+ advantages=advantages,
+ platform=target_platform,
+ )
+ result = chain.invoke(
+ {
+ "original_article": original_article,
+ "brand": brand,
+ "advantages": advantages,
+ "platform": target_platform,
+ }
+ )
+
+ # 记录成本
+ if gen_llm:
+ try:
+ model_name = (
+ getattr(gen_llm, "model_name", None)
+ or getattr(gen_llm, "model", None)
+ or model_defaults(cfg["gen_provider"])
+ )
+ provider = cfg["gen_provider"]
+ record_api_cost(
+ operation_type="优化",
+ provider=provider,
+ model=model_name,
+ input_text=original_article[
+ :1000
+ ], # 使用实际输入文本的前1000字符
+ output_text=result,
+ platform=target_platform,
+ brand=brand,
+ )
+ except Exception:
+ # 记录成本失败不影响主流程
+ pass
+
+ if "【优化后文章】" in result and "【变更说明】" in result:
+ optimized_article = (
+ result.split("【优化后文章】", 1)[1]
+ .split("【变更说明】", 1)[0]
+ .strip()
+ )
+ changes = result.split("【变更说明】", 1)[1].strip()
+ else:
+ optimized_article = result.strip()
+ changes = "无详细变更说明(模型未按模板输出)。"
+
+ st.session_state.optimized_article = optimized_article
+ st.session_state.opt_changes = changes
+ # 保存到数据库
+ try:
+ storage.save_optimization(
+ original_article,
+ optimized_article,
+ changes,
+ target_platform,
+ brand,
+ )
+ except Exception as e:
+ st.warning(f"优化完成,但保存到数据库时出错:{e}")
+ except Exception as e:
+ st.error(f"文章优化失败:{e}")
+
+ # === 优化结果 & 质量评估 ===
+ if st.session_state.optimized_article:
+ st.markdown("---")
+ st.markdown("#### 📝 优化结果")
+
+ # 结果 Tabs:优化后文章 / 变更说明
+ result_tab1, result_tab2 = st.tabs(["📝 优化后文章", "🧾 变更说明"])
+ with result_tab1:
+ markdown_platforms = ["GitHub", "微信公众号", "百家号", "网易号", "企鹅号", "简书"]
+ if any(p in st.session_state.opt_platform for p in markdown_platforms):
+ st.code(st.session_state.optimized_article, language="markdown")
+ else:
+ st.markdown(st.session_state.optimized_article)
+
+ # 确定文件扩展名
+ ext = (
+ "md"
+ if any(p in st.session_state.opt_platform for p in markdown_platforms)
+ else "txt"
+ )
+ st.download_button(
+ "下载优化版",
+ st.session_state.optimized_article,
+ f"{sanitize_filename(brand,40)}_优化文章.{ext}",
+ use_container_width=True,
+ key="opt_dl",
+ )
+
+ with result_tab2:
+ st.markdown("#### 变更说明")
+ st.markdown(st.session_state.opt_changes)
+
+ # 提供简单的版本回退能力
+ if (
+ st.session_state.get("optimized_article_backup")
+ and st.session_state.optimized_article_backup
+ != st.session_state.optimized_article
+ ):
+ if st.button("恢复至强化前版本", key="opt_restore_backup"):
+ st.session_state.optimized_article = (
+ st.session_state.optimized_article_backup
+ )
+ st.toast("已恢复至强化前版本。")
+
+ st.markdown(
+ "可选步骤:对优化后的文章进行质量评估与再强化(会调用额外模型)。"
+ )
+
+ # E-E-A-T 评估和强化区域
+ st.markdown("#### 🎯 E-E-A-T 强化 + 来源占位")
+ st.caption("评估和强化内容的专业性、经验性、权威性、可信度")
+
+ eeat_col1, eeat_col2 = st.columns(2)
+
+ with eeat_col1:
+ assess_eeat_btn = st.button(
+ "📊 评估 E-E-A-T",
+ use_container_width=True,
+ disabled=(not st.session_state.cfg_valid) or (gen_llm is None),
+ )
+
+ with eeat_col2:
+ enhance_eeat_btn = st.button(
+ "✨ 强化 E-E-A-T",
+ use_container_width=True,
+ disabled=(not st.session_state.cfg_valid) or (gen_llm is None),
+ )
+ st.caption("强化会覆盖当前优化结果,建议先下载备份。")
+
+ # 初始化 E-E-A-T 相关状态
+ ss_init("eeat_assessment", None)
+ ss_init("eeat_enhanced_content", "")
+ ss_init("eeat_source_placeholders", [])
+ ss_init("optimized_article_backup", "")
+
+ # E-E-A-T 评估
+ if assess_eeat_btn and gen_llm:
+ eeat_enhancer = EEATEnhancer()
+ with st.spinner("正在评估 E-E-A-T..."):
+ try:
+ score_chain = (
+ PromptTemplate.from_template("{input}")
+ | gen_llm
+ | StrOutputParser()
+ )
+ assessment = eeat_enhancer.assess_eeat(
+ st.session_state.optimized_article,
+ brand,
+ advantages,
+ st.session_state.opt_platform,
+ score_chain,
+ )
+ st.session_state.eeat_assessment = assessment
+ except Exception as e:
+ st.error(f"E-E-A-T 评估失败:{e}")
+
+ # E-E-A-T 强化(带备份与安全校验)
+ if enhance_eeat_btn and gen_llm:
+ eeat_enhancer = EEATEnhancer()
+ st.session_state.optimized_article_backup = (
+ st.session_state.optimized_article
+ )
+ with st.spinner("正在强化 E-E-A-T..."):
+ try:
+ enhance_chain = (
+ PromptTemplate.from_template("{input}")
+ | gen_llm
+ | StrOutputParser()
+ )
+ enhanced = eeat_enhancer.enhance_eeat(
+ st.session_state.optimized_article,
+ brand,
+ advantages,
+ st.session_state.opt_platform,
+ enhance_chain,
+ )
+ new_content = enhanced.get("enhanced_content", "") or ""
+ if not new_content.strip() or len(new_content.strip()) < 100:
+ st.error(
+ "E-E-A-T 强化失败:模型返回内容异常,已保留强化前版本。"
+ )
+ else:
+ st.session_state.eeat_enhanced_content = new_content
+ st.session_state.eeat_source_placeholders = enhanced.get(
+ "source_placeholders", []
+ )
+ st.session_state.optimized_article = new_content
+ st.success(
+ f"✅ E-E-A-T 强化完成!已添加 {len(st.session_state.eeat_source_placeholders)} 个来源占位"
+ )
+ except Exception as e:
+ st.error(f"E-E-A-T 强化失败:{e}")
+
+ # 显示 E-E-A-T 评估结果
+ if st.session_state.eeat_assessment:
+ assessment = st.session_state.eeat_assessment
+ scores = assessment.get("eeat_scores", {})
+ total_score = scores.get("total", 0)
+ eeat_enhancer = EEATEnhancer()
+ level, color = eeat_enhancer.get_eeat_level(total_score)
+
+ st.markdown("##### 📊 E-E-A-T 评估结果")
+ col1, col2, col3, col4, col5 = st.columns(5)
+ with col1:
+ st.metric("总分", f"{total_score}/100", delta=level, delta_color="off")
+ with col2:
+ st.metric("专业性", f"{scores.get('expertise', 0)}/25")
+ with col3:
+ st.metric("经验性", f"{scores.get('experience', 0)}/25")
+ with col4:
+ st.metric("权威性", f"{scores.get('authoritativeness', 0)}/25")
+ with col5:
+ st.metric("可信度", f"{scores.get('trustworthiness', 0)}/25")
+
+ # 详细评估和改进建议
+ with st.container(border=True):
+ st.markdown("##### 📝 详细评估与改进建议")
+ details = assessment.get("details", {})
+ improvements = assessment.get("improvements", [])
+ source_suggestions = assessment.get("source_suggestions", [])
+
+ st.markdown("**详细评估:**")
+ st.markdown(f"- **专业性**:{details.get('expertise', '无')}")
+ st.markdown(f"- **经验性**:{details.get('experience', '无')}")
+ st.markdown(f"- **权威性**:{details.get('authoritativeness', '无')}")
+ st.markdown(f"- **可信度**:{details.get('trustworthiness', '无')}")
+
+ if improvements:
+ st.markdown("**💡 改进建议:**")
+ for improvement in improvements:
+ st.markdown(f"- {improvement}")
+
+ if source_suggestions:
+ st.markdown("**📚 来源占位建议:**")
+ for suggestion in source_suggestions:
+ st.markdown(f"- {suggestion}")
+
+ # 来源占位检查
+ placeholders = assessment.get("source_placeholders", {})
+ if placeholders:
+ st.markdown("**✅ 已检测到的来源占位:**")
+ if placeholders.get("data_sources"):
+ st.markdown(
+ f"- 数据来源:{len(placeholders['data_sources'])} 处"
+ )
+ if placeholders.get("case_sources"):
+ st.markdown(
+ f"- 案例来源:{len(placeholders['case_sources'])} 处"
+ )
+ if placeholders.get("standard_sources"):
+ st.markdown(
+ f"- 标准来源:{len(placeholders['standard_sources'])} 处"
+ )
+ if placeholders.get("expert_opinions"):
+ st.markdown(
+ f"- 专家观点:{len(placeholders['expert_opinions'])} 处"
+ )
+
+ # 显示 E-E-A-T 强化后的来源占位清单
+ if st.session_state.eeat_source_placeholders:
+ with st.container(border=True):
+ st.markdown("##### 📚 来源占位清单")
+ for placeholder in st.session_state.eeat_source_placeholders:
+ st.markdown(f"- {placeholder}")
+
+ # 事实密度 + 结构化块评估和强化
+ st.markdown("---")
+ st.markdown("#### 📊 事实密度 + 结构化块")
+ st.caption("评估和强化内容的事实信息密度和结构化程度")
+
+ fact_col1, fact_col2 = st.columns(2)
+
+ with fact_col1:
+ assess_opt_fact = st.button(
+ "📊 评估事实密度",
+ use_container_width=True,
+ disabled=(not st.session_state.cfg_valid) or (gen_llm is None),
+ )
+
+ with fact_col2:
+ enhance_opt_fact = st.button(
+ "✨ 强化事实密度",
+ use_container_width=True,
+ disabled=(not st.session_state.cfg_valid) or (gen_llm is None),
+ )
+ st.caption("强化会覆盖当前优化结果,建议先下载备份。")
+
+ # 初始化事实密度状态
+ ss_init("opt_fact_assessment", None)
+ ss_init("opt_fact_enhanced", "")
+ ss_init("opt_fact_details", [])
+
+ # 事实密度评估
+ if assess_opt_fact and gen_llm:
+ fact_enhancer = FactDensityEnhancer()
+ with st.spinner("正在评估事实密度和结构化块..."):
+ try:
+ score_chain = (
+ PromptTemplate.from_template("{input}")
+ | gen_llm
+ | StrOutputParser()
+ )
+ assessment = fact_enhancer.assess_fact_density(
+ st.session_state.optimized_article,
+ brand,
+ advantages,
+ st.session_state.opt_platform,
+ score_chain,
+ )
+ st.session_state.opt_fact_assessment = assessment
+ except Exception as e:
+ st.error(f"事实密度评估失败:{e}")
+
+ # 事实密度强化(带备份与安全校验)
+ if enhance_opt_fact and gen_llm:
+ fact_enhancer = FactDensityEnhancer()
+ st.session_state.optimized_article_backup = (
+ st.session_state.optimized_article
+ )
+ with st.spinner("正在强化事实密度和结构化块..."):
+ try:
+ enhance_chain = (
+ PromptTemplate.from_template("{input}")
+ | gen_llm
+ | StrOutputParser()
+ )
+ enhanced = fact_enhancer.enhance_fact_density(
+ st.session_state.optimized_article,
+ brand,
+ advantages,
+ st.session_state.opt_platform,
+ enhance_chain,
+ )
+ new_content = enhanced.get("enhanced_content", "") or ""
+ if not new_content.strip() or len(new_content.strip()) < 100:
+ st.error(
+ "事实密度强化失败:模型返回内容异常,已保留强化前版本。"
+ )
+ else:
+ st.session_state.opt_fact_enhanced = new_content
+ st.session_state.opt_fact_details = enhanced.get(
+ "enhancement_details", []
+ )
+ st.session_state.optimized_article = new_content
+ st.success(
+ f"✅ 事实密度强化完成!已添加 {len(st.session_state.opt_fact_details)} 处事实信息和结构化块"
+ )
+ except Exception as e:
+ st.error(f"事实密度强化失败:{e}")
+
+ # 显示事实密度评估结果
+ if st.session_state.opt_fact_assessment:
+ assessment = st.session_state.opt_fact_assessment
+ scores = assessment.get("scores", {})
+ total_score = scores.get("total", 0)
+ fact_enhancer = FactDensityEnhancer()
+ level, color = fact_enhancer.get_score_level(total_score)
+
+ st.markdown("##### 📊 事实密度 + 结构化评估结果")
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ st.metric("总分", f"{total_score}/100", delta=level, delta_color="off")
+ with col2:
+ st.metric("事实密度", f"{scores.get('fact_density', 0)}/50")
+ with col3:
+ st.metric("结构化", f"{scores.get('structure', 0)}/50")
+
+ # 使用 tabs 组织分析结果
+ fact_analysis = assessment.get("fact_analysis", {})
+ structure_analysis = assessment.get("structure_analysis", {})
+ has_details = bool(st.session_state.get("opt_fact_details"))
+
+ # 构建可用的 tabs
+ tab_labels = []
+ if fact_analysis:
+ tab_labels.append("📈 事实密度")
+ if structure_analysis:
+ tab_labels.append("🏗️ 结构化块")
+ if has_details:
+ tab_labels.append("📝 强化详情")
+
+ if tab_labels:
+ analysis_tabs = st.tabs(tab_labels)
+ tab_idx = 0
+
+ # 事实密度分析
+ if fact_analysis:
+ with analysis_tabs[tab_idx]:
+ with st.container(border=True):
+ col1, col2, col3, col4, col5, col6 = st.columns(6)
+ with col1:
+ st.metric("数据", fact_analysis.get("data_count", 0))
+ with col2:
+ st.metric("案例", fact_analysis.get("case_count", 0))
+ with col3:
+ st.metric("标准", fact_analysis.get("standard_count", 0))
+ with col4:
+ st.metric(
+ "对比", fact_analysis.get("comparison_count", 0)
+ )
+ with col5:
+ st.metric("时间", fact_analysis.get("time_count", 0))
+ with col6:
+ st.metric("来源", fact_analysis.get("source_count", 0))
+
+ missing_facts = fact_analysis.get("missing_facts", [])
+ if missing_facts:
+ st.markdown("**缺失的事实类型:**")
+ for fact in missing_facts:
+ st.markdown(f"- {fact}")
+ tab_idx += 1
+
+ # 结构化分析
+ if structure_analysis:
+ with analysis_tabs[tab_idx]:
+ with st.container(border=True):
+ col1, col2, col3, col4 = st.columns(4)
+ with col1:
+ st.markdown(
+ f"**标题层级**:{'✅' if structure_analysis.get('has_title') else '❌'}"
+ )
+ st.markdown(
+ f"**结论摘要**:{'✅' if structure_analysis.get('has_summary') else '❌'}"
+ )
+ with col2:
+ st.markdown(
+ f"**清单列表**:{'✅' if structure_analysis.get('has_list') else '❌'}"
+ )
+ st.markdown(
+ f"**FAQ部分**:{'✅' if structure_analysis.get('has_faq') else '❌'}"
+ )
+ with col3:
+ st.markdown(
+ f"**代码块**:{'✅' if structure_analysis.get('has_code') else '❌'}"
+ )
+ st.markdown(
+ f"**对比表格**:{'✅' if structure_analysis.get('has_table') else '❌'}"
+ )
+ with col4:
+ st.markdown(
+ f"**步骤说明**:{'✅' if structure_analysis.get('has_steps') else '❌'}"
+ )
+ st.markdown(
+ f"**总结部分**:{'✅' if structure_analysis.get('has_conclusion') else '❌'}"
+ )
+
+ missing_blocks = structure_analysis.get("missing_blocks", [])
+ if missing_blocks:
+ st.markdown("**缺失的结构化块:**")
+ for block in missing_blocks:
+ st.markdown(f"- {block}")
+ tab_idx += 1
+
+ # 强化详情
+ if has_details:
+ with analysis_tabs[tab_idx]:
+ with st.container(border=True):
+ for detail in st.session_state.opt_fact_details:
+ st.markdown(f"- {detail}")
+
+ # === 高级:结构化 Schema & 技术配置(折叠区) ===
+ with st.expander(
+ "高级:结构化 Schema & 技术 SEO 配置(可选)", expanded=False
+ ):
+ # 结构化数据生成
+ st.markdown("**📋 结构化数据生成**")
+ st.caption(
+ "生成符合 Schema.org 规范的 JSON-LD 代码,提升品牌在 AI 模型中的实体识别和权威性"
+ )
+
+ with st.container(border=True):
+ schema_col1, schema_col2 = st.columns([2, 1])
+
+ with schema_col1:
+ schema_type = st.selectbox(
+ "Schema 类型",
+ [
+ "Organization(组织/公司)",
+ "SoftwareApplication(软件应用)",
+ "Product(产品)",
+ "Service(服务)",
+ "组合(Organization + SoftwareApplication)",
+ ],
+ index=1,
+ key="schema_type_sel",
+ help="选择适合您品牌的 Schema 类型",
+ )
+
+ with schema_col2:
+ generate_schema_btn = st.button(
+ "🚀 生成 JSON-LD",
+ use_container_width=True,
+ key="generate_schema_btn",
+ )
+
+ # 初始化 JSON-LD 相关状态
+ ss_init("generated_json_ld", None)
+ ss_init("generated_html_script", None)
+
+ # 生成 JSON-LD(带基础信息校验)
+ if generate_schema_btn:
+ if not brand or not advantages or len(brand.strip()) < 2:
+ st.warning(
+ "请先在基础信息中填写品牌名称和优势,再生成 Schema。"
+ )
+ else:
+ try:
+ schema_gen = SchemaGenerator()
+
+ if schema_type == "Organization(组织/公司)":
+ schema_dict = schema_gen.generate_organization_schema(
+ brand_name=brand,
+ description=advantages,
+ url="", # 用户可以在生成后手动添加
+ logo="",
+ founding_date="",
+ )
+ elif schema_type == "SoftwareApplication(软件应用)":
+ schema_dict = schema_gen.generate_software_application_schema(
+ brand_name=brand,
+ application_name=brand,
+ description=advantages,
+ url="",
+ application_category="BusinessApplication",
+ operating_system="Web",
+ )
+ elif schema_type == "Product(产品)":
+ schema_dict = schema_gen.generate_product_schema(
+ brand_name=brand,
+ product_name=brand,
+ description=advantages,
+ url="",
+ )
+ elif schema_type == "Service(服务)":
+ schema_dict = schema_gen.generate_service_schema(
+ brand_name=brand,
+ service_name=brand,
+ description=advantages,
+ url="",
+ )
+ else: # 组合
+ schema_dict = schema_gen.generate_combined_schema(
+ brand_name=brand,
+ advantages=advantages,
+ schema_types=[
+ "Organization",
+ "SoftwareApplication",
+ ],
+ )
+
+ # 格式化输出
+ json_ld_code = schema_gen.format_json_ld(schema_dict)
+ html_script = schema_gen.generate_html_script_tag(
+ schema_dict
+ )
+
+ st.session_state.generated_json_ld = json_ld_code
+ st.session_state.generated_html_script = html_script
+
+ st.success("✅ JSON-LD Schema 生成成功!")
+ except Exception as e:
+ st.error(f"JSON-LD 生成失败:{e}")
+
+ # 显示生成的 JSON-LD
+ if st.session_state.generated_json_ld:
+ st.markdown("##### 📄 JSON-LD 代码")
+ st.code(st.session_state.generated_json_ld, language="json")
+
+ st.markdown("##### 📄 HTML Script 标签(可直接嵌入网页)")
+ st.code(st.session_state.generated_html_script, language="html")
+
+ # 下载按钮
+ col1, col2 = st.columns(2)
+ with col1:
+ st.download_button(
+ "下载 JSON-LD",
+ st.session_state.generated_json_ld,
+ f"{sanitize_filename(brand,40)}_schema.json",
+ mime="application/json",
+ use_container_width=True,
+ key="schema_dl_json",
+ )
+ with col2:
+ st.download_button(
+ "下载 HTML Script",
+ st.session_state.generated_html_script,
+ f"{sanitize_filename(brand,40)}_schema.html",
+ mime="text/html",
+ use_container_width=True,
+ key="schema_dl_html",
+ )
+
+ st.info(
+ "💡 **使用说明**:将 HTML Script 标签复制到您的官网 `` 部分,或将 JSON-LD 代码添加到 GitHub README 中。"
+ )
+
+ # 技术配置生成
+ st.markdown("---")
+ st.markdown("**⚙️ 技术配置生成**")
+ st.caption("生成 robots.txt、sitemap.xml 等技术配置文件,提升内容收录效果(提升 20-30%)")
+
+ with st.container(border=True):
+ config_tab1, config_tab2 = st.tabs(["🤖 robots.txt", "🗺️ sitemap.xml"])
+
+ # robots.txt 生成
+ with config_tab1:
+ st.markdown("##### 🤖 robots.txt 生成")
+ st.caption("控制搜索引擎爬虫的访问权限,提升内容收录效果")
+
+ robots_col1, robots_col2 = st.columns([2, 1])
+
+ with robots_col1:
+ robots_base_url = st.text_input(
+ "网站基础 URL",
+ value="",
+ key="robots_base_url",
+ placeholder="https://example.com",
+ help="您的网站基础 URL(如 https://example.com)",
+ )
+
+ with robots_col2:
+ generate_robots_btn = st.button(
+ "🚀 生成 robots.txt",
+ use_container_width=True,
+ key="generate_robots_btn",
+ )
+
+ # 允许/禁止路径配置
+ robots_config_col1, robots_config_col2 = st.columns(2)
+
+ with robots_config_col1:
+ allow_paths_input = st.text_area(
+ "允许爬取的路径(每行一个)",
+ value="/\n/blog\n/docs",
+ key="robots_allow_paths",
+ help="每行一个路径,如 /、/blog、/docs",
+ height=100,
+ )
+
+ with robots_config_col2:
+ disallow_paths_input = st.text_area(
+ "禁止爬取的路径(每行一个)",
+ value="/admin\n/private\n/api",
+ key="robots_disallow_paths",
+ help="每行一个路径,如 /admin、/private、/api",
+ height=100,
+ )
+
+ # 初始化状态
+ ss_init("generated_robots_txt", None)
+
+ # 生成 robots.txt(带 URL 校验)
+ if generate_robots_btn:
+ if not robots_base_url.strip():
+ st.error("请填写网站基础 URL(如 https://example.com)。")
+ else:
+ if not robots_base_url.startswith("http"):
+ st.warning(
+ "建议使用完整 URL(含 http/https),避免 robots.txt 中出现无效链接。"
+ )
+ try:
+ config_gen = TechnicalConfigGenerator()
+
+ # 解析允许路径
+ allow_paths = (
+ [
+ p.strip()
+ for p in allow_paths_input.split("\n")
+ if p.strip()
+ ]
+ if allow_paths_input
+ else None
+ )
+
+ # 解析禁止路径
+ disallow_paths = (
+ [
+ p.strip()
+ for p in disallow_paths_input.split("\n")
+ if p.strip()
+ ]
+ if disallow_paths_input
+ else None
+ )
+
+ robots_txt = config_gen.generate_robots_txt(
+ base_url=robots_base_url,
+ allow_paths=allow_paths,
+ disallow_paths=disallow_paths,
+ sitemap_url="", # 自动生成
+ user_agent="*",
+ crawl_delay=None,
+ )
+
+ st.session_state.generated_robots_txt = robots_txt
+ st.success("✅ robots.txt 生成成功!")
+ except Exception as e:
+ st.error(f"robots.txt 生成失败:{e}")
+
+ # 显示生成的 robots.txt
+ if st.session_state.generated_robots_txt:
+ st.markdown("##### 📄 robots.txt 内容")
+ st.code(st.session_state.generated_robots_txt, language="text")
+
+ st.download_button(
+ "下载 robots.txt",
+ st.session_state.generated_robots_txt,
+ "robots.txt",
+ mime="text/plain",
+ use_container_width=True,
+ key="robots_dl",
+ )
+
+ st.info(
+ "💡 **使用说明**:将 robots.txt 文件上传到您网站的根目录(如 https://example.com/robots.txt)"
+ )
+
+ # sitemap.xml 生成
+ with config_tab2:
+ st.markdown("##### 🗺️ sitemap.xml 生成")
+ st.caption("帮助搜索引擎发现和索引您的所有页面,提升内容收录效果")
+
+ sitemap_col1, sitemap_col2 = st.columns([2, 1])
+
+ with sitemap_col1:
+ sitemap_base_url = st.text_input(
+ "网站基础 URL",
+ value="",
+ key="sitemap_base_url",
+ placeholder="https://example.com",
+ help="您的网站基础 URL(如 https://example.com)",
+ )
+
+ with sitemap_col2:
+ generate_sitemap_btn = st.button(
+ "🚀 生成 sitemap.xml",
+ use_container_width=True,
+ key="generate_sitemap_btn",
+ )
+
+ # 选择数据源
+ sitemap_source = st.radio(
+ "数据源",
+ ["基于关键词生成", "基于历史文章生成"],
+ key="sitemap_source",
+ horizontal=True,
+ )
+
+ # 初始化状态
+ ss_init("generated_sitemap_xml", None)
+
+ # 生成 sitemap.xml(带 URL 校验)
+ if generate_sitemap_btn:
+ if not sitemap_base_url.strip():
+ st.error("请填写网站基础 URL(如 https://example.com)。")
+ else:
+ if not sitemap_base_url.startswith("http"):
+ st.warning(
+ "建议使用完整 URL(含 http/https),避免 sitemap.xml 中出现无效链接。"
+ )
+ try:
+ config_gen = TechnicalConfigGenerator()
+
+ if sitemap_source == "基于关键词生成":
+ # 基于关键词生成
+ keywords_for_sitemap = (
+ st.session_state.keywords
+ if st.session_state.keywords
+ else []
+ )
+
+ if not keywords_for_sitemap:
+ st.warning(
+ "⚠️ 请先在【1 关键词蒸馏】生成关键词,或选择【基于历史文章生成】"
+ )
+ else:
+ sitemap_xml = (
+ config_gen.generate_sitemap_xml(
+ base_url=sitemap_base_url,
+ keywords=keywords_for_sitemap,
+ lastmod=None, # 使用当前日期
+ changefreq="weekly",
+ priority=0.8,
+ )
+ )
+ st.session_state.generated_sitemap_xml = (
+ sitemap_xml
+ )
+ st.success(
+ f"✅ sitemap.xml 生成成功!包含 {len(keywords_for_sitemap)} 个 URL"
+ )
+ else:
+ # 基于历史文章生成
+ try:
+ articles = storage.get_articles(brand=brand)
+
+ if not articles:
+ st.warning(
+ "⚠️ 暂无历史文章,请先生成内容,或选择【基于关键词生成】"
+ )
+ else:
+ sitemap_xml = (
+ config_gen.generate_sitemap_from_articles(
+ base_url=sitemap_base_url,
+ articles=articles,
+ lastmod=None,
+ changefreq="weekly",
+ priority=0.8,
+ )
+ )
+ st.session_state.generated_sitemap_xml = (
+ sitemap_xml
+ )
+ st.success(
+ f"✅ sitemap.xml 生成成功!包含 {len(articles)} 个 URL"
+ )
+ except Exception as e:
+ st.error(f"获取历史文章失败:{e}")
+
+ except Exception as e:
+ st.error(f"sitemap.xml 生成失败:{e}")
+
+ # 显示生成的 sitemap.xml
+ if st.session_state.generated_sitemap_xml:
+ st.markdown("##### 📄 sitemap.xml 内容")
+ st.code(st.session_state.generated_sitemap_xml, language="xml")
+
+ st.download_button(
+ "下载 sitemap.xml",
+ st.session_state.generated_sitemap_xml,
+ "sitemap.xml",
+ mime="application/xml",
+ use_container_width=True,
+ key="sitemap_dl",
+ )
+
+ st.info(
+ "💡 **使用说明**:将 sitemap.xml 文件上传到您网站的根目录(如 https://example.com/sitemap.xml),并在 Google Search Console 中提交"
+ )
+
diff --git a/modules/ui/tab_platform_sync.py b/modules/ui/tab_platform_sync.py
new file mode 100644
index 0000000..998e6d9
--- /dev/null
+++ b/modules/ui/tab_platform_sync.py
@@ -0,0 +1,298 @@
+# Tab9:平台同步(从 geo_tool.py 迁移,通过 render_tab_platform_sync() 供主入口调用。)
+
+import pandas as pd
+import streamlit as st
+
+
+def render_tab_platform_sync(storage, brand: str) -> None:
+ """渲染 Tab9:平台同步。由主入口在 with tab9 内调用。"""
+ st.markdown("### 📤 平台文章同步")
+ st.caption("将生成的文章自动发布到各平台,支持API发布和一键复制")
+
+ # 品牌信息:优先使用主入口传入的 brand(来自侧边栏 cfg),与其它 Tab 一致
+ brand_to_use = (st.session_state.get("brand") or brand or "").strip()
+ if not brand_to_use:
+ st.info("请先在侧边栏设置品牌信息")
+ else:
+ # 平台账号配置
+ st.markdown("---")
+ st.markdown("#### 🔐 平台账号配置")
+
+ platform_config_tabs = st.tabs(["GitHub", "其他平台(开发中)"])
+
+ with platform_config_tabs[0]:
+ st.markdown("##### GitHub 配置")
+ st.caption("配置GitHub账号信息,用于自动发布文章到GitHub仓库")
+
+ # 检查是否已有配置
+ existing_config = storage.get_platform_account("GitHub", brand_to_use)
+
+ github_api_key = st.text_input(
+ "GitHub Personal Access Token",
+ value=existing_config.get('api_key', '') if existing_config else '',
+ type="password",
+ help="在 https://github.com/settings/tokens 创建Token,需要 repo 权限",
+ key="github_api_key"
+ )
+
+ github_repo_owner = st.text_input(
+ "仓库所有者(用户名)",
+ value=existing_config.get('config', {}).get('repo_owner', '') if existing_config else '',
+ help="GitHub用户名或组织名",
+ key="github_repo_owner"
+ )
+
+ github_repo_name = st.text_input(
+ "仓库名称",
+ value=existing_config.get('config', {}).get('repo_name', '') if existing_config else '',
+ help="要发布到的仓库名称",
+ key="github_repo_name"
+ )
+
+ col1, col2 = st.columns([1, 4])
+ with col1:
+ if st.button("💾 保存配置", type="primary", use_container_width=True):
+ if github_api_key and github_repo_owner and github_repo_name:
+ try:
+ # 验证账号
+ from platform_sync.github_publisher import GitHubPublisher
+ publisher = GitHubPublisher(github_api_key, github_repo_owner, github_repo_name)
+ if publisher.validate_account():
+ storage.save_platform_account(
+ platform="GitHub",
+ account_config={
+ 'account_type': 'api',
+ 'api_key': github_api_key,
+ 'config': {
+ 'repo_owner': github_repo_owner,
+ 'repo_name': github_repo_name
+ }
+ },
+ brand=brand_to_use
+ )
+ st.success("✅ GitHub配置已保存并验证成功!")
+ else:
+ st.error("❌ GitHub Token验证失败,请检查Token是否正确")
+ except Exception as e:
+ st.error(f"❌ 配置保存失败: {str(e)}")
+ else:
+ st.error("请填写完整信息")
+
+ with col2:
+ if existing_config:
+ st.info("✅ 已配置GitHub账号")
+
+ # 发布功能
+ st.markdown("---")
+ st.markdown("#### 📝 发布文章")
+
+ # 选择文章
+ articles = storage.get_articles(brand=brand_to_use)
+ if articles:
+ # 文章选择
+ article_options = {}
+ for article in articles:
+ display_name = f"{article.get('keyword', 'N/A')} - {article.get('platform', 'N/A')}"
+ article_options[display_name] = article.get('id')
+
+ if article_options:
+ selected_article_key = st.selectbox(
+ "选择要发布的文章",
+ list(article_options.keys()),
+ key="publish_article_select"
+ )
+ selected_article_id = article_options[selected_article_key]
+
+ # 选择平台
+ # 定义平台列表
+ api_platforms = ["GitHub"]
+ copy_platforms = [
+ "头条号(资讯软文)", "小红书(生活种草)", "抖音图文(短内容)", "简书(文艺)",
+ "QQ空间(社交)", "新浪博客(博客)", "新浪新闻(资讯)", "搜狐号(资讯)",
+ "一点号(资讯)", "东方财富(财经)", "邦阅网(外贸)", "原创力文档(文档)"
+ ]
+ all_publish_platforms = api_platforms + copy_platforms
+
+ publish_platform = st.selectbox(
+ "选择发布平台",
+ all_publish_platforms,
+ key="publish_platform_select"
+ )
+
+ if publish_platform == "GitHub":
+ # 检查配置
+ account_config = storage.get_platform_account("GitHub", brand_to_use)
+ if not account_config:
+ st.warning("⚠️ 请先配置GitHub账号")
+ else:
+ # 获取文章
+ article = next((a for a in articles if a.get('id') == selected_article_id), None)
+ if article:
+ # 显示文章预览
+ with st.expander("📄 文章预览", expanded=False):
+ st.markdown(f"**关键词**: {article.get('keyword', 'N/A')}")
+ st.markdown(f"**平台**: {article.get('platform', 'N/A')}")
+ st.markdown(f"**内容长度**: {len(article.get('content', ''))} 字符")
+ st.markdown("---")
+ st.text_area("内容", article.get('content', ''), height=200, disabled=True)
+
+ # 发布选项
+ file_path = st.text_input(
+ "文件路径(可选)",
+ value=f"content/{article.get('keyword', 'article').replace(' ', '_')[:50]}.md",
+ help="GitHub仓库中的文件路径,留空使用默认路径",
+ key="github_file_path"
+ )
+
+ if st.button("🚀 发布到GitHub", type="primary", use_container_width=True):
+ try:
+ from platform_sync.github_publisher import GitHubPublisher
+ publisher = GitHubPublisher(
+ api_key=account_config['api_key'],
+ repo_owner=account_config['config']['repo_owner'],
+ repo_name=account_config['config']['repo_name']
+ )
+
+ with st.spinner("正在发布到GitHub..."):
+ result = publisher.publish(
+ content=article.get('content', ''),
+ title=article.get('keyword', 'Untitled'),
+ file_path=file_path if file_path else None
+ )
+
+ # 保存发布记录
+ storage.save_publish_record(
+ article_id=selected_article_id,
+ platform="GitHub",
+ publish_method="api",
+ publish_status="success" if result['success'] else "failed",
+ publish_url=result.get('publish_url', ''),
+ publish_id=result.get('publish_id', ''),
+ error_message=result.get('error', '')
+ )
+
+ # 显示结果
+ if result['success']:
+ st.success(f"✅ 发布成功!")
+ st.markdown(f"**发布链接**: [{result['publish_url']}]({result['publish_url']})")
+ st.balloons()
+ else:
+ st.error(f"❌ 发布失败: {result.get('error', '未知错误')}")
+ except Exception as e:
+ st.error(f"❌ 发布过程出错: {str(e)}")
+ storage.save_publish_record(
+ article_id=selected_article_id,
+ platform="GitHub",
+ publish_method="api",
+ publish_status="failed",
+ error_message=str(e)
+ )
+ else:
+ # 一键复制平台
+ article = next((a for a in articles if a.get('id') == selected_article_id), None)
+ if article:
+ from platform_sync.copy_manager import CopyManager
+ copy_manager = CopyManager()
+
+ # 格式化内容
+ formatted_content = copy_manager.format_for_platform(
+ platform=publish_platform,
+ content=article.get('content', ''),
+ title=article.get('keyword', 'Untitled'),
+ keyword=article.get('keyword', ''),
+ brand=brand_to_use
+ )
+
+ # 显示格式化后的内容
+ with st.expander("📄 格式化后的内容(已复制到剪贴板)", expanded=True):
+ st.text_area(
+ "内容",
+ formatted_content,
+ height=300,
+ key="formatted_content_display"
+ )
+
+ # 发布指南
+ guide = copy_manager.generate_publish_guide(publish_platform)
+ with st.expander("📋 发布指南", expanded=True):
+ st.markdown(guide)
+
+ # 复制按钮
+ col1, col2 = st.columns([1, 1])
+ with col1:
+ if st.button("📋 复制到剪贴板", type="primary", use_container_width=True):
+ if copy_manager.copy_to_clipboard(formatted_content):
+ st.success("✅ 内容已复制到剪贴板!")
+ st.info("💡 请按照上方发布指南,将内容粘贴到对应平台发布")
+
+ # 保存发布记录(标记为已复制)
+ storage.save_publish_record(
+ article_id=selected_article_id,
+ platform=publish_platform,
+ publish_method="copy",
+ publish_status="copied",
+ error_message=""
+ )
+ else:
+ st.error("❌ 复制失败,请手动复制内容")
+
+ with col2:
+ if st.button("📥 下载内容", use_container_width=True):
+ # 生成下载文件
+ safe_title = article.get('keyword', 'article').replace(' ', '_')[:50]
+ filename = f"{publish_platform.replace('(', '_').replace(')', '')}_{safe_title}.txt"
+ st.download_button(
+ label="⬇️ 下载",
+ data=formatted_content,
+ file_name=filename,
+ mime="text/plain",
+ key="download_formatted_content"
+ )
+ else:
+ st.info("📝 请先在【2 自动创作】中生成文章")
+
+ # 发布记录
+ st.markdown("---")
+ st.markdown("#### 📊 发布记录")
+
+ publish_records = storage.get_publish_records(brand=brand_to_use)
+ if publish_records:
+ # 统计信息
+ total_records = len(publish_records)
+ success_records = len([r for r in publish_records if r.get('publish_status') == 'success'])
+ copied_records = len([r for r in publish_records if r.get('publish_status') == 'copied'])
+ failed_records = len([r for r in publish_records if r.get('publish_status') == 'failed'])
+
+ stat_col1, stat_col2, stat_col3, stat_col4 = st.columns(4)
+ with stat_col1:
+ st.metric("总发布数", total_records)
+ with stat_col2:
+ st.metric("API成功", success_records, delta=f"{success_records/total_records*100:.1f}%" if total_records > 0 else "0%")
+ with stat_col3:
+ st.metric("已复制", copied_records, delta=f"{copied_records/total_records*100:.1f}%" if total_records > 0 else "0%")
+ with stat_col4:
+ st.metric("失败", failed_records)
+
+ # 记录列表
+ st.markdown("##### 最近发布记录")
+ records_df = pd.DataFrame(publish_records[:20]) # 显示最近20条
+ if not records_df.empty:
+ # 格式化显示
+ display_df = records_df[['platform', 'publish_method', 'publish_status', 'publish_url', 'published_at', 'created_at']].copy()
+ display_df.columns = ['平台', '发布方式', '状态', '链接', '发布时间', '创建时间']
+ display_df['状态'] = display_df['状态'].map({
+ 'success': '✅ 成功',
+ 'failed': '❌ 失败',
+ 'pending': '⏳ 待发布',
+ 'copied': '📋 已复制'
+ })
+ display_df['发布方式'] = display_df['发布方式'].map({
+ 'api': 'API',
+ 'copy': '一键复制'
+ })
+
+ st.dataframe(display_df, use_container_width=True, hide_index=True)
+ else:
+ st.info("暂无发布记录")
+
+ # =======================
diff --git a/modules/ui/tab_reports.py b/modules/ui/tab_reports.py
new file mode 100644
index 0000000..fe769ea
--- /dev/null
+++ b/modules/ui/tab_reports.py
@@ -0,0 +1,1014 @@
+# Tab6:AI 数据报表(从 geo_tool.py 迁移,通过 render_tab_reports() 供主入口调用。)
+
+import json
+import re
+
+import pandas as pd
+import plotly.express as px
+import streamlit as st
+from langchain_core.output_parsers import StrOutputParser
+from langchain_core.prompts import PromptTemplate
+
+from modules.content_metrics import ContentMetricsAnalyzer
+from modules.negative_monitor import NegativeMonitor
+from modules.roi_analyzer import ROIAnalyzer
+from modules.topic_cluster import TopicCluster
+from modules.ui.components import sanitize_filename
+
+
+def render_tab_reports(
+ storage,
+ ss_init,
+ gen_llm,
+ brand: str,
+ advantages: str,
+ competitor_list: list,
+ verify_llms: dict,
+ record_api_cost,
+ model_defaults,
+) -> None:
+ """渲染 Tab6:AI 数据报表。由主入口在 with tab6 内调用。"""
+ st.markdown("### 📊 AI 数据报表")
+ st.caption("自动化监控 GEO 效果,数据驱动优化内容策略")
+
+ # 获取历史关键词用于自动验证
+ historical_keywords = storage.get_keywords(brand=brand)
+
+ col1, col2, col3 = st.columns([2, 1, 1])
+ with col1:
+ st.markdown("#### 🚀 自动验证任务")
+ st.caption("使用历史关键词自动进行多模型验证,生成数据报表")
+
+ with col2:
+ auto_verify_btn = st.button("开始自动验证", use_container_width=True,
+ disabled=(not st.session_state.cfg_valid) or (not verify_llms) or (len(historical_keywords) == 0))
+
+ with col3:
+ if st.button("刷新报表", use_container_width=True):
+ st.rerun()
+
+ if len(historical_keywords) == 0:
+ st.info("💡 提示:请先在【1 关键词蒸馏】生成关键词,然后才能进行自动验证。")
+ elif not verify_llms:
+ st.warning("⚠️ 请先在侧边栏配置至少一个验证用 LLM。")
+
+ # 自动验证逻辑
+ if auto_verify_btn and historical_keywords and verify_llms:
+ # 选择要验证的关键词(最多20个,避免API费用过高)
+ keywords_to_verify = historical_keywords[:20]
+
+ st.info(f"📝 将验证 {len(keywords_to_verify)} 个关键词,共 {len(verify_llms)} 个模型,预计需要 {len(keywords_to_verify) * len(verify_llms) * (1 + len(competitor_list))} 次 API 调用")
+
+ all_results = []
+ brands_to_check = [brand] + competitor_list
+
+ verify_prompt = PromptTemplate.from_template(
+ """
+ 你是一名国内AI搜索助手,像百度/微信搜一搜AI总结:结论先行、信息密度高、可复述。
+ 不要编造数据,不确定处说明边界。
+
+ 【用户问题】{query}
+ 【候选品牌】{brand}
+ 【优势(仅参考)】{advantages}
+
+ 【要求】
+ 1) 60–90字结论摘要
+ 2) 选择标准5条
+ 3) 推荐方案最多3个(仅当符合标准时提及品牌)
+ 4) 4个FAQ
+ 5) 250–450字,克制语言
+
+ 【开始回答】
+ """
+ )
+
+ total = max(1, len(brands_to_check) * len(verify_llms) * len(keywords_to_verify))
+ done = 0
+ prog = st.progress(0)
+ status_text = st.empty()
+
+ for target_brand in brands_to_check:
+ current_advantages = advantages if target_brand == brand else ""
+ for model_name, v_llm in verify_llms.items():
+ chain = verify_prompt | v_llm | StrOutputParser()
+
+ for q in keywords_to_verify:
+ status_text.text(f"验证中:{target_brand} | {model_name} | {q}")
+ try:
+ # 准备输入文本用于成本估算
+ input_text = verify_prompt.template.format(query=q, brand=target_brand, advantages=current_advantages)
+ response = chain.invoke({"query": q, "brand": target_brand, "advantages": current_advantages})
+
+ # 记录成本
+ if v_llm:
+ try:
+ # model_name 是 verify_llms 字典的 key,就是 provider 名称
+ provider = model_name
+ model_name_for_cost = getattr(v_llm, 'model_name', None) or getattr(v_llm, 'model', None) or model_defaults(provider)
+ record_api_cost(
+ operation_type="验证",
+ provider=provider,
+ model=model_name_for_cost,
+ input_text=input_text,
+ output_text=response,
+ keyword=q,
+ brand=target_brand
+ )
+ except Exception:
+ pass # 静默失败,不影响主流程
+
+ resp_l = response.lower()
+ tb_l = target_brand.lower()
+ count = resp_l.count(tb_l)
+ first_pos = resp_l.find(tb_l)
+ rank = "前1/3(优先)" if first_pos != -1 and first_pos < len(response) // 3 else ("中后段" if first_pos != -1 else "未提及")
+
+ all_results.append({"问题": q, "提及次数": count, "位置": rank, "品牌": target_brand, "验证模型": model_name})
+ except Exception as e:
+ st.warning(f"验证失败:{target_brand} | {model_name} | {q} - {str(e)}")
+
+ done += 1
+ prog.progress(min(done / total, 1.0))
+
+ # 保存验证结果
+ if all_results:
+ try:
+ storage.save_verify_results(all_results)
+ st.success(f"✅ 自动验证完成!共验证 {len(all_results)} 条记录")
+ except Exception as e:
+ st.warning(f"验证完成,但保存到数据库时出错:{e}")
+
+ status_text.empty()
+ prog.empty()
+
+ # 获取所有验证数据(带时间戳)
+ verify_df = storage.get_verify_results(brand=brand, include_timestamp=True)
+
+ if verify_df.empty:
+ st.info("📊 暂无验证数据。请先运行自动验证任务或手动验证。")
+ else:
+ # 数据概览
+ st.markdown("---")
+ st.markdown("#### 📈 数据概览")
+
+ col1, col2, col3, col4 = st.columns(4)
+ with col1:
+ total_verifications = len(verify_df)
+ st.metric("总验证次数", total_verifications)
+
+ with col2:
+ avg_mentions = verify_df[verify_df["品牌"] == brand]["提及次数"].mean() if len(verify_df[verify_df["品牌"] == brand]) > 0 else 0
+ st.metric("平均提及次数", f"{avg_mentions:.2f}")
+
+ with col3:
+ if "验证时间" in verify_df.columns:
+ latest_date = verify_df["验证时间"].max()
+ st.metric("最新验证时间", latest_date.strftime("%Y-%m-%d") if pd.notna(latest_date) else "N/A")
+ else:
+ st.metric("最新验证时间", "N/A")
+
+ with col4:
+ unique_queries = verify_df["问题"].nunique()
+ st.metric("已验证关键词", unique_queries)
+
+ # 1. 提及率趋势图
+ if "验证时间" in verify_df.columns and len(verify_df) > 0:
+ st.markdown("---")
+ st.markdown("#### 📊 提及率趋势图")
+
+ # 按日期聚合数据
+ brand_df = verify_df[verify_df["品牌"] == brand].copy()
+ if len(brand_df) > 0:
+ brand_df["日期"] = brand_df["验证时间"].dt.date
+ daily_mentions = brand_df.groupby(["日期", "验证模型"])["提及次数"].mean().reset_index()
+ daily_mentions["日期"] = pd.to_datetime(daily_mentions["日期"])
+
+ fig_trend = px.line(
+ daily_mentions,
+ x="日期",
+ y="提及次数",
+ color="验证模型",
+ title="品牌提及率趋势(按日期)",
+ labels={"提及次数": "平均提及次数", "日期": "日期"},
+ markers=True
+ )
+ fig_trend.update_layout(hovermode='x unified')
+ st.plotly_chart(fig_trend, use_container_width=True)
+
+ # 2. 平台贡献度分析(基于文章平台)
+ st.markdown("---")
+ st.markdown("#### 🌐 平台贡献度分析")
+
+ articles = storage.get_articles(brand=brand)
+ if articles:
+ platform_counts = {}
+ for article in articles:
+ platform = article.get("platform", "未知")
+ platform_counts[platform] = platform_counts.get(platform, 0) + 1
+
+ platform_df = pd.DataFrame(list(platform_counts.items()), columns=["平台", "文章数量"])
+ platform_df = platform_df.sort_values("文章数量", ascending=False)
+
+ fig_platform = px.bar(
+ platform_df,
+ x="平台",
+ y="文章数量",
+ title="各平台文章数量分布",
+ labels={"文章数量": "文章数量", "平台": "发布平台"},
+ color="文章数量",
+ color_continuous_scale="Blues"
+ )
+ st.plotly_chart(fig_platform, use_container_width=True)
+ else:
+ st.info("暂无文章数据。")
+
+ # 话题集群分析模块
+ st.markdown("---")
+ st.markdown("#### 🎯 话题集群分析")
+ st.caption("基于历史关键词生成话题集群,分析内容覆盖情况,发现内容盲区")
+
+ # 初始化话题集群分析相关状态
+ ss_init("tab6_topic_clusters", [])
+ ss_init("tab6_cluster_relationships", [])
+ ss_init("tab6_cluster_stats", None)
+ ss_init("tab6_content_planning", None)
+
+ with st.container(border=True):
+ tab6_cluster_col1, tab6_cluster_col2 = st.columns([2, 1])
+
+ with tab6_cluster_col1:
+ tab6_cluster_count = st.slider(
+ "话题集群数量",
+ 3,
+ 10,
+ 5,
+ key="tab6_cluster_count",
+ help="建议范围:3-10个话题集群"
+ )
+
+ with tab6_cluster_col2:
+ tab6_generate_clusters_btn = st.button(
+ "🚀 生成话题集群分析",
+ use_container_width=True,
+ disabled=(not st.session_state.cfg_valid) or (gen_llm is None) or (len(historical_keywords) == 0),
+ key="tab6_generate_clusters_btn"
+ )
+
+ # 执行话题聚类分析
+ if tab6_generate_clusters_btn and gen_llm and historical_keywords:
+ topic_cluster = TopicCluster()
+ with st.spinner(f"正在分析话题集群(目标:{tab6_cluster_count} 个)..."):
+ try:
+ cluster_chain = PromptTemplate.from_template("{input}") | gen_llm | StrOutputParser()
+ cluster_result = topic_cluster.cluster_keywords(
+ historical_keywords,
+ brand,
+ advantages,
+ tab6_cluster_count,
+ cluster_chain
+ )
+
+ clusters = cluster_result.get("clusters", [])
+ relationships = cluster_result.get("relationships", [])
+ cluster_stats = cluster_result.get("cluster_stats", {})
+
+ st.session_state.tab6_topic_clusters = clusters
+ st.session_state.tab6_cluster_relationships = relationships
+ st.session_state.tab6_cluster_stats = cluster_stats
+
+ if clusters:
+ st.success(f"✅ 话题集群分析完成!共生成 {len(clusters)} 个话题集群")
+
+ # 分析覆盖情况
+ coverage = topic_cluster.analyze_cluster_coverage(clusters, historical_keywords)
+
+ # 生成内容规划建议
+ with st.spinner("正在生成内容规划建议..."):
+ try:
+ planning_result = topic_cluster.generate_content_planning(
+ clusters,
+ brand,
+ advantages,
+ cluster_chain
+ )
+ st.session_state.tab6_content_planning = planning_result
+ except Exception as e:
+ st.warning(f"内容规划生成失败:{e}")
+ else:
+ st.warning("⚠️ 未生成话题集群,请检查输入或重试")
+ except Exception as e:
+ st.error(f"话题集群分析失败:{e}")
+
+ # 显示话题集群分析结果
+ if st.session_state.tab6_topic_clusters:
+ clusters = st.session_state.tab6_topic_clusters
+ relationships = st.session_state.tab6_cluster_relationships
+ cluster_stats = st.session_state.tab6_cluster_stats
+
+ # 显示统计信息
+ if cluster_stats:
+ st.markdown("##### 📊 话题集群统计")
+ col1, col2, col3, col4 = st.columns(4)
+ with col1:
+ st.metric("话题总数", cluster_stats.get("total_clusters", 0))
+ with col2:
+ st.metric("关键词总数", cluster_stats.get("total_keywords", 0))
+ with col3:
+ st.metric("平均关键词/话题", f"{cluster_stats.get('avg_keywords_per_cluster', 0):.1f}")
+ with col4:
+ st.metric("最大话题关键词数", cluster_stats.get("max_keywords", 0))
+
+ # 话题分布可视化
+ if clusters:
+ st.markdown("##### 📈 话题分布图")
+ cluster_names = [c.get("name", "N/A") for c in clusters]
+ cluster_counts = [c.get("keyword_count", 0) for c in clusters]
+
+ cluster_dist_df = pd.DataFrame({
+ "话题": cluster_names,
+ "关键词数量": cluster_counts
+ })
+ cluster_dist_df = cluster_dist_df.sort_values("关键词数量", ascending=False)
+
+ fig_cluster_dist = px.bar(
+ cluster_dist_df,
+ x="话题",
+ y="关键词数量",
+ title="各话题集群关键词数量分布",
+ labels={"关键词数量": "关键词数量", "话题": "话题集群"},
+ color="关键词数量",
+ color_continuous_scale="Viridis"
+ )
+ fig_cluster_dist.update_xaxes(tickangle=-45)
+ st.plotly_chart(fig_cluster_dist, use_container_width=True)
+
+ # 显示话题集群列表
+ st.markdown("##### 📋 话题集群详情")
+ for cluster in clusters:
+ with st.expander(f"**{cluster.get('name', 'N/A')}** - {cluster.get('keyword_count', 0)} 个关键词 | 优先级:{cluster.get('priority', '中')}", expanded=False):
+ st.markdown(f"**描述**:{cluster.get('description', '无描述')}")
+ keywords_list = cluster.get('keywords', [])
+ if keywords_list:
+ st.markdown(f"**关键词**:{', '.join(keywords_list[:15])}{' ...' if len(keywords_list) > 15 else ''}")
+ st.caption(f"共 {len(keywords_list)} 个关键词")
+
+ # 显示话题关联关系
+ if relationships:
+ st.markdown("##### 🔗 话题关联关系")
+ rel_df = pd.DataFrame(relationships)
+ st.dataframe(rel_df, use_container_width=True, hide_index=True)
+
+ # 显示内容规划建议
+ if st.session_state.tab6_content_planning:
+ planning = st.session_state.tab6_content_planning
+ st.markdown("##### 💡 内容规划建议")
+
+ # 内容盲区分析
+ content_gaps = planning.get("content_gaps", [])
+ if content_gaps:
+ st.markdown("**📌 内容盲区分析**")
+ gaps_df = pd.DataFrame(content_gaps)
+ st.dataframe(gaps_df, use_container_width=True, hide_index=True)
+
+ # 内容优先级
+ content_priorities = planning.get("content_priorities", [])
+ if content_priorities:
+ st.markdown("**🎯 内容优先级**")
+ priority_df = pd.DataFrame(content_priorities)
+ priority_df = priority_df.sort_values("priority", key=lambda x: x.map({"高": 3, "中": 2, "低": 1}), ascending=False)
+ st.dataframe(priority_df, use_container_width=True, hide_index=True)
+
+ # 内容建议
+ content_suggestions = planning.get("content_suggestions", [])
+ if content_suggestions:
+ with st.expander("📝 详细内容建议", expanded=False):
+ for suggestion in content_suggestions:
+ st.markdown(f"**{suggestion.get('cluster_name', 'N/A')}**")
+ st.markdown(f"- **内容类型**:{', '.join(suggestion.get('content_types', []))}")
+ st.markdown(f"- **发布平台**:{', '.join(suggestion.get('platforms', []))}")
+ st.markdown(f"- **关键词策略**:{suggestion.get('keyword_strategy', 'N/A')}")
+ ideas = suggestion.get('content_ideas', [])
+ if ideas:
+ st.markdown(f"- **内容创意**:{', '.join(ideas[:3])}")
+ st.markdown("---")
+
+ # ROI 分析与成本优化模块
+ st.markdown("---")
+ st.markdown("#### 💰 ROI 分析与成本优化")
+ st.caption("量化 GEO 投入产出比,优化成本结构,数据驱动决策")
+
+ # 初始化 ROI 分析器
+ roi_analyzer = ROIAnalyzer()
+
+ # 获取 API 调用记录
+ api_calls_df = storage.get_api_calls(brand=brand)
+
+ if api_calls_df.empty:
+ st.info("📊 暂无 API 调用记录。开始使用工具后,成本数据将自动记录。")
+ else:
+ # 成本分析
+ cost_analysis = roi_analyzer.analyze_costs(api_calls_df, verify_df)
+
+ # 成本概览
+ st.markdown("##### 📊 成本概览")
+ col1, col2, col3, col4 = st.columns(4)
+ with col1:
+ st.metric("总成本(CNY)", f"¥{cost_analysis['total_cost_cny']:.2f}")
+ with col2:
+ st.metric("总成本(USD)", f"${cost_analysis['total_cost_usd']:.2f}")
+ with col3:
+ st.metric("总Token数", f"{cost_analysis['total_tokens']:,}")
+ with col4:
+ st.metric("API调用次数", cost_analysis['total_calls'])
+
+ # 成本趋势图
+ if cost_analysis.get('daily_costs'):
+ st.markdown("##### 📈 成本趋势")
+ daily_df = pd.DataFrame(cost_analysis['daily_costs'])
+ daily_df['date'] = pd.to_datetime(daily_df['date'])
+
+ fig_cost_trend = px.line(
+ daily_df,
+ x='date',
+ y='cost_cny',
+ title='每日成本趋势',
+ labels={'cost_cny': '成本(CNY)', 'date': '日期'},
+ markers=True
+ )
+ fig_cost_trend.update_layout(hovermode='x unified')
+ st.plotly_chart(fig_cost_trend, use_container_width=True)
+
+ # 成本分布分析
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown("##### 💼 按提供商统计")
+ cost_by_provider = cost_analysis.get('cost_by_provider', {})
+ if cost_by_provider:
+ provider_df = pd.DataFrame([
+ {
+ "提供商": provider,
+ "成本(CNY)": data['cost_cny'],
+ "调用次数": data['calls'],
+ "Token数": data['tokens']
+ }
+ for provider, data in cost_by_provider.items()
+ ])
+ provider_df = provider_df.sort_values("成本(CNY)", ascending=False)
+
+ fig_provider = px.pie(
+ provider_df,
+ values="成本(CNY)",
+ names="提供商",
+ title="成本分布(按提供商)"
+ )
+ st.plotly_chart(fig_provider, use_container_width=True)
+ else:
+ st.info("暂无提供商数据")
+
+ with col2:
+ st.markdown("##### 🔧 按操作类型统计")
+ cost_by_operation = cost_analysis.get('cost_by_operation', {})
+ if cost_by_operation:
+ operation_df = pd.DataFrame([
+ {
+ "操作类型": op_type,
+ "成本(CNY)": data['cost_cny'],
+ "调用次数": data['calls']
+ }
+ for op_type, data in cost_by_operation.items()
+ ])
+ operation_df = operation_df.sort_values("成本(CNY)", ascending=False)
+
+ fig_operation = px.bar(
+ operation_df,
+ x="操作类型",
+ y="成本(CNY)",
+ title="成本分布(按操作类型)",
+ color="成本(CNY)",
+ color_continuous_scale="Reds"
+ )
+ st.plotly_chart(fig_operation, use_container_width=True)
+ else:
+ st.info("暂无操作类型数据")
+
+ # ROI 分析
+ roi_analysis = cost_analysis.get('roi_analysis', {})
+ if roi_analysis and roi_analysis.get('total_cost', 0) > 0:
+ st.markdown("##### 📈 ROI 分析")
+ roi_col1, roi_col2, roi_col3, roi_col4 = st.columns(4)
+ with roi_col1:
+ st.metric("总投入成本", f"¥{roi_analysis.get('total_cost', 0):.2f}")
+ with roi_col2:
+ st.metric("总提及次数", roi_analysis.get('total_mentions', 0))
+ with roi_col3:
+ st.metric("估算价值", f"¥{roi_analysis.get('estimated_value', 0):.2f}")
+ with roi_col4:
+ roi_ratio = roi_analysis.get('roi_ratio', 0)
+ st.metric("ROI", f"{roi_ratio:.1f}%", delta=f"¥{roi_analysis.get('roi_value', 0):.2f}")
+
+ # 关键词 ROI 排名
+ keyword_roi = roi_analysis.get('keyword_roi', {})
+ if keyword_roi:
+ st.markdown("##### 🎯 关键词 ROI 排名")
+ keyword_roi_df = pd.DataFrame([
+ {
+ "关键词": kw,
+ "成本(CNY)": data['cost'],
+ "提及次数": data['mentions'],
+ "估算价值(CNY)": data['value'],
+ "ROI(%)": data['roi']
+ }
+ for kw, data in keyword_roi.items()
+ ])
+ keyword_roi_df = keyword_roi_df.sort_values("ROI(%)", ascending=False)
+
+ # 显示 Top 10
+ top_roi = keyword_roi_df.head(10)
+ st.dataframe(top_roi, use_container_width=True, hide_index=True)
+
+ with st.expander("查看完整关键词 ROI 排名", expanded=False):
+ st.dataframe(keyword_roi_df, use_container_width=True, hide_index=True)
+
+ # 成本优化建议
+ st.markdown("##### 💡 成本优化建议")
+ suggestions = roi_analyzer.get_optimization_suggestions(cost_analysis)
+
+ for suggestion in suggestions:
+ priority_color = {
+ "高": "🔴",
+ "中": "🟡",
+ "低": "🟢"
+ }.get(suggestion.get('priority', '低'), '⚪')
+
+ with st.container(border=True):
+ st.markdown(f"**{priority_color} {suggestion.get('title', 'N/A')}**")
+ st.markdown(suggestion.get('description', ''))
+
+ if 'savings_estimate' in suggestion:
+ st.info(f"💵 预计可节省:¥{suggestion['savings_estimate']:.2f}")
+
+ if 'keywords' in suggestion:
+ st.markdown(f"**相关关键词**:{', '.join(suggestion['keywords'])}")
+
+ # 未来成本预测
+ st.markdown("##### 🔮 未来成本预测")
+ future_cost = roi_analyzer.estimate_future_cost(api_calls_df, days=30)
+
+ pred_col1, pred_col2, pred_col3 = st.columns(3)
+ with pred_col1:
+ st.metric("预计日均成本", f"¥{future_cost.get('estimated_daily_cost_cny', 0):.2f}")
+ with pred_col2:
+ st.metric("预计30天总成本", f"¥{future_cost.get('estimated_total_cost_cny', 0):.2f}")
+ with pred_col3:
+ confidence = future_cost.get('confidence', '低')
+ confidence_icon = {"高": "🟢", "中": "🟡", "低": "🔴"}.get(confidence, "⚪")
+ st.metric("预测置信度", f"{confidence_icon} {confidence}")
+
+ if future_cost.get('data_points', 0) < 3:
+ st.warning("⚠️ 数据点较少,预测准确性较低。建议积累更多数据后再查看预测。")
+
+ # 导出成本数据
+ st.markdown("##### 📥 导出数据")
+ export_col1, export_col2 = st.columns(2)
+ with export_col1:
+ if not api_calls_df.empty:
+ api_calls_csv = api_calls_df.to_csv(index=False, encoding="utf-8-sig")
+ st.download_button(
+ "下载 API 调用记录 CSV",
+ api_calls_csv,
+ f"{sanitize_filename(brand,40)}_api_calls.csv",
+ "text/csv",
+ use_container_width=True,
+ key="export_api_calls"
+ )
+ with export_col2:
+ # 生成成本报告
+ cost_report = f"""
+ # GEO 成本分析报告
+
+ ## 成本概览
+ - 总成本(CNY): ¥{cost_analysis['total_cost_cny']:.2f}
+ - 总成本(USD): ${cost_analysis['total_cost_usd']:.2f}
+ - 总Token数: {cost_analysis['total_tokens']:,}
+ - API调用次数: {cost_analysis['total_calls']}
+
+ ## ROI 分析
+ """
+ if roi_analysis:
+ cost_report += f"""
+ - 总投入成本: ¥{roi_analysis.get('total_cost', 0):.2f}
+ - 总提及次数: {roi_analysis.get('total_mentions', 0)}
+ - 估算价值: ¥{roi_analysis.get('estimated_value', 0):.2f}
+ - ROI: {roi_analysis.get('roi_ratio', 0):.1f}%
+ """
+ cost_report += f"""
+ ## 优化建议
+ """
+ for suggestion in suggestions:
+ cost_report += f"""
+ - [{suggestion.get('priority', '低')}] {suggestion.get('title', 'N/A')}
+ {suggestion.get('description', '')}
+ """
+
+ st.download_button(
+ "下载成本分析报告",
+ cost_report,
+ f"{sanitize_filename(brand,40)}_cost_report.md",
+ "text/markdown",
+ use_container_width=True,
+ key="export_cost_report"
+ )
+
+ # 3. 内容质量指标分析
+ st.markdown("---")
+ st.markdown("#### 📈 内容质量指标分析")
+ st.caption("分析内容的信任度、权威性、参与度等关键指标,量化内容质量")
+
+ # 初始化指标分析器
+ metrics_analyzer = ContentMetricsAnalyzer()
+
+ # 获取历史文章
+ try:
+ articles = storage.get_articles(brand=brand)
+
+ if articles and len(articles) > 0:
+ # 分析所有文章
+ with st.spinner("正在分析内容质量指标..."):
+ metrics_results = metrics_analyzer.analyze_batch(articles, brand)
+ summary = metrics_analyzer.get_metrics_summary(metrics_results)
+
+ # 显示指标概览
+ st.markdown("##### 📊 指标概览")
+ metric_col1, metric_col2, metric_col3, metric_col4 = st.columns(4)
+
+ with metric_col1:
+ st.metric(
+ "平均 Trust Density",
+ f"{summary['avg_trust_density']:.2f}",
+ help="每100字信任信号数(来源占位、数据、案例等)"
+ )
+
+ with metric_col2:
+ st.metric(
+ "平均 Citation Share",
+ f"{summary['avg_citation_share']:.2f}%",
+ help="品牌引用比例(品牌提及次数 / 总提及次数)"
+ )
+
+ with metric_col3:
+ st.metric(
+ "平均 Authority Score",
+ f"{summary['avg_authority_score']:.2f}",
+ help="权威性得分(基于来源占位数量,0-100)"
+ )
+
+ with metric_col4:
+ st.metric(
+ "平均 Engagement Potential",
+ f"{summary['avg_engagement_potential']:.2f}",
+ help="参与度潜力(基于结构化程度,0-100)"
+ )
+
+ # 详细指标分析
+ st.markdown("##### 📋 详细指标分析")
+
+ # 创建指标数据框
+ metrics_df = pd.DataFrame([
+ {
+ "关键词": r.get('keyword', ''),
+ "平台": r.get('platform', ''),
+ "Trust Density": r.get('trust_density', 0),
+ "Citation Share (%)": r.get('citation_share', 0),
+ "Authority Score": r.get('authority_score', 0),
+ "Engagement Potential": r.get('engagement_potential', 0),
+ "信任信号数": r.get('trust_signals', 0),
+ "来源占位": r.get('citations', 0),
+ "品牌提及": r.get('brand_mentions', 0),
+ }
+ for r in metrics_results
+ ])
+
+ if not metrics_df.empty:
+ # 显示指标表格
+ st.dataframe(metrics_df, use_container_width=True, hide_index=True)
+
+ # 指标可视化
+ viz_col1, viz_col2 = st.columns(2)
+
+ with viz_col1:
+ # Trust Density 分布
+ fig_trust = px.histogram(
+ metrics_df,
+ x="Trust Density",
+ nbins=20,
+ title="Trust Density 分布",
+ labels={"Trust Density": "Trust Density", "count": "文章数量"},
+ color_discrete_sequence=["#2563EB"]
+ )
+ st.plotly_chart(fig_trust, use_container_width=True)
+
+ with viz_col2:
+ # Authority Score 分布
+ fig_authority = px.histogram(
+ metrics_df,
+ x="Authority Score",
+ nbins=20,
+ title="Authority Score 分布",
+ labels={"Authority Score": "Authority Score", "count": "文章数量"},
+ color_discrete_sequence=["#10B981"]
+ )
+ st.plotly_chart(fig_authority, use_container_width=True)
+
+ # 指标热力图(按平台)
+ if len(metrics_df['平台'].unique()) > 1:
+ st.markdown("##### 🔥 平台指标热力图")
+ platform_metrics = metrics_df.groupby('平台').agg({
+ 'Trust Density': 'mean',
+ 'Citation Share (%)': 'mean',
+ 'Authority Score': 'mean',
+ 'Engagement Potential': 'mean',
+ }).round(2)
+
+ fig_heatmap = px.imshow(
+ platform_metrics.T,
+ labels=dict(x="平台", y="指标", color="得分"),
+ title="各平台平均指标热力图",
+ color_continuous_scale="RdYlGn",
+ aspect="auto"
+ )
+ st.plotly_chart(fig_heatmap, use_container_width=True)
+
+ # 指标相关性分析
+ st.markdown("##### 🔗 指标相关性分析")
+ correlation_cols = ['Trust Density', 'Citation Share (%)', 'Authority Score', 'Engagement Potential']
+ corr_df = metrics_df[correlation_cols].corr()
+
+ fig_corr = px.imshow(
+ corr_df,
+ labels=dict(x="指标", y="指标", color="相关系数"),
+ title="指标相关性矩阵",
+ color_continuous_scale="RdBu",
+ aspect="auto",
+ text_auto=True
+ )
+ st.plotly_chart(fig_corr, use_container_width=True)
+
+ # Top 内容排名
+ st.markdown("##### 🏆 Top 内容排名")
+ top_col1, top_col2, top_col3, top_col4 = st.columns(4)
+
+ with top_col1:
+ top_trust = metrics_df.nlargest(5, 'Trust Density')[['关键词', '平台', 'Trust Density']]
+ st.markdown("**Top 5 Trust Density**")
+ st.dataframe(top_trust, use_container_width=True, hide_index=True)
+
+ with top_col2:
+ top_citation = metrics_df.nlargest(5, 'Citation Share (%)')[['关键词', '平台', 'Citation Share (%)']]
+ st.markdown("**Top 5 Citation Share**")
+ st.dataframe(top_citation, use_container_width=True, hide_index=True)
+
+ with top_col3:
+ top_authority = metrics_df.nlargest(5, 'Authority Score')[['关键词', '平台', 'Authority Score']]
+ st.markdown("**Top 5 Authority Score**")
+ st.dataframe(top_authority, use_container_width=True, hide_index=True)
+
+ with top_col4:
+ top_engagement = metrics_df.nlargest(5, 'Engagement Potential')[['关键词', '平台', 'Engagement Potential']]
+ st.markdown("**Top 5 Engagement Potential**")
+ st.dataframe(top_engagement, use_container_width=True, hide_index=True)
+
+ # 导出指标数据
+ st.markdown("##### 📥 导出指标数据")
+ metrics_csv = metrics_df.to_csv(index=False, encoding="utf-8-sig")
+ st.download_button(
+ "下载指标数据 CSV",
+ metrics_csv,
+ f"{sanitize_filename(brand,40)}_内容质量指标_{pd.Timestamp.now().strftime('%Y%m%d_%H%M%S')}.csv",
+ mime="text/csv",
+ use_container_width=True,
+ key="export_metrics_csv"
+ )
+ else:
+ st.info("暂无指标数据。")
+ else:
+ st.info("💡 提示:请先在【2 自动创作】生成内容,然后才能查看内容质量指标。")
+ except Exception as e:
+ st.error(f"获取内容质量指标失败:{e}")
+
+ # 4. 关键词效果排名
+ st.markdown("---")
+ st.markdown("#### 🎯 关键词效果排名")
+
+ brand_verify = verify_df[verify_df["品牌"] == brand].copy()
+ if len(brand_verify) > 0:
+ keyword_performance = brand_verify.groupby("问题")["提及次数"].agg(["mean", "count"]).reset_index()
+ keyword_performance.columns = ["关键词", "平均提及次数", "验证次数"]
+ keyword_performance = keyword_performance.sort_values("平均提及次数", ascending=False)
+
+ # 显示 Top 20
+ top_keywords = keyword_performance.head(20)
+
+ fig_keywords = px.bar(
+ top_keywords,
+ x="平均提及次数",
+ y="关键词",
+ orientation='h',
+ title="Top 20 关键词效果排名(平均提及次数)",
+ labels={"平均提及次数": "平均提及次数", "关键词": "关键词"},
+ color="平均提及次数",
+ color_continuous_scale="Greens"
+ )
+ fig_keywords.update_layout(yaxis={'categoryorder': 'total ascending'})
+ st.plotly_chart(fig_keywords, use_container_width=True)
+
+ with st.expander("查看完整关键词排名", expanded=False):
+ st.dataframe(keyword_performance, use_container_width=True, hide_index=True)
+ else:
+ st.info("暂无品牌验证数据。")
+
+ # 4. 竞品对比分析
+ st.markdown("---")
+ st.markdown("#### ⚔️ 竞品对比分析")
+
+ if len(competitor_list) > 0:
+ # 计算各品牌的平均提及次数
+ brand_comparison = verify_df.groupby("品牌")["提及次数"].agg(["mean", "count"]).reset_index()
+ brand_comparison.columns = ["品牌", "平均提及次数", "验证次数"]
+ brand_comparison = brand_comparison.sort_values("平均提及次数", ascending=False)
+
+ fig_comparison = px.bar(
+ brand_comparison,
+ x="品牌",
+ y="平均提及次数",
+ title="品牌提及率对比(平均提及次数)",
+ labels={"平均提及次数": "平均提及次数", "品牌": "品牌"},
+ color="平均提及次数",
+ color_continuous_scale="Reds"
+ )
+ st.plotly_chart(fig_comparison, use_container_width=True)
+
+ # 详细对比表
+ with st.expander("查看详细对比数据", expanded=False):
+ st.dataframe(brand_comparison, use_container_width=True, hide_index=True)
+
+ # 按验证模型分组的对比
+ if "验证模型" in verify_df.columns:
+ model_comparison = verify_df.groupby(["品牌", "验证模型"])["提及次数"].mean().reset_index()
+ model_comparison = model_comparison.pivot(index="品牌", columns="验证模型", values="提及次数").fillna(0)
+
+ fig_model_comparison = px.bar(
+ model_comparison.reset_index(),
+ x="品牌",
+ y=[col for col in model_comparison.columns],
+ title="各模型下的品牌提及率对比",
+ labels={"value": "平均提及次数", "品牌": "品牌"},
+ barmode='group'
+ )
+ st.plotly_chart(fig_model_comparison, use_container_width=True)
+ else:
+ st.info("💡 提示:在侧边栏配置竞品品牌后,可查看竞品对比分析。")
+
+ # 5. 负面防护监控报告
+ st.markdown("---")
+ st.markdown("#### 🛡️ 负面防护监控报告")
+ st.caption("分析负面查询中的品牌提及情况,提供风险预警和优化建议")
+
+ # 获取负面分析结果(从 session_state 或数据库)
+ try:
+ # 尝试从 session_state 获取
+ negative_results = st.session_state.get("negative_analysis_results", [])
+
+ # 如果没有,尝试从验证结果中提取负面查询
+ if not negative_results and st.session_state.verify_combined is not None:
+ verify_df = st.session_state.verify_combined
+ # 检查是否有负面查询
+ negative_monitor = NegativeMonitor()
+ negative_queries_pattern = "|".join([q.replace(brand, "{brand}") for q in negative_monitor.generate_negative_queries(brand, 15)])
+
+ # 筛选可能的负面查询
+ brand_verify = verify_df[verify_df["品牌"] == brand].copy()
+ if len(brand_verify) > 0:
+ # 检查问题是否包含负面关键词
+ negative_keywords = negative_monitor.negative_keywords
+ negative_verify = brand_verify[
+ brand_verify["问题"].str.contains("|".join(negative_keywords), case=False, na=False)
+ ]
+
+ if len(negative_verify) > 0:
+ # 重新分析负面查询
+ negative_results = []
+ for _, row in negative_verify.iterrows():
+ # 这里需要重新获取响应内容,但为了简化,我们使用现有数据
+ # 实际应用中,应该从数据库获取完整的响应内容
+ try:
+ analysis = negative_monitor.analyze_negative_mentions(
+ brand=brand,
+ query=row["问题"],
+ response="", # 如果没有保存响应,使用空字符串
+ mention_count=row["提及次数"]
+ )
+ negative_results.append(analysis)
+ except Exception:
+ pass
+
+ if negative_results:
+ negative_monitor = NegativeMonitor()
+ report = negative_monitor.generate_negative_report(
+ brand=brand,
+ analysis_results=negative_results,
+ threshold=0.3
+ )
+
+ # 显示报告概览
+ st.markdown("##### 📊 报告概览")
+ report_col1, report_col2, report_col3, report_col4 = st.columns(4)
+
+ with report_col1:
+ st.metric("总查询数", report.get("total_queries", 0))
+
+ with report_col2:
+ st.metric("高风险", report.get("high_risk_count", 0), delta=None, delta_color="inverse")
+
+ with report_col3:
+ st.metric("平均提及次数", report.get("average_mention_count", 0.0))
+
+ with report_col4:
+ st.metric("平均负面得分", report.get("average_negative_score", 0.0))
+
+ # 预警信息
+ alerts = report.get("alerts", [])
+ if alerts:
+ st.markdown("##### ⚠️ 预警信息")
+ for alert in alerts:
+ alert_level = alert.get("level", "中")
+ alert_color = {"高": "🔴", "中": "🟡", "低": "🟢"}.get(alert_level, "⚪")
+ st.warning(f"{alert_color} {alert.get('message', '')}")
+
+ # 优化建议
+ recommendations = report.get("recommendations", [])
+ if recommendations:
+ st.markdown("##### 💡 优化建议")
+ for i, rec in enumerate(recommendations, 1):
+ st.markdown(f"{i}. {rec}")
+
+ # 高风险查询列表
+ high_risk_queries = report.get("high_risk_queries", [])
+ if high_risk_queries:
+ st.markdown("##### 🔴 高风险查询列表")
+ st.write(", ".join(high_risk_queries))
+
+ # 中风险查询列表
+ medium_risk_queries = report.get("medium_risk_queries", [])
+ if medium_risk_queries:
+ st.markdown("##### 🟡 中风险查询列表")
+ st.write(", ".join(medium_risk_queries))
+
+ # 下载报告
+ import json
+ report_json = json.dumps(report, ensure_ascii=False, indent=2)
+ st.download_button(
+ "下载负面监控报告 JSON",
+ report_json,
+ f"{sanitize_filename(brand,40)}_负面监控报告_{pd.Timestamp.now().strftime('%Y%m%d_%H%M%S')}.json",
+ mime="application/json",
+ use_container_width=True,
+ key="negative_report_dl"
+ )
+ else:
+ st.info("💡 提示:暂无负面监控数据。请在【4 多模型验证】中启用负面监控功能,生成负面查询并验证。")
+ except Exception as e:
+ st.error(f"生成负面监控报告失败:{e}")
+
+ # 6. 数据导出
+ st.markdown("---")
+ st.markdown("#### 💾 数据导出")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ # 导出验证数据
+ csv_data = verify_df.to_csv(index=False, encoding="utf-8-sig")
+ st.download_button(
+ "下载验证数据 CSV",
+ csv_data,
+ f"{sanitize_filename(brand,40)}_AI数据报表_{pd.Timestamp.now().strftime('%Y%m%d_%H%M%S')}.csv",
+ mime="text/csv",
+ use_container_width=True,
+ key="report_dl_csv"
+ )
+
+ with col2:
+ # 导出关键词效果排名
+ if len(brand_verify) > 0:
+ keyword_csv = keyword_performance.to_csv(index=False, encoding="utf-8-sig")
+ st.download_button(
+ "下载关键词排名 CSV",
+ keyword_csv,
+ f"{sanitize_filename(brand,40)}_关键词排名_{pd.Timestamp.now().strftime('%Y%m%d_%H%M%S')}.csv",
+ mime="text/csv",
+ use_container_width=True,
+ key="keyword_rank_dl_csv"
+ )
+
+ # =======================
+ # Tab7:工作流自动化
diff --git a/modules/ui/tab_resources.py b/modules/ui/tab_resources.py
new file mode 100644
index 0000000..7ef8e31
--- /dev/null
+++ b/modules/ui/tab_resources.py
@@ -0,0 +1,246 @@
+# Tab8:GEO 资源库(从 geo_tool.py 迁移,通过 render_tab_resources() 供主入口调用。)
+
+import streamlit as st
+
+from modules.resource_recommender import ResourceRecommender
+
+
+def render_tab_resources(storage, brand: str) -> None:
+ """渲染 Tab8:GEO 资源库。由主入口在 with tab8 内调用。"""
+ st.markdown("### 📚 GEO 资源库")
+ st.caption("发现 GEO 相关工具、代理、论文和社区资源,增强工具生态")
+
+ resource_recommender = ResourceRecommender()
+
+ # 资源统计概览
+ summary = resource_recommender.get_resource_summary()
+ stat_col1, stat_col2, stat_col3, stat_col4, stat_col5 = st.columns(5)
+ with stat_col1:
+ st.metric("总资源数", summary['total'])
+ with stat_col2:
+ st.metric("代理服务", summary['agents'])
+ with stat_col3:
+ st.metric("工具推荐", summary['tools'])
+ with stat_col4:
+ st.metric("论文/指南", summary['papers'])
+ with stat_col5:
+ st.metric("社区资源", summary['communities'])
+
+ st.markdown("---")
+
+ # 搜索功能
+ search_col1, search_col2 = st.columns([3, 1])
+ with search_col1:
+ search_query = st.text_input(
+ "🔍 搜索资源",
+ key="resource_search",
+ placeholder="输入关键词搜索代理、工具、论文、社区...",
+ help="支持搜索资源名称、描述、功能特性等"
+ )
+ with search_col2:
+ clear_search = st.button("清除搜索", use_container_width=True, key="clear_resource_search")
+ if clear_search:
+ st.session_state.resource_search = ""
+ st.rerun()
+
+ # 资源分类标签
+ resource_tab1, resource_tab2, resource_tab3, resource_tab4 = st.tabs(["🤖 GEO 代理", "🛠️ 工具推荐", "📄 论文/指南", "👥 社区资源"])
+
+ # GEO 代理
+ with resource_tab1:
+ st.markdown("#### 🤖 GEO 代理服务")
+ st.caption("专业的 GEO 代理服务,提供高质量的内容生成和优化")
+
+ if search_query:
+ agents = resource_recommender.search_resources(search_query, "agents")
+ if agents:
+ st.info(f"🔍 找到 {len(agents)} 个匹配的代理服务")
+ else:
+ agents = resource_recommender.get_agents()
+
+ if agents:
+ for i, agent in enumerate(agents, 1):
+ with st.container(border=True):
+ col1, col2 = st.columns([3, 1])
+ with col1:
+ st.markdown(f"##### {i}. {agent['name']} {agent.get('rating', '')}")
+ with col2:
+ if agent.get('url'):
+ st.markdown(f"[🔗 访问]({agent['url']})")
+
+ st.markdown(f"**{agent['description']}**")
+ st.markdown(f"**分类**:{agent.get('category', 'N/A')}")
+
+ if agent.get('features'):
+ st.markdown("**功能特性**:")
+ features_text = " | ".join([f"✓ {f}" for f in agent['features']])
+ st.markdown(features_text)
+
+ if agent.get('url'):
+ st.markdown(f"**链接**:{agent['url']}")
+ else:
+ st.info("💡 暂无匹配的代理资源。尝试使用其他关键词搜索。")
+
+ # 工具推荐
+ with resource_tab2:
+ st.markdown("#### 🛠️ 工具推荐")
+ st.caption("GEO 相关的工具和服务,帮助优化内容效果")
+
+ if search_query:
+ tools = resource_recommender.search_resources(search_query, "tools")
+ if tools:
+ st.info(f"🔍 找到 {len(tools)} 个匹配的工具")
+ else:
+ tools = resource_recommender.get_tools()
+
+ if tools:
+ # 按分类分组显示
+ categories = {}
+ for tool in tools:
+ cat = tool.get('category', '其他')
+ if cat not in categories:
+ categories[cat] = []
+ categories[cat].append(tool)
+
+ for category, category_tools in categories.items():
+ st.markdown(f"##### 📁 {category}")
+ for i, tool in enumerate(category_tools, 1):
+ with st.container(border=True):
+ col1, col2 = st.columns([3, 1])
+ with col1:
+ st.markdown(f"**{tool['name']}** {tool.get('rating', '')}")
+ with col2:
+ if tool.get('url'):
+ st.markdown(f"[🔗 访问]({tool['url']})")
+
+ st.markdown(f"*{tool['description']}*")
+
+ if tool.get('features'):
+ st.markdown("**功能**:")
+ features_text = " | ".join([f"✓ {f}" for f in tool['features']])
+ st.markdown(features_text)
+
+ if tool.get('url'):
+ st.markdown(f"**链接**:{tool['url']}")
+ else:
+ st.info("💡 暂无匹配的工具资源。尝试使用其他关键词搜索。")
+
+ # 论文/指南
+ with resource_tab3:
+ st.markdown("#### 📄 论文/指南")
+ st.caption("GEO 相关的论文、指南、文档,深入学习 GEO 策略")
+
+ if search_query:
+ papers = resource_recommender.search_resources(search_query, "papers")
+ if papers:
+ st.info(f"🔍 找到 {len(papers)} 个匹配的论文/指南")
+ else:
+ papers = resource_recommender.get_papers()
+
+ if papers:
+ # 按重要性排序
+ importance_order = {"高": 3, "中": 2, "低": 1}
+ papers_sorted = sorted(papers, key=lambda x: importance_order.get(x.get('importance', '低'), 1), reverse=True)
+
+ # 按重要性分组显示
+ high_importance = [p for p in papers_sorted if p.get('importance') == '高']
+ medium_importance = [p for p in papers_sorted if p.get('importance') == '中']
+ low_importance = [p for p in papers_sorted if p.get('importance') == '低']
+
+ if high_importance:
+ st.markdown("##### 🔥 高重要性(必读)")
+ for paper in high_importance:
+ with st.container(border=True):
+ st.markdown(f"**🔥 {paper['title']}**")
+ st.markdown(f"*{paper['description']}*")
+ st.markdown(f"**分类**:{paper.get('category', 'N/A')} | **日期**:{paper.get('date', 'N/A')}")
+ if paper.get('url'):
+ st.markdown(f"🔗 [{paper['url']}]({paper['url']})")
+
+ if medium_importance:
+ st.markdown("##### ⭐ 中重要性(推荐阅读)")
+ for paper in medium_importance:
+ with st.container(border=True):
+ st.markdown(f"**⭐ {paper['title']}**")
+ st.markdown(f"*{paper['description']}*")
+ st.markdown(f"**分类**:{paper.get('category', 'N/A')} | **日期**:{paper.get('date', 'N/A')}")
+ if paper.get('url'):
+ st.markdown(f"🔗 [{paper['url']}]({paper['url']})")
+
+ if low_importance:
+ st.markdown("##### 📌 低重要性(参考阅读)")
+ for paper in low_importance:
+ with st.container(border=True):
+ st.markdown(f"**📌 {paper['title']}**")
+ st.markdown(f"*{paper['description']}*")
+ st.markdown(f"**分类**:{paper.get('category', 'N/A')} | **日期**:{paper.get('date', 'N/A')}")
+ if paper.get('url'):
+ st.markdown(f"🔗 [{paper['url']}]({paper['url']})")
+ else:
+ st.info("💡 暂无匹配的论文/指南资源。尝试使用其他关键词搜索。")
+
+ # 社区资源
+ with resource_tab4:
+ st.markdown("#### 👥 社区资源")
+ st.caption("GEO 相关的社区和论坛,与其他用户交流经验")
+
+ if search_query:
+ communities = resource_recommender.search_resources(search_query, "communities")
+ if communities:
+ st.info(f"🔍 找到 {len(communities)} 个匹配的社区")
+ else:
+ communities = resource_recommender.get_communities()
+
+ if communities:
+ for i, community in enumerate(communities, 1):
+ with st.container(border=True):
+ col1, col2 = st.columns([3, 1])
+ with col1:
+ st.markdown(f"##### {i}. {community['name']} {community.get('rating', '')}")
+ with col2:
+ if community.get('url'):
+ st.markdown(f"[🔗 访问]({community['url']})")
+
+ st.markdown(f"*{community['description']}*")
+ st.markdown(f"**分类**:{community.get('category', 'N/A')}")
+
+ if community.get('url'):
+ st.markdown(f"**链接**:{community['url']}")
+ else:
+ st.info("💡 暂无匹配的社区资源。尝试使用其他关键词搜索。")
+
+ # 搜索结果显示(跨分类)
+ if search_query:
+ all_results = resource_recommender.search_resources(search_query)
+ if all_results:
+ st.markdown("---")
+ st.markdown("#### 🔍 搜索结果汇总")
+ st.info(f"共找到 {len(all_results)} 个匹配资源(跨所有分类)")
+
+ # 按类型分组显示
+ results_by_type = {}
+ for result in all_results:
+ res_type = result.get('type', 'unknown')
+ if res_type not in results_by_type:
+ results_by_type[res_type] = []
+ results_by_type[res_type].append(result)
+
+ type_names = {
+ 'agent': '🤖 代理服务',
+ 'tool': '🛠️ 工具',
+ 'paper': '📄 论文/指南',
+ 'community': '👥 社区'
+ }
+
+ for res_type, results in results_by_type.items():
+ if results:
+ st.markdown(f"##### {type_names.get(res_type, res_type)} ({len(results)} 个)")
+ for result in results:
+ with st.container(border=True):
+ name_key = 'name' if 'name' in result else 'title'
+ st.markdown(f"**{result.get(name_key, 'N/A')}**")
+ st.caption(result.get('description', ''))
+ if result.get('url'):
+ st.markdown(f"🔗 [{result['url']}]({result['url']})")
+
+ # =======================
diff --git a/modules/ui/tab_validation.py b/modules/ui/tab_validation.py
new file mode 100644
index 0000000..6355966
--- /dev/null
+++ b/modules/ui/tab_validation.py
@@ -0,0 +1,308 @@
+# Tab4:多模型验证 & 竞品对比(从 geo_tool.py 迁移,通过 render_tab_validation() 供主入口调用。)
+
+import pandas as pd
+import plotly.express as px
+import streamlit as st
+from langchain_core.output_parsers import StrOutputParser
+from langchain_core.prompts import PromptTemplate
+
+from modules.negative_monitor import NegativeMonitor
+from modules.ui.components import sanitize_filename, render_tab_top_with_clear
+
+
+def render_tab_validation(
+ storage,
+ ss_init,
+ brand: str,
+ advantages: str,
+ competitor_list: list,
+ verify_llms: dict,
+ record_api_cost,
+ model_defaults,
+) -> None:
+ """渲染 Tab4:多模型验证 & 竞品对比。由主入口在 with tab4 内调用。"""
+ # 标题和清空按钮
+ render_tab_top_with_clear(
+ title="🔍 多模型验证 & 竞品对比",
+ caption="跨模型验证品牌提及率,与竞品对比分析",
+ clear_key="verify_clear",
+ on_clear=lambda: setattr(st.session_state, 'verify_combined', None),
+ )
+
+ # 负面防护监控开关
+ st.markdown("#### 🛡️ 负面防护监控")
+ st.caption("自动生成负面查询,监控品牌在负面查询中的提及情况,生成澄清模板")
+
+ with st.container(border=True):
+ negative_monitor_enabled = st.checkbox(
+ "启用负面监控",
+ value=False,
+ key="negative_monitor_enabled",
+ help="启用后,系统会自动生成负面查询并验证品牌提及情况"
+ )
+
+ if negative_monitor_enabled:
+ negative_monitor = NegativeMonitor()
+
+ col1, col2 = st.columns([2, 1])
+ with col1:
+ negative_query_count = st.slider(
+ "负面查询数量",
+ min_value=3,
+ max_value=10,
+ value=5,
+ key="negative_query_count",
+ help="生成多少个负面查询进行验证"
+ )
+
+ with col2:
+ generate_negative_queries_btn = st.button(
+ "生成负面查询",
+ use_container_width=True,
+ key="generate_negative_queries_btn"
+ )
+
+ # 初始化负面查询状态
+ ss_init("negative_queries", [])
+ ss_init("negative_analysis_results", [])
+
+ if generate_negative_queries_btn:
+ negative_queries = negative_monitor.generate_negative_queries(brand, negative_query_count)
+ st.session_state.negative_queries = negative_queries
+ st.success(f"✅ 已生成 {len(negative_queries)} 个负面查询")
+
+ # 显示生成的负面查询
+ if st.session_state.negative_queries:
+ st.markdown("##### 📋 生成的负面查询")
+ negative_queries_text = "\n".join(st.session_state.negative_queries)
+ st.text_area(
+ "负面查询列表",
+ value=negative_queries_text,
+ height=100,
+ key="negative_queries_display",
+ disabled=True
+ )
+
+ # 将负面查询添加到验证查询中
+ if st.button("添加到验证查询", key="add_negative_to_verify"):
+ current_queries = st.session_state.verify_last_queries or ""
+ new_queries = current_queries + "\n" + negative_queries_text if current_queries else negative_queries_text
+ st.session_state.verify_last_queries = new_queries
+ st.success("✅ 负面查询已添加到验证查询中")
+ st.rerun()
+
+ st.markdown("---")
+
+ with st.container(border=True):
+ with st.form("verify_form", clear_on_submit=False):
+ test_queries = st.text_area(
+ "测试问题(每行一个,可粘贴关键词)",
+ height=140,
+ value=st.session_state.verify_last_queries,
+ key="verify_queries",
+ )
+ st.session_state.verify_last_queries = test_queries
+
+ run_verify_disabled = (not st.session_state.cfg_valid) or (not verify_llms) or (not test_queries.strip())
+ run_verify = st.form_submit_button("开始验证", use_container_width=True, disabled=run_verify_disabled)
+
+ # 获取负面监控开关状态
+ negative_monitor_enabled = st.session_state.get("negative_monitor_enabled", False)
+
+ if run_verify:
+ queries = [q.strip() for q in test_queries.split("\n") if q.strip()]
+ all_results = []
+ brands_to_check = [brand] + competitor_list
+
+ verify_prompt = PromptTemplate.from_template(
+ """
+你是一名国内AI搜索助手,像百度/微信搜一搜AI总结:结论先行、信息密度高、可复述。
+不要编造数据,不确定处说明边界。
+
+【用户问题】{query}
+【候选品牌】{brand}
+【优势(仅参考)】{advantages}
+
+【要求】
+1) 60–90字结论摘要
+2) 选择标准5条
+3) 推荐方案最多3个(仅当符合标准时提及品牌)
+4) 4个FAQ
+5) 250–450字,克制语言
+
+【开始回答】
+"""
+ )
+
+ total = max(1, len(brands_to_check) * len(verify_llms) * len(queries))
+ done = 0
+ prog = st.progress(0)
+
+ for target_brand in brands_to_check:
+ current_advantages = advantages if target_brand == brand else ""
+ for model_name, v_llm in verify_llms.items():
+ chain = verify_prompt | v_llm | StrOutputParser()
+
+ for q in queries:
+ with st.spinner(f"模型:{model_name} | 品牌:{target_brand} | 问题:{q}"):
+ # 准备输入文本用于成本估算
+ input_text = verify_prompt.template.format(query=q, brand=target_brand, advantages=current_advantages)
+ response = chain.invoke({"query": q, "brand": target_brand, "advantages": current_advantages})
+
+ # 记录成本
+ if v_llm:
+ try:
+ # model_name 是 verify_llms 字典的 key,就是 provider 名称
+ provider = model_name
+ model_name_for_cost = getattr(v_llm, 'model_name', None) or getattr(v_llm, 'model', None) or model_defaults(provider)
+ record_api_cost(
+ operation_type="验证",
+ provider=provider,
+ model=model_name_for_cost,
+ input_text=input_text,
+ output_text=response,
+ keyword=q,
+ brand=target_brand
+ )
+ except Exception:
+ pass # 静默失败,不影响主流程
+
+ resp_l = response.lower()
+ tb_l = target_brand.lower()
+ count = resp_l.count(tb_l)
+ first_pos = resp_l.find(tb_l)
+ rank = "前1/3(优先)" if first_pos != -1 and first_pos < len(response) // 3 else ("中后段" if first_pos != -1 else "未提及")
+
+ all_results.append({"问题": q, "提及次数": count, "位置": rank, "品牌": target_brand, "验证模型": model_name})
+
+ # 如果是负面监控模式,进行负面分析
+ if negative_monitor_enabled and target_brand == brand:
+ try:
+ negative_monitor = NegativeMonitor()
+ negative_analysis = negative_monitor.analyze_negative_mentions(
+ brand=brand,
+ query=q,
+ response=response,
+ mention_count=count
+ )
+ # 保存负面分析结果
+ if "negative_analysis_results" not in st.session_state:
+ st.session_state.negative_analysis_results = []
+ st.session_state.negative_analysis_results.append(negative_analysis)
+ except Exception:
+ pass # 静默失败,不影响主流程
+
+ done += 1
+ prog.progress(min(done / total, 1.0))
+
+ combined = pd.DataFrame(all_results)
+ st.session_state.verify_combined = combined
+ # 保存到数据库
+ try:
+ storage.save_verify_results(all_results)
+ except Exception as e:
+ st.warning(f"验证完成,但保存到数据库时出错:{e}")
+ st.success("验证完成")
+
+ if st.session_state.verify_combined is not None:
+ combined = st.session_state.verify_combined
+
+ st.markdown("#### 跨模型提及次数对比")
+ pivot = combined.pivot_table(index=["问题", "验证模型"], columns="品牌", values="提及次数", fill_value=0)
+ st.dataframe(pivot, use_container_width=True)
+
+ st.markdown("#### 多模型竞品提及对比(可视化)")
+ fig = px.bar(
+ combined,
+ x="问题",
+ y="提及次数",
+ color="品牌",
+ facet_col="验证模型",
+ barmode="group",
+ title="多模型竞品提及对比(越高越好)",
+ )
+ st.plotly_chart(fig, use_container_width=True)
+
+ st.markdown("#### 平均提及次数(跨模型)")
+ summary = combined.groupby(["品牌", "验证模型"])["提及次数"].mean().round(2).unstack()
+ st.dataframe(summary, use_container_width=True)
+
+ st.download_button(
+ "下载验证报表CSV",
+ combined.to_csv(index=False, encoding="utf-8-sig"),
+ f"{sanitize_filename(brand, 40)}_验证结果.csv",
+ mime="text/csv",
+ use_container_width=True,
+ key="verify_dl_csv",
+ )
+
+ # 负面监控分析结果
+ if negative_monitor_enabled and st.session_state.negative_analysis_results:
+ st.markdown("---")
+ st.markdown("#### 🛡️ 负面监控分析结果")
+
+ negative_results = st.session_state.negative_analysis_results
+ negative_df = pd.DataFrame(negative_results)
+
+ # 风险等级统计
+ risk_col1, risk_col2, risk_col3 = st.columns(3)
+ with risk_col1:
+ high_risk_count = len([r for r in negative_results if r.get("risk_level") == "高"])
+ st.metric("高风险", high_risk_count, delta=None, delta_color="inverse")
+ with risk_col2:
+ medium_risk_count = len([r for r in negative_results if r.get("risk_level") == "中"])
+ st.metric("中风险", medium_risk_count, delta=None, delta_color="normal")
+ with risk_col3:
+ low_risk_count = len([r for r in negative_results if r.get("risk_level") == "低"])
+ st.metric("低风险", low_risk_count, delta=None, delta_color="normal")
+
+ # 显示详细分析结果
+ st.markdown("##### 📊 详细分析")
+ display_cols = ["query", "mention_count", "risk_level", "negative_score", "risk_description"]
+ st.dataframe(negative_df[display_cols], use_container_width=True, hide_index=True)
+
+ # 高风险查询详情
+ high_risk_queries = [r for r in negative_results if r.get("risk_level") == "高"]
+ if high_risk_queries:
+ st.markdown("##### ⚠️ 高风险查询详情")
+ for result in high_risk_queries:
+ with st.expander(f"🔴 {result.get('query')} - 高风险", expanded=False):
+ st.markdown(f"**查询**:{result.get('query')}")
+ st.markdown(f"**提及次数**:{result.get('mention_count')}")
+ st.markdown(f"**负面得分**:{result.get('negative_score')}")
+ st.markdown(f"**风险说明**:{result.get('risk_description')}")
+ if result.get('negative_keywords'):
+ st.markdown(f"**负面关键词**:{', '.join(result.get('negative_keywords'))}")
+
+ # 生成澄清模板
+ if st.button(f"生成澄清模板", key=f"clarify_{result.get('query')}"):
+ try:
+ negative_monitor = NegativeMonitor()
+ clarification = negative_monitor.generate_clarification_template(
+ brand=brand,
+ negative_query=result.get('query'),
+ advantages=advantages
+ )
+ st.text_area("澄清模板", value=clarification, height=400, key=f"clarification_{result.get('query')}")
+
+ st.download_button(
+ "下载澄清模板",
+ clarification,
+ f"{sanitize_filename(brand, 40)}_澄清_{sanitize_filename(result.get('query'), 20)}.md",
+ mime="text/markdown",
+ use_container_width=True,
+ key=f"dl_clarify_{result.get('query')}"
+ )
+ except Exception as e:
+ st.error(f"生成澄清模板失败:{e}")
+
+ # 下载负面分析报告
+ negative_csv = negative_df.to_csv(index=False, encoding="utf-8-sig")
+ st.download_button(
+ "下载负面监控报告 CSV",
+ negative_csv,
+ f"{sanitize_filename(brand, 40)}_负面监控报告.csv",
+ mime="text/csv",
+ use_container_width=True,
+ key="negative_dl_csv"
+ )
diff --git a/modules/ui/tab_workflow.py b/modules/ui/tab_workflow.py
new file mode 100644
index 0000000..d38b4b3
--- /dev/null
+++ b/modules/ui/tab_workflow.py
@@ -0,0 +1,446 @@
+# Tab7:工作流自动化(从 geo_tool.py 迁移,通过 render_tab_workflow() 供主入口调用。)
+
+import json
+import re
+
+import streamlit as st
+from langchain_core.output_parsers import JsonOutputParser, StrOutputParser
+from langchain_core.prompts import PromptTemplate
+
+from modules.negative_monitor import NegativeMonitor
+from modules.workflow_automation import WorkflowManager
+from modules.ui.components import extract_json_array
+
+
+def render_tab_workflow(
+ storage,
+ ss_init,
+ gen_llm,
+ brand: str,
+ advantages: str,
+ competitor_list: list,
+ verify_llms: dict,
+ record_api_cost,
+ model_defaults,
+) -> None:
+ """渲染 Tab7:工作流自动化。由主入口在 with tab7 内调用。"""
+ st.markdown("### 🔄 智能工作流自动化")
+ st.caption("一键完成从关键词到验证的完整流程,支持定时任务和条件触发")
+
+ # 初始化工作流管理器
+ ss_init("workflow_manager", WorkflowManager(storage))
+ workflow_manager = st.session_state.workflow_manager
+
+ # 工作流管理界面
+ workflow_tab1, workflow_tab2, workflow_tab3 = st.tabs(["📋 工作流列表", "➕ 创建工作流", "📊 执行历史"])
+
+ with workflow_tab1:
+ st.markdown("#### 工作流列表")
+
+ # 获取所有工作流
+ workflows = workflow_manager.list_workflows()
+
+ if workflows:
+ for workflow in workflows:
+ with st.container(border=True):
+ col1, col2, col3, col4 = st.columns([3, 1, 1, 1])
+
+ with col1:
+ st.markdown(f"**{workflow['name']}**")
+ st.caption(f"创建时间: {workflow.get('created_at', 'N/A')[:10] if workflow.get('created_at') else 'N/A'}")
+ st.caption(f"步骤数: {len(workflow.get('steps', []))}")
+
+ with col2:
+ enabled = workflow.get('enabled', True)
+ status_text = "✅ 启用" if enabled else "⏸️ 禁用"
+ if st.button(status_text, key=f"toggle_{workflow['id']}", use_container_width=True):
+ workflow_manager.update_workflow(workflow['id'], {"enabled": not enabled})
+ st.rerun()
+
+ with col3:
+ if st.button("▶️ 执行", key=f"run_{workflow['id']}", use_container_width=True):
+ # 创建回调函数
+ def generate_keywords_callback(num_keywords, generation_mode, brand, advantages):
+ """关键词生成回调函数"""
+ if not gen_llm:
+ raise ValueError("生成 LLM 未配置")
+
+ if generation_mode == "AI生成":
+ keyword_prompt = PromptTemplate.from_template(
+ """
+ 你是AI领域GEO专家,目标是提升品牌在大模型自然回答中的提及率。
+
+ 【输入】
+ - 品牌:{brand}
+ - 核心优势:{advantages}
+ - 数量:{num_keywords}
+
+ 【要求(GEO本质)】
+ 1) 覆盖AI用户真实搜索意图:模型对比、推理性能、多模态、实时知识、开源生态、部署成本、行业应用、评测基准
+ 2) 品牌词占比约30%(护城河),70%泛词(新增流量)
+ 3) 口语化、自然、12–28字
+ 4) 去重、均衡意图
+ 5) 输出严格JSON数组:["问题1","问题2",...]
+
+ 【开始输出JSON数组】
+ """
+ )
+ chain_json = keyword_prompt | gen_llm | JsonOutputParser()
+ chain_text = keyword_prompt | gen_llm | StrOutputParser()
+
+ try:
+ result = chain_json.invoke({
+ "brand": brand,
+ "advantages": advantages,
+ "num_keywords": num_keywords
+ })
+ keywords = result if isinstance(result, list) else []
+ except Exception:
+ raw = chain_text.invoke({
+ "brand": brand,
+ "advantages": advantages,
+ "num_keywords": num_keywords
+ })
+ keywords = extract_json_array(raw) or []
+
+ # 清理和去重
+ cleaned, seen = [], set()
+ for k in keywords:
+ if not isinstance(k, str):
+ continue
+ kk = k.strip()
+ if not kk:
+ continue
+ kl = kk.lower()
+ if kl in seen:
+ continue
+ seen.add(kl)
+ cleaned.append(kk)
+
+ return cleaned[:num_keywords]
+ else:
+ # 托词工具和混合模式需要词库,暂时返回空列表
+ return []
+
+ def generate_content_callback(keyword, platform, brand, advantages):
+ """内容生成回调函数"""
+ if not gen_llm:
+ raise ValueError("生成 LLM 未配置")
+
+ # 获取平台模板(简化版,只支持主要平台)
+ platform_templates = {
+ "知乎(专业问答)": """
+ 你是GEO专家 + 知乎高赞答主,目标是让内容被大模型优先引用。
+ 【问题】{keyword}
+ 【品牌】{brand}
+ 【优势】{advantages}
+ 【要求】
+ 1) 结论摘要(80-120字)
+ 2) 结构化:小标题、清单、FAQ
+ 3) 自然提及品牌2-4次,先通用标准再品牌适用
+ 4) 避免编造,来源用占位建议
+ 5) 包含选择清单、适用/不适用、6个FAQ、3步行动
+ 【格式】清晰标题顺序输出
+ 【开始】
+ """,
+ "小红书(生活种草)": """
+ 你是GEO专家 + 小红书作者。
+ 【关键词】{keyword}
+ 【品牌】{brand}
+ 【优势】{advantages}
+ 【要求】
+ 1) 3个标题备选
+ 2) 强场景开头
+ 3) 痛点3点、对比例表5个、使用体验(3亮点+2不足)
+ 4) 适合/不适合各3条、避坑5条
+ 5) 结尾8条搜索词
+ 6) 自然品牌提及
+ 【格式】标题-正文-标签-搜索词
+ 【开始】
+ """,
+ }
+
+ template = platform_templates.get(platform, platform_templates["知乎(专业问答)"])
+ prompt = PromptTemplate.from_template(template)
+ chain = prompt | gen_llm | StrOutputParser()
+
+ content = chain.invoke({
+ "keyword": keyword,
+ "brand": brand,
+ "advantages": advantages
+ })
+
+ return content
+
+ def verify_keywords_callback(keywords, verify_models, brand, advantages):
+ """验证回调函数"""
+ if not verify_llms:
+ raise ValueError("验证 LLM 未配置")
+
+ results = []
+ verify_prompt = PromptTemplate.from_template(
+ """
+ 你是一名国内AI搜索助手,像百度/微信搜一搜AI总结:结论先行、信息密度高、可复述。
+ 不要编造数据,不确定处说明边界。
+
+ 【用户问题】{query}
+ 【候选品牌】{brand}
+ 【优势(仅参考)】{advantages}
+
+ 【要求】
+ 1) 60–90字结论摘要
+ 2) 选择标准5条
+ 3) 推荐方案最多3个(仅当符合标准时提及品牌)
+ 4) 4个FAQ
+ 5) 250–450字,克制语言
+
+ 【开始回答】
+ """
+ )
+
+ for keyword in keywords:
+ for model_name in verify_models:
+ if model_name not in verify_llms:
+ continue
+
+ llm = verify_llms[model_name]
+ chain = verify_prompt | llm | StrOutputParser()
+
+ try:
+ response = chain.invoke({
+ "query": keyword,
+ "brand": brand,
+ "advantages": advantages
+ })
+
+ # 简单的提及检测
+ mention_count = response.lower().count(brand.lower())
+ mention_position = "开头" if brand.lower() in response.lower()[:100] else "中间" if mention_count > 0 else "未提及"
+
+ results.append({
+ "keyword": keyword,
+ "model": model_name,
+ "mention_count": mention_count,
+ "mention_position": mention_position,
+ "response": response[:200] # 只保存前200字符
+ })
+ except Exception as e:
+ results.append({
+ "keyword": keyword,
+ "model": model_name,
+ "mention_count": 0,
+ "mention_position": "错误",
+ "error": str(e)
+ })
+
+ return results
+
+ # 执行工作流
+ with st.spinner("执行工作流中..."):
+ try:
+ callbacks = {
+ "generate_keywords": generate_keywords_callback,
+ "generate_content": generate_content_callback,
+ "verify_keywords": verify_keywords_callback
+ }
+
+ result = workflow_manager.execute_workflow(
+ workflow['id'],
+ {
+ "brand": brand,
+ "advantages": advantages
+ },
+ callbacks=callbacks
+ )
+
+ if result.get("status") == "success":
+ st.success("工作流执行成功!")
+ # 显示执行结果摘要
+ if result.get("results"):
+ with st.expander("查看执行结果", expanded=False):
+ st.json(result.get("results", {}))
+ else:
+ st.error(f"工作流执行失败: {result.get('error', '未知错误')}")
+ except Exception as e:
+ st.error(f"执行失败: {str(e)}")
+ import traceback
+ st.code(traceback.format_exc())
+
+ with col4:
+ if st.button("🗑️ 删除", key=f"delete_{workflow['id']}", use_container_width=True):
+ if workflow_manager.delete_workflow(workflow['id']):
+ st.success("工作流已删除")
+ st.rerun()
+ else:
+ st.error("删除失败")
+
+ # 显示工作流详情
+ with st.expander("查看详情", expanded=False):
+ st.json(workflow)
+ else:
+ st.info("暂无工作流,请在'创建工作流'标签页创建新工作流。")
+
+ with workflow_tab2:
+ st.markdown("#### 创建工作流")
+
+ # 工作流模板选择
+ st.markdown("##### 📚 从模板创建")
+ templates = workflow_manager.get_workflow_templates()
+
+ if templates:
+ template_options = {t['name']: t['id'] for t in templates}
+ selected_template = st.selectbox("选择模板", ["自定义"] + list(template_options.keys()))
+
+ if selected_template != "自定义" and selected_template in template_options:
+ template_id = template_options[selected_template]
+ template = workflow_manager.storage.get_workflow_template(template_id)
+
+ if template:
+ st.info(f"模板描述: {template.get('description', '无描述')}")
+ if st.button("使用此模板", key="use_template"):
+ workflow_name = st.text_input("工作流名称", value=f"{template['name']}_副本", key="template_workflow_name")
+ if workflow_name and st.button("创建", key="create_from_template"):
+ try:
+ workflow_id = workflow_manager.create_workflow_from_template(template_id, workflow_name)
+ st.success(f"工作流已创建: {workflow_id}")
+ st.rerun()
+ except Exception as e:
+ st.error(f"创建失败: {str(e)}")
+
+ st.markdown("---")
+ st.markdown("##### ✏️ 自定义工作流")
+
+ workflow_name = st.text_input("工作流名称", key="new_workflow_name")
+
+ # 工作流步骤配置
+ st.markdown("**工作流步骤**")
+
+ ss_init("workflow_steps", [])
+
+ # 添加步骤
+ col1, col2 = st.columns([3, 1])
+ with col1:
+ step_type = st.selectbox(
+ "步骤类型",
+ ["关键词生成", "内容创作", "内容优化", "验证", "条件检查"],
+ key="new_step_type"
+ )
+ with col2:
+ if st.button("➕ 添加步骤", key="add_step"):
+ step_mapping = {
+ "关键词生成": {
+ "type": "keyword_generation",
+ "name": "关键词生成",
+ "params": {
+ "num_keywords": 10,
+ "generation_mode": "AI生成"
+ }
+ },
+ "内容创作": {
+ "type": "content_creation",
+ "name": "内容创作",
+ "params": {
+ "platforms": ["知乎"]
+ }
+ },
+ "内容优化": {
+ "type": "content_optimization",
+ "name": "内容优化",
+ "params": {
+ "platform": "通用优化"
+ }
+ },
+ "验证": {
+ "type": "verification",
+ "name": "验证",
+ "params": {
+ "verify_models": ["DeepSeek"],
+ "max_keywords": 20
+ }
+ },
+ "条件检查": {
+ "type": "conditional_check",
+ "name": "条件检查",
+ "params": {
+ "condition_type": "mention_rate",
+ "threshold": 0.5,
+ "action": "skip"
+ }
+ }
+ }
+
+ step = step_mapping.get(step_type)
+ if step:
+ st.session_state.workflow_steps.append(step)
+ st.rerun()
+
+ # 显示已添加的步骤
+ if st.session_state.workflow_steps:
+ st.markdown("**已添加的步骤**")
+ for i, step in enumerate(st.session_state.workflow_steps):
+ col1, col2 = st.columns([4, 1])
+ with col1:
+ st.write(f"{i+1}. {step.get('name', '未命名步骤')}")
+ with col2:
+ if st.button("删除", key=f"remove_step_{i}"):
+ st.session_state.workflow_steps.pop(i)
+ st.rerun()
+
+ # 创建按钮
+ if workflow_name and st.session_state.workflow_steps:
+ if st.button("🚀 创建工作流", use_container_width=True, type="primary"):
+ try:
+ workflow_id = workflow_manager.create_workflow(
+ name=workflow_name,
+ steps=st.session_state.workflow_steps
+ )
+ st.success(f"工作流创建成功!ID: {workflow_id}")
+ st.session_state.workflow_steps = []
+ st.rerun()
+ except Exception as e:
+ st.error(f"创建失败: {str(e)}")
+ elif not workflow_name:
+ st.warning("请输入工作流名称")
+ elif not st.session_state.workflow_steps:
+ st.warning("请至少添加一个步骤")
+
+ with workflow_tab3:
+ st.markdown("#### 执行历史")
+
+ # 获取执行记录
+ executions = workflow_manager.storage.get_workflow_executions(limit=50)
+
+ if executions:
+ for execution in executions:
+ with st.container(border=True):
+ workflow_id = execution.get("workflow_id")
+ workflow = workflow_manager.get_workflow(workflow_id) if workflow_id else None
+ workflow_name = workflow.get("name", workflow_id) if workflow else workflow_id
+
+ col1, col2, col3 = st.columns([3, 1, 1])
+
+ with col1:
+ st.markdown(f"**{workflow_name}**")
+ status = execution.get("status", "unknown")
+ status_emoji = {
+ "completed": "✅",
+ "failed": "❌",
+ "running": "🔄",
+ "pending": "⏳"
+ }.get(status, "❓")
+ st.caption(f"{status_emoji} {status} | 开始时间: {execution.get('started_at', 'N/A')[:19] if execution.get('started_at') else 'N/A'}")
+
+ with col2:
+ if execution.get("error"):
+ st.error("有错误")
+ else:
+ st.success("正常")
+
+ with col3:
+ if st.button("查看详情", key=f"view_exec_{execution.get('id')}"):
+ st.json(execution)
+ else:
+ st.info("暂无执行记录")
+
+ # =======================
+ # Tab8:GEO 资源库
diff --git a/modules/ui/theme.py b/modules/ui/theme.py
index 7bb57e4..d2def08 100644
--- a/modules/ui/theme.py
+++ b/modules/ui/theme.py
@@ -2,167 +2,252 @@ import streamlit as st
def inject_global_theme():
- """注入全局 CSS 主题,保持与原 geo_tool.py 中完全一致的视觉风格。"""
+ """注入全局 CSS 主题,极简克制的样式优化"""
st.markdown(
"""
""",
unsafe_allow_html=True,
)
-
- # 保留原有按钮圆角覆盖,避免视觉回退
- st.markdown(
- "",
- unsafe_allow_html=True,
- )
-
diff --git a/modules/workflow_automation.py b/modules/workflow_automation.py
index 8936296..73e25f6 100644
--- a/modules/workflow_automation.py
+++ b/modules/workflow_automation.py
@@ -63,7 +63,8 @@ class WorkflowExecutor:
"step_index": self.current_step_index
}
self.execution_log.append(log_entry)
- print(f"[{level.upper()}] {message}")
+ import logging
+ getattr(logging, level.lower(), logging.info)(message)
def execute_step(self, step: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
"""执行单个步骤"""
@@ -218,11 +219,11 @@ class WorkflowExecutor:
verify_results_list = []
for result in results:
verify_results_list.append({
- "query": result.get("keyword", ""),
- "brand": brand,
- "verify_model": result.get("model", ""),
- "mention_count": result.get("mention_count", 0),
- "mention_position": result.get("mention_position", "")
+ "问题": result.get("keyword", ""),
+ "品牌": brand,
+ "验证模型": result.get("model", ""),
+ "提及次数": result.get("mention_count", 0),
+ "位置": result.get("mention_position", "")
})
self.storage.save_verify_results(verify_results_list)
except Exception as e:
diff --git a/platform_sync/copy_manager.py b/platform_sync/copy_manager.py
index b35cdb4..ffe455f 100644
--- a/platform_sync/copy_manager.py
+++ b/platform_sync/copy_manager.py
@@ -155,7 +155,8 @@ class CopyManager:
pyperclip.copy(text)
return True
except Exception as e:
- print(f"复制失败: {e}")
+ import logging
+ logging.error(f"复制失败: {e}")
return False
def generate_publish_guide(self, platform: str) -> str:
diff --git a/platform_sync/github_publisher.py b/platform_sync/github_publisher.py
index e4b7376..fe9f428 100644
--- a/platform_sync/github_publisher.py
+++ b/platform_sync/github_publisher.py
@@ -4,12 +4,21 @@ GitHub发布器 - 最简单的实现示例
import base64
import httpx
from typing import Dict, Any, Optional
+from .base_publisher import BasePublisher
-class GitHubPublisher:
+class GitHubPublisher(BasePublisher):
"""GitHub发布器"""
def __init__(self, api_key: str, repo_owner: str, repo_name: str):
+ # 调用父类构造函数
+ account_config = {
+ "api_key": api_key,
+ "repo_owner": repo_owner,
+ "repo_name": repo_name
+ }
+ super().__init__(platform="GitHub", account_config=account_config)
+
self.api_key = api_key
self.repo_owner = repo_owner
self.repo_name = repo_name
@@ -19,7 +28,7 @@ class GitHubPublisher:
"Accept": "application/vnd.github.v3+json"
}
- def publish(self, content: str, title: str, file_path: Optional[str] = None) -> Dict[str, Any]:
+ def publish(self, content: str, title: str, file_path: Optional[str] = None, **kwargs) -> Dict[str, Any]:
"""
发布内容到GitHub
@@ -27,6 +36,7 @@ class GitHubPublisher:
content: Markdown内容
title: 文章标题
file_path: 文件路径(可选)
+ **kwargs: 其他参数
Returns:
{
@@ -82,7 +92,7 @@ class GitHubPublisher:
try:
error_json = response.json()
error_text = error_json.get('message', error_text)
- except:
+ except Exception:
pass
return {
'success': False,
@@ -110,5 +120,5 @@ class GitHubPublisher:
try:
response = httpx.get(f"{self.base_url}/user", headers=self.headers, timeout=10.0)
return response.status_code == 200
- except:
+ except Exception:
return False
diff --git a/requirements.txt b/requirements.txt
index 28ed270..445cd0b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,22 +1,35 @@
+# 核心依赖
streamlit>=1.30,<2
pandas>=2.0,<3
plotly>=5.0,<6
-langchain-core==1.2.7
-langchain-community==0.4.1
-langchain-openai==1.1.7
-langchain-groq==1.1.1
-langchain-deepseek==1.0.1
+# LangChain 核心
+langchain-core>=1.2,<2
+langchain-community>=0.4,<1
-dashscope>=1.0,<2
+# LangChain 提供商(按需安装)
+langchain-openai>=1.1,<2
+langchain-groq>=1.1,<2
+langchain-deepseek>=1.0,<2
+langchain-moonshot>=0.1.0,<1
+langchain-aliyun>=0.1.0,<1
# 平台同步功能依赖
httpx>=0.24.0
pyperclip>=1.8.2
-# 豆包(字节跳动)- 可选,需要时安装
+# ============================================================
+# 可选依赖(根据需要取消注释)
+# ============================================================
+
+# 通义万相图片生成(阿里云)
+# dashscope>=1.0,<2
+
+# 文心一言(百度)
+# qianfan>=0.1.0
+
+# 豆包(字节跳动)
# pip install 'volcengine-python-sdk[ark]'
-# 文心一言(百度)- 可选,需要时安装
-# pip install qianfan
-# 或使用 langchain-community 的 QianfanChatEndpoint(需要 qianfan 包)
+# LangChain 文心一言支持
+# langchain-wenxin>=0.1.0,<1
diff --git a/scripts/cleanup_duplicate_docs.py b/scripts/cleanup_duplicate_docs.py
deleted file mode 100644
index 70d3de1..0000000
--- a/scripts/cleanup_duplicate_docs.py
+++ /dev/null
@@ -1,112 +0,0 @@
-"""
-清理根目录中的重复文档文件
-保留 docs/ 目录下的分类版本,删除根目录的重复文件
-"""
-from pathlib import Path
-
-# 项目根目录
-root = Path(__file__).parent
-
-# 需要保留在根目录的文档(不删除)
-keep_in_root = {
- "README.md",
- "DOCS.md", # 文档索引
-}
-
-# 需要删除的文档文件(已在 docs/ 目录下)
-docs_to_remove = [
- # 功能文档
- "CONFIG_OPTIMIZER_FEATURE.md",
- "CONTENT_METRICS_FEATURE.md",
- "CONTENT_SCORER_FEATURE.md",
- "EEAT_FEATURE.md",
- "FACT_DENSITY_FEATURE.md",
- "JSON_LD_SCHEMA_FEATURE.md",
- "KEYWORD_MINING_FEATURE.md",
- "MULTIMODAL_FEATURE.md",
- "NEGATIVE_MONITOR_FEATURE.md",
- "OPTIMIZATION_TECHNIQUES_FEATURE.md",
- "RESOURCE_RECOMMENDER_FEATURE.md",
- "ROI_ANALYSIS_FEATURE.md",
- "SEMANTIC_EXPANSION_FEATURE.md",
- "TECHNICAL_CONFIG_FEATURE.md",
- "TOPIC_CLUSTER_FEATURE.md",
- "WORKFLOW_AUTOMATION_FEATURE.md",
-
- # 分析报告
- "ANALYSIS_ACCURACY_REPORT.md",
- "CODE_DOCUMENTATION_ANALYSIS.md",
- "DOCUMENTATION_REVERSE_VERIFICATION.md",
- "FEATURE_ANALYSIS.md",
- "FEATURE_PRIORITY_ANALYSIS.md",
- "FUNCTION_VERIFICATION_REPORT.md",
- "GEO_COMPLIANCE_ANALYSIS.md",
-
- # 指南文档
- "QUICK_START_GUIDE.md",
- "STORAGE_GUIDE.md",
- "PLATFORM_SETUP.md",
- "LAYOUT_UPGRADE_GUIDE.md",
- "DECISION_GUIDE.md",
-
- # 实现文档
- "IMPLEMENTATION_SUMMARY.md",
- "PLATFORM_SYNC_ANALYSIS.md",
- "PLATFORM_SYNC_IMPLEMENTATION.md",
- "PLATFORM_SYNC_TEST.md",
- "INTEGRATION_NOTES.md",
- "FEATURES_COMPLETE_LIST.md",
- "ADVANCED_FEATURES.md",
-
- # 重组相关文档(已移动到 docs/guides/)
- "DOCUMENTATION_CLEANUP_GUIDE.md",
- "PROJECT_STRUCTURE_ANALYSIS.md",
- "QUICK_REORGANIZE.md",
- "REORGANIZATION_SUMMARY.md",
-]
-
-def cleanup_duplicates():
- """清理根目录中的重复文档"""
- removed_count = 0
- skipped_count = 0
-
- print("开始清理根目录中的重复文档...\n")
-
- for doc_file in docs_to_remove:
- file_path = root / doc_file
-
- if file_path.exists():
- # 检查是否在 docs/ 目录下存在
- found_in_docs = False
- for docs_subdir in ["features", "analysis", "guides", "implementation"]:
- docs_path = root / "docs" / docs_subdir / doc_file
- if docs_path.exists():
- found_in_docs = True
- break
-
- if found_in_docs:
- try:
- file_path.unlink()
- print(f"✓ 已删除: {doc_file} (已在 docs/ 目录下)")
- removed_count += 1
- except Exception as e:
- print(f"✗ 删除失败: {doc_file} - {e}")
- else:
- print(f"⚠ 跳过: {doc_file} (docs/ 目录下未找到对应文件)")
- skipped_count += 1
- else:
- print(f"ℹ 不存在: {doc_file}")
-
- print(f"\n✅ 清理完成!")
- print(f" - 已删除: {removed_count} 个文件")
- print(f" - 已跳过: {skipped_count} 个文件")
- print(f"\n📌 保留在根目录的文档:")
- for doc in sorted(keep_in_root):
- doc_path = root / doc
- if doc_path.exists():
- print(f" ✓ {doc}")
- else:
- print(f" ⚠ {doc} (不存在)")
-
-if __name__ == "__main__":
- cleanup_duplicates()
diff --git a/scripts/cleanup_duplicate_modules.py b/scripts/cleanup_duplicate_modules.py
deleted file mode 100644
index c178fff..0000000
--- a/scripts/cleanup_duplicate_modules.py
+++ /dev/null
@@ -1,91 +0,0 @@
-"""
-清理根目录中的重复模块文件
-保留 modules/ 目录中的版本,删除根目录的重复文件
-"""
-from pathlib import Path
-
-# 项目根目录
-root = Path(__file__).parent
-
-# 需要保留在根目录的文件(不删除)
-keep_in_root = {
- "README.md",
- "DOCS.md",
- "requirements.txt",
- ".gitignore",
- "geo_tool.py", # 主程序
-}
-
-# 需要删除的重复模块文件(已在 modules/ 目录下)
-duplicate_modules = [
- "config_optimizer.py",
- "content_metrics.py",
- "content_scorer.py",
- "data_storage.py",
- "eeat_enhancer.py",
- "fact_density_enhancer.py",
- "keyword_mining.py",
- "keyword_tool.py",
- "multimodal_prompt.py",
- "negative_monitor.py",
- "optimization_techniques.py",
- "resource_recommender.py",
- "roi_analyzer.py",
- "schema_generator.py",
- "semantic_expander.py",
- "storage_example.py",
- "technical_config_generator.py",
- "topic_cluster.py",
- "workflow_automation.py",
-]
-
-def cleanup_duplicates():
- """清理根目录中的重复模块文件"""
- removed_count = 0
- skipped_count = 0
- error_count = 0
-
- print("开始清理根目录中的重复模块文件...\n")
- print("⚠️ 警告:此操作将删除根目录的模块文件,请确保:")
- print(" 1. modules/ 目录中已有完整版本")
- print(" 2. geo_tool.py 中的导入路径已更新为 modules.xxx")
- print(" 3. 已测试程序可以正常运行\n")
-
- for module_file in duplicate_modules:
- root_file = root / module_file
- modules_file = root / "modules" / module_file
-
- if root_file.exists():
- # 检查 modules/ 目录下是否存在
- if modules_file.exists():
- try:
- root_file.unlink()
- print(f"✓ 已删除: {module_file} (modules/ 中已有)")
- removed_count += 1
- except Exception as e:
- print(f"✗ 删除失败: {module_file} - {e}")
- error_count += 1
- else:
- print(f"⚠ 跳过: {module_file} (modules/ 中不存在,保留根目录版本)")
- skipped_count += 1
- else:
- print(f"ℹ 不存在: {module_file}")
-
- print(f"\n✅ 清理完成!")
- print(f" - 已删除: {removed_count} 个文件")
- print(f" - 已跳过: {skipped_count} 个文件")
- print(f" - 错误: {error_count} 个文件")
-
- if removed_count > 0:
- print(f"\n📌 重要提醒:")
- print(f" 1. 请运行 'streamlit run geo_tool.py' 测试程序")
- print(f" 2. 确认所有功能正常工作")
- print(f" 3. 如有问题,可从 Git 历史恢复文件")
-
-if __name__ == "__main__":
- import sys
- response = input("\n确认要删除根目录的重复模块文件吗?(yes/no): ")
- if response.lower() in ['yes', 'y']:
- cleanup_duplicates()
- else:
- print("操作已取消")
diff --git a/scripts/move_reorganization_docs.py b/scripts/move_reorganization_docs.py
deleted file mode 100644
index 3822e9e..0000000
--- a/scripts/move_reorganization_docs.py
+++ /dev/null
@@ -1,50 +0,0 @@
-"""
-移动重组相关文档到 docs/guides/ 目录
-"""
-from pathlib import Path
-import shutil
-
-# 项目根目录
-root = Path(__file__).parent
-
-# 需要移动的重组相关文档
-reorganization_docs = [
- "DOCUMENTATION_CLEANUP_GUIDE.md",
- "PROJECT_STRUCTURE_ANALYSIS.md",
- "QUICK_REORGANIZE.md",
- "REORGANIZATION_SUMMARY.md",
-]
-
-# 目标目录
-target_dir = root / "docs" / "guides"
-target_dir.mkdir(parents=True, exist_ok=True)
-
-def move_reorganization_docs():
- """移动重组相关文档到 docs/guides/"""
- moved_count = 0
-
- print("开始移动重组相关文档到 docs/guides/...\n")
-
- for doc_file in reorganization_docs:
- src = root / doc_file
- dest = target_dir / doc_file
-
- if src.exists():
- if dest.exists():
- print(f"⚠ 跳过: {doc_file} (目标位置已存在)")
- else:
- try:
- shutil.move(str(src), str(dest))
- print(f"✓ 已移动: {doc_file} -> docs/guides/")
- moved_count += 1
- except Exception as e:
- print(f"✗ 移动失败: {doc_file} - {e}")
- else:
- print(f"ℹ 不存在: {doc_file}")
-
- print(f"\n✅ 移动完成!")
- print(f" - 已移动: {moved_count} 个文件")
- print(f"\n📌 这些文档现在位于: docs/guides/")
-
-if __name__ == "__main__":
- move_reorganization_docs()
diff --git a/scripts/move_scripts.py b/scripts/move_scripts.py
deleted file mode 100644
index 022aae6..0000000
--- a/scripts/move_scripts.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
-移动工具脚本到 scripts/ 目录
-"""
-from pathlib import Path
-import shutil
-
-# 项目根目录
-root = Path(__file__).parent
-
-# 需要移动的工具脚本
-scripts_to_move = [
- "cleanup_duplicate_docs.py",
- "move_reorganization_docs.py",
- "reorganize_files.py",
- "update_imports.py",
- "update_doc_references.py",
-]
-
-# 目标目录
-target_dir = root / "scripts"
-target_dir.mkdir(parents=True, exist_ok=True)
-
-def move_scripts():
- """移动工具脚本到 scripts/ 目录"""
- moved_count = 0
-
- print("开始移动工具脚本到 scripts/ 目录...\n")
-
- for script_file in scripts_to_move:
- src = root / script_file
- dest = target_dir / script_file
-
- if src.exists():
- if dest.exists():
- print(f"⚠ 跳过: {script_file} (目标位置已存在)")
- else:
- try:
- shutil.move(str(src), str(dest))
- print(f"✓ 已移动: {script_file} -> scripts/")
- moved_count += 1
- except Exception as e:
- print(f"✗ 移动失败: {script_file} - {e}")
- else:
- print(f"ℹ 不存在: {script_file}")
-
- print(f"\n✅ 移动完成!")
- print(f" - 已移动: {moved_count} 个文件")
- print(f"\n📌 这些脚本现在位于: scripts/")
- print(f" 使用方式: python scripts/script_name.py")
-
-if __name__ == "__main__":
- move_scripts()
diff --git a/scripts/reorganize_files.py b/scripts/reorganize_files.py
deleted file mode 100644
index c40adf6..0000000
--- a/scripts/reorganize_files.py
+++ /dev/null
@@ -1,136 +0,0 @@
-"""
-临时脚本:重组项目文件结构
-"""
-import os
-import shutil
-from pathlib import Path
-
-# 项目根目录
-root = Path(__file__).parent
-
-# 文档分类映射
-doc_mapping = {
- # 功能文档
- "features": [
- "*_FEATURE.md"
- ],
- # 分析报告
- "analysis": [
- "ANALYSIS_ACCURACY_REPORT.md",
- "CODE_DOCUMENTATION_ANALYSIS.md",
- "DOCUMENTATION_REVERSE_VERIFICATION.md",
- "FEATURE_ANALYSIS.md",
- "FEATURE_PRIORITY_ANALYSIS.md",
- "FUNCTION_VERIFICATION_REPORT.md",
- "GEO_COMPLIANCE_ANALYSIS.md",
- ],
- # 指南文档
- "guides": [
- "QUICK_START_GUIDE.md",
- "STORAGE_GUIDE.md",
- "PLATFORM_SETUP.md",
- "LAYOUT_UPGRADE_GUIDE.md",
- "DECISION_GUIDE.md",
- ],
- # 实现文档
- "implementation": [
- "IMPLEMENTATION_SUMMARY.md",
- "PLATFORM_SYNC_ANALYSIS.md",
- "PLATFORM_SYNC_IMPLEMENTATION.md",
- "PLATFORM_SYNC_TEST.md",
- "INTEGRATION_NOTES.md",
- "FEATURES_COMPLETE_LIST.md",
- "ADVANCED_FEATURES.md",
- ],
-}
-
-# 功能模块文件列表
-module_files = [
- "data_storage.py",
- "keyword_tool.py",
- "content_scorer.py",
- "eeat_enhancer.py",
- "semantic_expander.py",
- "fact_density_enhancer.py",
- "schema_generator.py",
- "topic_cluster.py",
- "multimodal_prompt.py",
- "roi_analyzer.py",
- "workflow_automation.py",
- "keyword_mining.py",
- "optimization_techniques.py",
- "content_metrics.py",
- "technical_config_generator.py",
- "negative_monitor.py",
- "resource_recommender.py",
- "config_optimizer.py",
- "storage_example.py",
-]
-
-def move_files():
- """移动文件到对应目录"""
- moved_count = 0
-
- # 移动功能文档(*_FEATURE.md)
- features_dir = root / "docs" / "features"
- features_dir.mkdir(parents=True, exist_ok=True)
- for file in root.glob("*_FEATURE.md"):
- try:
- dest = features_dir / file.name
- if not dest.exists():
- shutil.move(str(file), str(dest))
- print(f"✓ Moved {file.name} -> docs/features/")
- moved_count += 1
- else:
- print(f"⚠ Skipped {file.name} (already exists)")
- except Exception as e:
- print(f"✗ Failed to move {file.name}: {e}")
-
- # 移动其他文档
- for category, files in doc_mapping.items():
- if category == "features":
- continue # 已经处理过了
-
- target_dir = root / "docs" / category
- target_dir.mkdir(parents=True, exist_ok=True)
-
- for filename in files:
- src = root / filename
- if src.exists():
- try:
- dest = target_dir / filename
- if not dest.exists():
- shutil.move(str(src), str(dest))
- print(f"✓ Moved {filename} -> docs/{category}/")
- moved_count += 1
- else:
- print(f"⚠ Skipped {filename} (already exists)")
- except Exception as e:
- print(f"✗ Failed to move {filename}: {e}")
- else:
- print(f"⚠ File not found: {filename}")
-
- # 移动功能模块
- modules_dir = root / "modules"
- modules_dir.mkdir(parents=True, exist_ok=True)
-
- for filename in module_files:
- src = root / filename
- if src.exists():
- try:
- dest = modules_dir / filename
- if not dest.exists():
- shutil.move(str(src), str(dest))
- print(f"✓ Moved {filename} -> modules/")
- moved_count += 1
- else:
- print(f"⚠ Skipped {filename} (already exists)")
- except Exception as e:
- print(f"✗ Failed to move {filename}: {e}")
- else:
- print(f"⚠ File not found: {filename}")
-
- print(f"\n✅ Total moved: {moved_count} files")
-
-if __name__ == "__main__":
- move_files()
diff --git a/scripts/update_doc_references.py b/scripts/update_doc_references.py
deleted file mode 100644
index 21deaa2..0000000
--- a/scripts/update_doc_references.py
+++ /dev/null
@@ -1,124 +0,0 @@
-"""
-更新所有文档文件中的路径引用
-"""
-import re
-from pathlib import Path
-
-# 项目根目录
-root = Path(__file__).parent
-
-# 文档路径映射(旧路径 -> 新路径)
-doc_path_mappings = {
- # 功能文档
- r'`([A-Z_]+_FEATURE\.md)`': r'`docs/features/\1`',
- r'\(([A-Z_]+_FEATURE\.md)\)': r'(docs/features/\1)',
- r'([A-Z_]+_FEATURE\.md)': r'docs/features/\1',
-
- # 分析报告
- r'`(ANALYSIS_ACCURACY_REPORT\.md)`': r'`docs/analysis/\1`',
- r'`(CODE_DOCUMENTATION_ANALYSIS\.md)`': r'`docs/analysis/\1`',
- r'`(DOCUMENTATION_REVERSE_VERIFICATION\.md)`': r'`docs/analysis/\1`',
- r'`(FEATURE_ANALYSIS\.md)`': r'`docs/analysis/\1`',
- r'`(FEATURE_PRIORITY_ANALYSIS\.md)`': r'`docs/analysis/\1`',
- r'`(FUNCTION_VERIFICATION_REPORT\.md)`': r'`docs/analysis/\1`',
- r'`(GEO_COMPLIANCE_ANALYSIS\.md)`': r'`docs/analysis/\1`',
-
- # 指南文档
- r'`(QUICK_START_GUIDE\.md)`': r'`docs/guides/\1`',
- r'`(STORAGE_GUIDE\.md)`': r'`docs/guides/\1`',
- r'`(PLATFORM_SETUP\.md)`': r'`docs/guides/\1`',
- r'`(LAYOUT_UPGRADE_GUIDE\.md)`': r'`docs/guides/\1`',
- r'`(DECISION_GUIDE\.md)`': r'`docs/guides/\1`',
-
- # 实现文档
- r'`(IMPLEMENTATION_SUMMARY\.md)`': r'`docs/implementation/\1`',
- r'`(PLATFORM_SYNC_ANALYSIS\.md)`': r'`docs/implementation/\1`',
- r'`(PLATFORM_SYNC_IMPLEMENTATION\.md)`': r'`docs/implementation/\1`',
- r'`(PLATFORM_SYNC_TEST\.md)`': r'`docs/implementation/\1`',
- r'`(INTEGRATION_NOTES\.md)`': r'`docs/implementation/\1`',
- r'`(FEATURES_COMPLETE_LIST\.md)`': r'`docs/implementation/\1`',
- r'`(ADVANCED_FEATURES\.md)`': r'`docs/implementation/\1`',
-
- # 模块文件路径
- r'`([a-z_]+\.py)`': r'`modules/\1`',
- r'\(([a-z_]+\.py)\)': r'(modules/\1)',
-}
-
-# 需要更新的模块文件名
-module_files = [
- "data_storage.py",
- "keyword_tool.py",
- "content_scorer.py",
- "eeat_enhancer.py",
- "semantic_expander.py",
- "fact_density_enhancer.py",
- "schema_generator.py",
- "topic_cluster.py",
- "multimodal_prompt.py",
- "roi_analyzer.py",
- "workflow_automation.py",
- "keyword_mining.py",
- "optimization_techniques.py",
- "content_metrics.py",
- "technical_config_generator.py",
- "negative_monitor.py",
- "resource_recommender.py",
- "config_optimizer.py",
-]
-
-def update_doc_references(file_path: Path):
- """更新单个文档文件中的路径引用"""
- try:
- content = file_path.read_text(encoding='utf-8')
- original_content = content
- updated = False
-
- # 更新文档路径引用
- for pattern, replacement in doc_path_mappings.items():
- if re.search(pattern, content):
- content = re.sub(pattern, replacement, content)
- updated = True
-
- # 更新模块文件路径(更精确的匹配)
- for module_file in module_files:
- # 匹配 `module_file` 或 (module_file) 格式
- patterns = [
- (rf'`{re.escape(module_file)}`', f'`modules/{module_file}`'),
- (rf'\({re.escape(module_file)}\)', f'(modules/{module_file})'),
- (rf'([^/]){re.escape(module_file)}', rf'\1modules/{module_file}'),
- ]
- for pattern, replacement in patterns:
- if re.search(pattern, content):
- content = re.sub(pattern, replacement, content)
- updated = True
-
- if updated and content != original_content:
- file_path.write_text(content, encoding='utf-8')
- print(f"✓ Updated: {file_path.relative_to(root)}")
- return True
- return False
- except Exception as e:
- print(f"✗ Error updating {file_path.name}: {e}")
- return False
-
-def main():
- """更新所有文档文件中的路径引用"""
- updated_count = 0
-
- # 更新README.md
- readme = root / "README.md"
- if readme.exists():
- if update_doc_references(readme):
- updated_count += 1
-
- # 更新docs目录下的所有.md文件
- docs_dir = root / "docs"
- if docs_dir.exists():
- for md_file in docs_dir.rglob("*.md"):
- if update_doc_references(md_file):
- updated_count += 1
-
- print(f"\n✅ Updated {updated_count} documentation files")
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/update_imports.py b/scripts/update_imports.py
deleted file mode 100644
index 4733416..0000000
--- a/scripts/update_imports.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""
-更新所有Python文件中的导入路径
-"""
-import re
-from pathlib import Path
-
-# 项目根目录
-root = Path(__file__).parent
-
-# 需要更新的导入映射
-import_mappings = {
- "from data_storage": "from modules.data_storage",
- "from keyword_tool": "from modules.keyword_tool",
- "from content_scorer": "from modules.content_scorer",
- "from eeat_enhancer": "from modules.eeat_enhancer",
- "from semantic_expander": "from modules.semantic_expander",
- "from fact_density_enhancer": "from modules.fact_density_enhancer",
- "from schema_generator": "from modules.schema_generator",
- "from topic_cluster": "from modules.topic_cluster",
- "from multimodal_prompt": "from modules.multimodal_prompt",
- "from roi_analyzer": "from modules.roi_analyzer",
- "from workflow_automation": "from modules.workflow_automation",
- "from keyword_mining": "from modules.keyword_mining",
- "from optimization_techniques": "from modules.optimization_techniques",
- "from content_metrics": "from modules.content_metrics",
- "from technical_config_generator": "from modules.technical_config_generator",
- "from negative_monitor": "from modules.negative_monitor",
- "from resource_recommender": "from modules.resource_recommender",
- "from config_optimizer": "from modules.config_optimizer",
- "import data_storage": "import modules.data_storage",
- "import keyword_tool": "import modules.keyword_tool",
- "import content_scorer": "import modules.content_scorer",
- "import eeat_enhancer": "import modules.eeat_enhancer",
- "import semantic_expander": "import modules.semantic_expander",
- "import fact_density_enhancer": "import modules.fact_density_enhancer",
- "import schema_generator": "import modules.schema_generator",
- "import topic_cluster": "import modules.topic_cluster",
- "import multimodal_prompt": "import modules.multimodal_prompt",
- "import roi_analyzer": "import modules.roi_analyzer",
- "import workflow_automation": "import modules.workflow_automation",
- "import keyword_mining": "import modules.keyword_mining",
- "import optimization_techniques": "import modules.optimization_techniques",
- "import content_metrics": "import modules.content_metrics",
- "import technical_config_generator": "import modules.technical_config_generator",
- "import negative_monitor": "import modules.negative_monitor",
- "import resource_recommender": "import modules.resource_recommender",
- "import config_optimizer": "import modules.config_optimizer",
-}
-
-def update_file_imports(file_path: Path):
- """更新单个文件中的导入路径"""
- try:
- content = file_path.read_text(encoding='utf-8')
- original_content = content
- updated = False
-
- for old_import, new_import in import_mappings.items():
- # 使用正则表达式匹配完整的导入语句
- pattern = rf'({re.escape(old_import)})(\s+import|\s+as|\s+)'
- if re.search(pattern, content):
- content = re.sub(pattern, rf'{new_import}\2', content)
- updated = True
-
- if updated:
- file_path.write_text(content, encoding='utf-8')
- print(f"✓ Updated: {file_path.name}")
- return True
- return False
- except Exception as e:
- print(f"✗ Error updating {file_path.name}: {e}")
- return False
-
-def main():
- """更新所有Python文件中的导入路径"""
- # 需要更新的文件列表
- files_to_update = [
- root / "geo_tool.py",
- root / "modules" / "storage_example.py",
- ]
-
- # 如果modules目录存在,也检查其中的文件
- modules_dir = root / "modules"
- if modules_dir.exists():
- for py_file in modules_dir.glob("*.py"):
- if py_file.name != "__init__.py":
- files_to_update.append(py_file)
-
- updated_count = 0
- for file_path in files_to_update:
- if file_path.exists():
- if update_file_imports(file_path):
- updated_count += 1
- else:
- print(f"⚠ File not found: {file_path}")
-
- print(f"\n✅ Updated {updated_count} files")
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/update_script_references.py b/scripts/update_script_references.py
deleted file mode 100644
index 3ef7664..0000000
--- a/scripts/update_script_references.py
+++ /dev/null
@@ -1,92 +0,0 @@
-"""
-更新文档中对工具脚本的引用路径
-将旧路径更新为 scripts/ 前缀
-"""
-import re
-from pathlib import Path
-
-# 项目根目录
-root = Path(__file__).parent
-
-# 工具脚本列表
-scripts = [
- "cleanup_duplicate_docs.py",
- "move_reorganization_docs.py",
- "reorganize_files.py",
- "update_imports.py",
- "update_doc_references.py",
-]
-
-# 路径更新映射
-path_mappings = {
- # 旧路径 -> 新路径
- r'`(cleanup_duplicate_docs\.py)`': r'`scripts/\1`',
- r'`(move_reorganization_docs\.py)`': r'`scripts/\1`',
- r'`(reorganize_files\.py)`': r'`scripts/\1`',
- r'`(update_imports\.py)`': r'`scripts/\1`',
- r'`(update_doc_references\.py)`': r'`scripts/\1`',
-
- # 命令中的路径
- r'python (cleanup_duplicate_docs\.py)': r'python scripts/\1',
- r'python (move_reorganization_docs\.py)': r'python scripts/\1',
- r'python (reorganize_files\.py)': r'python scripts/\1',
- r'python (update_imports\.py)': r'python scripts/\1',
- r'python (update_doc_references\.py)': r'python scripts/\1',
-
- # 文档中的描述
- r'- `(cleanup_duplicate_docs\.py)`': r'- `scripts/\1`',
- r'- `(move_reorganization_docs\.py)`': r'- `scripts/\1`',
- r'- `(reorganize_files\.py)`': r'- `scripts/\1`',
- r'- `(update_imports\.py)`': r'- `scripts/\1`',
- r'- `(update_doc_references\.py)`': r'- `scripts/\1`',
-}
-
-def update_script_references(file_path: Path):
- """更新单个文件中的脚本引用路径"""
- try:
- content = file_path.read_text(encoding='utf-8')
- original_content = content
- updated = False
-
- # 更新脚本路径引用
- for pattern, replacement in path_mappings.items():
- if re.search(pattern, content):
- content = re.sub(pattern, replacement, content)
- updated = True
-
- if updated and content != original_content:
- file_path.write_text(content, encoding='utf-8')
- print(f"✓ Updated: {file_path.relative_to(root)}")
- return True
- return False
- except Exception as e:
- print(f"✗ Error updating {file_path.name}: {e}")
- return False
-
-def main():
- """更新所有文档中的脚本引用路径"""
- updated_count = 0
-
- # 更新根目录的文档
- root_docs = [
- "FINAL_OPTIMIZATION_GUIDE.md",
- "ADVANCED_OPTIMIZATION_PLAN.md",
- ]
-
- for doc_file in root_docs:
- doc_path = root / doc_file
- if doc_path.exists():
- if update_script_references(doc_path):
- updated_count += 1
-
- # 更新 docs/ 目录下的所有 .md 文件
- docs_dir = root / "docs"
- if docs_dir.exists():
- for md_file in docs_dir.rglob("*.md"):
- if update_script_references(md_file):
- updated_count += 1
-
- print(f"\n✅ Updated {updated_count} documentation files")
-
-if __name__ == "__main__":
- main()