baseline: Clover 独立仓库首次基线提交

将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-16 11:30:22 +08:00
commit 6a2632da70
253 changed files with 27467 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
# Clover 修改处理方案 · 第二批(🟡 做了一半)
> 现状代码均已逐行读核。每项含现状/改后/文件/风险/验收。
---
## 修6 · ai_call_logs 记录不全
**根因**
- `pipeline_io.py:78-91` 只在文案后写1条`prompt_tokens/completion_tokens` 从不填httpx响应没解析usage
- `run_image_generation`(L122-216) 整个函数**无任何 AiCallLog 写入**——生图调用零留痕。
**现状代码**pipeline_io.py:78-91文案日志
```python
log = AiCallLog(
workspace_id=workspace_id, user_id=task.operator_id, key_id=key_id,
task_id=task.id, provider="apiports", model=clients._model,
call_type="text", success=llm_success, latency_ms=latency_ms,
) # 缺 prompt_tokens / completion_tokens
```
**改后**:分两处。
**① 文案日志补token**依赖_call_llm返回usage需text_variants.py一并改透传usage工作量上升。一期可降级token填None保持现状但**至少把生图日志补上**。
**② 生图日志新增**run_image_generation 末尾db.commit前
```python
# 生图调用留痕(与文案对称)
try:
img_log = AiCallLog(
workspace_id=workspace_id, user_id=task.operator_id, key_id=None,
task_id=task.id, provider=getattr(clients, "_image_provider", "apiports"),
model="gpt-image-2", call_type="image",
success=any(not r.get("error") for r in image_results),
latency_ms=image_latency_ms, # 需在gather前后记时间
)
db.add(img_log)
except Exception as e:
logger.warning("生图 ai_call_logs 写入失败(非阻断): %s", e)
```
gather 前加 `t0 = time.monotonic()`,后算 `image_latency_ms`
**改动文件**`pipeline_io.py`生图日志约12行token数透传需 text_variants.py + 调整_call_llm返回约20行建议二期
**风险**飞轮一期是否强依赖token数若不依赖token降级、只补生图成败/耗时/provider即可。
**验收**跑一次任务ai_call_logs 表里文案+生图各一条,生图条 provider/model/success/latency 齐。
**待拍板**token数这期补不补建议先补生图留痕token二期。
---
## 修7 · 文案 Gemini 兜底层未接
**根因**:现链路 LLM(Claude)→本地模板 两层text_variants.py:89-91`gemini_factory.gemini_generate` 只服务生图文案无Gemini通道。
**现状代码**text_variants.py:89-91
```python
copies: list[dict] = await _generate_in_batches(llm_client, product, count, extra)
if not copies:
copies = list(build_local_drafts(product, count)) # 直接掉本地兜底
```
**改后**Claude失败后、本地兜底前插Gemini文案。需 gemini_factory 加 chat 方法(现只有图)。
```python
copies = await _generate_in_batches(llm_client, product, count, extra)
if not copies:
logger.warning("Claude文案失败,尝试Gemini兜底")
try:
copies = await _generate_via_gemini(product, count, extra) # 新函数
except Exception as e:
logger.error("Gemini文案兜底也失败: %s", e)
if not copies:
copies = list(build_local_drafts(product, count)) # 最终本地兜底
```
新增 `_generate_via_gemini`:调 gemini chat → 复用 parse_json_array。
**改动文件**`text_variants.py`约15行`gemini_factory.py`加chat_complete方法约15行
**风险**①Gemini文案质量/JSON格式遵守度要验可能不如Claude ②Gemini key 是否各operator都配了没配则这层形同虚设。
**验收**停Claude(模拟503)确认走Gemini出文案而非直接本地模板。
**待拍板**一期是否需要三层我建议——若operator普遍没配Gemini key先不接留两层(Claude+本地)即可。
---
## 修8 · 上传只查 content-type 无 magic number
**根因**`products.py:174` 只查 `file.content_type`HTTP header可伪造。无字节头校验。
**现状代码**api/v1/products.py:174-179
```python
if file.content_type not in _ALLOWED_CONTENT_TYPES:
raise_business(f"不支持的文件类型 {file.content_type}...")
data = await file.read()
if len(data) > _MAX_SIZE_BYTES:
raise_business("文件超过 10 MB 限制")
```
**改后**读完data后加真实字节头校验。
```python
data = await file.read()
if len(data) > _MAX_SIZE_BYTES:
raise_business("文件超过 10 MB 限制")
# magic number 校验真实文件类型(防伪造content-type)
if not (data[:3] == b"\xff\xd8\xff" # JPEG
or data[:8] == b"\x89PNG\r\n\x1a\n" # PNG
or (data[:4] == b"RIFF" and data[8:12] == b"WEBP")): # WebP
raise_business("文件内容不是有效的 JPEG/PNG/WebP 图片")
```
**改动文件**`backend/app/api/v1/products.py`约6行
**风险**:无。
**验收**:把 .exe 改名 .jpg + 伪造 content-type 上传 → 被拒。
---
## 修9 · 文案优化轮默认关确认题非bug
**现状**text_variants.py:107-110`TEXT_OPTIMIZE_ENABLED=false` 默认关。
注释写明apiports 60s网关下优化轮的失败要白等60s才503拖慢出文案+100s+。
**这是有意为之的工程权衡不是bug。** 两个选择:
- **A建议保持关**等北哥prompt方案到位再开架构已留位改环境变量即开
- **B 改造绕60s**优化轮也走分批小请求≤4条/批降低单请求触发60s概率。工作量M。
**改动文件**A=0改动B=text_variants.py优化轮分批约20行
**待拍板**A还是B我建议A。
---
## 修10 · 无 key 不置灰引导
**根因**:配置页有"暂无Key请录入"提示ApiKeysTab.tsx:92`/tasks/new` 开任务页不检测key、不置灰入口。用户没录key也能点开始→任务必然失败。
**现状**`NewTaskForm.tsx:38,112-115` 只处理"产品入镜但无参考图"的置灰(blockedByNoImage)无key检测。
**改后**开任务页加载时查当前用户是否有key无则置灰"开始"+引导。
```tsx
// NewTaskForm 加载时
const { data: keys } = useApiKeys(); // 复用配置页的key查询
const noKey = !keys || keys.length === 0;
const blocked = blockedByNoImage || noKey;
// 开始按钮 disabled={blocked}
{noKey && <Banner> API Key <Link href="/config"></Link></Banner>}
```
**改动文件**`frontend/src/components/tasks/NewTaskForm.tsx`约10行、可能复用key查询hook。
**风险**:无。
**验收**清空key→开任务页"开始"置灰+引导文案+跳配置链接。
---
> 第二批小结修8magic number无歧义可直接做修6生图日志建议做、token降级修7/修9需你拍板多半可降级/保持现状修10纯前端体验补强。