image_asset + checkpoint(background_render groups 등)에서 nodes/edges를 구조키 조인으로 조립(메인 파이프라인 무접촉). 프론트는 React Flow + dagre로 렌더, 클릭 시 역방향 BFS로 상류 하이라이트.@xyflow/react v12 · dagre.
python -m http.server --bind 0.0.0.0), open 금지. 커밋은 사용자 GO 시에만.| 파일 | 책임 | 신규/수정 |
|---|---|---|
backend/app/schemas/pipeline.py | PipelineGraphResponse / PipelineNode / PipelineEdge Pydantic 스키마 | 신규 |
backend/app/services/pipeline_graph_service.py | nodes/edges 조립(읽기 전용): DB 노드 + checkpoint virtual 노드 + 엣지 복원 + verification | 신규 |
backend/app/api/v1/pipeline.py | GET /pipeline-graph 라우트 (프로젝트 prefix) | 신규 |
backend/app/api/v1/__init__.py 또는 라우터 등록부 | pipeline 라우터 include | 수정(1줄) |
backend/tests/services/test_pipeline_graph_service.py | 엣지 복원 결정론 단위 테스트 | 신규 |
frontend/src/hooks/api/usePipelineGraph.ts | react-query 훅 (GET pipeline-graph) | 신규 |
frontend/src/components/pipeline/layout.ts | dagre 레이어드 좌표 계산 + 역방향 BFS 조상 계산 | 신규 |
frontend/src/components/pipeline/PipelineNode.tsx | 커스텀 이미지 노드(썸네일 lazy + 배지 + 디밍) | 신규 |
frontend/src/components/pipeline/NodeDetailPanel.tsx | 풀해상 + 프롬프트 펼침 + 직접입력/상류 분리 + 라벨 배지 | 신규 |
frontend/src/pages/PipelineCanvas.tsx | 페이지: 에피소드 선택·필터·React Flow·하이라이트 상태 | 신규 |
frontend/src/components/pipeline/__tests__/layout.test.ts | BFS/레이아웃 결정론 테스트(vitest) | 신규 |
frontend/src/App.tsx | 라우트 /projects/:id/pipeline | 수정 |
frontend/src/components/layout/Sidebar.tsx | 프로젝트 메뉴 항목 | 수정 |
frontend/src/i18n/ko.json | nav.project.pipeline | 수정 |
frontend/package.json | @xyflow/react, dagre, @types/dagre | 수정 |
PipelineNode {
id: str # ImageAsset.id OR "virtual:<sha1(png_path)>"
node_origin: "image_asset" | "checkpoint_virtual"
asset_type: str # reference|floor_plan|chain_bg|background_chain_node|composite|scene|guide
guide_kind: str | None # virtual 노드일 때 (composition_guide|pose_guide|anchor_sketch ...)
entity_id, entity_short_id, entity_name, entity_type: str | None
still_id: str | None; scene_index: int | None; shot_index: int | None
episode_id: str | None; variant_label: str | None
thumb_url: str | None # image_asset 노드만 (/file?thumb=1). virtual 은 full_url 직접
full_url: str
prompt: str | None # image_asset.prompt_used
status, created_at: str | None
}
PipelineEdge {
source: str; target: str # node ids
kind: "i2i" | "reference" | "background" | "fp" | "prev_scene"
confidence: "direct_fk" | "checkpoint_structural" | "lineage_reference" | "call_log_label_seen" | "planned_only"
verification_status: "structural_inferred" | "label_seen" | "unverified" | "conflict"
bg_id: str | None
}
PipelineGraphResponse { episode_id, nodes: PipelineNode[], edges: PipelineEdge[], episodes: {id,label}[] }
Files: Create schemas/pipeline.py, services/pipeline_graph_service.py(골격), tests/services/test_pipeline_graph_service.py.
Produces: build_pipeline_graph(db, project_id, episode_id) -> PipelineGraphResponse. list_episodes(db, project_id).
image_asset(project_id, episode_id)만 노드로 변환(엣지 빈 배열)하도록 최소 구현.Files: Modify service, Modify test.
source_image_id/parent_image_id → edge(source=부모, target=자식, kind=i2i, confidence=direct_fk, verification=structural_inferred).reference_image_ids(JSON 파싱, 자산 UUID) → 각 ref→scene edge(kind=reference, confidence=lineage_reference).Files: Modify service, Modify test. 설계 §9.1 그대로.
background_render.data.groups(Phase7) → 없으면 background_chain_render.data.groups(Phase5) → legacy data.locations[loc].shot_backgrounds[]. (체크포인트 로드는 기존 _load_prev_checkpoint 패턴 재사용, 읽기 전용)groups[bg_id] = {png_path, shot_ids[], location_id} 에서: shot_ids(Sxx_Shotyy) → (scene_index, shot_index) 파싱 → 해당 scene 노드.image_asset where asset_type='chain_bg' AND episode_id AND variant_type=bg_id. 없으면 png_path 기반 virtual 노드(node_origin='checkpoint_virtual').entity_id==location_id)→bg(kind=fp, confidence=checkpoint_structural 또는 inferred).scene_still.dependent_scene_id → 직전 씬 primary scene 노드(kind=prev_scene).Files: Modify service, Modify test. 설계 §9.2.
image_service_helpers._lookup_actual_refs_batch 패턴 참고하여 llm_call_log를 metadata_json.still_id(+entity_id)·operation_type으로 조인 → 씬별 actual_labels_seen 수집(읽기 전용, 새 helper).label_seen, 구조만이면 structural_inferred 유지, 충돌이면 conflict. (라벨→UUID 단정 금지)prompt_authoritative(user_prompt) 선택 필드 부착(있을 때만).Files: Create api/v1/pipeline.py, Modify 라우터 등록.
GET /api/v1/projects/{project_id}/pipeline-graph?episode_id= → verify_project_access 의존성 재사용, build_pipeline_graph 호출. episode_id 없으면 첫 에피소드.Files: Modify package.json, Create usePipelineGraph.ts, components/pipeline/layout.ts, __tests__/layout.test.ts.
computeLayout(nodes, edges): {id, x, y}[](dagre 래퍼, 좌→우 layered), upstreamAncestors(nodeId, edges): Set<string>(역방향 BFS), directInputs(nodeId, edges): string[].npm i @xyflow/react dagre @types/dagre.upstreamAncestors('C')={'A','B'}, directInputs('C')=['B']. 사이클 방어(visited). 분기 그래프 검증.useStillImages 패턴) 작성.Files: Create PipelineNode.tsx, PipelineCanvas.tsx. App/Sidebar/i18n 수정.
AppShell title projectId 래핑 + 에피소드 선택기 + React Flow(<ReactFlow nodes edges>) + dagre 좌표 주입.thumb_url, loading="lazy") + 타입/엔티티 배지 + 엔티티별 색상(design 범례) + 디밍 클래스. virtual 노드 점선 테두리 + guide 배지./projects/:id/pipeline + Sidebar 항목 + nav.project.pipeline ko.json.upstreamAncestors set 계산 → set 밖 노드/엣지 opacity 0.15. 배경 클릭 시 해제.full_url) + 프롬프트(prompt, 펼침/접힘) + prompt_authoritative(있으면) + 직접 입력 목록 / 상류 체인 분리 + lineage 배지 + verification 배지.node_origin='checkpoint_virtual', asset_type='guide', guide_kind). 실제 저장 위치는 구현 시 확인.llm_call_log 실제 attach와 모순 없는지 교차 확인.