- main.py: 4360 → 146 lines (96.6% reduction), entry layer only - services/: rate_limiter, autostart, persona, connection, profile, hotspot, content, engagement, scheduler, queue_ops (10 business modules) - ui/app.py: all Gradio UI code extracted into build_app(cfg, analytics) - Fix: with gr.Blocks() indented inside build_app function - Fix: cfg.all property (not get_all method) - Fix: STATUS_LABELS, get_persona_keywords, fetch_proactive_notes imports - Fix: queue_ops module-level set_publish_callback moved into configure() - Fix: pub_queue.format_*() wrapped as queue_format_table/calendar helpers - All 14 files syntax-verified, build_app() runtime-verified - 58/58 tasks complete"
6.0 KiB
Context
main.py 是整个项目的单一入口,目前 4359 行,包含 10+ 个业务域的业务逻辑、全局状态、UI 组件和事件绑定。上一轮重构已将 Tab 1(内容创作)提取为 ui/tab_create.py,建立了 Tab 模块化的模式。本次设计延续该模式,将业务逻辑提取为 services/ 目录,并完成剩余 Tab 的 UI 拆分。
已有外部服务层:config_manager.py、llm_service.py、sd_service.py、mcp_client.py、analytics_service.py、publish_queue.py。
main.py 中剩余的函数是对这些服务的编排层,应该独立成模块而非继续膨胀在入口文件里。
约束:
- Python 3.10+(当前环境)
- 不引入新的外部依赖
- 重构不改变任何业务行为
Goals / Non-Goals
Goals:
- 将
main.py瘦身至 ~300 行纯入口层(导入 + UI 组装 +app.launch()) - 建立清晰的分层:
services/(业务编排)→ui/(Gradio 组件)→main.py(入口) - 消除模块间隐式依赖,所有依赖通过函数参数显式传递
- 保持所有函数签名、Gradio 回调绑定不变
Non-Goals:
- 不重构或重写任何现有业务逻辑(本次是纯搬迁)
- 不改变
config_manager.py、llm_service.py等已有服务层 - 不引入类/面向对象重构(保持现有函数式风格)
- 不添加单元测试(独立变更)
Decisions
D1:分层架构 —— services/ 不依赖 ui/,ui/ 不依赖 services/
main.py (入口层:组装 + 启动)
├── services/ (业务编排层:纯 Python,无 Gradio)
│ ├── connection.py
│ ├── content.py
│ ├── hotspot.py
│ ├── engagement.py
│ ├── rate_limiter.py
│ ├── profile.py
│ ├── persona.py
│ ├── scheduler.py
│ ├── queue_ops.py
│ └── autostart.py
├── ui/ (UI 层:Gradio 组件 + 事件绑定)
│ ├── tab_create.py ← 已存在
│ ├── tab_hotspot.py
│ ├── tab_engage.py
│ ├── tab_profile.py
│ ├── tab_auto.py
│ ├── tab_queue.py
│ ├── tab_analytics.py
│ └── tab_settings.py
为何不让 ui/ 依赖 services/: 每个 build_tab() 接收回调函数作为参数(已有 tab_create.py 的模式),而非直接 import service。这样 UI 层完全解耦,可独立测试或替换。
D2:共享单例通过 main.py 初始化,作为参数传入
cfg、mcp、analytics、pub_queue、queue_publisher 仍在 main.py 顶层初始化。Service 函数需要它们时通过函数参数接收,不在 service 模块顶层 import。
为何不在各 service 模块初始化单例: 防止循环依赖、防止多次初始化、保持测试时可替换。
D3:有状态模块使用模块级变量(不封装成类)
rate_limiter、scheduler、engagement 内部有 threading.Event、_daily_stats 等状态。保持现有模块级变量风格(不改成类),仅将变量和函数整体搬迁到对应模块。
为何不改成类: 本次目标是结构拆分,不是重构设计模式,避免引入额外变更风险。
D4:迁移策略 —— 先提取后删除,不做重定向
每个域的迁移步骤:
- 在 service 模块中写入函数(复制粘贴 + 调整 import)
- 在
main.py中删除对应函数,改为from services.xxx import ... - 运行
ast.parse()验证语法 - 运行应用验证启动不报错
为何不做 main.py 中的临时 re-export: 简单场景直接删+导入更清晰,且 Gradio 回调绑定在 main.py 中通过变量名引用,只需保证同名变量在作用域内即可。
D5:UI Tab 模块统一使用 build_tab(fn_*, ...) 签名
复用 tab_create.py 已建立的模式:
- 每个
build_tab()接收所需的回调函数和共享 Gradio 组件作为参数 - 函数内部创建本 Tab 的所有 Gradio 组件及事件绑定
- 返回
dict,包含需要被其他 Tab 或app.load()引用的组件
D6:services/ 和 ui/ 均需 __init__.py
使用空文件标记为 Python 包,与 ui/__init__.py 已有做法一致。
Risks / Trade-offs
- [风险] 大量 import 调整可能遗漏 → 每个模块完成后执行
ast.parse()+ 应用启动验证,逐域推进 - [风险] 全局状态的隐式共享 → 调度器、限流器的模块级变量在模块首次 import 时初始化,Python 模块单例语义保证只初始化一次,行为与当前一致
- [权衡]
build_tab()参数列表长 → 与现有tab_create.py的做法一致,接受这种显式依赖的冗长性,可在后续变更中引入 dataclass 参数包
Migration Plan
按以下顺序逐域提取,每步验证后再继续:
services/rate_limiter.py—— 无外部依赖,最安全的起点services/autostart.py—— 独立,平台相关逻辑隔离services/persona.py—— 仅依赖cfgservices/connection.py—— 依赖cfg、llm_service、sd_service、mcp_clientservices/profile.py—— 依赖mcp_clientservices/hotspot.py—— 依赖llm_service、mcp_clientservices/content.py—— 依赖多个服务,最复杂services/engagement.py—— 依赖rate_limiter、mcp_clientservices/scheduler.py—— 依赖engagement、contentservices/queue_ops.py—— 依赖content、pub_queueui/tab_hotspot.py~ui/tab_settings.py—— 7 个 Tab UI 拆分
回滚策略: 所有修改通过 git 追踪;每个 service 提取为一个独立 commit,任意步骤可 git revert。
Open Questions
_auto_log列表(被engagement和scheduler共同写入)归属哪个模块?
→ 暂定置于services/scheduler.py,engagement接收log_fn回调参数queue_publisher的 callback 注册(set_publish_callback)在哪里调用?
→ 保留在main.py初始化段,callback 函数迁移到services/queue_ops.py