Files
C.E.L_Slide_test2/Front/client/src/components/PipelineTracePanel.tsx
T
kmlee1andClaude Opus 4.8 b836e79ee1 wip: phase_z2 evidence 파이프라인 + matching 실험(phase2~26) + 프론트 trace 패널 진행분 스냅샷
- src: phase_z2 composition/mapper/pipeline/placement_planner/retry, ai_fallback(prompts/schema/validate), mdx_text_atoms 신규
- Front: PipelineTracePanel 신규, FramePanel/SlideCanvas/Home/designAgentApi 등 갱신 + 테스트 4종 추가
- templates/phase_z2: catalog(component_expansion_registry, node_slot_mapping 신규), frames, families, slide_base 갱신
- tests/matching: phase2~26 매칭 실험 스크립트·리포트·온톨로지 전체 (미커밋 진행분)
- tests: b4_v4 evidence, task5~28.5 시리즈, regression(imp95 baseline) 등 신규 테스트 대량 추가
- docs/reference: MDX 구조 인벤토리, MDX→Frame 구조 계약 문서
- scripts: mdx 계약/parity/coverage/viewport 체크, gitea comment, run sync 유틸
- .gitignore: tmp*.json, chromedriver, .orchestrator, *.pkl, Front_test* 등 임시/스냅샷 제외

미완성 작업의 보존용 스냅샷 커밋 (2026-07-02)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:03:42 +09:00

217 lines
10 KiB
TypeScript

