mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-12 04:29:51 +08:00
feat(mcp-server): get_selection reads the cells the user selected in the shell
The server asks the preview tab for the selection the way it asks for an export (a random request id in GET /api/state, the answer in a POST with that id, 10 s to answer). The shell answers through the editor bridge with each cell's id, label, an edge's ends, a shape's geometry and the page on screen; without a same-origin editor it says so, and the tool names the external draw.io. The classic page cannot answer, so the tool says that at once. screenshot_diagram's description now says a page selector renders that page without changing the page on screen (PNG exports by pageId already did).
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
parseMxfile,
|
||||
serializeMxfile,
|
||||
} from "@/packages/mcp-server/src/pages.ts"
|
||||
import type { SelectionAnswer } from "@/packages/mcp-server/src/selection.ts"
|
||||
|
||||
export type ExportFormat = "png" | "svg" | "xmlsvg"
|
||||
|
||||
@@ -35,6 +36,8 @@ export interface ServerState {
|
||||
exportXml: string | null
|
||||
exportOptions: { width?: number; pageId?: string } | null
|
||||
exportId: string | null
|
||||
/** A get_selection request waiting for this tab's answer */
|
||||
selectionId?: string | null
|
||||
}
|
||||
|
||||
/** An entry of GET /api/history */
|
||||
@@ -93,6 +96,9 @@ export interface SyncCanvas {
|
||||
currentXml(): string
|
||||
/** The page on screen; null when unknown (external draw.io) */
|
||||
currentPageId(): string | null
|
||||
/** The cells selected in the editor, for get_selection; `unavailable`
|
||||
* when the page cannot reach the editor (external draw.io) */
|
||||
readSelection(): SelectionAnswer
|
||||
}
|
||||
|
||||
export type SyncStatus = "waiting" | "connected" | "offline"
|
||||
@@ -305,6 +311,9 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
||||
let forceReload = false
|
||||
let pendingSyncExport = false
|
||||
let syncExportSeq = 0
|
||||
// The selection request answered last: the server shows a request until
|
||||
// the answer arrives, and polls overlap
|
||||
let answeredSelectionId: string | null = null
|
||||
let status: SyncStatus = "waiting"
|
||||
let interval: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
@@ -722,9 +731,23 @@ export function createMcpSync(options: SyncOptions): McpSync {
|
||||
) {
|
||||
startMcpExport(s, justLoaded)
|
||||
}
|
||||
// Extension point (plan step 6, the get_selection tool): a
|
||||
// selection request in the state would be answered here, like
|
||||
// the sync request above, with the ids the editor bridge reads
|
||||
// A selection request (get_selection): the editor's selection
|
||||
// on the page the user is viewing, once draw.io is up (the
|
||||
// bridge attaches to it at its first load), and not while a
|
||||
// projection replaces that page
|
||||
if (
|
||||
s.selectionId &&
|
||||
s.selectionId !== answeredSelectionId &&
|
||||
isReady &&
|
||||
!projectionExportActive
|
||||
) {
|
||||
answeredSelectionId = s.selectionId
|
||||
postJson("/state", {
|
||||
sessionId,
|
||||
selectionId: s.selectionId,
|
||||
selection: canvas.readSelection(),
|
||||
}).catch(() => {})
|
||||
}
|
||||
} catch {
|
||||
setStatus("offline")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import { toast } from "sonner"
|
||||
import { useDiagram } from "@/contexts/diagram-context"
|
||||
import { useDictionary } from "@/hooks/use-dictionary"
|
||||
import { diffDiagrams } from "@/lib/diagram-diff"
|
||||
import { highlightChangedCells } from "@/lib/drawio/editor-bridge"
|
||||
import {
|
||||
highlightChangedCells,
|
||||
readSelectionDetails,
|
||||
} from "@/lib/drawio/editor-bridge"
|
||||
import { useCanvasStore } from "@/stores/canvas-store"
|
||||
import { createMcpSync, type McpSync, type SyncStatus } from "./mcp-sync-core"
|
||||
import type { ShellConfig } from "./runtime-config"
|
||||
@@ -53,6 +56,15 @@ export function useMcpSync(config: ShellConfig): {
|
||||
diagramRef.current.requestExport(request, timeoutMs),
|
||||
currentXml: () => diagramRef.current.chartXMLRef.current,
|
||||
currentPageId: () => useCanvasStore.getState().currentPageId,
|
||||
// The bridge reads the editor directly; without it (an
|
||||
// external draw.io) the selection cannot be read
|
||||
readSelection: () =>
|
||||
readSelectionDetails() ?? {
|
||||
pageId: null,
|
||||
pageName: null,
|
||||
cells: [],
|
||||
unavailable: true,
|
||||
},
|
||||
},
|
||||
onNotice: (notice) =>
|
||||
toast(dictRef.current.shell[notice], { duration: 8000 }),
|
||||
|
||||
Reference in New Issue
Block a user