上线版: 产品表单统一+form嵌套修复+用户管理+部署+三套叙事
- 产品编辑入口统一走 ProductFormFull(卖点/风格/人群/品牌词全字段); 修复开任务页 <form> 套 <form> 致"编辑产品"报错、改不了、跳回首个产品 - dashboard 入口卡片对齐实际路由: 系统管理(/config) 与 工作配置(/settings) 分开; settings ?tab=products 直达改用挂载后读 URL, 消除 hydration mismatch - 新增用户管理(users API/admin service/改密页) + alembic 022/023/024 - 上线部署: Dockerfile / docker-compose.prod+https / nginx https / .env.example - A8 三套正交叙事(痛点/场景/成分背书) + beige 调色去AI化 + 飞轮 text_import 高权重信号 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,13 @@ def _parse_fission_response(
|
||||
build_fallback_image_plan, build_fallback_notes,
|
||||
)
|
||||
|
||||
def _fallback(reason: str) -> list[dict]:
|
||||
notes = build_fallback_notes(source_note, product, note_count, image_count)
|
||||
for note in notes:
|
||||
note["is_fallback"] = True
|
||||
note["fallback_reason"] = reason
|
||||
return notes
|
||||
|
||||
notes = notes_array_from_parsed(parse_json_array(raw))
|
||||
if not notes:
|
||||
# parse_json_array 只认数组;再试整段当对象解析(容错单对象返回)
|
||||
@@ -42,7 +49,7 @@ def _parse_fission_response(
|
||||
notes = []
|
||||
if not notes:
|
||||
logger.warning("裂变 LLM 返回不可解析,启用品类兜底。raw[:120]=%s", str(raw)[:120])
|
||||
return build_fallback_notes(source_note, product, note_count, image_count)
|
||||
return _fallback("llm_parse_failed")
|
||||
|
||||
out: list[dict] = []
|
||||
for n in notes:
|
||||
@@ -53,17 +60,21 @@ def _parse_fission_response(
|
||||
if not isinstance(plan, list) or len(plan) != image_count:
|
||||
n["imagePlan"] = build_fallback_image_plan(n, image_count)
|
||||
out.append(n)
|
||||
return out or build_fallback_notes(source_note, product, note_count, image_count)
|
||||
return out or _fallback("llm_empty_notes")
|
||||
|
||||
|
||||
def _score_notes(clients, notes: list[dict], source_note: dict, banned: list[str]) -> None:
|
||||
def _score_notes(
|
||||
clients, notes: list[dict], source_note: dict, banned: list[str],
|
||||
product: dict | None = None,
|
||||
) -> None:
|
||||
"""对每套笔记 LLM 评分(限2并发),结果就地写回 note['_score']/_passed/_block。
|
||||
|
||||
Celery 同步环境:用 asyncio.run 跑一个内部 gather(信号量限2并发,
|
||||
对齐生图限流,避免中转站 429)。
|
||||
D1修复:product 传给 llm_score_copy → build_score_prompt 组装【产品语境】,
|
||||
评委不再盲评,买点转化/痛点人群精准两维能基于产品信息给分。
|
||||
Celery 同步环境:用 asyncio.run 跑一个内部 gather(信号量限2并发)。
|
||||
"""
|
||||
import asyncio
|
||||
from app.services.ai_engine.llm_scorer import llm_score_copy
|
||||
from app.services.ai_engine.llm_scorer import llm_score_copy, ScoringUnavailableError
|
||||
from app.services.ai_engine.constants import QUALITY_PASS_SCORE
|
||||
|
||||
async def _run() -> None:
|
||||
@@ -78,8 +89,17 @@ def _score_notes(clients, notes: list[dict], source_note: dict, banned: list[str
|
||||
async with sem:
|
||||
try:
|
||||
r = await llm_score_copy(
|
||||
clients, copy, source_note, banned, pass_score=QUALITY_PASS_SCORE,
|
||||
clients, copy, product or source_note, banned,
|
||||
pass_score=QUALITY_PASS_SCORE,
|
||||
)
|
||||
except ScoringUnavailableError as exc:
|
||||
# B1红线对齐:评分两通道都挂≠质量差,明确标记不可用,绝不静默记0分糊弄
|
||||
logger.error("裂变评分通道不可用(非质量问题): %s", exc)
|
||||
note["_scoring_unavailable"] = True
|
||||
note["_score"] = 0
|
||||
note["_block"] = False
|
||||
note["_passed"] = False
|
||||
return
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning("裂变评分异常,记0分不达标: %s", exc)
|
||||
r = {"score": 0, "passed": False, "banned_words_found": []}
|
||||
@@ -91,6 +111,11 @@ def _score_notes(clients, notes: list[dict], source_note: dict, banned: list[str
|
||||
await asyncio.gather(*(_one(n) for n in notes))
|
||||
|
||||
asyncio.run(_run())
|
||||
# 整批评分全因通道不可用而失败 → 抛错让上层(execute_fission_pipeline)感知,
|
||||
# 标记任务"评分服务不可用"而非误判成完成/质量差
|
||||
if notes and all(n.get("_scoring_unavailable") for n in notes):
|
||||
raise ScoringUnavailableError(
|
||||
f"裂变 {len(notes)} 套笔记评分全部失败:评分通道(apiports+codeproxy)均不可用")
|
||||
|
||||
|
||||
def execute_fission_pipeline(db: Session, clients, fission_id: int, source_task_id: int) -> dict:
|
||||
@@ -147,7 +172,8 @@ def execute_fission_pipeline(db: Session, clients, fission_id: int, source_task_
|
||||
logger.warning("裂变 LLM 调用失败,启用品类兜底: %s", exc)
|
||||
|
||||
notes = _parse_fission_response(raw, source_note, product, n, image_count)
|
||||
_score_notes(clients, notes, source_note, banned)
|
||||
# D1修复:传 product 给评委,不再盲评(买点转化/痛点人群精准需要产品语境)
|
||||
_score_notes(clients, notes, source_note, banned, product=product)
|
||||
|
||||
# 排序:过线优先,再按分降序;取前 N 套(不足用兜底草稿补齐)
|
||||
notes.sort(key=lambda x: (x.get("_passed", False), x.get("_score", 0)), reverse=True)
|
||||
@@ -175,5 +201,9 @@ def execute_fission_pipeline(db: Session, clients, fission_id: int, source_task_
|
||||
from app.services.fission_images import generate_fission_images
|
||||
generate_fission_images(db, clients, ft, product, image_count, saved_ids)
|
||||
|
||||
ft.status = "completed"; db.commit()
|
||||
return {"fission_id": fission_id, "status": "completed", "note_ids": saved_ids}
|
||||
has_fallback = any(bool(note.get("is_fallback")) for note in chosen)
|
||||
# status 字段是 varchar(20),"completed_with_fallback"(23字符)会撑爆触发 DataError 1406,
|
||||
# 故用短码 "completed_fb"(倩倩姐2026-06-30修);语义=完成但含降级草稿。
|
||||
ft.status = "completed_fb" if has_fallback else "completed"
|
||||
db.commit()
|
||||
return {"fission_id": fission_id, "status": ft.status, "note_ids": saved_ids}
|
||||
|
||||
Reference in New Issue
Block a user