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

191 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Clover 修改处理方案 · 第一批(⚠️ 做了不合格)
> 倩倩姐审方案专用。**未拍板不动代码**。每项含:现状代码 / 改后代码 / 改动文件清单 / 风险 / 验收。
> 现状代码均已逐行读核,行号真实。
---
## 修1 · 90分阈值过滤形同虚设
**根因**`pipeline_io.py:96-106` 循环把 `candidates_raw` 全部入库,没看 `passed`
text_variants.py 已给每条算了 `passed``text_variants.py:101`),但 pipeline 没用它过滤。
**现状代码**pipeline_io.py:95-108
```python
seq = seq_start
for i, c in enumerate(candidates_raw):
tc = TextCandidate(
workspace_id=workspace_id, task_id=task.id,
source=CandidateSource.AI,
angle_label=c.get("angle_label") or c.get("angle", ""),
content=json.dumps(c, ensure_ascii=False),
score_json=json.dumps(c.get("score_detail", []), ensure_ascii=False),
banned_word_status=BannedWordStatus(c.get("banned_word_status", "pass")),
)
db.add(tc)
db.flush()
```
**改后代码**
```python
# 90分过滤只入库 passed 的;硬拦截(hard_block)无论分数都剔除
qualified = [c for c in candidates_raw
if c.get("passed") and c.get("banned_word_status") != "hard_block"]
# 回退兜底:全员未达标时不空屏,取分最高的 N 条入库并标记 below_threshold
if not qualified:
fallback = sorted(candidates_raw, key=lambda x: x.get("score", 0), reverse=True)
qualified = [c for c in fallback if c.get("banned_word_status") != "hard_block"][:task.text_count]
for c in qualified:
c["below_threshold"] = True # 前端可标"未达90分,仅供参考"
seq = seq_start
for i, c in enumerate(qualified):
tc = TextCandidate(...) # 同现状circ 改成遍历 qualified
db.add(tc)
db.flush()
...
push_fn(..., "text_candidate", {
..., "below_threshold": c.get("below_threshold", False), # 新增透传
}, seq)
```
**改动文件**
- `backend/app/workers/pipeline_io.py`run_text_generation约改15行
- `frontend/src/components/tasks/TextCandidateCard.tsx`below_threshold时显黄标约5行可选
**风险**阈值90偏严正常出文案可能常触发回退。回退逻辑必须有否则用户看到空列表比现在更糟
**验收**①跑任务列表只剩≥90分 ②人为造全低分确认回退展示TopN且带黄标 ③hard_block的永不出现。
**待拍板**回退要不要我建议要空屏体验更差。below_threshold黄标前端做不做
---
## 修2 · 品牌词字段缺失(文案+生图同根因,一次解两处)
**根因**`products` 表无 `brand_keyword`model:product.py:28-40 无此字段)。
但三处代码都在读它:`_text_prompt.py:137``storyboard.py:133``image_gen.py:140`
`product.get("brand_keyword")` 永远 None → 文案不植入品牌词、生图特写图不带品牌词,静默失效。
**改动分5步**
**① 新建迁移** `backend/alembic/versions/008_product_brand_keyword.py`
```python
"""add brand_keyword to products
Revision ID: 008
Revises: 007
"""
from alembic import op
import sqlalchemy as sa
revision = "008"
down_revision = "007"
branch_labels = None
depends_on = None
def upgrade():
op.add_column("products", sa.Column(
"brand_keyword", sa.String(64), nullable=True,
comment="品牌词,植入文案每条+生图特写图(第2/6张)"))
def downgrade():
op.drop_column("products", "brand_keyword")
```
**② 模型加字段** `models/product.py`image_path 后插一行):
```python
brand_keyword: Mapped[str | None] = mapped_column(String(64)) # 品牌词,植入文案+生图
```
**③ DTO加字段** `api/v1/products.py:24 ProductCreate`
```python
brand_keyword: str | None = None # 品牌词
```
`create_product`(L85) 构造 Product 时加 `brand_keyword=body.brand_keyword`
`_fmt_product`(L49) 返回加 `"brand_keyword": p.brand_keyword`
**④ 前端录入框**:产品表单加"品牌词"输入(`ProductSubComponents.tsx` 或产品创建表单)。
**⑤ 确认读取生效**:三处读取代码已存在,加字段后自动拿到值,无需改。
**改动文件**008迁移(新)、model/product.py、api/v1/products.py、前端产品表单。
**风险**:①北哥环境要 `alembic upgrade head` 跑迁移 ②老产品该列NULL→读到空串三处已用 `or ""` 兜底,不报错。
**验收**:建产品填"龙石"→生成文案每条含"龙石"、生图第2/6张画面带"龙石"。
**待拍板**:品牌词录在**产品级**(随产品固定,方案默认)还是**任务级**(每次任务可改)
---
## 修4 · 侧边栏导航404先放修4修3叠字单列文末
**根因**`Sidebar.tsx:19-28` 的 NAV_ITEMS 8条里4条指向不存在的页
- `/tasks`(无 page`/images`(无 page`/tasks/build`(无 page`/tasks/list`(无 page
实际存在的页:/dashboard /tasks/new /tasks/[id]/text /tasks/[id]/images /tasks/[id]/confirm /review /history /config
**现状代码**Sidebar.tsx:19-28
```tsx
const NAV_ITEMS: NavItem[] = [
{ label: '工作台', href: '/dashboard', icon: '🏠' },
{ label: '开新任务', href: '/tasks/new', icon: '✏️' },
{ label: '选择文案', href: '/tasks', icon: '📝' }, // 404
{ label: '选择图片', href: '/images', icon: '🖼️' }, // 404
{ label: '建立任务', href: '/tasks/build', icon: '⚙️' }, // 404
{ label: '任务列表', href: '/tasks/list', icon: '📋' }, // 404
{ label: '历史归档', href: '/history', icon: '🗂️' },
{ label: '配置中心', href: '/config', roles: ['admin'], icon: '⚙️' },
];
```
**改后代码**删掉4条死链。选文案/选图片是任务流程内的步骤页(/tasks/[id]/text 等),不该做全局导航,进了具体任务才有。审核页`/review`原本漏在导航里,补上。
```tsx
const NAV_ITEMS: NavItem[] = [
{ label: '工作台', href: '/dashboard', icon: '🏠' },
{ label: '开新任务', href: '/tasks/new', icon: '✏️' },
{ label: '审核', href: '/review', roles: ['admin', 'leader'], icon: '✅' },
{ label: '历史归档', href: '/history', icon: '🗂️' },
{ label: '配置中心', href: '/config', roles: ['admin'], icon: '⚙️' },
];
```
**改动文件**`frontend/src/components/layout/Sidebar.tsx`改NAV_ITEMS数组
**风险**:无,纯导航项删改。
**验收**点遍每个导航项都进到真实页无404。
**待拍板**:审核页要不要进侧边栏(现状代码注释提到/review角色守卫但导航里没有建议补上。
---
## 修5 · 文案调用无网络退避重试
**根因**`text_variants.py:49 _generate_one_batch` 只在JSON解析失败时重试2次网络层503/超时不退避。
生图侧已有成熟退避 `_retry`image_gen.py:51-63文案没复用。
**现状代码**text_variants.py:47-56
```python
async def _generate_one_batch(llm_client, product, batch_n, extra) -> list[dict]:
for attempt in range(2):
raw = await _call_llm(llm_client, build_prompt(product, batch_n, extra_rules=extra))
parsed = parse_json_array(raw)
if parsed:
return parsed
logger.warning(...)
return []
```
**改后代码**_call_llm 返回空含503/超时时退避重试。注意退避会拉长耗时须控制总时长不撞60s网关——故退避只1次、间隔短(2s)。
```python
async def _generate_one_batch(llm_client, product, batch_n, extra) -> list[dict]:
for attempt in range(3): # 解析失败/网络空 共最多3次
raw = await _call_llm(llm_client, build_prompt(product, batch_n, extra_rules=extra))
parsed = parse_json_array(raw)
if parsed:
return parsed
if not raw and attempt < 2: # 网络层空响应→短退避再试
await asyncio.sleep(2 * (attempt + 1)) # 2s,4s
logger.warning(...)
return []
```
**改动文件**`backend/app/services/ai_engine/text_variants.py`_generate_one_batch约改5行
**风险**退避叠加可能逼近60s网关上限故只退避2次、间隔短。若仍超时本地兜底(build_local_drafts)已兜底。
**验收**模拟apiports 503确认自动退避重试而非直接掉本地兜底。
---
> 修3瓶身中文叠字技术最不确定与建13套图合并放"第三批·需新建/重构"文件细化避免和无歧义的纯bug混在一起。