mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-12 20:49:53 +08:00
Conflicts resolved in 16 files. Where main's new shell, classic page and tests depend on a wire format or a signature the cleanup had changed, main wins; where main only extended code the cleanup had restructured, main's change is carried over into the new structure: - MCP HTTP server: the poll response keeps main's separate export fields (exportFormat, exportXml, exportOptions, exportId with a random id) instead of the cleanup's pendingExport object; the classic preview page is main's version - MCP index: start_session keeps the cleanup's structure and gains main's BROWSER=none, previewUrl and same-origin note; get_selection is written in the registerTool style; create_new_diagram keeps the cleanup's helpers with main's comment - history: addHistory takes main's source parameter and returns the index again (main's signature) - new-diagram: the cleanup's prepareNewModel split plus main's page ids for diagrams without one - diagram context: one ExportResult type (main's, with optional fields) for both the tagged exports and main's requestExport; the cleanup's single export resolver stays - versions store and version-text test: main's VersionItem base type and describeChangeSummary rename, without the legacy history conversion the cleanup removed - dictionaries: main's shell section added; openFileHint stays removed (unused on main too) - tests: the server wiring test keeps the SDK client and gains main's session cases; the HTTP server test uses main's export fields; the DOM polyfill comes from tests/setup.ts - package.json: main's postcss kept, the cleanup's removals kept; lockfile regenerated
133 lines
4.9 KiB
TypeScript
133 lines
4.9 KiB
TypeScript
import { nanoid } from "nanoid"
|
|
import { create } from "zustand"
|
|
import type { VersionItem } from "@/components/canvas/versions-context"
|
|
|
|
/**
|
|
* One diagram state produced by an AI change, shown as a card in the chat.
|
|
* What the card displays is the VersionItem part; the rest ties the version
|
|
* to its chat turn. The number stays when older versions are dropped; the
|
|
* "before" document is kept only on the latest version; older sessions
|
|
* have no pageId (the first page).
|
|
*/
|
|
export interface DiagramVersion extends VersionItem {
|
|
/** Index of the user message that started this turn */
|
|
turnIndex: number
|
|
toolCallId?: string
|
|
/** Saved with the session: this version is what the canvas shows */
|
|
onCanvas?: boolean
|
|
/** Saved with the session: this version's change was undone */
|
|
undone?: boolean
|
|
}
|
|
|
|
// The two flags live on the versions so they are saved with the session
|
|
function withFlags(
|
|
versions: DiagramVersion[],
|
|
onCanvasId: string | null,
|
|
undoneId: string | null,
|
|
): DiagramVersion[] {
|
|
return versions.map(({ onCanvas, undone, ...rest }) => ({
|
|
...rest,
|
|
...(rest.id === onCanvasId && { onCanvas: true }),
|
|
...(rest.id === undoneId && { undone: true }),
|
|
}))
|
|
}
|
|
|
|
// Versions are stored with the session; keep the list bounded
|
|
export const MAX_VERSIONS = 20
|
|
|
|
interface VersionsState {
|
|
versions: DiagramVersion[]
|
|
/** The change on the latest version was undone with its card's button */
|
|
undoneVersionId: string | null
|
|
/** The version the canvas shows (null after an undo or hand edits) */
|
|
onCanvasVersionId: string | null
|
|
setVersions: (versions: DiagramVersion[]) => void
|
|
addVersion: (version: Omit<DiagramVersion, "id" | "number">) => string
|
|
updateVersion: (id: string, patch: Partial<DiagramVersion>) => void
|
|
/** Drop versions from turns that are being re-run (edit or regenerate) */
|
|
removeFromTurn: (turnIndex: number) => void
|
|
/** The change of this version was undone; null: no undone change */
|
|
setUndone: (id: string | null) => void
|
|
/** This version is on the canvas again (restored or redone) */
|
|
setOnCanvas: (id: string) => void
|
|
/** The canvas was changed by hand (undo included): no version is on it */
|
|
clearCanvasFlags: () => void
|
|
clear: () => void
|
|
}
|
|
|
|
export const useVersionsStore = create<VersionsState>((set) => ({
|
|
versions: [],
|
|
undoneVersionId: null,
|
|
onCanvasVersionId: null,
|
|
setVersions: (versions) =>
|
|
set({
|
|
// A missing thumbnail could not be made: no "rendering" forever.
|
|
versions: versions.map((v) => ({
|
|
...v,
|
|
svg: v.svg ?? "",
|
|
})),
|
|
undoneVersionId: versions.find((v) => v.undone)?.id ?? null,
|
|
onCanvasVersionId: versions.find((v) => v.onCanvas)?.id ?? null,
|
|
}),
|
|
addVersion: (version) => {
|
|
const id = nanoid(10)
|
|
set((state) => {
|
|
// Only the newest version keeps its "before" XML
|
|
const older = state.versions.map(({ beforeXml, ...rest }) => rest)
|
|
const number = (state.versions.at(-1)?.number ?? 0) + 1
|
|
const next = [...older, { ...version, id, number }].slice(
|
|
-MAX_VERSIONS,
|
|
)
|
|
return {
|
|
versions: withFlags(next, id, null),
|
|
undoneVersionId: null,
|
|
onCanvasVersionId: id,
|
|
}
|
|
})
|
|
return id
|
|
},
|
|
updateVersion: (id, patch) =>
|
|
set((state) => ({
|
|
versions: state.versions.map((v) =>
|
|
v.id === id ? { ...v, ...patch } : v,
|
|
),
|
|
})),
|
|
removeFromTurn: (turnIndex) =>
|
|
set((state) => {
|
|
const versions = state.versions.filter(
|
|
(v) => v.turnIndex < turnIndex,
|
|
)
|
|
// The canvas goes back to the snapshot before that turn, which
|
|
// can hold hand edits made after the latest kept version
|
|
return {
|
|
versions: withFlags(versions, null, null),
|
|
undoneVersionId: null,
|
|
onCanvasVersionId: null,
|
|
}
|
|
}),
|
|
setUndone: (id) =>
|
|
set((state) => ({
|
|
versions: withFlags(state.versions, null, id),
|
|
undoneVersionId: id,
|
|
onCanvasVersionId: null,
|
|
})),
|
|
setOnCanvas: (id) =>
|
|
set((state) => ({
|
|
versions: withFlags(state.versions, id, null),
|
|
undoneVersionId: null,
|
|
onCanvasVersionId: id,
|
|
})),
|
|
clearCanvasFlags: () =>
|
|
set((state) =>
|
|
state.onCanvasVersionId === null && state.undoneVersionId === null
|
|
? state
|
|
: {
|
|
versions: withFlags(state.versions, null, null),
|
|
undoneVersionId: null,
|
|
onCanvasVersionId: null,
|
|
},
|
|
),
|
|
clear: () =>
|
|
set({ versions: [], undoneVersionId: null, onCanvasVersionId: null }),
|
|
}))
|