📝 docs(project): 添加开源社区标准文档与 CI 工作流
Some checks failed
CI / Lint (ruff) (push) Has been cancelled
CI / Import Check (push) Has been cancelled

- 新增 GitHub Issue 模板(Bug 报告、功能请求)和 Pull Request 模板
- 新增 Code of Conduct(贡献者行为准则)和 Security Policy(安全政策)
- 新增 CI 工作流(GitHub Actions),包含 ruff 代码检查和导入验证
- 新增开发依赖文件 requirements-dev.txt

📦 build(ci): 配置 GitHub Actions 持续集成

- 在 push 到 main 分支和 pull request 时自动触发 CI
- 添加 lint 任务执行 ruff 代码风格检查
- 添加 import-check 任务验证核心服务模块导入

♻️ refactor(structure): 重构项目目录结构

- 将根目录的 6 个服务模块迁移至 services/ 包
- 更新所有相关文件的导入语句(main.py、ui/、services/)
- 根目录仅保留 main.py 作为唯一 Python 入口文件

🔧 chore(config): 调整配置和资源文件路径

- 将 config.json 移至 config/ 目录,更新相关引用
- 将个人头像图片移至 assets/faces/ 目录,更新 .gitignore
- 更新 Dockerfile 和 docker-compose.yml 中的配置路径

📝 docs(readme): 完善 README 文档

- 添加项目状态徽章(Python 版本、License、CI)
- 更新项目结构图反映实际目录布局
- 修正使用指南中的 Tab 名称和操作路径
- 替换 your-username 占位符为格式提示

🗑️ chore(cleanup): 清理冗余文件

- 删除旧版备份文件、测试脚本、临时记录和运行日志
- 删除散落的个人图片文件(已归档至 assets/faces/)
This commit is contained in:
zhoujie 2026-02-27 22:12:39 +08:00
parent b5deafa2cc
commit 2ba87c8f6e
58 changed files with 1119 additions and 468 deletions

37
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -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 等)
## 附加信息
任何其他与问题相关的上下文、截图或日志文件,请粘贴于此。

View File

@ -0,0 +1,23 @@
---
name: 功能请求
about: 为本项目提出新点子或改进建议
title: "[FEATURE] "
labels: enhancement
assignees: ''
---
## 背景与需求
请描述你目前遇到的问题或痛点。例如:"当我想要 [...] 时,总是感到不便,因为 [...]"
## 期望的解决方案
请清晰描述你希望实现的功能或行为。
## 替代方案
你是否考虑过其他解决方案或变通方法?请描述。
## 附加信息
你可以在此添加任何截图、参考链接或其他有助于理解该功能请求的上下文信息。

35
.github/pull_request_template.md vendored Normal file
View File

@ -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 特别关注的地方,请在此说明。

48
.github/workflows/ci.yml vendored Normal file
View File

@ -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')"

9
.gitignore vendored
View File

@ -16,7 +16,7 @@ venv/
env/ env/
# ========== 敏感配置 ========== # ========== 敏感配置 ==========
config.json config/config.json
cookies.json cookies.json
*.cookie *.cookie
@ -43,3 +43,10 @@ config copy.json
# ========== 临时文件 ========== # ========== 临时文件 ==========
*.tmp *.tmp
*.bak *.bak
# ========== 个人媒体资产(隐私,不入版本控制) ==========
assets/faces/
# ========== 自动生成的启动脚本(含机器绝对路径) ==========
scripts/_autostart.bat
scripts/_autostart.vbs

83
CODE_OF_CONDUCT.md Normal file
View File

@ -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

View File

@ -28,12 +28,13 @@ RUN apt-get update && \
COPY --from=builder /install /usr/local COPY --from=builder /install /usr/local
# 复制项目代码 # 复制项目代码
COPY config_manager.py llm_service.py sd_service.py mcp_client.py main.py ./ COPY main.py requirements.txt ./
COPY requirements.txt ./ COPY services/ services/
COPY config.example.json ./ 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 默认端口 # Gradio 默认端口
EXPOSE 7860 EXPOSE 7860

View File

