feat: implement Story 3.2 — AI diagram generation from natural language
Add complete AI-powered diagram generation pipeline: natural language input → type inference → graph patch generation → validated canvas render with ELK.js layout animation. Includes adversarial code review fixes for diagramType enum validation, duplicate ID detection, tool part history preservation, PATCH error handling, and graphData structural validation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
93
apps/web/src/modules/copilot/hooks/useGraphMutation.ts
Normal file
93
apps/web/src/modules/copilot/hooks/useGraphMutation.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { api } from "~/lib/api/client";
|
||||
import { useGraphStore } from "~/modules/diagram/stores/useGraphStore";
|
||||
import { graphToFlow } from "~/modules/diagram/lib/graph-converter";
|
||||
|
||||
import type { DiagramType, GraphData } from "~/modules/diagram/types/graph";
|
||||
|
||||
interface GraphPatchData {
|
||||
meta: {
|
||||
diagramType: string;
|
||||
title: string;
|
||||
version?: string;
|
||||
layoutDirection?: "DOWN" | "RIGHT" | "LEFT" | "UP";
|
||||
edgeRouting?: "ORTHOGONAL" | "SPLINES" | "POLYLINE";
|
||||
};
|
||||
nodes: GraphData["nodes"];
|
||||
edges: GraphData["edges"];
|
||||
pools?: GraphData["pools"];
|
||||
groups?: GraphData["groups"];
|
||||
}
|
||||
|
||||
export function useGraphMutation(diagramId: string, diagramType: DiagramType) {
|
||||
const setNodes = useGraphStore((s) => s.setNodes);
|
||||
const setEdges = useGraphStore((s) => s.setEdges);
|
||||
const setLayoutDirection = useGraphStore((s) => s.setLayoutDirection);
|
||||
const setEdgeRouting = useGraphStore((s) => s.setEdgeRouting);
|
||||
const requestLayout = useGraphStore((s) => s.requestLayout);
|
||||
|
||||
const applyGraphPatch = useCallback(
|
||||
(patch: GraphPatchData) => {
|
||||
const effectiveDiagramType =
|
||||
(patch.meta.diagramType as DiagramType) ?? diagramType;
|
||||
|
||||
const graphData: GraphData = {
|
||||
meta: {
|
||||
version: patch.meta.version ?? "1",
|
||||
title: patch.meta.title,
|
||||
diagramType: effectiveDiagramType,
|
||||
layoutDirection: patch.meta.layoutDirection,
|
||||
edgeRouting: patch.meta.edgeRouting,
|
||||
},
|
||||
nodes: patch.nodes,
|
||||
edges: patch.edges,
|
||||
pools: patch.pools,
|
||||
groups: patch.groups,
|
||||
};
|
||||
|
||||
const { nodes, edges } = graphToFlow(graphData);
|
||||
|
||||
setNodes(nodes);
|
||||
setEdges(edges);
|
||||
|
||||
if (graphData.meta?.layoutDirection) {
|
||||
setLayoutDirection(graphData.meta.layoutDirection);
|
||||
}
|
||||
if (graphData.meta?.edgeRouting) {
|
||||
setEdgeRouting(graphData.meta.edgeRouting);
|
||||
}
|
||||
|
||||
requestLayout();
|
||||
|
||||
// Persist graphData to database (fire-and-forget with error reporting)
|
||||
api.diagrams[":id"]
|
||||
.$patch({
|
||||
param: { id: diagramId },
|
||||
json: { graphData: graphData as unknown as Record<string, unknown> },
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) {
|
||||
toast.error("Failed to save diagram — changes may be lost on reload");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Failed to save diagram — changes may be lost on reload");
|
||||
});
|
||||
},
|
||||
[
|
||||
diagramId,
|
||||
diagramType,
|
||||
setNodes,
|
||||
setEdges,
|
||||
setLayoutDirection,
|
||||
setEdgeRouting,
|
||||
requestLayout,
|
||||
],
|
||||
);
|
||||
|
||||
return { applyGraphPatch };
|
||||
}
|
||||
Reference in New Issue
Block a user