refactor(ai-outfit): 标题生成改为「一次请求·纯提示词·多条按序填」 (§19.22)

把标题生成从「逐行看图、各生成1条、每行一次请求」改为一次请求、纯提示词
(不传图)、生成多条、按序回填(数量由用户写进提示词,docs/11 §17.1/§17.7)。

- ai_text_service:新增 extract_titles_from_response(多行→多条、逐行去
  序号/引号、丢空)+ AiTextClient.generate_texts(一次POST返回多条,与
  generate_text 共用 _post);generate_text/extract_text 改为取首条
- ai_title:generate_titles(一次请求、image_path=None 纯文本);移除
  generate_title/_reference_image/render_title_prompt(不再逐行看图/替换占位符)
- config_service:DEFAULT_TITLE_PROMPT 改批量风格(生成多条、每行一条)
- 面板 _TitleWorker:一次 generate_titles → 按序 write_title_result 回填;
  N>行数多的丢+日志、N<行数后面行留空+日志;请求异常→日志+成功0;去掉行间节流
- _start_title 不再传 request_interval

测试:extract_titles 多行→多条/去序号引号、generate_texts 多条且无图、
generate_titles 一次请求/异常。全套 py37 通过(test_config_service 的
packaging 模板失败属并行 §19.13,与本改动无关)。离屏冒烟:3/3 精确、
5/3 丢弃、2/3 留空 三种分发均按序回填 Excel A + 日志正确。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 15:59:37 +08:00
co-authored by Claude Opus 4.8
parent 52bc6c0c50
commit e7fab724fc
8 changed files with 216 additions and 194 deletions
+26 -1
View File
@@ -1372,4 +1372,29 @@
- [x] `ai_outfit_panel.py` `_find_title_model`:命中条目后,用 `detect_api_type(url, api_type)` 判类型;若 ∈ {`API_IMAGES`, `API_IMAGES_EDITS`} → 返回错误「『{名字}』是图片模型({api_type}),不能生成文字标题,请改选 chat/gemini 文本模型」(`config` 为 None)。校验顺序:先 `api_config_errors`,再图片类型判定
- [x] 行为不变确认:`_resolve_title_model_config` 拿到 error 仍是 `QMessageBox.warning` + 返回 None;`_start_title` 据此中止(已有逻辑,无需改)
- [x] 测试:`test_ai_outfit_panel.py` 加用例——`title_model` 命中 `images_edits` 模型 → `_find_title_model` 返回 `(None, 含「图片模型」的错误)`;既有命中(chat)/缺失用例保持绿
- [x] 验证:相关单测 + 全套 py37 通过;离屏冒烟:`title_model` 指向图片模型 → 点「生成标题」前即弹窗中止、不开跑
- [x] 验证:相关单测 + 全套 py37 通过;离屏冒烟:`title_model` 指向图片模型 → 点「生成标题」前即弹窗中止、不开跑
### 19.22 标题生成改为「一次请求·纯提示词·多条按序填」 — docs/11 §17.1 / §17.7
前置阅读:
- `docs/11-ai-outfit.md`(§17 总述、§17.1 数据流、§17.2 文本服务、§17.4 运行、§17.7 决策)
- `src/services/ai_text_service.py`(`generate_text`/`extract_text_from_response`/`_clean_title`)
- `src/core/ai_title.py`(`generate_title`/`render_title_prompt`/`_reference_image`——本任务替换)
- `src/services/config_service.py`(`DEFAULT_TITLE_PROMPT`)
- `src/app/widgets/ai_outfit_panel.py`(`_TitleWorker`/`_start_title`/`_on_title_*`)
- `tests/test_ai_title.py` / `test_ai_text_service.py`(既有用例需改)
背景:
用户确认把标题生成从「逐行看图、各生成 1 条、每行一次请求」改为**一次请求、纯提示词(不传图)、生成多条、按序回填**;数量由用户自写进提示词;条数与行数对不上时多丢/少留空 + 日志(§17.7)。
任务:
- [x] `ai_text_service.py`:新增 `extract_titles_from_response(data) -> List[str]`(原始文本按行拆 + 逐行清洗,复用提取序号/引号的 `_clean_title` 逐行版);新增 `AiTextClient.generate_texts(prompt, image_path=None) -> List[str]`(与 `generate_text` 共用一段 POST,返回多条);`generate_text` 保留(= 多条取首条)
- [x] `core/ai_title.py`:新增 `generate_titles(prompt, model_config, api_client=None) -> List[str]`(一次请求、`image_path=None` 纯文本);移除 `generate_title`/`_reference_image`/`render_title_prompt`(不再逐行看图、不替换占位符——提示词原样发)
- [x] `config_service.py`:`DEFAULT_TITLE_PROMPT` 改批量风格(让模型生成多条、每行一条、不带序号/引号;示例含数量如「生成 10 条」)
- [x] `ai_outfit_panel.py` `_TitleWorker`:改为一次 `generate_titles` → 按序 `write_title_result` 回填、逐条 `progress` 刷新明细表;`N>行数`多的丢+日志、`N<行数`后面行留空+日志;请求异常 → 日志 + 成功 0 收尾;去掉行间 `request_interval` sleep
- [x] `ai_outfit_panel.py` `_start_title`:不再传 `request_interval`;图片模型开跑前拦截(§19.21)保持
- [x] 测试:`test_ai_text_service.py` 加 `extract_titles_from_response`(多行→多条、去序号/引号、丢空)+ `generate_texts`(mock,返回多条、无图 payload 不含 image_url);`test_ai_title.py` 改为 `generate_titles`(一次请求多条、客户端异常);删旧 `generate_title` 用例
- [x] 验证:相关单测 + 全套 py37 通过;离屏冒烟:一次请求返回 6 条 → 6 行 A 按序回填;返回 3 条/8 条 → 兜底(留空/丢弃)+ 日志;payload 不含图片