Some checks failed
CI / Tests / 🧪 Test (push) Has been cancelled
Story 3.4: AI semantic suggestions with accept/reject workflow - ProposalBar overlay with visual diff - Accept/reject flow with graph snapshot restore - useProposalDiff hook for change summary - System prompt scoping for selected elements Story 3.5: New diagram wizard with AI type inference - CreateDiagramDialog with AI type inference (Haiku) - initialDescription prop for chat-first flow - Auto-send on mount with hasSentInitial ref guard - DB migration for diagram description column Story 3.6: Hover affordances and command palette - HoverAffordances toolbar (5 AI actions, debounced) - CommandPalette (Cmd+K) with AI, nav, Go to Node - prefillChat/fitViewRequested/focusNodeId actions - Code review: getNodesBounds, onOpenRightPanel, timer cleanup, test count fix 374 tests passing (251 web + 123 AI). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
107 lines
3.0 KiB
TypeScript
107 lines
3.0 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"];
|
|
}
|
|
|
|
function patchToGraphData(patch: GraphPatchData, fallbackType: DiagramType): GraphData {
|
|
const effectiveDiagramType =
|
|
(patch.meta.diagramType as DiagramType) ?? fallbackType;
|
|
|
|
return {
|
|
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,
|
|
};
|
|
}
|
|
|
|
export function persistGraphData(diagramId: string, graphData: GraphData) {
|
|
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");
|
|
});
|
|
}
|
|
|
|
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 graphData = patchToGraphData(patch, diagramType);
|
|
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();
|
|
persistGraphData(diagramId, graphData);
|
|
},
|
|
[
|
|
diagramId,
|
|
diagramType,
|
|
setNodes,
|
|
setEdges,
|
|
setLayoutDirection,
|
|
setEdgeRouting,
|
|
requestLayout,
|
|
],
|
|
);
|
|
|
|
const proposeGraphPatch = useCallback(
|
|
(patch: GraphPatchData) => {
|
|
const graphData = patchToGraphData(patch, diagramType);
|
|
useGraphStore.getState().proposeChanges(graphData);
|
|
},
|
|
[diagramType],
|
|
);
|
|
|
|
return { applyGraphPatch, proposeGraphPatch };
|
|
}
|