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>
94 lines
2.7 KiB
TypeScript
94 lines
2.7 KiB
TypeScript
"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 };
|
|
}
|