@ -13,6 +13,11 @@
<a href="#常见问题">FAQ</a> <a href="#常见问题">FAQ</a>
<a href="#贡献指南">贡献</a> <a href="#贡献指南">贡献</a>
</p> </p>
<p align="center">
<img src="https://img.shields.io/badge/python-3.10+-blue" alt="Python">
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
<img src="https://github.com/<your-github-username>/xhs-autobot/actions/workflows/ci.yml/badge.svg" alt="CI">
</p>
</p> </p>
--- ---
@ -91,7 +96,7 @@
```bash ```bash
# 1. 克隆项目 # 1. 克隆项目
git clone https://github.com/your-username/xhs-autobot.git git clone https://github.com/<your-github-username>/xhs-autobot.git
cd xhs-autobot cd xhs-autobot
# 2. 创建虚拟环境(推荐) # 2. 创建虚拟环境(推荐)
@ -105,8 +110,8 @@ source .venv/bin/activate
pip install -r requirements.txt pip install -r requirements.txt
# 4. 复制配置文件并填写你的 API Key # 4. 复制配置文件并填写你的 API Key
cp config.example.json config.json cp config/config.example.json config/config.json
# 编辑 config.json填写 api_key、base_url 等 # 编辑 config/config.json填写 api_key、base_url 等
# 5. 启动! # 5. 启动!
python main.py python main.py
@ -120,12 +125,12 @@ python main.py
```bash ```bash
# 1. 克隆项目 # 1. 克隆项目
git clone https://github.com/your-username/xhs-autobot.git git clone https://github.com/<your-github-username>/xhs-autobot.git
cd xhs-autobot cd xhs-autobot
# 2. 准备配置文件 # 2. 准备配置文件
cp config.example.json config.json cp config/config.example.json config/config.json
# 编辑 config.json填写 api_key、base_url 等 # 编辑 config/config.json填写 api_key、base_url 等
# ⚠️ mcp_url 改为容器网络地址: # ⚠️ mcp_url 改为容器网络地址:
# "mcp_url": "http://xhs-mcp:18060/mcp" # "mcp_url": "http://xhs-mcp:18060/mcp"
@ -164,7 +169,7 @@ docker compose exec xhs-autobot bash
#### 启用 Stable Diffusion需要 NVIDIA GPU #### 启用 Stable Diffusion需要 NVIDIA GPU
编辑 `docker-compose.yml`,取消 `sd-webui` 部分的注释,并将 `config.json` 中的 `sd_url` 改为: 编辑 `docker-compose.yml`,取消 `sd-webui` 部分的注释,并将 `config/config.json` 中的 `sd_url` 改为:
```json ```json
"sd_url": "http://sd-webui:7860" "sd_url": "http://sd-webui:7860"
@ -205,11 +210,11 @@ python launch.py --api
### 首次使用流程 ### 首次使用流程
1. **配置 LLM**展开「⚙️ 全局设置」,添加 LLM 提供商API Key + Base URL点击「连接 LLM」 1. **配置 LLM**切换到「⚙️ 配置」 Tab,添加 LLM 提供商API Key + Base URL点击「连接 LLM」
2. **连接 SD**(可选)— 填写 SD WebUI URL点击「连接 SD」 2. **连接 SD**(可选)— 在「⚙️ 配置」 Tab 填写 SD WebUI URL点击「连接 SD」
3. **检查 MCP** — 点击「检查 MCP」确认小红书服务正常 3. **检查 MCP** — 点击「检查 MCP」确认小红书服务正常
4. **登录小红书** — 切换到「🔐 账号登录」 Tab扫码登录 4. **登录小红书** — 切换到「🔐 账号登录」 Tab扫码登录
5. **选择人设** — 在人设下拉框选择博主人设(影响文案风格 + 图片视觉) 5. **选择人设** — 在「⚙️ 配置」 Tab 的人设下拉框选择博主人设(影响文案风格 + 图片视觉)
6. **开始创作** — 切换到「✨ 内容创作」Tab输入主题一键生成 6. **开始创作** — 切换到「✨ 内容创作」Tab输入主题一键生成
### 自动化运营 ### 自动化运营
@ -243,7 +248,7 @@ python launch.py --api
## ⚙️ 配置说明 ## ⚙️ 配置说明
配置文件 `config.json` 会在运行时自动创建和保存。首次使用请从 `config.example.json` 复制: 配置文件 `config/config.json` 会在运行时自动创建和保存。首次使用请从 `config/config.example.json` 复制:
```json ```json
{ {
@ -280,24 +285,47 @@ python launch.py --api
``` ```
xhs-autobot/ xhs-autobot/
├── main.py # 主程序入口 (Gradio UI + 业务逻辑 + 8 个 Tab) ├── main.py # 主程序入口 (Gradio UI + 事件绑定)
├── config_manager.py # 配置管理模块 (单例、自动保存) ├── config/ # 配置文件目录
├── llm_service.py # LLM 服务封装 (文案生成、热点分析、评论回复、SD Prompt 指南) │ ├── config.json # 运行时配置 (gitignore)
├── sd_service.py # Stable Diffusion 服务封装 (3 模型适配、9 人设视觉方案) │ └── config.example.json # 配置模板
├── mcp_client.py # 小红书 MCP 客户端 (搜索、发布、评论、点赞) ├── logs/ # 运行日志 (gitignore)
├── analytics_service.py # 笔记数据分析 & 权重学习服务 │ └── autobot.log
├── publish_queue.py # 内容排期队列 (SQLite + 后台 Publisher) ├── docs/ # 参考文档
├── config.json # 运行时配置 (gitignore) │ └── mcp.md # xiaohongshu-mcp 参考文档
├── config.example.json # 配置模板 ├── scripts/ # 运行时生成的启动脚本 (gitignore)
│ ├── _autostart.bat
│ └── _autostart.vbs
├── requirements.txt # Python 依赖 ├── requirements.txt # Python 依赖
├── requirements-dev.txt # 开发工具依赖 (ruff 等)
├── Dockerfile # Docker 镜像构建 ├── Dockerfile # Docker 镜像构建
├── docker-compose.yml # Docker Compose 编排 ├── docker-compose.yml # Docker Compose 编排
├── .dockerignore # Docker 构建排除规则 ├── services/ # 业务逻辑层
├── xhs_workspace/ # 导出的文案和图片 (gitignore) │ ├── config_manager.py # 配置管理模块 (单例、自动保存)
│ ├── publish_queue.db # 排期队列数据库 │ ├── llm_service.py # LLM 服务封装 (文案生成、热点分析等)
│ ├── analytics_data.json # 笔记表现数据 │ ├── sd_service.py # Stable Diffusion 服务封装 (3 模型适配、9 人设视觉方案)
│ └── content_weights.json # 内容权重数据 │ ├── mcp_client.py # 小红书 MCP 客户端 (搜索、发布、评论、点赞)
└── autobot.log # 运行日志 (gitignore) │ ├── 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/
<details> <details>
<summary><b>Q: 如何添加自定义人设?</b></summary> <summary><b>Q: 如何添加自定义人设?</b></summary>
在人设下拉框中直接输入自定义人设描述即可(支持自由输入)。如需配套主题池和关键词,需在 `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` 中添加。
</details> </details>
<details> <details>

36
SECURITY.md Normal file
View File

@ -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 中公开致谢(如您同意)
感谢您帮助保持本项目的安全!

View File

@ -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 图,想混排自己拍的照片,增加“本地上传”按钮。第三阶段:自动化运营 (解放双手)自动回复机器人:根据设定的人设(知性姐姐/毒舌博主)自动回评论。定时任务:设置一个队列,让它自己跑。

View File

@ -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"

View File

@ -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

View File

@ -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✅ 测试完成")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 MiB

View File

@ -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
}

View File

@ -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="
}

