Files
next-ai-draw-io/contexts/diagram-context.tsx
Dayuan Jiang 443a937370 fix: prevent duplicate diagram history entries on message send (#48)
When sending a message, the history was being added twice because:
1. handleExport() triggers exportDiagram() which adds to history
2. AI responds and loadDiagram() is called, which internally triggers
   another export event in DrawIO, adding a duplicate entry

Added expectHistoryExportRef flag to track user-initiated exports and
only add to history when the export was explicitly requested.
2025-12-03 13:53:16 +09:00

107 lines
3.3 KiB
TypeScript

"use client";
import React, { createContext, useContext, useRef, useState } from "react";
import type { DrawIoEmbedRef } from "react-drawio";
import { extractDiagramXML } from "../lib/utils";
interface DiagramContextType {
chartXML: string;
latestSvg: string;
diagramHistory: { svg: string; xml: string }[];
loadDiagram: (chart: string) => void;
handleExport: () => void;
resolverRef: React.Ref<((value: string) => void) | null>;
drawioRef: React.Ref<DrawIoEmbedRef | null>;
handleDiagramExport: (data: any) => void;
clearDiagram: () => void;
}
const DiagramContext = createContext<DiagramContextType | undefined>(undefined);
export function DiagramProvider({ children }: { children: React.ReactNode }) {
const [chartXML, setChartXML] = useState<string>("");
const [latestSvg, setLatestSvg] = useState<string>("");
const [diagramHistory, setDiagramHistory] = useState<
{ svg: string; xml: string }[]
>([]);
const drawioRef = useRef<DrawIoEmbedRef | null>(null);
const resolverRef = useRef<((value: string) => void) | null>(null);
// Track if we're expecting an export for history (user-initiated)
const expectHistoryExportRef = useRef<boolean>(false);
const handleExport = () => {
if (drawioRef.current) {
// Mark that this export should be saved to history
expectHistoryExportRef.current = true;
drawioRef.current.exportDiagram({
format: "xmlsvg",
});
}
};
const loadDiagram = (chart: string) => {
if (drawioRef.current) {
drawioRef.current.load({
xml: chart,
});
}
};
const handleDiagramExport = (data: any) => {
const extractedXML = extractDiagramXML(data.data);
setChartXML(extractedXML);
setLatestSvg(data.data);
// Only add to history if this was a user-initiated export
if (expectHistoryExportRef.current) {
setDiagramHistory((prev) => [
...prev,
{
svg: data.data,
xml: extractedXML,
},
]);
expectHistoryExportRef.current = false;
}
if (resolverRef.current) {
resolverRef.current(extractedXML);
resolverRef.current = null;
}
};
const clearDiagram = () => {
const emptyDiagram = `<mxfile><diagram name="Page-1" id="page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>`;
loadDiagram(emptyDiagram);
setChartXML(emptyDiagram);
setLatestSvg("");
setDiagramHistory([]);
};
return (
<DiagramContext.Provider
value={{
chartXML,
latestSvg,
diagramHistory,
loadDiagram,
handleExport,
resolverRef,
drawioRef,
handleDiagramExport,
clearDiagram,
}}
>
{children}
</DiagramContext.Provider>
);
}
export function useDiagram() {
const context = useContext(DiagramContext);
if (context === undefined) {
throw new Error("useDiagram must be used within a DiagramProvider");
}
return context;
}