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贡献

+

+ Python + License: MIT + CI +

--- @@ -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 + ![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//autobot/actions/workflows/ci.yml/badge.svg)](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