View File

@ -11,8 +11,8 @@ services:
ports: ports:
- "7860:7860" - "7860:7860"
volumes: volumes:
# 配置文件挂载(首次请从 config.example.json 复制并填写) # 配置文件挂载(首次请从 config/config.example.json 复制并填写)
- ./config.json:/app/config.json - ./config/config.json:/app/config/config.json
# 工作目录(导出的文案 & 图片) # 工作目录(导出的文案 & 图片)
- ./xhs_workspace:/app/xhs_workspace - ./xhs_workspace:/app/xhs_workspace
environment: environment:

View File

15
main.py
View File

@ -17,11 +17,11 @@ from PIL import Image
import matplotlib import matplotlib
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
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
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 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 mcp_client import MCPClient, get_mcp_client from services.mcp_client import MCPClient, get_mcp_client
from analytics_service import AnalyticsService from services.analytics_service import AnalyticsService
from ui.tab_create import build_tab from ui.tab_create import build_tab
# ================= matplotlib 中文字体配置 ================= # ================= matplotlib 中文字体配置 =================
@ -37,12 +37,13 @@ plt.rcParams["axes.unicode_minus"] = False
# ================= 日志配置 ================= # ================= 日志配置 =================
os.makedirs("logs", exist_ok=True)
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[ handlers=[
logging.StreamHandler(), logging.StreamHandler(),
logging.FileHandler("autobot.log", encoding="utf-8"), logging.FileHandler("logs/autobot.log", encoding="utf-8"),
], ],
) )
logger = logging.getLogger("autobot") logger = logging.getLogger("autobot")
@ -59,7 +60,7 @@ mcp = get_mcp_client(cfg.get("mcp_url", "http://localhost:18060/mcp"))
analytics = AnalyticsService(OUTPUT_DIR) analytics = AnalyticsService(OUTPUT_DIR)
# ================= 发布队列 ================= # ================= 发布队列 =================
from publish_queue import ( from services.publish_queue import (
PublishQueue, QueuePublisher, PublishQueue, QueuePublisher,
STATUS_DRAFT, STATUS_APPROVED, STATUS_SCHEDULED, STATUS_PUBLISHING, STATUS_DRAFT, STATUS_APPROVED, STATUS_SCHEDULED, STATUS_PUBLISHING,
STATUS_PUBLISHED, STATUS_FAILED, STATUS_REJECTED, STATUS_LABELS, STATUS_PUBLISHED, STATUS_FAILED, STATUS_REJECTED, STATUS_LABELS,

View File

@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-26

View File

@ -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/` 路径无关,但同属根目录非标准文件** → 此次保留,自动启动脚本属于有效使用场景

View File

@ -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 变更,无依赖变更

View File

@ -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 将其显示为已忽略文件

View File

@ -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/` 已被忽略,根目录无残留冗余文件

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-27

View File

@ -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

View File

@ -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 CIruff 代码检查 + 导入验证)
- `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 部署、依赖均无影响

View File

@ -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 成功安装,无版本冲突

View File

@ -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 上报

View File

@ -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-github-username>` 格式提示,不出现 `your-username` 字符串
### Requirement: README 使用指南与当前 UI 结构匹配
`README.md` 中的「使用指南」和「首次使用流程」章节 SHALL 引用当前正确的 Tab 名称和操作路径,与 `ui/app.py` 实际 Tab 顺序保持一致(⚙️ 配置 Tab 已迁移,不再是「展开全局设置折叠块」)。
#### Scenario: 首次使用步骤描述与 UI 一致
- **WHEN** 新用户按照 README「首次使用流程」操作
- **THEN** README 中描述的 Tab 名称和操作入口 SHALL 与实际 Gradio UI 一致,用户无需猜测

View File

@ -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.<module> 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 .<module> import ...`,不依赖根目录在 `sys.path` 中的位置。
#### Scenario: services 内部导入独立于运行上下文
- **WHEN** 在任意工作目录执行 `python -m services.scheduler`(或类似模块测试)
- **THEN** 内部相对导入 SHALL 正常解析,不因工作目录不同而失败

View File

@ -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. 完善 READMEoss-readme-polish
- [x] **7.1** 在 README 标题下方添加徽章Python、MIT License、CI Status
```markdown
![Python](https://img.shields.io/badge/python-3.10+-blue)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![CI](https://github.com/<your-github-username>/autobot/actions/workflows/ci.yml/badge.svg)](https://github.com/<your-github-username>/autobot/actions/workflows/ci.yml)
```
> 将 `<your-github-username>` 替换为实际 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 正常加载,无启动错误

View File

@ -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 成功安装,无版本冲突

View File

@ -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 上报

View File

@ -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-github-username>` 格式提示,不出现 `your-username` 字符串
### Requirement: README 使用指南与当前 UI 结构匹配
`README.md` 中的「使用指南」和「首次使用流程」章节 SHALL 引用当前正确的 Tab 名称和操作路径,与 `ui/app.py` 实际 Tab 顺序保持一致(⚙️ 配置 Tab 已迁移,不再是「展开全局设置折叠块」)。
#### Scenario: 首次使用步骤描述与 UI 一致
- **WHEN** 新用户按照 README「首次使用流程」操作
- **THEN** README 中描述的 Tab 名称和操作入口 SHALL 与实际 Gradio UI 一致,用户无需猜测

View File

@ -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 将其显示为已忽略文件

3
requirements-dev.txt Normal file
View File

@ -0,0 +1,3 @@
# 开发与 CI 依赖(不包含生产运行时依赖)
# 安装命令pip install -r requirements-dev.txt
ruff>=0.4.0

View File

@ -14,12 +14,12 @@ _STARTUP_REG_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run"
def _get_startup_script_path() -> str: def _get_startup_script_path() -> str:
"""获取启动脚本路径(.vbs 静默启动,不弹黑窗)""" """获取启动脚本路径(.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: def _get_startup_bat_path() -> str:
"""获取启动 bat 路径""" """获取启动 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(): def _create_startup_scripts():
@ -35,6 +35,7 @@ def _create_startup_scripts():
# 创建 bat # 创建 bat
bat_path = _get_startup_bat_path() bat_path = _get_startup_bat_path()
os.makedirs(os.path.dirname(bat_path), exist_ok=True)
bat_content = f"""@echo off bat_content = f"""@echo off
cd /d "{app_dir}" cd /d "{app_dir}"
"{venv_python}" "{main_script}" "{venv_python}" "{main_script}"

View File

@ -13,7 +13,8 @@ logger = logging.getLogger(__name__)
_KEYRING_PLACEHOLDER = "[keyring]" _KEYRING_PLACEHOLDER = "[keyring]"
_KEYRING_SERVICE = "autobot_xhs" _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" OUTPUT_DIR = "xhs_workspace"
DEFAULT_CONFIG = { DEFAULT_CONFIG = {

View File

@ -8,10 +8,10 @@ import logging
import gradio as gr import gradio as gr
from config_manager import ConfigManager from .config_manager import ConfigManager
from llm_service import LLMService from .llm_service import LLMService
from sd_service import SDService, get_model_profile_info from .sd_service import SDService, get_model_profile_info
from mcp_client import get_mcp_client from .mcp_client import get_mcp_client
logger = logging.getLogger("autobot") logger = logging.getLogger("autobot")
cfg = ConfigManager() cfg = ConfigManager()

View File

@ -11,12 +11,12 @@ import logging
from PIL import Image from PIL import Image
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
from services.connection import _get_llm_config from .connection import _get_llm_config
from services.persona import _resolve_persona from .persona import _resolve_persona
logger = logging.getLogger("autobot") logger = logging.getLogger("autobot")
cfg = ConfigManager() cfg = ConfigManager()

View File

@ -6,10 +6,10 @@ import logging
import gradio as gr import gradio as gr
from mcp_client import get_mcp_client from .mcp_client import get_mcp_client
from llm_service import LLMService from .llm_service import LLMService
from services.connection import _get_llm_config from .connection import _get_llm_config
from services.hotspot import _pick_from_cache, _set_cache, _get_cache from .hotspot import _pick_from_cache, _set_cache, _get_cache
logger = logging.getLogger("autobot") logger = logging.getLogger("autobot")

View File

@ -7,10 +7,10 @@ import logging
import gradio as gr import gradio as gr
from llm_service import LLMService from .llm_service import LLMService
from mcp_client import get_mcp_client from .mcp_client import get_mcp_client
from services.connection import _get_llm_config from .connection import _get_llm_config
from services.persona import _resolve_persona from .persona import _resolve_persona
logger = logging.getLogger("autobot") logger = logging.getLogger("autobot")

View File

@ -4,7 +4,7 @@ services/persona.py
""" """
import random import random
import logging import logging
from config_manager import ConfigManager from .config_manager import ConfigManager
logger = logging.getLogger("autobot") logger = logging.getLogger("autobot")
cfg = ConfigManager() cfg = ConfigManager()

View File

@ -9,7 +9,7 @@ import logging
import matplotlib import matplotlib
import matplotlib.pyplot as plt 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"] _font_candidates = ["Microsoft YaHei", "SimHei", "PingFang SC", "WenQuanYi Micro Hei"]
for _fn in _font_candidates: for _fn in _font_candidates:

View File

@ -6,17 +6,17 @@ import os
import time import time
import logging import logging
from config_manager import ConfigManager, OUTPUT_DIR from .config_manager import ConfigManager, OUTPUT_DIR
from publish_queue import ( from .publish_queue import (
PublishQueue, QueuePublisher, PublishQueue, QueuePublisher,
STATUS_DRAFT, STATUS_APPROVED, STATUS_SCHEDULED, STATUS_PUBLISHING, STATUS_DRAFT, STATUS_APPROVED, STATUS_SCHEDULED, STATUS_PUBLISHING,
STATUS_PUBLISHED, STATUS_FAILED, STATUS_REJECTED, STATUS_LABELS, STATUS_PUBLISHED, STATUS_FAILED, STATUS_REJECTED, STATUS_LABELS,
) )
from mcp_client import get_mcp_client from .mcp_client import get_mcp_client
from services.connection import _get_llm_config from .connection import _get_llm_config
from services.persona import DEFAULT_TOPICS, DEFAULT_STYLES, _resolve_persona from .persona import DEFAULT_TOPICS, DEFAULT_STYLES, _resolve_persona
from services.content import generate_copy, generate_images from .content import generate_copy, generate_images
from services.rate_limiter import _increment_stat, _clear_error_streak from .rate_limiter import _increment_stat, _clear_error_streak
cfg = ConfigManager() cfg = ConfigManager()
logger = logging.getLogger("autobot") logger = logging.getLogger("autobot")

View File

@ -12,23 +12,23 @@ from datetime import datetime
from PIL import Image from PIL import Image
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 services.rate_limiter import ( from .rate_limiter import (
_op_history, _daily_stats, DAILY_LIMITS, _op_history, _daily_stats, DAILY_LIMITS,
_reset_daily_stats_if_needed, _reset_daily_stats_if_needed,
_check_daily_limit, _increment_stat, _record_error, _check_daily_limit, _increment_stat, _record_error,
_clear_error_streak, _is_in_cooldown, _is_in_operating_hours, _clear_error_streak, _is_in_cooldown, _is_in_operating_hours,
_get_stats_summary, _get_stats_summary,
) )
from services.persona import ( from .persona import (
DEFAULT_TOPICS, DEFAULT_STYLES, DEFAULT_COMMENT_KEYWORDS, DEFAULT_TOPICS, DEFAULT_STYLES, DEFAULT_COMMENT_KEYWORDS,
get_persona_keywords, _resolve_persona, get_persona_keywords, _resolve_persona,
) )
from services.connection import _get_llm_config from .connection import _get_llm_config
from analytics_service import AnalyticsService from .analytics_service import AnalyticsService
cfg = ConfigManager() cfg = ConfigManager()
logger = logging.getLogger("autobot") logger = logging.getLogger("autobot")
@ -51,7 +51,7 @@ _auto_running = threading.Event()
_auto_thread: threading.Thread | None = None _auto_thread: threading.Thread | None = None
_auto_log: list[str] = [] _auto_log: list[str] = []
from services.rate_limiter import ( from .rate_limiter import (
_op_history, _daily_stats, DAILY_LIMITS, _op_history, _daily_stats, DAILY_LIMITS,
_reset_daily_stats_if_needed, _reset_daily_stats_if_needed,
_check_daily_limit, _increment_stat, _record_error, _check_daily_limit, _increment_stat, _record_error,
@ -59,7 +59,7 @@ from services.rate_limiter import (
_get_stats_summary, _get_stats_summary,
) )
from services.persona import ( from .persona import (
DEFAULT_PERSONAS, RANDOM_PERSONA_LABEL, PERSONA_POOL_MAP, DEFAULT_PERSONAS, RANDOM_PERSONA_LABEL, PERSONA_POOL_MAP,
DEFAULT_TOPICS, DEFAULT_STYLES, DEFAULT_COMMENT_KEYWORDS, DEFAULT_TOPICS, DEFAULT_STYLES, DEFAULT_COMMENT_KEYWORDS,
_match_persona_pools, get_persona_topics, get_persona_keywords, _match_persona_pools, get_persona_topics, get_persona_keywords,
@ -1068,7 +1068,7 @@ def stop_learn_scheduler():
# ================================================== # ==================================================
# Windows 开机自启管理 # Windows 开机自启管理
# ================================================== # ==================================================
from services.autostart import ( from .autostart import (
is_autostart_enabled, enable_autostart, disable_autostart, toggle_autostart, is_autostart_enabled, enable_autostart, disable_autostart, toggle_autostart,
) )

View File

@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
SD_TIMEOUT = 1800 # 图片生成可能需要较长时间 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 增强词、负面提示词、三档预设 # 每个模型的最优参数、prompt 增强词、负面提示词、三档预设

View File

@ -8,9 +8,9 @@ import logging
import gradio as gr import gradio as gr
from config_manager import ConfigManager from services.config_manager import ConfigManager
from sd_service import SDService, DEFAULT_NEGATIVE, FACE_IMAGE_PATH, SD_PRESET_NAMES, get_sd_preset, SD_MODEL_PROFILES from services.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.analytics_service import AnalyticsService
from ui.tab_create import build_tab from ui.tab_create import build_tab
from services.connection import ( from services.connection import (
@ -53,7 +53,7 @@ from services.queue_ops import (
) )
from services.autostart import is_autostart_enabled, toggle_autostart from services.autostart import is_autostart_enabled, toggle_autostart
from services.content import generate_copy, generate_images, one_click_export, publish_to_xhs 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") logger = logging.getLogger("autobot")

BIN
zjz.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 MiB