# 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 保持一致、可维护的拆分方式。