feat(frontend): add Front/ — Vite/React frontend with backend pipeline integration

Mirror of design_agent_front/design-agent/ for shipping alongside backend.

Vite plugin (vitePluginPhaseZApi) endpoints :
  - POST /api/run   — spawn `python -m src.phase_z2_pipeline` with overrides
  - GET  /api/sample-mdx?mdx=03/04/05 — fixed sample MDX
  - GET  /frame-preview/{n} — figma preview thumbnails
  - GET  /data/runs/{run_id}/{path} — pipeline artifacts (final.html, step*.json, ...)

Env toggle forward (보고용) :
  PHASE_Z_ALLOW_RESTRUCTURE / PHASE_Z_ALLOW_REJECT / PHASE_Z_MAX_RANK=32

Components :
  - LeftMdxPanel (03/04/05 fix list + section tree)
  - SlideCanvas (iframe + slideOverrideCss prop for inline CSS inject)
  - FramePanel (label priority + confidence sort)
  - LayoutPanel

README with mermaid diagrams covering the 5-step demo flow.
node_modules / dist / .manus-logs / .env excluded via .gitignore.
This commit is contained in:
2026-05-14 14:48:42 +09:00
parent 52ccb7fc8b
commit 0f0d3fa91f
99 changed files with 20280 additions and 0 deletions
@@ -0,0 +1,101 @@
/**
* BottomActions - 하단 액션 버튼 영역
*
* 생성하기, 다운로드, 연동하기 버튼 컴포넌트
*/
import { Sparkles, Download, Link2, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import type { SlidePlan, UserSelection } from "../types/designAgent";
import { serializeSlidePlan } from "../utils/slidePlanUtils";
interface BottomActionsProps {
slidePlan: SlidePlan | null;
userSelection: UserSelection;
isLoading: boolean;
onGenerate: () => void;
}
export default function BottomActions({
slidePlan,
userSelection,
isLoading,
onGenerate,
}: BottomActionsProps) {
const handleDownload = () => {
if (!slidePlan) {
toast.error("슬라이드 플랜이 없습니다. 먼저 생성하기를 눌러주세요.");
return;
}
const json = serializeSlidePlan(slidePlan, userSelection);
console.log("[Download] SlidePlan JSON:", json);
// JSON 파일 다운로드
const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `slide-plan-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
toast.success("SlidePlan JSON이 다운로드되었습니다.");
};
const handleConnect = () => {
toast.info("연동하기 기능은 파이프라인 연결 후 활성화됩니다.");
};
return (
<div className="flex items-center justify-center gap-3 px-6 py-3 bg-white border-t border-slate-200">
<div className="flex items-center gap-1.5 mr-2">
<span className="w-5 h-5 rounded-full bg-blue-600 text-white text-xs flex items-center justify-center font-bold">4</span>
<span className="text-xs text-slate-500 font-medium"></span>
</div>
{/* 생성하기 */}
<Button
onClick={onGenerate}
disabled={isLoading}
className="gap-2 min-w-[120px]"
size="default"
>
{isLoading ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
...
</>
) : (
<>
<Sparkles className="w-4 h-4" />
</>
)}
</Button>
{/* 다운로드 */}
<Button
variant="outline"
onClick={handleDownload}
disabled={!slidePlan || isLoading}
className="gap-2 min-w-[120px]"
size="default"
>
<Download className="w-4 h-4" />
</Button>
{/* 연동하기 */}
<Button
variant="outline"
onClick={handleConnect}
className="gap-2 min-w-[120px] text-slate-500"
size="default"
>
<Link2 className="w-4 h-4" />
</Button>
</div>
);
}