mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-12 12:39:52 +08:00
fix(mcp-server): review fixes for the version cards and History
The shell's version cards (shell/node-versions-source.ts): - a version's change and undo target are the state it replaced, the History entry right before its first copy, not the card before it: after a restore those differ, and undo went to the wrong version (and not where restore_version steps_back=1 goes) - a card restores the newest copy of its content, as restore_version does, so page settings the user changed (a "user" copy) are kept - a blank page after a drawing is a clear of the canvas, a version of its own; only the blank page before any drawing is hidden - numbers and changes are keyed by content, not by the first copy's id, so a version keeps them when its first copy drops out of the server's 20-entry buffer; the caches start over for another server state (the process restarted: entry ids name other content) The server's History (src/history.ts): - firstCopyIds compares each entry with the first of every group only: a bare model matches any page name, so "same content" is not transitive, and a card could show one document and restore another - the time and pages fields had no reader; pages parsed every XML once more on every write Reading History (src/http-server.ts, shell/mcp-sync-core.ts): - GET /api/state and a push's answer carry a History key (entry count, newest id, the entry on the canvas); the shell reads History again only when it changes, so a hand edit no longer downloads every entry's XML and thumbnail - a failed History read is told again at the next poll - a History list from a state the poll has not seen yet is dropped
This commit is contained in:
@@ -38,6 +38,9 @@ export interface ServerState {
|
||||
exportId: string | null
|
||||
/** A get_selection request waiting for this tab's answer */
|
||||
selectionId?: string | null
|
||||
/** Changes when History or the entry on the canvas does (the version
|
||||
* cards read History again then); older servers send none */
|
||||
historyKey?: string
|
||||
}
|
||||
|
||||
/** An entry of GET /api/history */
|
||||
@@ -48,8 +51,6 @@ export interface HistoryEntryInfo {
|
||||
xml: string
|
||||
/** Who made it: "user", "recover", "restore"; null for the AI's writes */
|
||||
source: string | null
|
||||
time: number
|
||||
pages: number
|
||||
/** The oldest entry with the same content (itself when it is the first) */
|
||||
firstId: number
|
||||
}
|
||||
@@ -323,18 +324,20 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
||||
onStatus?.(next)
|
||||
}
|
||||
|
||||
// Listeners of onServerChange, told once per server version seen (any
|
||||
// source: a write, the tab's own push) and whenever History changed
|
||||
// without one (a rejected edit kept, a thumbnail stored)
|
||||
// Listeners of onServerChange, told once per History key seen (the
|
||||
// server sends it with every state and push answer; a write, a restore
|
||||
// and the first hand edit change it, further hand edits do not) and
|
||||
// whenever History changed without one (a rejected edit kept, a
|
||||
// thumbnail stored)
|
||||
const changeListeners = new Set<() => void>()
|
||||
let notedVersion = 0
|
||||
let notedKey: string | null = null
|
||||
let notedStateId: string | null = null
|
||||
const emitChange = () => {
|
||||
for (const listener of changeListeners) listener()
|
||||
}
|
||||
const noteServerVersion = (version: number, sid: string | null) => {
|
||||
if (version === notedVersion && sid === notedStateId) return
|
||||
notedVersion = version
|
||||
const noteHistory = (key: string, sid: string | null) => {
|
||||
if (key === notedKey && sid === notedStateId) return
|
||||
notedKey = key
|
||||
notedStateId = sid
|
||||
emitChange()
|
||||
}
|
||||
@@ -477,7 +480,7 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
||||
if (sid !== stateId || d.version < currentVersion) return
|
||||
currentVersion = d.version
|
||||
lastXml = xml
|
||||
noteServerVersion(currentVersion, stateId)
|
||||
noteHistory(d.historyKey ?? String(d.version), stateId)
|
||||
// The canvas changed while this edit was on its way, to
|
||||
// something no pending autosave will send (an undo back to
|
||||
// the previous version): send it now. A sync reply is
|
||||
@@ -709,7 +712,7 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
||||
justLoaded = true
|
||||
}
|
||||
// Also the tab's own push, and a version a projection holds back
|
||||
noteServerVersion(s.version, stateId)
|
||||
noteHistory(s.historyKey ?? String(s.version), stateId)
|
||||
// A sync request (get_diagram): after the load above, so draw.io
|
||||
// exports what it just loaded; never while a one-page projection
|
||||
// is on screen, which would be sent as the whole document
|
||||
@@ -825,13 +828,24 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
||||
async fetchHistory() {
|
||||
// A list for a state the server recreated meanwhile has old ids
|
||||
const sid = stateId
|
||||
// A failed read is told again at the next poll: no other event
|
||||
// comes for the same History
|
||||
const failed = () => {
|
||||
notedKey = null
|
||||
return null
|
||||
}
|
||||
try {
|
||||
const r = await api(
|
||||
`/history?sessionId=${encodeURIComponent(sessionId)}`,
|
||||
)
|
||||
if (!r.ok) return null
|
||||
if (!r.ok) return failed()
|
||||
const d = await r.json()
|
||||
if (sid !== stateId) return null
|
||||
// A list from a state the poll has not seen yet (another
|
||||
// process answers): its poll tells the cards to read again
|
||||
if (typeof d.stateId === "string" && d.stateId !== sid) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
entries: d.entries ?? [],
|
||||
stateId: sid,
|
||||
@@ -839,7 +853,7 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
||||
typeof d.currentId === "number" ? d.currentId : null,
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
return failed()
|
||||
}
|
||||
},
|
||||
async restoreEntry(id, listStateId) {
|
||||
|
||||
Reference in New Issue
Block a user