import { AlertTriangle, CheckCircle2, GitBranch, Layers3 } from "lucide-react";
import type React from "react";
import type { AiTraceSummary, PipelineTraceSummary } from "../services/designAgentApi";
interface PipelineTracePanelProps {
trace: PipelineTraceSummary | null | undefined;
aiTrace?: AiTraceSummary | null;
}
const stateClass: Record<string, string> = {
rendered: "bg-emerald-50 text-emerald-700 border-emerald-200",
render_blocked: "bg-red-50 text-red-700 border-red-200",
filtered: "bg-amber-50 text-amber-700 border-amber-200",
covered_not_rendered: "bg-orange-50 text-orange-700 border-orange-200",
uncovered: "bg-slate-50 text-slate-500 border-slate-200",
};
function Chip({ children, tone = "slate" }: { children: React.ReactNode; tone?: "slate" | "red" | "green" | "amber" }) {
const tones = {
slate: "bg-slate-100 text-slate-600",
red: "bg-red-100 text-red-700",
green: "bg-emerald-100 text-emerald-700",
amber: "bg-amber-100 text-amber-700",
};
return (
<span className={`inline-flex items-center rounded px-1.5 py-0.5 text-[9px] font-black uppercase ${tones[tone]}`}>
{children}
</span>
);
}
export default function PipelineTracePanel({ trace, aiTrace }: PipelineTracePanelProps) {
if (!trace) {
return (
<div className="h-full flex items-center justify-center bg-slate-50 p-8 text-center text-slate-400">
<div>
<GitBranch className="mx-auto mb-3 h-10 w-10 opacity-20" />
<p className="text-[10px] font-black uppercase tracking-[0.18em]">No Trace Loaded</p>
</div>
</div>
);
}
return (
<div className="h-full overflow-y-auto bg-white p-4 text-slate-700">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<GitBranch className="h-4 w-4 text-blue-500" />
<h3 className="text-[10px] font-black uppercase tracking-widest text-slate-500">Pipeline Trace</h3>
</div>
<Chip tone={trace.warnings.length > 0 ? "amber" : "green"}>{trace.status}</Chip>
</div>
{trace.warnings.length > 0 && (
<div className="mb-4 rounded border border-amber-200 bg-amber-50 p-3">
<div className="mb-1 flex items-center gap-1.5 text-[10px] font-black uppercase text-amber-700">
<AlertTriangle className="h-3.5 w-3.5" />
Warnings
</div>
<div className="flex flex-wrap gap-1">
{trace.warnings.map((warning) => (
<Chip key={warning} tone="amber">{warning}</Chip>
))}
</div>
</div>
)}
<section className="mb-5">
<div className="mb-2 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-slate-400">
<Layers3 className="h-3.5 w-3.5" />
Sections
</div>
<div className="space-y-2">
{trace.sections.map((section) => (
<div key={section.id} className={`rounded border p-2 ${stateClass[section.state] ?? stateClass.uncovered}`}>
<div className="flex items-center justify-between gap-2">
<span className="font-mono text-[11px] font-bold">{section.id}</span>
<span className="text-[9px] font-black uppercase">{section.state}</span>
</div>
{section.title && <div className="mt-1 text-[11px] leading-snug">{section.title}</div>}
{section.child_ids.length > 0 && (
<div className="mt-1 text-[10px] text-slate-500">children: {section.child_ids.join(", ")}</div>
)}
{section.reasons.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1">
{section.reasons.map((reason) => (
<Chip key={reason} tone={section.state === "render_blocked" ? "red" : "amber"}>
{reason}
</Chip>
))}
</div>
)}
</div>
))}
</div>
</section>
<section className="mb-5">
<div className="mb-2 text-[10px] font-black uppercase tracking-widest text-slate-400">Units</div>
<div className="space-y-2">
{trace.units.map((unit) => (
<div key={unit.unit_id} className="rounded border border-slate-200 bg-slate-50 p-2">
<div className="font-mono text-[11px] font-bold text-slate-700">{unit.unit_id || "(empty unit)"}</div>
<div className="mt-1 grid grid-cols-2 gap-x-2 gap-y-1 text-[10px]">
<span>frame</span><span className="font-mono">{unit.selected_frame ?? "-"}</span>
<span>candidates</span><span className="font-mono">{unit.candidate_count}</span>
<span>path</span><span className="font-mono">{unit.selection_path ?? "-"}</span>
<span>label</span><span className="font-mono">{unit.label ?? "-"}</span>
</div>
{unit.warnings.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1">
{unit.warnings.map((warning) => <Chip key={warning} tone="amber">{warning}</Chip>)}
</div>
)}
</div>
))}
</div>
</section>
<section>
<div className="mb-2 text-[10px] font-black uppercase tracking-widest text-slate-400">Zones / Render</div>
<div className="space-y-2">
{trace.zones.map((zone) => {
const filled = zone.slot_status === "filled";
return (
<div key={zone.position} className="rounded border border-slate-200 bg-white p-2">
<div className="flex items-center justify-between">
<span className="font-mono text-[11px] font-bold">{zone.position}</span>
<Chip tone={filled ? "green" : "red"}>{zone.slot_status}</Chip>
</div>
<div className="mt-1 text-[10px] text-slate-500">sections: {zone.source_section_ids.join(", ") || "-"}</div>
<div className="mt-1 text-[10px] text-slate-500">template: <span className="font-mono">{zone.template_id ?? "-"}</span></div>
<div className="mt-1 text-[10px] text-slate-500">slot keys: {zone.slot_key_count}</div>
{zone.warnings.length > 0 ? (
<div className="mt-2 flex flex-wrap gap-1">
{zone.warnings.map((warning) => <Chip key={warning} tone="red">{warning}</Chip>)}
</div>
) : (
<div className="mt-2 flex items-center gap-1 text-[10px] text-emerald-600">
<CheckCircle2 className="h-3 w-3" />
render payload ready
</div>
)}
</div>
);
})}
</div>
</section>
<section className="mt-5">
<div className="mb-2 text-[10px] font-black uppercase tracking-widest text-slate-400">AI Status</div>
{!aiTrace ? (
<div className="rounded border border-slate-200 bg-slate-50 p-3 text-[11px] text-slate-500">
AI trace not loaded.
</div>
) : (
<div className="rounded border border-slate-200 bg-white p-3">
<div className="mb-2 flex flex-wrap gap-1">
<Chip tone={aiTrace.artifact_present ? "green" : "red"}>
{aiTrace.artifact_present ? "artifact" : "missing"}
</Chip>
<Chip tone={aiTrace.ai_enabled === false ? "amber" : aiTrace.ai_enabled ? "green" : "slate"}>
enabled {aiTrace.ai_enabled === null ? "unknown" : String(aiTrace.ai_enabled)}
</Chip>
<Chip tone={aiTrace.ai_called_count > 0 ? "green" : "slate"}>
called {aiTrace.ai_called_count}
</Chip>
<Chip tone={aiTrace.eligible_count > 0 ? "amber" : "slate"}>
eligible {aiTrace.eligible_count}
</Chip>
<Chip tone={aiTrace.error_count > 0 ? "red" : "slate"}>
errors {aiTrace.error_count}
</Chip>
</div>
<div className="grid grid-cols-2 gap-x-2 gap-y-1 text-[10px] text-slate-500">
<span>status</span><span className="font-mono">{aiTrace.status}</span>
<span>coverage</span><span className="font-mono">{aiTrace.coverage_status ?? "-"}</span>
<span>provider</span><span className="font-mono">{aiTrace.provider ?? "-"}</span>
<span>human review</span><span className="font-mono">{String(aiTrace.human_review_required)}</span>
</div>
{Object.keys(aiTrace.skip_reasons).length > 0 && (
<div className="mt-3">
<div className="mb-1 text-[10px] font-black uppercase text-slate-400">Skip Reasons</div>
<div className="flex flex-wrap gap-1">
{Object.entries(aiTrace.skip_reasons).map(([reason, count]) => (
<Chip key={reason} tone={reason === "called" ? "green" : "slate"}>
{reason}: {count}
</Chip>
))}
</div>
</div>
)}
{aiTrace.units.length > 0 && (
<div className="mt-3 space-y-2">
{aiTrace.units.map((unit) => (
<div key={`${unit.unit_index}-${unit.source_section_ids.join("+")}`} className="rounded bg-slate-50 p-2 text-[10px]">
<div className="flex items-center justify-between">
<span className="font-mono font-bold">{unit.source_section_ids.join("+") || `unit-${unit.unit_index}`}</span>
<Chip tone={unit.ai_called ? "green" : "slate"}>{unit.ai_called ? "called" : "not called"}</Chip>
</div>
<div className="mt-1 grid grid-cols-2 gap-x-2 gap-y-1 text-slate-500">
<span>route</span><span className="font-mono">{unit.route_hint ?? "-"}</span>
<span>skip</span><span className="font-mono">{unit.skip_reason ?? "-"}</span>
<span>apply</span><span className="font-mono">{unit.apply_status ?? "-"}</span>
<span>error</span><span className="font-mono">{unit.error ?? "-"}</span>
</div>
</div>
))}
</div>
)}
</div>
)}
</section>
</div>
);
}