From fb309299bf168feb9454a6d30091d1fcdadcfca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=9B=BD=E6=A0=8B?= Date: Thu, 30 Apr 2026 18:37:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=A7=E5=93=81=E8=A7=84?= =?UTF-8?q?=E6=A0=BC=E6=96=87=E6=A1=A3=E5=B9=B6=E4=BC=98=E5=8C=96=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- ...tor-geo-tool-py-structure_357e8386.plan.md | 166 - .cursorrules | 80 - .gitignore | 15 + .streamlit/config.toml | 11 +- .streamlit/secrets.toml.example | 41 + DOCS.md | 58 +- LICENSE | 21 + README.md | 471 +- docs/DIRECTORY_STRUCTURE_OPTIMIZATION.md | 305 -- docs/PRODUCT_SPEC.md | 172 + docs/analysis/ANALYSIS_ACCURACY_REPORT.md | 56 +- docs/analysis/CODE_DOCUMENTATION_ANALYSIS.md | 529 -- .../DOCUMENTATION_REVERSE_VERIFICATION.md | 237 +- docs/analysis/FEATURE_ANALYSIS.md | 303 -- docs/analysis/FEATURE_PRIORITY_ANALYSIS.md | 144 +- docs/analysis/FUNCTION_VERIFICATION_REPORT.md | 40 +- docs/analysis/IMAGE_GENERATION_DEBUG.md | 221 - docs/analysis/IMAGE_GENERATION_FIXES.md | 179 - docs/analysis/TAB1_CODE_REVIEW.md | 528 -- docs/analysis/TAB1_OPTIMIZATION_ANALYSIS.md | 1189 ----- docs/analysis/TAB2_CODE_REVIEW.md | 546 -- docs/analysis/TAB2_COMPREHENSIVE_REVIEW.md | 556 --- docs/analysis/TAB2_FIXES_SUMMARY.md | 285 -- docs/analysis/TAB2_IMPLEMENTATION_STATUS.md | 542 -- docs/analysis/TAB2_OPTIMIZATION_PLAN.md | 842 ---- docs/analysis/TAB_SPLIT_PATTERN.md | 140 + docs/features/AI_SEARCH_VERIFIER_FEATURE.md | 155 + docs/features/CONFIG_OPTIMIZER_FEATURE.md | 199 +- docs/features/CONTENT_METRICS_FEATURE.md | 23 +- docs/features/CONTENT_SCORER_FEATURE.md | 102 +- docs/features/CONTENT_UNIQUENESS_FEATURE.md | 154 + docs/features/EEAT_FEATURE.md | 47 +- docs/features/FACT_DENSITY_FEATURE.md | 135 +- docs/features/JSON_LD_SCHEMA_FEATURE.md | 279 +- .../features/KEYWORD_DATA_ENHANCER_FEATURE.md | 148 + docs/features/KEYWORD_MINING_FEATURE.md | 158 +- docs/features/KNOWLEDGE_BASE_FEATURE.md | 147 + docs/features/LLM_FACTORY_FEATURE.md | 130 + docs/features/MULTIMODAL_FEATURE.md | 154 +- docs/features/SEMANTIC_EXPANSION_FEATURE.md | 156 +- docs/guides/ADVANCED_OPTIMIZATION_PLAN.md | 188 - docs/guides/DECISION_GUIDE.md | 109 +- docs/guides/DOCUMENTATION_CLEANUP_GUIDE.md | 124 - docs/guides/FINAL_OPTIMIZATION_GUIDE.md | 203 - docs/guides/LAYOUT_UPGRADE_GUIDE.md | 73 +- docs/guides/MANUAL_CLEANUP_GUIDE.md | 185 - docs/guides/OPTIMIZATION_STATUS.md | 157 - docs/guides/PLATFORM_SETUP.md | 14 + docs/guides/PROJECT_STRUCTURE_ANALYSIS.md | 231 - docs/guides/QUICK_REORGANIZE.md | 139 - docs/guides/QUICK_START_GUIDE.md | 21 +- docs/guides/REFERENCE_UPDATE_SUMMARY.md | 122 - docs/guides/REORGANIZATION_SUMMARY.md | 243 - docs/guides/ROOT_DIRECTORY_RULES.md | 56 +- docs/guides/STORAGE_GUIDE.md | 27 +- docs/implementation/ADVANCED_FEATURES.md | 68 +- docs/implementation/FEATURES_COMPLETE_LIST.md | 57 +- geo_tool.py | 4403 ++--------------- modules/ai_search_verifier.py | 445 ++ modules/chat_doubao.py | 101 + modules/content_uniqueness.py | 332 ++ modules/data_storage.py | 60 +- modules/eeat_enhancer.py | 4 +- modules/keyword_data_enhancer.py | 354 ++ modules/keyword_tool.py | 2 +- modules/knowledge_base.py | 529 ++ modules/llm_factory.py | 161 + modules/roi_analyzer.py | 4 +- modules/schema_generator.py | 216 + modules/semantic_expander.py | 10 +- modules/services/__init__.py | 11 + modules/services/schema_service.py | 40 + modules/services/tech_config_service.py | 42 + modules/storage_example.py | 86 - modules/technical_config_generator.py | 14 +- modules/ui/__init__.py | 26 +- modules/ui/components.py | 142 + modules/ui/tab_autowrite.py | 43 +- modules/ui/tab_config_optimizer.py | 297 ++ modules/ui/tab_history.py | 115 + modules/ui/tab_keywords.py | 44 +- modules/ui/tab_knowledge.py | 242 + modules/ui/tab_optimize.py | 1092 ++++ modules/ui/tab_platform_sync.py | 298 ++ modules/ui/tab_reports.py | 1014 ++++ modules/ui/tab_resources.py | 246 + modules/ui/tab_validation.py | 308 ++ modules/ui/tab_workflow.py | 446 ++ modules/ui/theme.py | 341 +- modules/workflow_automation.py | 13 +- platform_sync/copy_manager.py | 3 +- platform_sync/github_publisher.py | 18 +- requirements.txt | 33 +- scripts/cleanup_duplicate_docs.py | 112 - scripts/cleanup_duplicate_modules.py | 91 - scripts/move_reorganization_docs.py | 50 - scripts/move_scripts.py | 52 - scripts/reorganize_files.py | 136 - scripts/update_doc_references.py | 124 - scripts/update_imports.py | 99 - scripts/update_script_references.py | 92 - 101 files changed, 9586 insertions(+), 14386 deletions(-) delete mode 100644 .cursor/plans/refactor-geo-tool-py-structure_357e8386.plan.md delete mode 100644 .cursorrules create mode 100644 .streamlit/secrets.toml.example create mode 100644 LICENSE delete mode 100644 docs/DIRECTORY_STRUCTURE_OPTIMIZATION.md create mode 100644 docs/PRODUCT_SPEC.md delete mode 100644 docs/analysis/CODE_DOCUMENTATION_ANALYSIS.md delete mode 100644 docs/analysis/FEATURE_ANALYSIS.md delete mode 100644 docs/analysis/IMAGE_GENERATION_DEBUG.md delete mode 100644 docs/analysis/IMAGE_GENERATION_FIXES.md delete mode 100644 docs/analysis/TAB1_CODE_REVIEW.md delete mode 100644 docs/analysis/TAB1_OPTIMIZATION_ANALYSIS.md delete mode 100644 docs/analysis/TAB2_CODE_REVIEW.md delete mode 100644 docs/analysis/TAB2_COMPREHENSIVE_REVIEW.md delete mode 100644 docs/analysis/TAB2_FIXES_SUMMARY.md delete mode 100644 docs/analysis/TAB2_IMPLEMENTATION_STATUS.md delete mode 100644 docs/analysis/TAB2_OPTIMIZATION_PLAN.md create mode 100644 docs/analysis/TAB_SPLIT_PATTERN.md create mode 100644 docs/features/AI_SEARCH_VERIFIER_FEATURE.md create mode 100644 docs/features/CONTENT_UNIQUENESS_FEATURE.md create mode 100644 docs/features/KEYWORD_DATA_ENHANCER_FEATURE.md create mode 100644 docs/features/KNOWLEDGE_BASE_FEATURE.md create mode 100644 docs/features/LLM_FACTORY_FEATURE.md delete mode 100644 docs/guides/ADVANCED_OPTIMIZATION_PLAN.md delete mode 100644 docs/guides/DOCUMENTATION_CLEANUP_GUIDE.md delete mode 100644 docs/guides/FINAL_OPTIMIZATION_GUIDE.md delete mode 100644 docs/guides/MANUAL_CLEANUP_GUIDE.md delete mode 100644 docs/guides/OPTIMIZATION_STATUS.md delete mode 100644 docs/guides/PROJECT_STRUCTURE_ANALYSIS.md delete mode 100644 docs/guides/QUICK_REORGANIZE.md delete mode 100644 docs/guides/REFERENCE_UPDATE_SUMMARY.md delete mode 100644 docs/guides/REORGANIZATION_SUMMARY.md create mode 100644 modules/ai_search_verifier.py create mode 100644 modules/chat_doubao.py create mode 100644 modules/content_uniqueness.py create mode 100644 modules/keyword_data_enhancer.py create mode 100644 modules/knowledge_base.py create mode 100644 modules/llm_factory.py create mode 100644 modules/services/__init__.py create mode 100644 modules/services/schema_service.py create mode 100644 modules/services/tech_config_service.py delete mode 100644 modules/storage_example.py create mode 100644 modules/ui/components.py create mode 100644 modules/ui/tab_config_optimizer.py create mode 100644 modules/ui/tab_history.py create mode 100644 modules/ui/tab_knowledge.py create mode 100644 modules/ui/tab_optimize.py create mode 100644 modules/ui/tab_platform_sync.py create mode 100644 modules/ui/tab_reports.py create mode 100644 modules/ui/tab_resources.py create mode 100644 modules/ui/tab_validation.py create mode 100644 modules/ui/tab_workflow.py delete mode 100644 scripts/cleanup_duplicate_docs.py delete mode 100644 scripts/cleanup_duplicate_modules.py delete mode 100644 scripts/move_reorganization_docs.py delete mode 100644 scripts/move_scripts.py delete mode 100644 scripts/reorganize_files.py delete mode 100644 scripts/update_doc_references.py delete mode 100644 scripts/update_imports.py delete mode 100644 scripts/update_script_references.py 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 智能内容优化平台 + +
+ +![Python](https://img.shields.io/badge/Python-3.10+-blue.svg) +![Streamlit](https://img.shields.io/badge/Streamlit-1.30+-red.svg) +![LangChain](https://img.shields.io/badge/LangChain-0.4+-green.svg) +![License](https://img.shields.io/badge/License-MIT-yellow.svg) + +**让您的品牌在 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()