Files
beige/backend/app/services/fission_images.py
yangqianqian d85dcd401b 第11环裂变重写:对齐上线版 split.js 一次LLM出N套完整笔记包
架构从"扇出N个GenerationTask各跑完整管道"改为"一次LLM调用直接出N套
完整笔记包(N=1~5)",落 FissionNote 表 + 独立展示页。

后端:
- 018迁移:fission_notes 表(文案JSON+score+passed+imagePlan+images+status)
- fission_prompt:FISSION_SYSTEM+三档参考度(low/mid/high)+normalize_tags+品类兜底
- fission_pipeline:一次LLM出N套→各评分(@80合格线)→排序→落库,不达标标
  needs_optimization 非丢弃;apiports 503 回落 codeproxy gpt-5.5 强档兜底
- fission_images:每套串行调现有生图接口(零改动image_gen/storyboard)
- tasks.py:run_fission_pipeline Celery task,删旧扇出注入
- api/v1/fission:进度聚合FissionNote + GET /fission/{id}/notes(剥内部字段)

前端:FissionProgress对齐状态机 + N套独立展示页 + FissionNoteCard

测试:test_fission_engine(19)+test_fission_pipeline(5) 全过;104 全量回归绿

实测task5(fanout=2,mid)端到端跑通:一次出2套→seq0=85过/seq1=79标优化→
生图codeproxy/edits→1024×1536去AI化→task completed→notes端点返完整数据

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:17:37 +08:00

82 lines
3.3 KiB
Python
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.
"""
app/services/fission_images.py — 第11环裂变 逐套生图(从 fission_pipeline 拆出)
复用单篇 generate_storyboard_imagescodeproxy edits 带产品参考图),结果存
FissionNote.images_json。拆分原因fission_pipeline.py 超 200 行红线上限。
🔴 生图引擎零改动;串行跑各套(套内 storyboard 已 asyncio.gather 并发)避免打爆中转站。
"""
import logging
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
def generate_fission_images(
db: Session, clients, ft, product: dict, image_count: int, note_ids: list[int],
) -> None:
"""逐套生图,结果存 FissionNote.images_json。
每套用该套自己的 note_json含定制 imagePlan当 note 喂生图引擎。
产品参考图沿用源产品同一张,与单篇一致。
"""
import asyncio
import json as _json
import os
from app.models.fission import FissionNote
from app.core.config import get_settings
from app.services.ai_engine.image_gen import generate_storyboard_images
from app.services.ai_engine.image_postprocessor import process_image
from app.workers.pipeline_io import _resolve_image_path
reference_images = None
_img_path = _resolve_image_path(product.get("image_path", ""))
if _img_path and os.path.isfile(_img_path):
try:
with open(_img_path, "rb") as f:
reference_images = [f.read()]
except Exception as e: # noqa: BLE001
logger.warning("裂变参考图读取失败,无参考图模式: %s", e)
upload_base = get_settings().UPLOAD_ABS_ROOT
for nid in note_ids:
fn = db.query(FissionNote).filter(FissionNote.id == nid).first()
if not fn:
continue
try:
note = _json.loads(fn.note_json) if fn.note_json else {}
except (ValueError, TypeError):
note = {}
try:
results = asyncio.run(generate_storyboard_images(
client=clients, note=note, product=product,
image_count=image_count, reference_images=reference_images,
))
except Exception as exc: # noqa: BLE001
logger.error("裂变套 seq=%s 生图失败: %s", fn.seq, exc)
fn.status = "failed"; db.commit()
continue
img_dir = os.path.join(upload_base, str(ft.workspace_id), f"fission_{ft.id}", str(fn.seq))
os.makedirs(img_dir, exist_ok=True)
images: list[dict] = []
for i, r in enumerate(results):
if r.get("error") or not r.get("image_bytes"):
images.append({"role": r.get("role", ""), "error": str(r.get("error", "生图失败"))[:64]})
continue
try:
processed = process_image(r["image_bytes"], aspect_ratio="3:4", resample_strength=1)
except Exception: # noqa: BLE001
processed = r["image_bytes"]
fname = f"{i + 1:02d}_{r['role']}.jpg"
with open(os.path.join(img_dir, fname), "wb") as f:
f.write(processed)
images.append({
"role": r["role"], "seq": i + 1,
"url": f"/uploads/{ft.workspace_id}/fission_{ft.id}/{fn.seq}/{fname}",
})
fn.images_json = _json.dumps(images, ensure_ascii=False)
fn.status = "image_done"
db.commit()