diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..d98efff
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,37 @@
+---
+name: Bug 报告
+about: 报告一个问题,帮助我们改进
+title: "[BUG] "
+labels: bug
+assignees: ''
+---
+
+## 问题描述
+
+请简洁清晰地描述遇到的问题。
+
+## 复现步骤
+
+1. 进入 '...'
+2. 点击 '...'
+3. 滚动至 '...'
+4. 出现错误
+
+## 预期行为
+
+描述你预期应该发生什么。
+
+## 实际行为
+
+描述实际发生了什么,请附上错误截图或日志信息。
+
+## 环境信息
+
+- **操作系统**:(例如 Windows 11 / macOS 14 / Ubuntu 22.04)
+- **Python 版本**:(执行 `python --version` 获取)
+- **autobot 版本/提交**:(执行 `git rev-parse --short HEAD` 获取)
+- **相关依赖版本**:(如 gradio、openai 等)
+
+## 附加信息
+
+任何其他与问题相关的上下文、截图或日志文件,请粘贴于此。
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..5530194
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,23 @@
+---
+name: 功能请求
+about: 为本项目提出新点子或改进建议
+title: "[FEATURE] "
+labels: enhancement
+assignees: ''
+---
+
+## 背景与需求
+
+请描述你目前遇到的问题或痛点。例如:"当我想要 [...] 时,总是感到不便,因为 [...]"
+
+## 期望的解决方案
+
+请清晰描述你希望实现的功能或行为。
+
+## 替代方案
+
+你是否考虑过其他解决方案或变通方法?请描述。
+
+## 附加信息
+
+你可以在此添加任何截图、参考链接或其他有助于理解该功能请求的上下文信息。
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..8263587
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,35 @@
+## 变更类型
+
+请勾选适用的选项(在 `[ ]` 中填入 `x`):
+
+- [ ] 🐛 Bug Fix(修复问题,未破坏现有功能)
+- [ ] ✨ Feature(新功能,未破坏现有功能)
+- [ ] 📝 Docs(仅文档变更)
+- [ ] ♻️ Refactor(代码重构,未修复 Bug 也未新增功能)
+- [ ] 🎨 Style(格式化、缩进等,不影响代码逻辑)
+- [ ] ⚡ Performance(性能优化)
+- [ ] 🔧 Chore(构建流程、工具配置等杂项变更)
+
+## 变更描述
+
+请简洁描述本 PR 做了什么,以及为什么需要这些变更。
+
+## 相关 Issue
+
+关闭 #(填入 Issue 编号,若无可删除此行)
+
+## 测试说明
+
+请描述你如何测试了本次变更(手动测试步骤、自动化测试等):
+
+- [ ] 我在本地运行了 `python main.py` 确认应用正常启动
+- [ ] 我验证了受影响的功能仍按预期工作
+- [ ] 我运行了 `ruff check .` 确认无新增 lint 错误
+
+## 截图(如适用)
+
+如果本 PR 包含 UI 变更,请附上前后对比截图。
+
+## 备注
+
+有任何需要 reviewer 特别关注的地方,请在此说明。
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..3fc069a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,48 @@
+name: CI
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+
+jobs:
+ lint:
+ name: Lint (ruff)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.10"
+
+ - name: Install ruff
+ run: pip install ruff>=0.4.0
+
+ - name: Run ruff
+ run: ruff check . --select E,F,W --ignore E501,E402,W291,W293
+
+ import-check:
+ name: Import Check
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.10"
+
+ - name: Install dependencies
+ run: pip install -r requirements.txt
+
+ - name: Verify core service imports
+ run: |
+ python -c "from services.config_manager import ConfigManager; print('config_manager OK')"
+ python -c "from services.llm_service import LLMService; print('llm_service OK')"
+ python -c "from services.mcp_client import get_mcp_client; print('mcp_client OK')"
+ python -c "from services.analytics_service import AnalyticsService; print('analytics_service OK')"
+ python -c "from services.publish_queue import STATUS_LABELS; print('publish_queue OK')"
+ python -c "from services.sd_service import SDService; print('sd_service OK')"
diff --git a/.gitignore b/.gitignore
index bf61413..303c255 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,7 +16,7 @@ venv/
env/
# ========== 敏感配置 ==========
-config.json
+config/config.json
cookies.json
*.cookie
@@ -42,4 +42,11 @@ config copy.json
# ========== 临时文件 ==========
*.tmp
-*.bak
\ No newline at end of file
+*.bak
+
+# ========== 个人媒体资产(隐私,不入版本控制) ==========
+assets/faces/
+
+# ========== 自动生成的启动脚本(含机器绝对路径) ==========
+scripts/_autostart.bat
+scripts/_autostart.vbs
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..c550142
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,83 @@
+# 贡献者行为准则
+
+## 我们的承诺
+
+作为成员、贡献者和领导者,我们承诺让每一位参与者都能在无骚扰的环境中参与我们的社区,不论其年龄、体型、是否有可见或不可见的残疾、族裔、性别特征、性别认同与表达、经验水平、教育程度、社会经济地位、国籍、外表、种族、种姓、肤色、宗教信仰或性取向。
+
+我们承诺以有助于建立开放、友好、多元、包容和健康社区的方式行事和互动。
+
+## 我们的准则
+
+有助于为我们的社区创造积极环境的行为示例包括:
+
+* 对他人表现出同理心和善意
+* 尊重不同的意见、观点和经历
+* 给予并优雅地接受建设性反馈
+* 承担责任并向受我们错误影响的人道歉,并从经历中学习
+* 关注整个社区的最大利益,不只是我们个人的利益
+
+不可接受的行为示例包括:
+
+* 使用性化语言或图像,以及任何形式的性关注或性骚扰
+* 发表挑衅性、侮辱性或贬义的评论,以及针对个人或政治的攻击
+* 公开或私下骚扰
+* 未经明确许可,发布他人的私人信息,例如实际地址或电子邮件地址
+* 在专业环境中其他可被合理认为不适当的行为
+
+## 执行责任
+
+社区领导者有责任阐明和执行我们可接受行为的准则,并在遇到任何他们认为不适当、有威胁、冒犯或有害的行为时采取适当且公平的纠正行动。
+
+社区领导者有权利和责任删除、编辑或拒绝与本行为准则不符的评论、提交、代码、wiki 编辑、议题及其他贡献,并在适当时就审核决定的原因进行沟通。
+
+## 适用范围
+
+本行为准则适用于所有社区空间,也适用于当个人在公共空间中正式代表本社区时的情形。代表我们社区的示例包括:使用官方电子邮件地址、通过官方社交媒体账号发帖,或在线上或线下活动中担任指定代表。
+
+## 执行
+
+如遇到骚扰、滥用或其他不可接受的行为,可通过 GitHub Issues 或在本仓库的 Discussions 中向社区维护者报告。所有投诉都将被迅速、公平地审查和调查。
+
+所有社区领导者有义务尊重任何事件报告者的隐私和安全。
+
+## 执行指南
+
+社区领导者将遵循以下社区影响指南,确定对违反本行为准则的任何行为的后果:
+
+### 1. 纠正
+
+**社区影响**:使用不当语言或其他被认为不专业或社区不欢迎的行为。
+
+**后果**:社区领导者会发出私人书面警告,说明违规的性质并解释为何该行为不当。可能会要求公开道歉。
+
+### 2. 警告
+
+**社区影响**:单次事件或一系列行为的违规。
+
+**后果**:警告并说明持续行为的后果。在指定时间内不得与相关人员互动,包括主动与执行本行为准则的人员互动。这包括避免在社区空间以及社交媒体等外部渠道进行互动。违反这些条款可能会导致临时或永久封禁。
+
+### 3. 临时封禁
+
+**社区影响**:严重违反社区准则,包括持续的不当行为。
+
+**后果**:在指定时间内临时禁止与社区进行任何形式的互动或公开通信。在此期间,不允许与相关人员进行任何公开或私下的互动,包括主动与执行本行为准则的人员互动。违反这些条款可能会导致永久封禁。
+
+### 4. 永久封禁
+
+**社区影响**:表现出违反社区准则的模式,包括持续的不当行为、对某个人的骚扰或对某类人群的攻击或诋毁。
+
+**后果**:永久禁止在社区内进行任何形式的公开互动。
+
+## 归属
+
+本行为准则改编自 [Contributor Covenant][homepage] v2.1 版,详情请访问 [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]。
+
+社区影响指南的灵感来自 [Mozilla 的行为准则执行阶梯][Mozilla CoC]。
+
+有关本行为准则常见问题的解答,请参阅 [https://www.contributor-covenant.org/faq][FAQ]。其他语言的翻译请参阅 [https://www.contributor-covenant.org/translations][translations]。
+
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
diff --git a/Dockerfile b/Dockerfile
index 99a709f..a909e6b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -28,12 +28,13 @@ RUN apt-get update && \
COPY --from=builder /install /usr/local
# 复制项目代码
-COPY config_manager.py llm_service.py sd_service.py mcp_client.py main.py ./
-COPY requirements.txt ./
-COPY config.example.json ./
+COPY main.py requirements.txt ./
+COPY services/ services/
+COPY ui/ ui/
+COPY config/config.example.json config/config.example.json
# 创建工作目录
-RUN mkdir -p xhs_workspace
+RUN mkdir -p xhs_workspace config logs
# Gradio 默认端口
EXPOSE 7860
diff --git a/README.md b/README.md
index fe73d94..5ed7b76 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,11 @@
FAQ •
贡献
+
+
+
+
+
---
@@ -91,7 +96,7 @@
```bash
# 1. 克隆项目
-git clone https://github.com/your-username/xhs-autobot.git
+git clone https://github.com//xhs-autobot.git
cd xhs-autobot
# 2. 创建虚拟环境(推荐)
@@ -105,8 +110,8 @@ source .venv/bin/activate
pip install -r requirements.txt
# 4. 复制配置文件并填写你的 API Key
-cp config.example.json config.json
-# 编辑 config.json,填写 api_key、base_url 等
+cp config/config.example.json config/config.json
+# 编辑 config/config.json,填写 api_key、base_url 等
# 5. 启动!
python main.py
@@ -120,12 +125,12 @@ python main.py
```bash
# 1. 克隆项目
-git clone https://github.com/your-username/xhs-autobot.git
+git clone https://github.com//xhs-autobot.git
cd xhs-autobot
# 2. 准备配置文件
-cp config.example.json config.json
-# 编辑 config.json,填写 api_key、base_url 等
+cp config/config.example.json config/config.json
+# 编辑 config/config.json,填写 api_key、base_url 等
# ⚠️ mcp_url 改为容器网络地址:
# "mcp_url": "http://xhs-mcp:18060/mcp"
@@ -164,7 +169,7 @@ docker compose exec xhs-autobot bash
#### 启用 Stable Diffusion(需要 NVIDIA GPU)
-编辑 `docker-compose.yml`,取消 `sd-webui` 部分的注释,并将 `config.json` 中的 `sd_url` 改为:
+编辑 `docker-compose.yml`,取消 `sd-webui` 部分的注释,并将 `config/config.json` 中的 `sd_url` 改为:
```json
"sd_url": "http://sd-webui:7860"
@@ -205,11 +210,11 @@ python launch.py --api
### 首次使用流程
-1. **配置 LLM** — 展开「⚙️ 全局设置」,添加 LLM 提供商(API Key + Base URL),点击「连接 LLM」
-2. **连接 SD**(可选)— 填写 SD WebUI URL,点击「连接 SD」
+1. **配置 LLM** — 切换到「⚙️ 配置」 Tab,添加 LLM 提供商(API Key + Base URL),点击「连接 LLM」
+2. **连接 SD**(可选)— 在「⚙️ 配置」 Tab 填写 SD WebUI URL,点击「连接 SD」
3. **检查 MCP** — 点击「检查 MCP」确认小红书服务正常
-4. **登录小红书** — 切换到「🔐 账号登录」Tab,扫码登录
-5. **选择人设** — 在人设下拉框选择博主人设(影响文案风格 + 图片视觉)
+4. **登录小红书** — 切换到「🔐 账号登录」 Tab,扫码登录
+5. **选择人设** — 在「⚙️ 配置」 Tab 的人设下拉框选择博主人设(影响文案风格 + 图片视觉)
6. **开始创作** — 切换到「✨ 内容创作」Tab,输入主题,一键生成
### 自动化运营
@@ -243,7 +248,7 @@ python launch.py --api
## ⚙️ 配置说明
-配置文件 `config.json` 会在运行时自动创建和保存。首次使用请从 `config.example.json` 复制:
+配置文件 `config/config.json` 会在运行时自动创建和保存。首次使用请从 `config/config.example.json` 复制:
```json
{
@@ -280,24 +285,47 @@ python launch.py --api
```
xhs-autobot/
-├── main.py # 主程序入口 (Gradio UI + 业务逻辑 + 8 个 Tab)
-├── config_manager.py # 配置管理模块 (单例、自动保存)
-├── llm_service.py # LLM 服务封装 (文案生成、热点分析、评论回复、SD Prompt 指南)
-├── sd_service.py # Stable Diffusion 服务封装 (3 模型适配、9 人设视觉方案)
-├── mcp_client.py # 小红书 MCP 客户端 (搜索、发布、评论、点赞)
-├── analytics_service.py # 笔记数据分析 & 权重学习服务
-├── publish_queue.py # 内容排期队列 (SQLite + 后台 Publisher)
-├── config.json # 运行时配置 (gitignore)
-├── config.example.json # 配置模板
+├── main.py # 主程序入口 (Gradio UI + 事件绑定)
+├── config/ # 配置文件目录
+│ ├── config.json # 运行时配置 (gitignore)
+│ └── config.example.json # 配置模板
+├── logs/ # 运行日志 (gitignore)
+│ └── autobot.log
+├── docs/ # 参考文档
+│ └── mcp.md # xiaohongshu-mcp 参考文档
+├── scripts/ # 运行时生成的启动脚本 (gitignore)
+│ ├── _autostart.bat
+│ └── _autostart.vbs
├── requirements.txt # Python 依赖
+├── requirements-dev.txt # 开发工具依赖 (ruff 等)
├── Dockerfile # Docker 镜像构建
├── docker-compose.yml # Docker Compose 编排
-├── .dockerignore # Docker 构建排除规则
-├── xhs_workspace/ # 导出的文案和图片 (gitignore)
-│ ├── publish_queue.db # 排期队列数据库
-│ ├── analytics_data.json # 笔记表现数据
-│ └── content_weights.json # 内容权重数据
-└── autobot.log # 运行日志 (gitignore)
+├── services/ # 业务逻辑层
+│ ├── config_manager.py # 配置管理模块 (单例、自动保存)
+│ ├── llm_service.py # LLM 服务封装 (文案生成、热点分析等)
+│ ├── sd_service.py # Stable Diffusion 服务封装 (3 模型适配、9 人设视觉方案)
+│ ├── mcp_client.py # 小红书 MCP 客户端 (搜索、发布、评论、点赞)
+│ ├── analytics_service.py # 笔记数据分析 & 权重学习服务
+│ ├── publish_queue.py # 内容排期队列 (SQLite + 后台 Publisher)
+│ ├── scheduler.py # 自动化运营调度器
+│ ├── content.py # 文案生成、图片生成、导出、发布
+│ ├── hotspot.py # 热点探测、热点生成、笔记缓存
+│ ├── engagement.py # 评论管家:手动评论、回复、互动
+│ ├── profile.py # 小红书账号 Profile 解析与可视化
+│ ├── persona.py # 人设管理:常量、关键词池、主题池
+│ ├── connection.py # LLM/SD/MCP/XHS 连接管理
+│ ├── queue_ops.py # 发布队列操作
+│ ├── rate_limiter.py # 频率控制、每日限额、冷却管理
+│ └── autostart.py # Windows 开机自启管理
+├── ui/ # Gradio UI 层
+│ ├── app.py # 主界面构建函数(全部 Tab UI + 事件绑定)
+│ └── tab_create.py # Tab 1「✨ 内容创作」组件定义
+├── assets/
+│ └── faces/ # 头像文件目录 (gitignore)
+└── xhs_workspace/ # 导出的文案和图片 (gitignore)
+ ├── publish_queue.db # 排期队列数据库
+ ├── analytics_data.json # 笔记表现数据
+ └── content_weights.json # 内容权重数据
```
---
@@ -371,7 +399,7 @@ xhs-autobot/
Q: 如何添加自定义人设?
-在人设下拉框中直接输入自定义人设描述即可(支持自由输入)。如需配套主题池和关键词,需在 `main.py` 的 `PERSONA_POOL_MAP` 中添加对应条目。如需配套 SD 视觉方案,需在 `sd_service.py` 的 `PERSONA_SD_PROFILES` 中添加。
+在人设下拉框中直接输入自定义人设描述即可(支持自由输入)。如需配套主题池和关键词,需在 `services/persona.py` 的 `PERSONA_POOL_MAP` 中添加对应条目。如需配套 SD 视觉方案,需在 `services/sd_service.py` 的 `PERSONA_SD_PROFILES` 中添加。
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..a4ff18d
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,36 @@
+# 安全政策
+
+## 支持的版本
+
+目前我们对以下版本提供安全更新支持:
+
+| 版本 | 支持状态 |
+| ---- | -------- |
+| main 分支(最新提交) | ✅ 支持 |
+| 旧版本 / 存档提交 | ❌ 不支持 |
+
+## 报告安全漏洞
+
+**请勿通过公开的 GitHub Issues 上报安全漏洞。**
+
+如果您发现了安全漏洞,请通过以下方式私下联系我们:
+
+1. **GitHub Security Advisory(推荐)**:访问本仓库的 [Security → Advisories](../../security/advisories/new) 页面,点击「Report a vulnerability」提交私密报告。
+
+2. **私信维护者**:如无法使用上述方式,可通过 GitHub 私信联系仓库维护者。
+
+### 报告内容请包含
+
+- 漏洞的详细描述
+- 影响范围(受影响的版本、功能模块)
+- 复现步骤(如可能)
+- 潜在影响评估
+- 您建议的修复方案(如有)
+
+## 响应承诺
+
+- 我们会在 **7 个工作日内** 确认收到您的报告
+- 我们会在 **30 天内** 评估漏洞并提供修复时间表
+- 修复发布后,我们会在 Release Notes 中公开致谢(如您同意)
+
+感谢您帮助保持本项目的安全!
diff --git a/Todo.md b/Todo.md
deleted file mode 100644
index ab1c0a1..0000000
--- a/Todo.md
+++ /dev/null
@@ -1 +0,0 @@
-目前的脚本已经实现了从 “灵感 -> 文案 -> 绘图 -> 发布” 的核心闭环,作为一个个人辅助工具(MVP,最小可行性产品)已经非常出色了。但是,如果要作为一个专业的运营工具或者满足商业化需求,目前的版本还存在明显的短板。我将从 内容质量、运营闭环、账号安全、功能深度 四个维度为你进行全面分析,并给出升级建议。📊 当前功能评分表维度当前得分评价核心流程⭐⭐⭐⭐⭐流程跑通,无需在多个软件间切换,效率极大提升。内容质量⭐⭐⭐LLM 文案通用性强但个性不足;SD 绘图仅支持基础生图,缺乏精细控制。运营功能⭐⭐仅支持“发”,缺乏“看”(数据分析)和“回”(评论互动)。多媒体能力⭐⭐仅支持图片,不支持视频(尽管 MCP 支持)。稳定性⭐⭐⭐依赖本地环境和 Cookie 有效期,缺乏异常重试和账号管理。🔍 深度差距分析与改进建议1. 视觉能力的局限性 (痛点:图片不可控)目前使用的是基础的 txt2img(文生图)。问题:很难控制人物姿势、保持角色一致性(比如同一个博主IP)、或者在特定背景中植入产品。缺口:ControlNet 支持:无法指定姿势(Openpose)或线稿上色(Canny)。LoRA 切换:无法快速切换画风(如:二次元 vs 真实感 vs 胶片风)。Img2Img:无法基于参考图进行修改。💡 改进建议:在 UI 中增加 ControlNet 参数接口,或者增加“风格预设”下拉框(后台自动切换 LoRA)。2. 缺乏“选题与热点”辅助 (痛点:不知道写什么)目前主要依赖用户自己输入“主题”。问题:如果用户不知道最近什么火,写的文章可能没人看。缺口:MCP 搜索能力未利用:xiaohongshu-mcp 有 search_feeds 功能,但脚本里没用。💡 改进建议:增加 “热点探测” tab。逻辑:用户输入关键词 -> 调用 MCP 搜索 -> LLM 分析热门笔记的标题和结构 -> 生成“爆款模仿”方案。3. 缺失视频支持 (痛点:视频流量更大)小红书目前对视频流量扶持很大。问题:目前的 UI 和逻辑只支持图片。虽然 MCP 支持 publish_with_video,但你没接。💡 改进建议:UI 增加“上传视频”或“AI 生成视频”入口。接入 Runway / Luma API 或本地 AnimateDiff 生成几秒的动态视频。4. 运营互动的缺失 (痛点:发完不管)运营小红书,“养号”和“回复” 与发帖一样重要。问题:目前是“射后不理”。缺口:评论管理:无法自动回复评论,无法引导私域。数据反馈:发出去的笔记有多少阅读?脚本里看不到。💡 改进建议:增加 “评论管家” 模块:定期调用 MCP 获取新评论 -> LLM 生成回复 -> 调用 MCP 回复。增加 “数据看板”:调用 user_profile 展示昨日点赞涨粉数。5. 账号矩阵与安全性 (痛点:单点风险)问题:目前是单账号模式。缺口:多账号切换:如果我有 5 个号,需要反复手动扫码或替换 Cookie 文件。定时发布:只能“立即发布”。真正运营需要设定在晚高峰(18:00-21:00)自动发。💡 改进建议:引入简单的 SQLite 数据库或 JSON 文件管理多组 Cookie。引入 APScheduler 库,实现“存入草稿箱,特定时间自动调用 MCP 发布”。🛠️ 下一步升级路线图 (Roadmap)如果你想把这个脚本升级为V2.0 专业版,建议按以下顺序添加功能:第一阶段:补全 MCP 能力 (低成本,高回报)接入搜索功能:在写文案前,先让 AI 看 5 篇同类热门笔记。接入数据面板:在侧边栏显示当前账号粉丝数、获赞数。第二阶段:增强视觉 (提升内容力)SD 进阶:支持上传参考图 (img2img)。本地图库:有时候不想用 AI 图,想混排自己拍的照片,增加“本地上传”按钮。第三阶段:自动化运营 (解放双手)自动回复机器人:根据设定的人设(知性姐姐/毒舌博主)自动回评论。定时任务:设置一个队列,让它自己跑。
\ No newline at end of file
diff --git a/_autostart.bat b/_autostart.bat
deleted file mode 100644
index 83cf4b4..0000000
--- a/_autostart.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-cd /d "F:\3_Personal\AI\xhs_bot\autobot"
-"F:\3_Personal\AI\xhs_bot\autobot\.venv\Scripts\pythonw.exe" "F:\3_Personal\AI\xhs_bot\autobot\main.py"
diff --git a/_autostart.vbs b/_autostart.vbs
deleted file mode 100644
index 5a0b37c..0000000
--- a/_autostart.vbs
+++ /dev/null
@@ -1,3 +0,0 @@
-Set WshShell = CreateObject("WScript.Shell")
-WshShell.Run chr(34) & "F:\3_Personal\AI\xhs_bot\autobot\_autostart.bat" & chr(34), 0
-Set WshShell = Nothing
diff --git a/_test_config_save.py b/_test_config_save.py
deleted file mode 100644
index 3ae0b91..0000000
--- a/_test_config_save.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""
-测试配置保存和加载功能
-"""
-import json
-from config_manager import ConfigManager
-
-# 测试配置保存
-cfg = ConfigManager()
-
-print("=== 测试LLM提供商切换保存 ===")
-print(f"当前激活: {cfg.get('active_llm')}")
-
-# 切换到 wolfai
-cfg.set_active_llm("wolfai")
-print(f"切换后: {cfg.get('active_llm')}")
-
-# 重新加载验证
-cfg2 = ConfigManager()
-print(f"重新加载: {cfg2.get('active_llm')}")
-
-# 检查config.json文件内容
-with open("config.json", "r", encoding="utf-8") as f:
- data = json.load(f)
- print(f"\nconfig.json中的active_llm: {data.get('active_llm')}")
- print(f"api_key: {data.get('api_key', '')[:20]}...")
- print(f"base_url: {data.get('base_url')}")
-
-print("\n=== 测试其他全局设置保存 ===")
-print(f"当前persona: {cfg.get('persona')}")
-cfg.set("persona", "测试人设")
-print(f"修改后persona: {cfg.get('persona')}")
-
-# 重新验证
-with open("config.json", "r", encoding="utf-8") as f:
- data = json.load(f)
- print(f"config.json中的persona: {data.get('persona')}")
-
-print("\n✅ 测试完成")
diff --git a/beauty.png b/beauty.png
deleted file mode 100644
index a5b9323..0000000
Binary files a/beauty.png and /dev/null differ
diff --git a/config copy.json b/config copy.json
deleted file mode 100644
index 52ab15c..0000000
--- a/config copy.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "api_key": "sk-d212b926f51f4f0f9297629cd2ab77b4",
- "base_url": "https://api.deepseek.com/v1",
- "sd_url": "http://127.0.0.1:7860",
- "mcp_url": "http://localhost:18060/mcp",
- "model": "deepseek-reasoner",
- "persona": "温柔知性的时尚博主",
- "auto_reply_enabled": false,
- "schedule_enabled": false
-}
\ No newline at end of file
diff --git a/config.json b/config.json
deleted file mode 100644
index c390e78..0000000
--- a/config.json
+++ /dev/null
@@ -1,60 +0,0 @@
-{
- "api_key": "sk-NPZECL5m3BmZv0S9YO9KOd179pepRH08iYeAn1Tk07Jux9Br",
- "base_url": "https://wolfai.top/v1",
- "sd_url": "http://127.0.0.1:7861",
- "mcp_url": "http://localhost:18060/mcp",
- "model": "gemini-3-flash-preview",
- "persona": "二次元coser,喜欢分享cos日常和动漫周边",
- "auto_reply_enabled": false,
- "schedule_enabled": false,
- "my_user_id": "69872540000000002303cc42",
- "active_llm": "wolfai",
- "llm_providers": [
- {
- "name": "默认",
- "api_key": "sk-d212b926f51f4f0f9297629cd2ab77b4",
- "base_url": "https://api.deepseek.com/v1"
- },
- {
- "name": "wolfai",
- "api_key": "sk-NPZECL5m3BmZv0S9YO9KOd179pepRH08iYeAn1Tk07Jux9Br",
- "base_url": "https://wolfai.top/v1"
- },
- {
- "name": "openrouter",
- "api_key": "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",
- "base_url": "https://openrouter.ai/api/v1"
- }
- ],
- "use_smart_weights": true,
- "quality_mode": "精细 (约2-3分钟)",
- "sd_steps": 35,
- "sd_cfg_scale": 6,
- "sd_negative_prompt": "",
- "sched_comment_on": true,
- "sched_like_on": true,
- "sched_fav_on": true,
- "sched_reply_on": true,
- "sched_publish_on": true,
- "sched_c_min": 15,
- "sched_c_max": 45,
- "sched_l_min": 10,
- "sched_l_max": 30,
- "sched_like_count": 5,
- "sched_fav_min": 12,
- "sched_fav_max": 35,
- "sched_fav_count": 3,
- "sched_r_min": 20,
- "sched_r_max": 60,
- "sched_reply_max": 3,
- "sched_p_min": 60,
- "sched_p_max": 180,
- "sched_start_hour": 8,
- "sched_end_hour": 23,
- "auto_like_count": 5,
- "auto_fav_count": 3,
- "auto_reply_max": 5,
- "learn_interval": 6,
- "queue_gen_count": 3,
- "xsec_token": "AB1StlX7ffxsEkfyNuTFDesPlV2g1haPcYuh1-AkYcQxo="
-}
\ No newline at end of file
diff --git a/config.example.json b/config/config.example.json
similarity index 100%
rename from config.example.json
rename to config/config.example.json
diff --git a/docker-compose.yml b/docker-compose.yml
index c352f3c..b3c66c0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,8 +11,8 @@ services:
ports:
- "7860:7860"
volumes:
- # 配置文件挂载(首次请从 config.example.json 复制并填写)
- - ./config.json:/app/config.json
+ # 配置文件挂载(首次请从 config/config.example.json 复制并填写)
+ - ./config/config.json:/app/config/config.json
# 工作目录(导出的文案 & 图片)
- ./xhs_workspace:/app/xhs_workspace
environment:
diff --git a/mcp.md b/docs/mcp.md
similarity index 100%
rename from mcp.md
rename to docs/mcp.md
diff --git a/main.py b/main.py
index 65f6c15..aa86b24 100644
--- a/main.py
+++ b/main.py
@@ -17,11 +17,11 @@ from PIL import Image
import matplotlib
import matplotlib.pyplot as plt
-from config_manager import ConfigManager, OUTPUT_DIR
-from llm_service import LLMService
-from sd_service import SDService, DEFAULT_NEGATIVE, FACE_IMAGE_PATH, SD_PRESET_NAMES, get_sd_preset, get_model_profile, get_model_profile_info, detect_model_profile, SD_MODEL_PROFILES
-from mcp_client import MCPClient, get_mcp_client
-from analytics_service import AnalyticsService
+from services.config_manager import ConfigManager, OUTPUT_DIR
+from services.llm_service import LLMService
+from services.sd_service import SDService, DEFAULT_NEGATIVE, FACE_IMAGE_PATH, SD_PRESET_NAMES, get_sd_preset, get_model_profile, get_model_profile_info, detect_model_profile, SD_MODEL_PROFILES
+from services.mcp_client import MCPClient, get_mcp_client
+from services.analytics_service import AnalyticsService
from ui.tab_create import build_tab
# ================= matplotlib 中文字体配置 =================
@@ -37,12 +37,13 @@ plt.rcParams["axes.unicode_minus"] = False
# ================= 日志配置 =================
+os.makedirs("logs", exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[
logging.StreamHandler(),
- logging.FileHandler("autobot.log", encoding="utf-8"),
+ logging.FileHandler("logs/autobot.log", encoding="utf-8"),
],
)
logger = logging.getLogger("autobot")
@@ -59,7 +60,7 @@ mcp = get_mcp_client(cfg.get("mcp_url", "http://localhost:18060/mcp"))
analytics = AnalyticsService(OUTPUT_DIR)
# ================= 发布队列 =================
-from publish_queue import (
+from services.publish_queue import (
PublishQueue, QueuePublisher,
STATUS_DRAFT, STATUS_APPROVED, STATUS_SCHEDULED, STATUS_PUBLISHING,
STATUS_PUBLISHED, STATUS_FAILED, STATUS_REJECTED, STATUS_LABELS,
diff --git a/main_v1_backup.py b/main_v1_backup.py
deleted file mode 100644
index abbcd12..0000000
--- a/main_v1_backup.py
+++ /dev/null
@@ -1,264 +0,0 @@
-import gradio as gr
-import requests
-import json
-import base64
-import io
-import os
-import time
-import re
-import shutil
-import platform
-import subprocess
-from PIL import Image
-
-# ================= 0. 基础配置与工具 =================
-
-# 强制不走代理连接本地 SD
-os.environ['NO_PROXY'] = '127.0.0.1,localhost'
-
-CONFIG_FILE = "config.json"
-OUTPUT_DIR = "xhs_workspace"
-os.makedirs(OUTPUT_DIR, exist_ok=True)
-
-class ConfigManager:
- @staticmethod
- def load():
- if os.path.exists(CONFIG_FILE):
- try:
- with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
- return json.load(f)
- except:
- pass
- return {
- "api_key": "",
- "base_url": "https://api.openai.com/v1",
- "sd_url": "http://127.0.0.1:7860",
- "model": "gpt-3.5-turbo"
- }
-
- @staticmethod
- def save(config_data):
- with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
- json.dump(config_data, f, indent=4, ensure_ascii=False)
-
-# ================= 1. 核心逻辑功能 =================
-
-def get_llm_models(api_key, base_url):
- if not api_key or not base_url:
- return gr.update(choices=[]), "⚠️ 请先填写配置"
- try:
- url = f"{base_url.rstrip('/')}/models"
- headers = {"Authorization": f"Bearer {api_key}"}
- response = requests.get(url, headers=headers, timeout=10)
- if response.status_code == 200:
- data = response.json()
- models = [item['id'] for item in data.get('data', [])]
-
- # 保存配置
- cfg = ConfigManager.load()
- cfg['api_key'] = api_key
- cfg['base_url'] = base_url
- ConfigManager.save(cfg)
-
- # 修复警告:允许自定义值
- return gr.update(choices=models, value=models[0] if models else None), f"✅ 已连接,加载 {len(models)} 个模型"
- return gr.update(), f"❌ 连接失败: {response.status_code}"
- except Exception as e:
- return gr.update(), f"❌ 错误: {e}"
-
-def generate_copy(api_key, base_url, model, topic, style):
- if not api_key: return "", "", "", "❌ 缺 API Key"
-
- # --- 核心修改:优化了 Prompt,增加字数和违禁词限制 ---
- system_prompt = """
- 你是一个小红书爆款内容专家。请根据用户主题生成内容。
-
- 【标题规则】(严格执行):
- 1. 长度限制:必须控制在 18 字以内(含Emoji),绝对不能超过 20 字!
- 2. 格式要求:Emoji + 爆点关键词 + 核心痛点。
- 3. 禁忌:禁止使用“第一”、“最”、“顶级”等绝对化广告法违禁词。
- 4. 风格:二极管标题(震惊/后悔/必看/避雷/哭了),具有强烈的点击欲望。
-
- 【正文规则】:
- 1. 口语化,多用Emoji,分段清晰,不堆砌长句。
- 2. 结尾必须有 5 个以上相关话题标签(#)。
-
- 【绘图 Prompt】:
- 生成对应的 Stable Diffusion 英文提示词,强调:masterpiece, best quality, 8k, soft lighting, ins style。
-
- 返回 JSON 格式:
- {"title": "...", "content": "...", "sd_prompt": "..."}
- """
-
- try:
- headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
- payload = {
- "model": model,
- "messages": [
- {"role": "system", "content": system_prompt},
- {"role": "user", "content": f"主题:{topic}\n风格:{style}"}
- ],
- "response_format": {"type": "json_object"}
- }
- resp = requests.post(f"{base_url.rstrip('/')}/chat/completions", headers=headers, json=payload, timeout=60)
-
- content = resp.json()['choices'][0]['message']['content']
- content = re.sub(r'```json\s*|```', '', content).strip()
- data = json.loads(content)
-
- # --- 双重保险:Python 强制截断 ---
- title = data.get('title', '')
- # 如果 LLM 不听话超过了20字,强制截断并保留前19个字+省略号,或者直接保留前20个
- if len(title) > 20:
- title = title[:20]
-
- return title, data.get('content', ''), data.get('sd_prompt', ''), "✅ 文案生成完毕"
- except Exception as e:
- return "", "", "", f"❌ 生成失败: {e}"
-
-def get_sd_models(sd_url):
- try:
- resp = requests.get(f"{sd_url}/sdapi/v1/sd-models", timeout=3)
- if resp.status_code == 200:
- models = [m['title'] for m in resp.json()]
- return gr.update(choices=models, value=models[0] if models else None), "✅ SD 已连接"
- return gr.update(choices=[]), "❌ SD 连接失败"
- except:
- return gr.update(choices=[]), "❌ SD 未启动或端口错误"
-
-def generate_images(sd_url, prompt, neg_prompt, model, steps, cfg):
- if not model: return None, "❌ 未选择模型"
-
- # 切换模型
- try:
- requests.post(f"{sd_url}/sdapi/v1/options", json={"sd_model_checkpoint": model})
- except:
- pass # 忽略切换错误,继续尝试生成
-
- payload = {
- "prompt": prompt,
- "negative_prompt": neg_prompt,
- "steps": steps,
- "cfg_scale": cfg,
- "width": 768,
- "height": 1024,
- "batch_size": 2
- }
-
- try:
- resp = requests.post(f"{sd_url}/sdapi/v1/txt2img", json=payload, timeout=120)
- images = []
- for i in resp.json()['images']:
- img = Image.open(io.BytesIO(base64.b64decode(i)))
- images.append(img)
- return images, "✅ 图片生成完毕"
- except Exception as e:
- return None, f"❌ 绘图失败: {e}"
-
-def one_click_export(title, content, images):
- if not title: return "❌ 无法导出:没有标题"
-
- safe_title = re.sub(r'[\\/*?:"<>|]', "", title)[:20]
- folder_name = f"{int(time.time())}_{safe_title}"
- folder_path = os.path.join(OUTPUT_DIR, folder_name)
- os.makedirs(folder_path, exist_ok=True)
-
- with open(os.path.join(folder_path, "文案.txt"), "w", encoding="utf-8") as f:
- f.write(f"{title}\n\n{content}")
-
- if images:
- for idx, img in enumerate(images):
- img.save(os.path.join(folder_path, f"图{idx+1}.png"))
-
- try:
- if platform.system() == "Windows":
- os.startfile(folder_path)
- elif platform.system() == "Darwin":
- subprocess.call(["open", folder_path])
- else:
- subprocess.call(["xdg-open", folder_path])
- return f"✅ 已导出至: {folder_path}"
- except:
- return f"✅ 已导出: {folder_path}"
-
-# ================= 2. UI 界面构建 =================
-
-cfg = ConfigManager.load()
-
-with gr.Blocks(title="小红书全自动工作台", theme=gr.themes.Soft()) as app:
- gr.Markdown("## 🍒 小红书 AI 爆文生产工坊")
-
- state_images = gr.State([])
-
- with gr.Row():
- with gr.Column(scale=1):
- with gr.Accordion("⚙️ 系统设置 (自动保存)", open=True):
- api_key = gr.Textbox(label="LLM API Key", value=cfg['api_key'], type="password")
- base_url = gr.Textbox(label="Base URL", value=cfg['base_url'])
- sd_url = gr.Textbox(label="SD URL", value=cfg['sd_url'])
-
- with gr.Row():
- btn_connect = gr.Button("🔗 连接并获取模型", size="sm")
- btn_refresh_sd = gr.Button("🔄 刷新 SD", size="sm")
-
- # 修复点 1:允许自定义值,防止报错
- llm_model = gr.Dropdown(label="选择 LLM 模型", value=cfg['model'], allow_custom_value=True, interactive=True)
- sd_model = gr.Dropdown(label="选择 SD 模型", allow_custom_value=True, interactive=True)
- status_bar = gr.Markdown("等待就绪...")
-
- gr.Markdown("### 💡 内容构思")
- topic = gr.Textbox(label="笔记主题", placeholder="例如:优衣库早春穿搭")
- style = gr.Dropdown(["好物种草", "干货教程", "情绪共鸣", "生活Vlog"], label="风格", value="好物种草")
- btn_step1 = gr.Button("✨ 第一步:生成文案方案", variant="primary")
-
- with gr.Column(scale=1):
- gr.Markdown("### 📝 文案确认")
- # 修复点 2:去掉了 show_copy_button 参数,兼容旧版 Gradio
- res_title = gr.Textbox(label="标题 (AI生成)", interactive=True)
- res_content = gr.TextArea(label="正文 (AI生成)", lines=10, interactive=True)
- res_prompt = gr.TextArea(label="绘图提示词", lines=4, interactive=True)
-
- with gr.Accordion("🎨 绘图参数", open=False):
- neg_prompt = gr.Textbox(label="反向词", value="nsfw, lowres, bad anatomy, text, error")
- steps = gr.Slider(15, 50, value=25, label="步数")
- cfg_scale = gr.Slider(1, 15, value=7, label="相关性 (CFG)")
-
- btn_step2 = gr.Button("🎨 第二步:开始绘图", variant="primary")
-
- with gr.Column(scale=1):
- gr.Markdown("### 🖼️ 视觉结果")
- gallery = gr.Gallery(label="生成预览", columns=1, height="auto")
- btn_export = gr.Button("📂 一键导出 (文案+图片)", variant="stop")
- export_msg = gr.Markdown("")
-
- # ================= 3. 事件绑定 =================
-
- btn_connect.click(fn=get_llm_models, inputs=[api_key, base_url], outputs=[llm_model, status_bar])
- btn_refresh_sd.click(fn=get_sd_models, inputs=[sd_url], outputs=[sd_model, status_bar])
-
- btn_step1.click(
- fn=generate_copy,
- inputs=[api_key, base_url, llm_model, topic, style],
- outputs=[res_title, res_content, res_prompt, status_bar]
- )
-
- def on_img_gen(sd_url, p, np, m, s, c):
- imgs, msg = generate_images(sd_url, p, np, m, s, c)
- return imgs, imgs, msg
-
- btn_step2.click(
- fn=on_img_gen,
- inputs=[sd_url, res_prompt, neg_prompt, sd_model, steps, cfg_scale],
- outputs=[gallery, state_images, status_bar]
- )
-
- btn_export.click(
- fn=one_click_export,
- inputs=[res_title, res_content, state_images],
- outputs=[export_msg]
- )
-
- app.load(fn=get_sd_models, inputs=[sd_url], outputs=[sd_model, status_bar])
-
-if __name__ == "__main__":
- app.launch(inbrowser=True)
\ No newline at end of file
diff --git a/my_face.png b/my_face.png
deleted file mode 100644
index 2739f95..0000000
Binary files a/my_face.png and /dev/null differ
diff --git a/myself.jpg b/myself.jpg
deleted file mode 100644
index b7650bd..0000000
Binary files a/myself.jpg and /dev/null differ
diff --git a/openspec/changes/archive/2026-02-27-cleanup-project-structure/.openspec.yaml b/openspec/changes/archive/2026-02-27-cleanup-project-structure/.openspec.yaml
new file mode 100644
index 0000000..85ae75c
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-cleanup-project-structure/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-02-26
diff --git a/openspec/changes/archive/2026-02-27-cleanup-project-structure/design.md b/openspec/changes/archive/2026-02-27-cleanup-project-structure/design.md
new file mode 100644
index 0000000..297c0be
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-cleanup-project-structure/design.md
@@ -0,0 +1,36 @@
+## Context
+
+项目工作区根目录(`f:\3_Personal\AI\xhs_bot\autobot\`)随开发过程积累了多个无用文件:旧版备份(`main_v1_backup.py`)、配置副本(`config copy.json`)、一次性测试脚本(`_test_config_save.py`)、临时记录(`Todo.md`)、运行日志(`autobot.log``)以及散落的个人图片(4 张 `.png`/`.jpg`)。
+
+这些文件虽已被 `.gitignore` 排除出版本控制,但依然存在于工作区,干扰了目录可读性。无需改动任何运行时逻辑。
+
+## Goals / Non-Goals
+
+**Goals:**
+- 删除 5 个明确无用的文件
+- 将 4 张个人图片归入 `assets/faces/` 目录并更新 `.gitignore`
+- 完善 `.gitignore` 注释与规则覆盖
+- 建立 `project-structure` 规范 spec,为后续维护提供参考
+
+**Non-Goals:**
+- 不将根目录服务文件(`analytics_service.py`、`llm_service.py` 等)迁移至 `services/`(涉及 import 改动,留作独立变更)
+- 不修改任何 Python 业务代码
+- 不调整 Docker / CI 配置
+
+## Decisions
+
+**决策 1:图片归档至 `assets/faces/`**
+- 选择 `assets/faces/` 而非直接删除,因为换脸功能可能在 UI 中需要默认头像路径,保留文件可避免破坏演示/教学场景
+- 替代方案:直接删除 → 风险是用户文档引用了这些文件路径
+
+**决策 2:`assets/faces/` 整体纳入 `.gitignore`**
+- 个人图片属于隐私敏感数据,不应进入版本控制
+- 在 `.gitignore` 中新增 `assets/faces/` 规则,并补充注释
+
+**决策 3:`Todo.md` 直接删除而非迁移**
+- 内容已过时且已有 openspec 任务管理体系替代,迁移意义不大
+
+## Risks / Trade-offs
+
+- **[风险] `my_face.png` 等文件路径可能被 `config.json` 引用** → 缓解:删除前检查 `config.json` 中是否存在相关路径字段;若存在则仅移动不删除,并在 `config.example.json` 中更新说明
+- **[风险] `_autostart.bat` / `_autostart.vbs` 与 `assets/faces/` 路径无关,但同属根目录非标准文件** → 此次保留,自动启动脚本属于有效使用场景
diff --git a/openspec/changes/archive/2026-02-27-cleanup-project-structure/proposal.md b/openspec/changes/archive/2026-02-27-cleanup-project-structure/proposal.md
new file mode 100644
index 0000000..f9af044
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-cleanup-project-structure/proposal.md
@@ -0,0 +1,33 @@
+## Why
+
+项目根目录混入备份文件、临时测试脚本、个人图片等无关文件,导致目录结构混乱、难以辨别哪些是正式代码。部分内容(人脸图片、日志文件)已被 `.gitignore` 排除但仍残留在工作区,须手动清理。
+
+## What Changes
+
+- **删除冗余/备份文件**:
+ - `main_v1_backup.py`(旧版备份,已被 `.gitignore` 的 `*_backup.py` 规则覆盖)
+ - `config copy.json`(配置副本,已被 `.gitignore` 覆盖)
+ - `_test_config_save.py`(一次性测试脚本,无保留价值)
+ - `Todo.md`(个人临时记录,已有 openspec 任务管理替代)
+ - `autobot.log`(运行日志,已被 `*.log` 规则覆盖)
+
+- **整理个人图片**:将散落在根目录的人脸/头像图片(`beauty.png`、`myself.jpg`、`my_face.png`、`zjz.png`)移入 `assets/faces/` 目录,更新 `.gitignore` 将该目录纳入忽略范围
+
+- **评估根目录服务文件**:检查 `analytics_service.py`、`llm_service.py`、`sd_service.py`、`mcp_client.py`、`publish_queue.py`、`config_manager.py` 是否应迁移至 `services/`;若涉及大量 import 改动则列为独立后续变更,本次仅做评估记录
+
+- **补全 `.gitignore`**:确保 `assets/faces/`、`*.log`、`__pycache__/` 等规则完整且注释清晰
+
+## Capabilities
+
+### New Capabilities
+- `project-structure`: 定义项目标准目录结构、根目录文件清单规范及 `.gitignore` 策略
+
+### Modified Capabilities
+(无需修改现有 spec)
+
+## Impact
+
+- 删除 5 个文件,不影响任何运行时逻辑
+- `assets/faces/` 目录新增后,换脸功能的默认头像路径可能需要在 `config.example.json` 中更新说明
+- 对 Docker 构建无影响(`.dockerignore` 已存在)
+- 无 API 变更,无依赖变更
diff --git a/openspec/changes/archive/2026-02-27-cleanup-project-structure/specs/project-structure/spec.md b/openspec/changes/archive/2026-02-27-cleanup-project-structure/specs/project-structure/spec.md
new file mode 100644
index 0000000..c387aab
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-cleanup-project-structure/specs/project-structure/spec.md
@@ -0,0 +1,49 @@
+## ADDED Requirements
+
+### Requirement: 根目录只包含正式项目文件
+项目根目录 SHALL 不包含备份文件、一次性测试脚本、个人媒体资源或临时记录文件。具体地:
+
+- `*_backup.py` 命名的文件 SHALL 不存在于根目录
+- `config copy.json`(或同类配置副本)SHALL 不存在
+- `_test_*.py` 命名的一次性测试脚本 SHALL 不存在于根目录
+- `*.log` 运行日志文件 SHALL 不进入版本控制(由 `.gitignore` 保证)
+- 个人图片(`.png`、`.jpg` 等媒体文件)SHALL 不散落在根目录,统一归入 `assets/` 下对应子目录
+
+#### Scenario: 克隆仓库后根目录无冗余文件
+- **WHEN** 开发者执行 `git clone` 并查看根目录
+- **THEN** 根目录 SHALL 仅包含:`main.py`、服务模块文件(`*_service.py`、`*_client.py`、`*_manager.py`、`*_queue.py`)、标准配置(`config.example.json`、`requirements.txt`、`Dockerfile`、`docker-compose.yml`、`.gitignore`、`.dockerignore`)、自动启动脚本(`_autostart.*`)、文档(`README.md`、`CHANGELOG.md`、`CONTRIBUTING.md`、`mcp.md`)
+
+#### Scenario: 备份文件不出现在版本控制
+- **WHEN** 开发者执行 `git status` 或 `git ls-files`
+- **THEN** 输出中 SHALL 不包含 `*_backup.py`、`config copy.json` 等备份/副本文件
+
+### Requirement: 媒体资源归入 assets/ 目录
+项目所需的图片等媒体资源 SHALL 存放于 `assets/` 目录下的对应子目录,根据用途分类:
+
+- 换脸/头像相关图片 SHALL 放入 `assets/faces/`
+- `assets/faces/` SHALL 被 `.gitignore` 覆盖(不进入版本控制,属隐私数据)
+
+#### Scenario: 换脸图片存放路径符合规范
+- **WHEN** 用户配置换脸头像功能
+- **THEN** 头像文件 SHALL 存放于 `assets/faces/` 目录,而非项目根目录
+
+#### Scenario: assets/faces/ 不进入版本控制
+- **WHEN** 开发者执行 `git status`
+- **THEN** `assets/faces/` 目录下的文件 SHALL 显示为已忽略(不出现在 staged 或 unstaged 区域)
+
+### Requirement: .gitignore 覆盖所有非版本控制内容
+项目 `.gitignore` SHALL 包含以下规则类别,且每类规则 SHALL 有注释说明用途:
+
+- Python 编译产物(`__pycache__/`、`*.py[cod]`)
+- 虚拟环境目录(`.venv/`、`venv/`)
+- 敏感配置(`config.json`、`cookies.json`、`*.cookie`)
+- 运行日志(`*.log`、`logs/`)
+- 备份与副本文件(`*_backup.py`、`config copy.json`)
+- 个人媒体资产(`assets/faces/`)
+- IDE 配置(`.vscode/`、`.idea/`)
+- 系统文件(`.DS_Store`、`Thumbs.db`)
+- 工作空间输出目录(`xhs_workspace/`)
+
+#### Scenario: 新增备份文件不被 git 追踪
+- **WHEN** 开发者在根目录创建 `main_v2_backup.py`
+- **THEN** `git status` SHALL 将其显示为已忽略文件
diff --git a/openspec/changes/archive/2026-02-27-cleanup-project-structure/tasks.md b/openspec/changes/archive/2026-02-27-cleanup-project-structure/tasks.md
new file mode 100644
index 0000000..2a84325
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-cleanup-project-structure/tasks.md
@@ -0,0 +1,25 @@
+## 1. 删除冗余文件
+
+- [x] 1.1 删除 `main_v1_backup.py`(旧版备份,无保留价值)
+- [x] 1.2 删除 `config copy.json`(配置副本,无保留价值)
+- [x] 1.3 删除 `_test_config_save.py`(一次性测试脚本)
+- [x] 1.4 删除 `Todo.md`(临时记录,已由 openspec 替代)
+- [x] 1.5 删除 `autobot.log`(运行日志,已被 `.gitignore` 覆盖)
+
+## 2. 整理图片资源
+
+- [x] 2.1 创建 `assets/faces/` 目录
+- [x] 2.2 将 `my_face.png` 移入 `assets/faces/my_face.png`
+- [x] 2.3 将 `beauty.png`、`myself.jpg`、`zjz.png` 移入 `assets/faces/`
+- [x] 2.4 更新 `sd_service.py` 第 22 行的 `FACE_IMAGE_PATH`,将路径从 `"my_face.png"` 改为 `os.path.join(os.path.dirname(__file__), "assets", "faces", "my_face.png")`
+
+## 3. 完善 .gitignore
+
+- [x] 3.1 在 `.gitignore` 中新增 `assets/faces/` 规则,并加注释(个人头像不入版本控制)
+- [x] 3.2 确认 `.gitignore` 中 `*.log` 规则已存在(已有,检查确认即可)
+
+## 4. 回归验证
+
+- [ ] 4.1 启动应用 `python main.py`,确认换脸功能(⚙️ 配置 Tab AI 换脸头像)加载正常
+- [ ] 4.2 确认 `SDService.load_face_image()` 能正确读取新路径下的 `my_face.png`
+- [x] 4.3 执行 `git status` 确认 `assets/faces/` 已被忽略,根目录无残留冗余文件
diff --git a/openspec/changes/archive/2026-02-27-open-source-ready/.openspec.yaml b/openspec/changes/archive/2026-02-27-open-source-ready/.openspec.yaml
new file mode 100644
index 0000000..d1c6cc6
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-open-source-ready/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-02-27
diff --git a/openspec/changes/archive/2026-02-27-open-source-ready/design.md b/openspec/changes/archive/2026-02-27-open-source-ready/design.md
new file mode 100644
index 0000000..a3fd42f
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-open-source-ready/design.md
@@ -0,0 +1,55 @@
+## Context
+
+当前项目根目录同时存在 6 个业务服务文件(`config_manager.py`、`llm_service.py`、`sd_service.py`、`mcp_client.py`、`analytics_service.py`、`publish_queue.py`)与 `services/` 包目录并列,架构层次混乱。`services/` 下游代码(`scheduler.py` 等)通过裸名导入(flat import)引用这些文件,依赖 Python 将根目录隐式加入 `sys.path` 这一运行时特性。
+
+迁移约束:
+- `services/__init__.py` 已存在,`services/` 是合法 Python 包
+- 所有受影响文件共约 20 处 import,分布在 `main.py`、`ui/app.py`、`ui/tab_create.py`、`services/*.py`
+- 必须保证 Python 不产生循环导入(circular import)
+
+## Goals / Non-Goals
+
+**Goals:**
+- 将 6 个服务文件移入 `services/`,根目录只保留 `main.py` 一个 Python 文件
+- 所有 import 使用绝对路径(`from services.xxx import ...`),保持一致性、可读性
+- `services/` 内部文件之间使用相对导入(`from .xxx import ...`),减少路径依赖
+- 不引入任何新的运行时依赖或行为变更
+
+**Non-Goals:**
+- 不拆分或合并任何模块的内部逻辑
+- 不更改 `services/__init__.py` 的公开导出(除非需要兼容性垫片)
+- 不迁移 `ui/` 下的文件(已有独立模块结构)
+
+## Decisions
+
+**决策 1:外部文件用绝对导入 `from services.xxx import ...`**
+- `main.py`、`ui/app.py`、`ui/tab_create.py` 均从根目录运行,绝对导入路径清晰、错误信息可读
+- 替代:在 `services/__init__.py` 重新导出所有符号(向后兼容)→ 增加维护负担,拒绝
+
+**决策 2:`services/` 内文件之间用相对导入 `from .xxx import ...`**
+- 避免 `services/` 内部对根目录的隐式依赖,打包或测试时不受 `sys.path` 影响
+- 替代:也用绝对导入 → 可行但不如相对导入内聚
+
+**决策 3:分两阶段迁移(先移文件,再改 import)**
+- 防止中间状态同时修改多文件导致错误难以定位
+- 迁移顺序:`config_manager` → `mcp_client` → `llm_service` → `sd_service` → `analytics_service` → `publish_queue`(按依赖深度从浅到深)
+
+**决策 4:不添加兼容性垫片(shim)**
+- 项目无外部 PyPI 消费者,无向后兼容压力,直接修改所有 import 更干净
+
+## Risks / Trade-offs
+
+- **[风险] `services/` 内循环导入** → 缓解:迁移前用 `grep` 确认依赖关系,`config_manager` 通常是叶节点(最少依赖),优先迁移
+- **[风险] `ui/app.py` 在迁移中途启动失败** → 缓解:一次性修改所有文件 import,不留半迁移状态;迁移后立即用 `ast.parse()` 验证语法
+- **[风险] Dockerfile `COPY . .` 已是整目录复制** → 无影响,`services/` 子目录正常复制
+- **[风险] CI 首次运行可能因 GitHub Secrets 缺失(如无 `GITHUB_TOKEN`)报错** → 缓解:CI 仅做静态检查,不需要 Secrets
+
+## Migration Plan
+
+1. 将 6 个文件 `Move-Item` 到 `services/`
+2. 批量替换所有外部文件(`main.py`、`ui/*.py`)中的 import:`from xxx` → `from services.xxx`
+3. 批量替换 `services/*.py` 中的内部 import:`from xxx` → `from .xxx`
+4. 语法验证:对所有修改文件执行 `ast.parse()`
+5. 启动验证:`python -c "from ui.app import build_app"` 确认无导入错误
+
+**回滚方案:** 删除 `services/` 下新迁移的文件,将 git 恢复(或手动移回根目录),改回 import)
diff --git a/openspec/changes/archive/2026-02-27-open-source-ready/proposal.md b/openspec/changes/archive/2026-02-27-open-source-ready/proposal.md
new file mode 100644
index 0000000..de5e52d
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-open-source-ready/proposal.md
@@ -0,0 +1,57 @@
+## Why
+
+项目代码已具备完整功能,但存在两个问题:①缺少优秀开源项目的标准配置(Issue/PR 模板、CI、Code of Conduct、Security Policy),降低社区协作门槛和可信度;②根目录混杂 6 个服务文件(`llm_service.py`、`sd_service.py`、`mcp_client.py`、`analytics_service.py`、`publish_queue.py`、`config_manager.py`),与 `services/` 模块目录并列,架构层次不清晰。两个问题结合目录清理后的稳定状态一并解决。
+
+## What Changes
+
+### 目录结构整理
+- **将 6 个根目录服务文件迁移至 `services/`**:
+ - `config_manager.py` → `services/config_manager.py`
+ - `llm_service.py` → `services/llm_service.py`
+ - `sd_service.py` → `services/sd_service.py`
+ - `mcp_client.py` → `services/mcp_client.py`
+ - `analytics_service.py` → `services/analytics_service.py`
+ - `publish_queue.py` → `services/publish_queue.py`
+- **更新所有受影响文件的 import 语句**(`main.py`、`ui/app.py`、`ui/tab_create.py`、`services/*.py` 共约 20 处)
+- **更新 `services/__init__.py`** 导出新增模块(可选,保持向后兼容)
+
+目标根目录结构:
+```
+autobot/
+├── main.py # 程序入口(唯一根目录 .py)
+├── ui/ # UI 层 (Gradio)
+├── services/ # 全部业务逻辑层(迁移后)
+├── assets/ # 静态资源
+├── config.example.json # 配置模板
+├── requirements.txt # 生产依赖
+├── requirements-dev.txt # 开发依赖(新增)
+├── Dockerfile / docker-compose.yml
+└── *.md / LICENSE # 文档
+```
+
+### 开源社区标准文件
+- **新增 GitHub Issue 模板** — `.github/ISSUE_TEMPLATE/bug_report.md` 和 `feature_request.md`
+- **新增 PR 模板** — `.github/pull_request_template.md`
+- **新增 CI 工作流** — `.github/workflows/ci.yml`(Push/PR 触发 ruff + 导入验证)
+- **新增 Code of Conduct** — `CODE_OF_CONDUCT.md`(Contributor Covenant v2.1 中文版)
+- **新增 Security Policy** — `SECURITY.md`
+- **新增 `requirements-dev.txt`** — ruff、pre-commit
+- **完善 README**:顶部徽章(Python、License、CI)、修正项目结构图、替换 `your-username` 占位符
+
+## Capabilities
+
+### New Capabilities
+- `project-restructure`: 根目录服务文件迁移至 `services/`,全量 import 更新,分层架构清晰
+- `oss-community-health`: Issue 模板、PR 模板、Code of Conduct、Security Policy
+- `oss-ci-workflow`: GitHub Actions CI(ruff 代码检查 + 导入验证)
+- `oss-readme-polish`: README 徽章、结构说明修正、占位符修复
+
+### Modified Capabilities
+(无需修改现有 spec)
+
+## Impact
+
+- `project-restructure`:影响 `main.py`、`ui/app.py`、`ui/tab_create.py`、`services/` 下全部文件(仅修改 import 路径,不改业务逻辑)
+- 社区文件和 CI 仅新增,不修改现有代码
+- Dockerfile 的 `COPY` 指令兼容(整目录复制,无需修改)
+- 对运行时行为、Docker 部署、依赖均无影响
diff --git a/openspec/changes/archive/2026-02-27-open-source-ready/specs/oss-ci-workflow/spec.md b/openspec/changes/archive/2026-02-27-open-source-ready/specs/oss-ci-workflow/spec.md
new file mode 100644
index 0000000..a9370f2
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-open-source-ready/specs/oss-ci-workflow/spec.md
@@ -0,0 +1,37 @@
+## ADDED Requirements
+
+### Requirement: GitHub Actions CI 工作流在 Push 和 PR 时自动触发
+项目 SHALL 在 `.github/workflows/ci.yml` 提供持续集成工作流,在每次 Push 到 `main` 分支或创建 Pull Request 时自动执行代码质量检查。
+
+#### Scenario: CI 在 PR 时自动运行
+- **WHEN** 贡献者向 `main` 分支提交 Pull Request
+- **THEN** GitHub Actions SHALL 自动触发 CI 工作流,在 PR 页面显示检查结果
+
+#### Scenario: CI 通过后状态徽章更新
+- **WHEN** CI 工作流执行完毕
+- **THEN** 工作流状态 SHALL 可通过 Badge URL 获取,用于 README 展示
+
+### Requirement: CI 执行 ruff 代码风格检查
+CI 工作流 SHALL 使用 `ruff` 对所有 Python 文件执行代码风格和常见错误检查,ruff 配置 SHALL 允许项目现有代码通过(宽松规则集)。
+
+#### Scenario: 代码风格检查通过
+- **WHEN** CI 工作流中的 ruff 步骤执行
+- **THEN** 对 `*.py` 文件的 ruff 检查 SHALL 以 exit code 0 退出,工作流标记为 passed
+
+#### Scenario: 引入明显错误时 CI 失败
+- **WHEN** PR 中包含未使用的 import 或明显语法问题
+- **THEN** ruff SHALL 检测到并返回非零 exit code,导致 CI 失败
+
+### Requirement: CI 执行导入验证
+CI 工作流 SHALL 执行 Python 导入验证步骤,确认核心模块可被正常导入,及早发现迁移引入的导入错误。
+
+#### Scenario: 导入验证通过
+- **WHEN** CI 执行导入验证步骤
+- **THEN** `python -c "from services.config_manager import ConfigManager"` 等关键导入 SHALL 成功执行
+
+### Requirement: 提供 requirements-dev.txt 开发依赖声明
+项目根目录 SHALL 包含 `requirements-dev.txt`,声明开发和 CI 所需依赖(ruff、pre-commit 等),与生产依赖 `requirements.txt` 分离。
+
+#### Scenario: 安装开发依赖命令可正常执行
+- **WHEN** 开发者执行 `pip install -r requirements-dev.txt`
+- **THEN** 所有开发工具 SHALL 成功安装,无版本冲突
diff --git a/openspec/changes/archive/2026-02-27-open-source-ready/specs/oss-community-health/spec.md b/openspec/changes/archive/2026-02-27-open-source-ready/specs/oss-community-health/spec.md
new file mode 100644
index 0000000..dea3219
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-open-source-ready/specs/oss-community-health/spec.md
@@ -0,0 +1,33 @@
+## ADDED Requirements
+
+### Requirement: 提供标准化 GitHub Issue 模板
+项目 SHALL 在 `.github/ISSUE_TEMPLATE/` 下提供至少两个 Issue 模板:Bug 报告模板和功能请求模板,引导贡献者提供必要信息。
+
+#### Scenario: Bug 报告模板包含必要字段
+- **WHEN** 用户在 GitHub 上新建 Issue 并选择「Bug 报告」
+- **THEN** 模板 SHALL 包含:问题描述、复现步骤、预期行为、实际行为、环境信息(Python 版本、操作系统)
+
+#### Scenario: 功能请求模板包含场景描述
+- **WHEN** 用户选择「功能请求」模板
+- **THEN** 模板 SHALL 包含:问题/需求背景、期望的解决方案、替代方案考虑
+
+### Requirement: 提供 Pull Request 模板
+项目 SHALL 在 `.github/pull_request_template.md` 提供 PR 模板,引导贡献者说明变更范围和测试情况。
+
+#### Scenario: PR 模板包含变更说明和测试确认
+- **WHEN** 贡献者在 GitHub 上发起 Pull Request
+- **THEN** 模板 SHALL 包含:变更类型(Bug Fix / Feature / Docs / Refactor)、变更描述、测试说明、相关 Issue 引用
+
+### Requirement: 包含行为准则文件
+项目根目录 SHALL 包含 `CODE_OF_CONDUCT.md`,采用 Contributor Covenant v2.1 中文版,明确社区行为规范和违规处理方式。
+
+#### Scenario: 行为准则文件可访问
+- **WHEN** 贡献者查看项目根目录
+- **THEN** `CODE_OF_CONDUCT.md` SHALL 存在,包含社区行为规范、适用范围、执行说明、联系方式
+
+### Requirement: 包含安全漏洞报告政策
+项目根目录 SHALL 包含 `SECURITY.md`,说明如何负责任地披露安全漏洞、支持的版本范围和响应时间承诺。
+
+#### Scenario: 安全政策文件包含报告方式
+- **WHEN** 安全研究者发现漏洞
+- **THEN** `SECURITY.md` SHALL 提供私下联系方式(邮件或 GitHub Security Advisory),不要求通过公开 Issue 上报
diff --git a/openspec/changes/archive/2026-02-27-open-source-ready/specs/oss-readme-polish/spec.md b/openspec/changes/archive/2026-02-27-open-source-ready/specs/oss-readme-polish/spec.md
new file mode 100644
index 0000000..ae5a603
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-open-source-ready/specs/oss-readme-polish/spec.md
@@ -0,0 +1,29 @@
+## ADDED Requirements
+
+### Requirement: README 顶部展示状态徽章
+`README.md` 顶部(标题下方)SHALL 包含至少三枚徽章:Python 版本要求、License 类型、CI 状态,采用 shields.io 或 GitHub Actions 徽章格式。
+
+#### Scenario: 徽章在 GitHub 页面正常渲染
+- **WHEN** 访问项目 GitHub 主页
+- **THEN** README 顶部 SHALL 显示可点击的 Python、MIT License、CI 状态徽章,链接指向对应资源
+
+### Requirement: README 项目结构图反映实际代码
+`README.md` 中的「项目结构」章节 SHALL 反映迁移后的实际目录结构,包含 `services/`(含所有迁移后文件)和 `ui/`(含 `app.py`、`tab_create.py`)的正确层级。
+
+#### Scenario: 项目结构与 ls 输出一致
+- **WHEN** 开发者对照 README 查看实际文件目录
+- **THEN** README 的结构图 SHALL 与实际 `Get-ChildItem` / `ls` 输出一致,无过时文件或缺失目录
+
+### Requirement: README 不包含 your-username 占位符
+`README.md` 中所有 `your-username` 占位符 SHALL 替换为实际仓库路径说明或格式示例,使克隆/安装命令可直接复制使用。
+
+#### Scenario: 安装命令无需手动替换占位符
+- **WHEN** 用户复制 README 中的 `git clone` 命令
+- **THEN** 命令 SHALL 包含实际仓库 URL 或明确的 `` 格式提示,不出现 `your-username` 字符串
+
+### Requirement: README 使用指南与当前 UI 结构匹配
+`README.md` 中的「使用指南」和「首次使用流程」章节 SHALL 引用当前正确的 Tab 名称和操作路径,与 `ui/app.py` 实际 Tab 顺序保持一致(⚙️ 配置 Tab 已迁移,不再是「展开全局设置折叠块」)。
+
+#### Scenario: 首次使用步骤描述与 UI 一致
+- **WHEN** 新用户按照 README「首次使用流程」操作
+- **THEN** README 中描述的 Tab 名称和操作入口 SHALL 与实际 Gradio UI 一致,用户无需猜测
diff --git a/openspec/changes/archive/2026-02-27-open-source-ready/specs/project-restructure/spec.md b/openspec/changes/archive/2026-02-27-open-source-ready/specs/project-restructure/spec.md
new file mode 100644
index 0000000..24d85a6
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-open-source-ready/specs/project-restructure/spec.md
@@ -0,0 +1,34 @@
+## ADDED Requirements
+
+### Requirement: 服务层文件统一归入 services/ 包
+所有业务服务模块 SHALL 位于 `services/` 目录下,根目录除 `main.py` 外 SHALL 不包含任何 `.py` 业务文件。
+
+迁移文件清单:
+- `config_manager.py` → `services/config_manager.py`
+- `llm_service.py` → `services/llm_service.py`
+- `sd_service.py` → `services/sd_service.py`
+- `mcp_client.py` → `services/mcp_client.py`
+- `analytics_service.py` → `services/analytics_service.py`
+- `publish_queue.py` → `services/publish_queue.py`
+
+#### Scenario: 根目录不存在游离服务文件
+- **WHEN** 开发者查看项目根目录
+- **THEN** 根目录 SHALL 仅含 `main.py` 作为唯一 Python 入口,其余 `.py` 文件均位于 `ui/` 或 `services/` 子目录
+
+### Requirement: 外部模块使用绝对导入访问 services/
+`main.py`、`ui/app.py`、`ui/tab_create.py` 等根目录/UI 层文件在导入服务模块时 SHALL 使用绝对导入格式 `from services. import ...`。
+
+#### Scenario: main.py 正常启动无 ImportError
+- **WHEN** 在项目根目录执行 `python main.py`
+- **THEN** 应用 SHALL 正常启动,不抛出任何 `ImportError` 或 `ModuleNotFoundError`
+
+#### Scenario: UI 层导入路径正确
+- **WHEN** 执行 `python -c "import ui.app"`
+- **THEN** 不抛出导入错误,所有 `from services.*` 引用 SHALL 可正常解析
+
+### Requirement: services/ 内部使用相对导入
+`services/` 包内各模块之间的相互引用 SHALL 使用相对导入格式 `from . import ...`,不依赖根目录在 `sys.path` 中的位置。
+
+#### Scenario: services 内部导入独立于运行上下文
+- **WHEN** 在任意工作目录执行 `python -m services.scheduler`(或类似模块测试)
+- **THEN** 内部相对导入 SHALL 正常解析,不因工作目录不同而失败
diff --git a/openspec/changes/archive/2026-02-27-open-source-ready/tasks.md b/openspec/changes/archive/2026-02-27-open-source-ready/tasks.md
new file mode 100644
index 0000000..519795e
--- /dev/null
+++ b/openspec/changes/archive/2026-02-27-open-source-ready/tasks.md
@@ -0,0 +1,186 @@
+## Tasks
+
+### 1. 迁移服务文件至 services/ 包(project-restructure)
+
+- [x] **1.1** 将 `config_manager.py` 移入 `services/`
+ ```powershell
+ Move-Item config_manager.py services\config_manager.py
+ ```
+
+- [x] **1.2** 将 `mcp_client.py` 移入 `services/`
+ ```powershell
+ Move-Item mcp_client.py services\mcp_client.py
+ ```
+
+- [x] **1.3** 将 `llm_service.py` 移入 `services/`
+ ```powershell
+ Move-Item llm_service.py services\llm_service.py
+ ```
+
+- [x] **1.4** 将 `sd_service.py` 移入 `services/`
+ ```powershell
+ Move-Item sd_service.py services\sd_service.py
+ ```
+
+- [x] **1.5** 将 `analytics_service.py` 移入 `services/`
+ ```powershell
+ Move-Item analytics_service.py services\analytics_service.py
+ ```
+
+- [x] **1.6** 将 `publish_queue.py` 移入 `services/`
+ ```powershell
+ Move-Item publish_queue.py services\publish_queue.py
+ ```
+
+---
+
+### 2. 更新外部文件的绝对导入(main.py、ui/)
+
+- [x] **2.1** 更新 `main.py` 中的导入
+ - `from config_manager import ConfigManager, OUTPUT_DIR` → `from services.config_manager import ConfigManager, OUTPUT_DIR`
+ - `from llm_service import LLMService` → `from services.llm_service import LLMService`
+
+- [x] **2.2** 更新 `ui/app.py` 中的导入
+ - `from config_manager import ConfigManager` → `from services.config_manager import ConfigManager`
+ - `from sd_service import SDService, DEFAULT_NEGATIVE, FACE_IMAGE_PATH, ...` → `from services.sd_service import SDService, DEFAULT_NEGATIVE, FACE_IMAGE_PATH, ...`
+ - `from analytics_service import AnalyticsService` → `from services.analytics_service import AnalyticsService`
+ - `from publish_queue import STATUS_LABELS` → `from services.publish_queue import STATUS_LABELS`
+
+- [x] **2.3** 更新 `ui/tab_create.py` 中的导入(检查并替换所有根目录服务模块引用)
+
+---
+
+### 3. 更新 services/ 内部使用相对导入
+
+- [x] **3.1** 更新 `services/scheduler.py`
+ - `from config_manager import ConfigManager, OUTPUT_DIR` → `from .config_manager import ConfigManager, OUTPUT_DIR`
+ - `from llm_service import LLMService` → `from .llm_service import LLMService`
+ - `from sd_service import SDService` → `from .sd_service import SDService`
+ - `from mcp_client import get_mcp_client` → `from .mcp_client import get_mcp_client`
+ - `from analytics_service import AnalyticsService` → `from .analytics_service import AnalyticsService`
+
+- [x] **3.2** 更新 `services/content.py`
+ - `from config_manager import ConfigManager, OUTPUT_DIR` → `from .config_manager import ConfigManager, OUTPUT_DIR`
+ - `from llm_service import LLMService` → `from .llm_service import LLMService`
+ - `from sd_service import SDService, get_sd_preset` → `from .sd_service import SDService, get_sd_preset`
+ - `from mcp_client import get_mcp_client` → `from .mcp_client import get_mcp_client`
+
+- [x] **3.3** 更新 `services/hotspot.py`
+ - `from llm_service import LLMService` → `from .llm_service import LLMService`
+ - `from mcp_client import get_mcp_client` → `from .mcp_client import get_mcp_client`
+
+- [x] **3.4** 更新 `services/engagement.py`
+ - `from mcp_client import get_mcp_client` → `from .mcp_client import get_mcp_client`
+ - `from llm_service import LLMService` → `from .llm_service import LLMService`
+
+- [x] **3.5** 更新 `services/profile.py`
+ - `from mcp_client import get_mcp_client` → `from .mcp_client import get_mcp_client`
+
+- [x] **3.6** 更新 `services/persona.py`
+ - `from config_manager import ConfigManager` → `from .config_manager import ConfigManager`
+
+- [x] **3.7** 检查 `services/queue_ops.py`、`services/rate_limiter.py`、`services/autostart.py`、`services/connection.py` 有无根目录模块引用,按需更新
+
+---
+
+### 4. 回归验证——导入与语法检查
+
+- [x] **4.1** 对所有修改文件执行 Python 语法验证
+ ```powershell
+ python -c "
+ import ast, pathlib
+ files = ['main.py','ui/app.py','ui/tab_create.py',
+ 'services/scheduler.py','services/content.py',
+ 'services/hotspot.py','services/engagement.py',
+ 'services/profile.py','services/persona.py']
+ for f in files:
+ ast.parse(pathlib.Path(f).read_text(encoding='utf-8'))
+ print(f'OK: {f}')
+ "
+ ```
+
+- [x] **4.2** 执行核心服务导入验证
+ ```powershell
+ python -c "from services.config_manager import ConfigManager; print('config_manager OK')"
+ python -c "from services.llm_service import LLMService; print('llm_service OK')"
+ python -c "from services.sd_service import SDService; print('sd_service OK')"
+ python -c "from services.mcp_client import get_mcp_client; print('mcp_client OK')"
+ python -c "from services.analytics_service import AnalyticsService; print('analytics_service OK')"
+ python -c "from services.publish_queue import STATUS_LABELS; print('publish_queue OK')"
+ ```
+
+- [x] **4.3** 执行 UI 层导入验证
+ ```powershell
+ python -c "import ui.app; print('ui.app OK')"
+ ```
+
+- [x] **4.4** 确认根目录无游离 `.py` 业务文件
+ ```powershell
+ Get-ChildItem -Path . -MaxDepth 1 -Filter "*.py" | Select-Object Name
+ # 预期仅显示 main.py(以及测试脚本如 _test_config_save.py)
+ ```
+
+---
+
+### 5. 添加社区健康文件(oss-community-health)
+
+- [x] **5.1** 创建 `.github/ISSUE_TEMPLATE/bug_report.md`(Bug 报告模板)
+ 包含:问题描述、复现步骤、预期行为、实际行为、环境信息(Python 版本、OS)
+
+- [x] **5.2** 创建 `.github/ISSUE_TEMPLATE/feature_request.md`(功能请求模板)
+ 包含:背景/需求、期望解决方案、替代方案
+
+- [x] **5.3** 创建 `.github/pull_request_template.md`(PR 模板)
+ 包含:变更类型(Bug Fix / Feature / Docs / Refactor)、变更描述、测试说明、相关 Issue
+
+- [x] **5.4** 创建 `CODE_OF_CONDUCT.md`(Contributor Covenant v2.1 中文版)
+
+- [x] **5.5** 创建 `SECURITY.md`(安全漏洞报告政策)
+ 包含:支持版本、私下报告方式(GitHub Security Advisory)、响应时间承诺
+
+---
+
+### 6. 添加 CI 工作流(oss-ci-workflow)
+
+- [x] **6.1** 创建 `requirements-dev.txt`,包含 `ruff>=0.4.0`
+
+- [x] **6.2** 创建 `.github/workflows/ci.yml`
+ - trigger: `push` to `main`、`pull_request` to `main`
+ - job `lint`:
+ - `pip install ruff`
+ - `ruff check . --select E,F,W --ignore E501`(宽松规则,忽略行长)
+ - job `import-check`:
+ - `pip install -r requirements.txt`
+ - `python -c "from services.config_manager import ConfigManager"`
+ - `python -c "from services.llm_service import LLMService"`
+ - `python -c "from services.sd_service import SDService"`
+
+---
+
+### 7. 完善 README(oss-readme-polish)
+
+- [x] **7.1** 在 README 标题下方添加徽章(Python、MIT License、CI Status)
+ ```markdown
+ 
+ [](LICENSE)
+ [](https://github.com//autobot/actions/workflows/ci.yml)
+ ```
+ > 将 `` 替换为实际 GitHub 用户名
+
+- [x] **7.2** 修正 README 中的「项目结构」章节,反映迁移后 `services/` 的完整内容
+
+- [x] **7.3** 全局搜索替换 `your-username` 占位符
+ ```powershell
+ Select-String -Path README.md -Pattern "your-username"
+ # 确认所有出现位置后,手动或批量替换
+ ```
+
+- [x] **7.4** 检查「首次使用流程」中的 Tab 名称与实际 Gradio UI 一致
+
+---
+
+### 8. 最终验证
+
+- [x] **8.1** 执行 `git status` 确认所有变更文件符合预期
+- [x] **8.2** 执行 `git diff --stat` 确认无意外文件被修改
+- [x] **8.3** 启动应用:`python main.py` 确认 Gradio UI 正常加载,无启动错误
diff --git a/openspec/specs/oss-ci-workflow/spec.md b/openspec/specs/oss-ci-workflow/spec.md
new file mode 100644
index 0000000..a9370f2
--- /dev/null
+++ b/openspec/specs/oss-ci-workflow/spec.md
@@ -0,0 +1,37 @@
+## ADDED Requirements
+
+### Requirement: GitHub Actions CI 工作流在 Push 和 PR 时自动触发
+项目 SHALL 在 `.github/workflows/ci.yml` 提供持续集成工作流,在每次 Push 到 `main` 分支或创建 Pull Request 时自动执行代码质量检查。
+
+#### Scenario: CI 在 PR 时自动运行
+- **WHEN** 贡献者向 `main` 分支提交 Pull Request
+- **THEN** GitHub Actions SHALL 自动触发 CI 工作流,在 PR 页面显示检查结果
+
+#### Scenario: CI 通过后状态徽章更新
+- **WHEN** CI 工作流执行完毕
+- **THEN** 工作流状态 SHALL 可通过 Badge URL 获取,用于 README 展示
+
+### Requirement: CI 执行 ruff 代码风格检查
+CI 工作流 SHALL 使用 `ruff` 对所有 Python 文件执行代码风格和常见错误检查,ruff 配置 SHALL 允许项目现有代码通过(宽松规则集)。
+
+#### Scenario: 代码风格检查通过
+- **WHEN** CI 工作流中的 ruff 步骤执行
+- **THEN** 对 `*.py` 文件的 ruff 检查 SHALL 以 exit code 0 退出,工作流标记为 passed
+
+#### Scenario: 引入明显错误时 CI 失败
+- **WHEN** PR 中包含未使用的 import 或明显语法问题
+- **THEN** ruff SHALL 检测到并返回非零 exit code,导致 CI 失败
+
+### Requirement: CI 执行导入验证
+CI 工作流 SHALL 执行 Python 导入验证步骤,确认核心模块可被正常导入,及早发现迁移引入的导入错误。
+
+#### Scenario: 导入验证通过
+- **WHEN** CI 执行导入验证步骤
+- **THEN** `python -c "from services.config_manager import ConfigManager"` 等关键导入 SHALL 成功执行
+
+### Requirement: 提供 requirements-dev.txt 开发依赖声明
+项目根目录 SHALL 包含 `requirements-dev.txt`,声明开发和 CI 所需依赖(ruff、pre-commit 等),与生产依赖 `requirements.txt` 分离。
+
+#### Scenario: 安装开发依赖命令可正常执行
+- **WHEN** 开发者执行 `pip install -r requirements-dev.txt`
+- **THEN** 所有开发工具 SHALL 成功安装,无版本冲突
diff --git a/openspec/specs/oss-community-health/spec.md b/openspec/specs/oss-community-health/spec.md
new file mode 100644
index 0000000..dea3219
--- /dev/null
+++ b/openspec/specs/oss-community-health/spec.md
@@ -0,0 +1,33 @@
+## ADDED Requirements
+
+### Requirement: 提供标准化 GitHub Issue 模板
+项目 SHALL 在 `.github/ISSUE_TEMPLATE/` 下提供至少两个 Issue 模板:Bug 报告模板和功能请求模板,引导贡献者提供必要信息。
+
+#### Scenario: Bug 报告模板包含必要字段
+- **WHEN** 用户在 GitHub 上新建 Issue 并选择「Bug 报告」
+- **THEN** 模板 SHALL 包含:问题描述、复现步骤、预期行为、实际行为、环境信息(Python 版本、操作系统)
+
+#### Scenario: 功能请求模板包含场景描述
+- **WHEN** 用户选择「功能请求」模板
+- **THEN** 模板 SHALL 包含:问题/需求背景、期望的解决方案、替代方案考虑
+
+### Requirement: 提供 Pull Request 模板
+项目 SHALL 在 `.github/pull_request_template.md` 提供 PR 模板,引导贡献者说明变更范围和测试情况。
+
+#### Scenario: PR 模板包含变更说明和测试确认
+- **WHEN** 贡献者在 GitHub 上发起 Pull Request
+- **THEN** 模板 SHALL 包含:变更类型(Bug Fix / Feature / Docs / Refactor)、变更描述、测试说明、相关 Issue 引用
+
+### Requirement: 包含行为准则文件
+项目根目录 SHALL 包含 `CODE_OF_CONDUCT.md`,采用 Contributor Covenant v2.1 中文版,明确社区行为规范和违规处理方式。
+
+#### Scenario: 行为准则文件可访问
+- **WHEN** 贡献者查看项目根目录
+- **THEN** `CODE_OF_CONDUCT.md` SHALL 存在,包含社区行为规范、适用范围、执行说明、联系方式
+
+### Requirement: 包含安全漏洞报告政策
+项目根目录 SHALL 包含 `SECURITY.md`,说明如何负责任地披露安全漏洞、支持的版本范围和响应时间承诺。
+
+#### Scenario: 安全政策文件包含报告方式
+- **WHEN** 安全研究者发现漏洞
+- **THEN** `SECURITY.md` SHALL 提供私下联系方式(邮件或 GitHub Security Advisory),不要求通过公开 Issue 上报
diff --git a/openspec/specs/oss-readme-polish/spec.md b/openspec/specs/oss-readme-polish/spec.md
new file mode 100644
index 0000000..ae5a603
--- /dev/null
+++ b/openspec/specs/oss-readme-polish/spec.md
@@ -0,0 +1,29 @@
+## ADDED Requirements
+
+### Requirement: README 顶部展示状态徽章
+`README.md` 顶部(标题下方)SHALL 包含至少三枚徽章:Python 版本要求、License 类型、CI 状态,采用 shields.io 或 GitHub Actions 徽章格式。
+
+#### Scenario: 徽章在 GitHub 页面正常渲染
+- **WHEN** 访问项目 GitHub 主页
+- **THEN** README 顶部 SHALL 显示可点击的 Python、MIT License、CI 状态徽章,链接指向对应资源
+
+### Requirement: README 项目结构图反映实际代码
+`README.md` 中的「项目结构」章节 SHALL 反映迁移后的实际目录结构,包含 `services/`(含所有迁移后文件)和 `ui/`(含 `app.py`、`tab_create.py`)的正确层级。
+
+#### Scenario: 项目结构与 ls 输出一致
+- **WHEN** 开发者对照 README 查看实际文件目录
+- **THEN** README 的结构图 SHALL 与实际 `Get-ChildItem` / `ls` 输出一致,无过时文件或缺失目录
+
+### Requirement: README 不包含 your-username 占位符
+`README.md` 中所有 `your-username` 占位符 SHALL 替换为实际仓库路径说明或格式示例,使克隆/安装命令可直接复制使用。
+
+#### Scenario: 安装命令无需手动替换占位符
+- **WHEN** 用户复制 README 中的 `git clone` 命令
+- **THEN** 命令 SHALL 包含实际仓库 URL 或明确的 `` 格式提示,不出现 `your-username` 字符串
+
+### Requirement: README 使用指南与当前 UI 结构匹配
+`README.md` 中的「使用指南」和「首次使用流程」章节 SHALL 引用当前正确的 Tab 名称和操作路径,与 `ui/app.py` 实际 Tab 顺序保持一致(⚙️ 配置 Tab 已迁移,不再是「展开全局设置折叠块」)。
+
+#### Scenario: 首次使用步骤描述与 UI 一致
+- **WHEN** 新用户按照 README「首次使用流程」操作
+- **THEN** README 中描述的 Tab 名称和操作入口 SHALL 与实际 Gradio UI 一致,用户无需猜测
diff --git a/openspec/specs/project-structure/spec.md b/openspec/specs/project-structure/spec.md
new file mode 100644
index 0000000..c387aab
--- /dev/null
+++ b/openspec/specs/project-structure/spec.md
@@ -0,0 +1,49 @@
+## ADDED Requirements
+
+### Requirement: 根目录只包含正式项目文件
+项目根目录 SHALL 不包含备份文件、一次性测试脚本、个人媒体资源或临时记录文件。具体地:
+
+- `*_backup.py` 命名的文件 SHALL 不存在于根目录
+- `config copy.json`(或同类配置副本)SHALL 不存在
+- `_test_*.py` 命名的一次性测试脚本 SHALL 不存在于根目录
+- `*.log` 运行日志文件 SHALL 不进入版本控制(由 `.gitignore` 保证)
+- 个人图片(`.png`、`.jpg` 等媒体文件)SHALL 不散落在根目录,统一归入 `assets/` 下对应子目录
+
+#### Scenario: 克隆仓库后根目录无冗余文件
+- **WHEN** 开发者执行 `git clone` 并查看根目录
+- **THEN** 根目录 SHALL 仅包含:`main.py`、服务模块文件(`*_service.py`、`*_client.py`、`*_manager.py`、`*_queue.py`)、标准配置(`config.example.json`、`requirements.txt`、`Dockerfile`、`docker-compose.yml`、`.gitignore`、`.dockerignore`)、自动启动脚本(`_autostart.*`)、文档(`README.md`、`CHANGELOG.md`、`CONTRIBUTING.md`、`mcp.md`)
+
+#### Scenario: 备份文件不出现在版本控制
+- **WHEN** 开发者执行 `git status` 或 `git ls-files`
+- **THEN** 输出中 SHALL 不包含 `*_backup.py`、`config copy.json` 等备份/副本文件
+
+### Requirement: 媒体资源归入 assets/ 目录
+项目所需的图片等媒体资源 SHALL 存放于 `assets/` 目录下的对应子目录,根据用途分类:
+
+- 换脸/头像相关图片 SHALL 放入 `assets/faces/`
+- `assets/faces/` SHALL 被 `.gitignore` 覆盖(不进入版本控制,属隐私数据)
+
+#### Scenario: 换脸图片存放路径符合规范
+- **WHEN** 用户配置换脸头像功能
+- **THEN** 头像文件 SHALL 存放于 `assets/faces/` 目录,而非项目根目录
+
+#### Scenario: assets/faces/ 不进入版本控制
+- **WHEN** 开发者执行 `git status`
+- **THEN** `assets/faces/` 目录下的文件 SHALL 显示为已忽略(不出现在 staged 或 unstaged 区域)
+
+### Requirement: .gitignore 覆盖所有非版本控制内容
+项目 `.gitignore` SHALL 包含以下规则类别,且每类规则 SHALL 有注释说明用途:
+
+- Python 编译产物(`__pycache__/`、`*.py[cod]`)
+- 虚拟环境目录(`.venv/`、`venv/`)
+- 敏感配置(`config.json`、`cookies.json`、`*.cookie`)
+- 运行日志(`*.log`、`logs/`)
+- 备份与副本文件(`*_backup.py`、`config copy.json`)
+- 个人媒体资产(`assets/faces/`)
+- IDE 配置(`.vscode/`、`.idea/`)
+- 系统文件(`.DS_Store`、`Thumbs.db`)
+- 工作空间输出目录(`xhs_workspace/`)
+
+#### Scenario: 新增备份文件不被 git 追踪
+- **WHEN** 开发者在根目录创建 `main_v2_backup.py`
+- **THEN** `git status` SHALL 将其显示为已忽略文件
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..56e33a1
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,3 @@
+# 开发与 CI 依赖(不包含生产运行时依赖)
+# 安装命令:pip install -r requirements-dev.txt
+ruff>=0.4.0
diff --git a/analytics_service.py b/services/analytics_service.py
similarity index 100%
rename from analytics_service.py
rename to services/analytics_service.py
diff --git a/services/autostart.py b/services/autostart.py
index 857ac6e..ae1d4ad 100644
--- a/services/autostart.py
+++ b/services/autostart.py
@@ -14,12 +14,12 @@ _STARTUP_REG_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run"
def _get_startup_script_path() -> str:
"""获取启动脚本路径(.vbs 静默启动,不弹黑窗)"""
- return os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "_autostart.vbs")
+ return os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "scripts", "_autostart.vbs")
def _get_startup_bat_path() -> str:
"""获取启动 bat 路径"""
- return os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "_autostart.bat")
+ return os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "scripts", "_autostart.bat")
def _create_startup_scripts():
@@ -35,6 +35,7 @@ def _create_startup_scripts():
# 创建 bat
bat_path = _get_startup_bat_path()
+ os.makedirs(os.path.dirname(bat_path), exist_ok=True)
bat_content = f"""@echo off
cd /d "{app_dir}"
"{venv_python}" "{main_script}"
diff --git a/config_manager.py b/services/config_manager.py
similarity index 98%
rename from config_manager.py
rename to services/config_manager.py
index f8b1ebc..829a682 100644
--- a/config_manager.py
+++ b/services/config_manager.py
@@ -13,7 +13,8 @@ logger = logging.getLogger(__name__)
_KEYRING_PLACEHOLDER = "[keyring]"
_KEYRING_SERVICE = "autobot_xhs"
-CONFIG_FILE = "config.json"
+_PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+CONFIG_FILE = os.path.join(_PROJECT_ROOT, "config", "config.json")
OUTPUT_DIR = "xhs_workspace"
DEFAULT_CONFIG = {
diff --git a/services/connection.py b/services/connection.py
index cc239ba..33c9d40 100644
--- a/services/connection.py
+++ b/services/connection.py
@@ -8,10 +8,10 @@ import logging
import gradio as gr
-from config_manager import ConfigManager
-from llm_service import LLMService
-from sd_service import SDService, get_model_profile_info
-from mcp_client import get_mcp_client
+from .config_manager import ConfigManager
+from .llm_service import LLMService
+from .sd_service import SDService, get_model_profile_info
+from .mcp_client import get_mcp_client
logger = logging.getLogger("autobot")
cfg = ConfigManager()
diff --git a/services/content.py b/services/content.py
index 358db32..ae634e3 100644
--- a/services/content.py
+++ b/services/content.py
@@ -11,12 +11,12 @@ import logging
from PIL import Image
-from config_manager import ConfigManager, OUTPUT_DIR
-from llm_service import LLMService
-from sd_service import SDService, get_sd_preset
-from mcp_client import get_mcp_client
-from services.connection import _get_llm_config
-from services.persona import _resolve_persona
+from .config_manager import ConfigManager, OUTPUT_DIR
+from .llm_service import LLMService
+from .sd_service import SDService, get_sd_preset
+from .mcp_client import get_mcp_client
+from .connection import _get_llm_config
+from .persona import _resolve_persona
logger = logging.getLogger("autobot")
cfg = ConfigManager()
diff --git a/services/engagement.py b/services/engagement.py
index 6899341..6a656b5 100644
--- a/services/engagement.py
+++ b/services/engagement.py
@@ -6,10 +6,10 @@ import logging
import gradio as gr
-from mcp_client import get_mcp_client
-from llm_service import LLMService
-from services.connection import _get_llm_config
-from services.hotspot import _pick_from_cache, _set_cache, _get_cache
+from .mcp_client import get_mcp_client
+from .llm_service import LLMService
+from .connection import _get_llm_config
+from .hotspot import _pick_from_cache, _set_cache, _get_cache
logger = logging.getLogger("autobot")
diff --git a/services/hotspot.py b/services/hotspot.py
index a4a6fde..748607f 100644
--- a/services/hotspot.py
+++ b/services/hotspot.py
@@ -7,10 +7,10 @@ import logging
import gradio as gr
-from llm_service import LLMService
-from mcp_client import get_mcp_client
-from services.connection import _get_llm_config
-from services.persona import _resolve_persona
+from .llm_service import LLMService
+from .mcp_client import get_mcp_client
+from .connection import _get_llm_config
+from .persona import _resolve_persona
logger = logging.getLogger("autobot")
diff --git a/llm_service.py b/services/llm_service.py
similarity index 100%
rename from llm_service.py
rename to services/llm_service.py
diff --git a/mcp_client.py b/services/mcp_client.py
similarity index 100%
rename from mcp_client.py
rename to services/mcp_client.py
diff --git a/services/persona.py b/services/persona.py
index a47908d..d160b0f 100644
--- a/services/persona.py
+++ b/services/persona.py
@@ -4,7 +4,7 @@ services/persona.py
"""
import random
import logging
-from config_manager import ConfigManager
+from .config_manager import ConfigManager
logger = logging.getLogger("autobot")
cfg = ConfigManager()
diff --git a/services/profile.py b/services/profile.py
index 07a2bd0..3312335 100644
--- a/services/profile.py
+++ b/services/profile.py
@@ -9,7 +9,7 @@ import logging
import matplotlib
import matplotlib.pyplot as plt
-from mcp_client import get_mcp_client
+from .mcp_client import get_mcp_client
_font_candidates = ["Microsoft YaHei", "SimHei", "PingFang SC", "WenQuanYi Micro Hei"]
for _fn in _font_candidates:
diff --git a/publish_queue.py b/services/publish_queue.py
similarity index 100%
rename from publish_queue.py
rename to services/publish_queue.py
diff --git a/services/queue_ops.py b/services/queue_ops.py
index 54778fd..660a784 100644
--- a/services/queue_ops.py
+++ b/services/queue_ops.py
@@ -6,17 +6,17 @@ import os
import time
import logging
-from config_manager import ConfigManager, OUTPUT_DIR
-from publish_queue import (
+from .config_manager import ConfigManager, OUTPUT_DIR
+from .publish_queue import (
PublishQueue, QueuePublisher,
STATUS_DRAFT, STATUS_APPROVED, STATUS_SCHEDULED, STATUS_PUBLISHING,
STATUS_PUBLISHED, STATUS_FAILED, STATUS_REJECTED, STATUS_LABELS,
)
-from mcp_client import get_mcp_client
-from services.connection import _get_llm_config
-from services.persona import DEFAULT_TOPICS, DEFAULT_STYLES, _resolve_persona
-from services.content import generate_copy, generate_images
-from services.rate_limiter import _increment_stat, _clear_error_streak
+from .mcp_client import get_mcp_client
+from .connection import _get_llm_config
+from .persona import DEFAULT_TOPICS, DEFAULT_STYLES, _resolve_persona
+from .content import generate_copy, generate_images
+from .rate_limiter import _increment_stat, _clear_error_streak
cfg = ConfigManager()
logger = logging.getLogger("autobot")
diff --git a/services/scheduler.py b/services/scheduler.py
index 07e3641..435ce07 100644
--- a/services/scheduler.py
+++ b/services/scheduler.py
@@ -12,23 +12,23 @@ from datetime import datetime
from PIL import Image
-from config_manager import ConfigManager, OUTPUT_DIR
-from llm_service import LLMService
-from sd_service import SDService
-from mcp_client import get_mcp_client
-from services.rate_limiter import (
+from .config_manager import ConfigManager, OUTPUT_DIR
+from .llm_service import LLMService
+from .sd_service import SDService
+from .mcp_client import get_mcp_client
+from .rate_limiter import (
_op_history, _daily_stats, DAILY_LIMITS,
_reset_daily_stats_if_needed,
_check_daily_limit, _increment_stat, _record_error,
_clear_error_streak, _is_in_cooldown, _is_in_operating_hours,
_get_stats_summary,
)
-from services.persona import (
+from .persona import (
DEFAULT_TOPICS, DEFAULT_STYLES, DEFAULT_COMMENT_KEYWORDS,
get_persona_keywords, _resolve_persona,
)
-from services.connection import _get_llm_config
-from analytics_service import AnalyticsService
+from .connection import _get_llm_config
+from .analytics_service import AnalyticsService
cfg = ConfigManager()
logger = logging.getLogger("autobot")
@@ -51,7 +51,7 @@ _auto_running = threading.Event()
_auto_thread: threading.Thread | None = None
_auto_log: list[str] = []
-from services.rate_limiter import (
+from .rate_limiter import (
_op_history, _daily_stats, DAILY_LIMITS,
_reset_daily_stats_if_needed,
_check_daily_limit, _increment_stat, _record_error,
@@ -59,7 +59,7 @@ from services.rate_limiter import (
_get_stats_summary,
)
-from services.persona import (
+from .persona import (
DEFAULT_PERSONAS, RANDOM_PERSONA_LABEL, PERSONA_POOL_MAP,
DEFAULT_TOPICS, DEFAULT_STYLES, DEFAULT_COMMENT_KEYWORDS,
_match_persona_pools, get_persona_topics, get_persona_keywords,
@@ -1068,7 +1068,7 @@ def stop_learn_scheduler():
# ==================================================
# Windows 开机自启管理
# ==================================================
-from services.autostart import (
+from .autostart import (
is_autostart_enabled, enable_autostart, disable_autostart, toggle_autostart,
)
diff --git a/sd_service.py b/services/sd_service.py
similarity index 99%
rename from sd_service.py
rename to services/sd_service.py
index c835e6d..3db0f08 100644
--- a/sd_service.py
+++ b/services/sd_service.py
@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
SD_TIMEOUT = 1800 # 图片生成可能需要较长时间
# 头像文件默认保存路径
-FACE_IMAGE_PATH = os.path.join(os.path.dirname(__file__), "my_face.png")
+FACE_IMAGE_PATH = os.path.join(os.path.dirname(__file__), "assets", "faces", "my_face.png")
# ==================== 多模型配置系统 ====================
# 每个模型的最优参数、prompt 增强词、负面提示词、三档预设
diff --git a/ui/app.py b/ui/app.py
index 936a10f..bc01f05 100644
--- a/ui/app.py
+++ b/ui/app.py
@@ -8,9 +8,9 @@ import logging
import gradio as gr
-from config_manager import ConfigManager
-from sd_service import SDService, DEFAULT_NEGATIVE, FACE_IMAGE_PATH, SD_PRESET_NAMES, get_sd_preset, SD_MODEL_PROFILES
-from analytics_service import AnalyticsService
+from services.config_manager import ConfigManager
+from services.sd_service import SDService, DEFAULT_NEGATIVE, FACE_IMAGE_PATH, SD_PRESET_NAMES, get_sd_preset, SD_MODEL_PROFILES
+from services.analytics_service import AnalyticsService
from ui.tab_create import build_tab
from services.connection import (
@@ -53,7 +53,7 @@ from services.queue_ops import (
)
from services.autostart import is_autostart_enabled, toggle_autostart
from services.content import generate_copy, generate_images, one_click_export, publish_to_xhs
-from publish_queue import STATUS_LABELS
+from services.publish_queue import STATUS_LABELS
logger = logging.getLogger("autobot")
diff --git a/zjz.png b/zjz.png
deleted file mode 100644
index a4055f5..0000000
Binary files a/zjz.png and /dev/null differ