Files
beige/plans/修改处理方案-第二批.md
yangqianqian 6a2632da70 baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:30:22 +08:00

6.7 KiB
Raw Blame History

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文案日志

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前

# 生图调用留痕(与文案对称)
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-91gemini_factory.gemini_generate 只服务生图文案无Gemini通道。

现状代码text_variants.py:89-91

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 方法(现只有图)。

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_typeHTTP header可伪造。无字节头校验。

现状代码api/v1/products.py:174-179

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后加真实字节头校验。

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-110TEXT_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无则置灰"开始"+引导。

// 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纯前端体验补强。