mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-10 03:29:50 +08:00
The web app will reuse the MCP server's XML engine instead of its own
copy in lib/utils.ts, so these modules now run in the browser too.
- Relative imports end in .ts, rewritten to .js by tsc
(rewriteRelativeImportExtensions); Next.js resolves them directly
- Every module uses the global DOMParser/XMLSerializer: native in the
browser, linkedom in Node via installDomPolyfill. pages.ts parsed with
linkedom but serialized with the global serializer, which throws in
the browser
- The saxes syntax check moves to xml-syntax.ts, so the browser does not
pull in linkedom; it now also rejects undeclared prefixes such as
xlink:, as the browser does
- Page decompression uses pako and atob instead of node:zlib and Buffer
- hasCells moves to pages.ts, away from the file system code
- The duplicate cell id check counts UserObject/object ids
- wrapCellsInModel drops comments and text before the first cell, which
the web app accepts today
- validateAndFixXml takes { strict: false } for diagrams with user content
- Web tests run these modules with a browser DOM (jsdom)
- saxes becomes a direct dependency of the web app
69 lines
1.7 KiB
TypeScript
69 lines
1.7 KiB
TypeScript
/**
|
|
* Simple diagram history - matches Next.js app pattern
|
|
* Stores {xml, svg} entries in a circular buffer
|
|
*/
|
|
|
|
import { log } from "./logger.ts"
|
|
|
|
const MAX_HISTORY = 20
|
|
|
|
interface HistoryEntry {
|
|
id: number // Stable across shifts of the circular buffer
|
|
xml: string
|
|
svg: string
|
|
}
|
|
|
|
let nextEntryId = 0
|
|
const historyStore = new Map<string, HistoryEntry[]>()
|
|
|
|
export function addHistory(sessionId: string, xml: string, svg = ""): number {
|
|
let history = historyStore.get(sessionId)
|
|
if (!history) {
|
|
history = []
|
|
historyStore.set(sessionId, history)
|
|
}
|
|
|
|
// Dedupe: skip if same as last entry
|
|
const last = history[history.length - 1]
|
|
if (last?.xml === xml) {
|
|
return history.length - 1
|
|
}
|
|
|
|
history.push({ id: nextEntryId++, xml, svg })
|
|
|
|
// Circular buffer
|
|
if (history.length > MAX_HISTORY) {
|
|
history.shift()
|
|
}
|
|
|
|
log.debug(`History: session=${sessionId}, entries=${history.length}`)
|
|
return history.length - 1
|
|
}
|
|
|
|
export function getHistory(sessionId: string): HistoryEntry[] {
|
|
return historyStore.get(sessionId) || []
|
|
}
|
|
|
|
/** Look up an entry by its id; the array index shifts as old entries drop. */
|
|
export function getHistoryEntry(
|
|
sessionId: string,
|
|
id: number,
|
|
): HistoryEntry | undefined {
|
|
return historyStore.get(sessionId)?.find((entry) => entry.id === id)
|
|
}
|
|
|
|
export function clearHistory(sessionId: string): void {
|
|
historyStore.delete(sessionId)
|
|
}
|
|
|
|
export function updateLastHistorySvg(sessionId: string, svg: string): boolean {
|
|
const history = historyStore.get(sessionId)
|
|
if (!history || history.length === 0) return false
|
|
const last = history[history.length - 1]
|
|
if (!last.svg) {
|
|
last.svg = svg
|
|
return true
|
|
}
|
|
return false
|
|
}
|