mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-11 12:09:53 +08:00
fix(chat): the AI reads and draws the page the user is viewing (#974)
* fix(chat): the AI reads and draws the page the user is viewing The model always got the first page and display_diagram replaced the whole file with one page, so drawing on page 3 of a multi-page file deleted the other pages (#241), and edits went to page 1 while the user looked at another page. Now a turn records the page the user is viewing when the message is sent. That page's model goes to the model (also as previousXml, on regenerate and in edit_diagram error messages), selected shapes are sent on any page, display_diagram and append_diagram replace only that page (a drawing that brings several pages still replaces the file), edit_diagram targets it, and the streaming preview draws on it. Compact cells and default styles take the layer and the edges of that page, in the preview and in editDiagram. The editor bridge replaces the current page in place when the other pages are unchanged, so Ctrl+Z undoes an AI change on a multi-page file in one step; when a full load is needed, the user's page is shown again afterwards. Version summaries and the compare dialog count the page the change was made on. * test: read the failed edit's output without unsafe optional chaining
This commit is contained in:
@@ -5,6 +5,7 @@ import type {
|
||||
ValidationStatus,
|
||||
} from "@/components/chat/ValidationCard"
|
||||
import type { LoadMode } from "@/contexts/diagram-context"
|
||||
import { pageModelXml, pageSelectorFor, placeOnPage } from "@/lib/diagram-pages"
|
||||
import type { ValidationResult } from "@/lib/diagram-validator"
|
||||
import { formatValidationFeedback } from "@/lib/diagram-validator"
|
||||
import { isMxCellXmlComplete } from "@/lib/utils"
|
||||
@@ -15,7 +16,8 @@ import { hasCells } from "@/packages/mcp-server/src/pages.ts"
|
||||
|
||||
const DEBUG = process.env.NODE_ENV === "development"
|
||||
|
||||
// display_diagram replaces the document with this one page
|
||||
// The page a drawing is wrapped in; on the canvas it replaces the page the
|
||||
// model works on, so the canvas page keeps its own id and name
|
||||
const NEW_PAGE = { pageId: "page-1", pageName: "Page-1" }
|
||||
|
||||
/**
|
||||
@@ -83,11 +85,14 @@ interface UseDiagramToolHandlersParams {
|
||||
// Failed VLM validations in the current user turn (reset on each user message)
|
||||
validationRetryCountRef: RefObject<number>
|
||||
chartXMLRef: RefObject<string>
|
||||
// The page the model reads and writes in this turn (the one the user
|
||||
// viewed when it began); null, or absent, means the first page
|
||||
turnPageIdRef?: RefObject<string | null>
|
||||
onDisplayChart: (
|
||||
xml: string,
|
||||
skipValidation?: boolean,
|
||||
mode?: LoadMode,
|
||||
meta?: { toolCallId?: string },
|
||||
meta?: { toolCallId?: string; pageId?: string | null },
|
||||
) => string | null
|
||||
onFetchChart: () => Promise<string>
|
||||
captureValidationPng?: () => Promise<string | null>
|
||||
@@ -119,6 +124,7 @@ export function useDiagramToolHandlers({
|
||||
processedToolCallsRef,
|
||||
validationRetryCountRef,
|
||||
chartXMLRef,
|
||||
turnPageIdRef,
|
||||
onDisplayChart,
|
||||
onFetchChart,
|
||||
captureValidationPng,
|
||||
@@ -182,11 +188,29 @@ export function useDiagramToolHandlers({
|
||||
const commit = (xml: string, toolCallId: string) => {
|
||||
const original = continuationOriginalRef.current
|
||||
if (original !== null) onDisplayChart(original, true, "revert")
|
||||
const error = onDisplayChart(xml, true, "commit", { toolCallId })
|
||||
const error = onDisplayChart(xml, true, "commit", {
|
||||
toolCallId,
|
||||
pageId: aiPageId(),
|
||||
})
|
||||
if (!error) continuationOriginalRef.current = null
|
||||
return error
|
||||
}
|
||||
|
||||
const aiPageId = () => turnPageIdRef?.current ?? null
|
||||
|
||||
// The diagram as the model sees it in the chat request: its page only
|
||||
const shownToModel = (xml: string) =>
|
||||
foldCells(pageModelXml(xml, aiPageId()) ?? xml)
|
||||
|
||||
// A drawn page goes onto the model's page of the canvas file, which
|
||||
// keeps its other pages; a drawing that brings several pages replaces
|
||||
// the file, and gets its variables
|
||||
const onAiPage = (drawnXml: string) =>
|
||||
keepFileVars(
|
||||
placeOnPage(drawnXml, chartXMLRef.current, aiPageId()),
|
||||
chartXMLRef.current,
|
||||
)
|
||||
|
||||
const takeOriginals = (): string | undefined => {
|
||||
const [originalXml] = editDiagramOriginalXmlRef.current.values()
|
||||
for (const id of editDiagramOriginalXmlRef.current.keys()) {
|
||||
@@ -265,10 +289,7 @@ NEXT STEP: Call append_diagram with the continuation XML.
|
||||
// finds the diagram already in place
|
||||
const prepared = prepareNewDiagram(finalXml, NEW_PAGE)
|
||||
const validationError = prepared.ok
|
||||
? commit(
|
||||
keepFileVars(prepared.xml, chartXMLRef.current),
|
||||
toolCall.toolCallId,
|
||||
)
|
||||
? commit(onAiPage(prepared.xml), toolCall.toolCallId)
|
||||
: prepared.error
|
||||
|
||||
if (validationError) {
|
||||
@@ -503,8 +524,12 @@ ${finalXml}
|
||||
}
|
||||
|
||||
// All or nothing, checked like the MCP server's edit_diagram.
|
||||
// The model sees the first page, so edits target it.
|
||||
const outcome = editDiagram(currentXml, operations, {})
|
||||
// The model sees its page, so edits target it.
|
||||
const outcome = editDiagram(
|
||||
currentXml,
|
||||
operations,
|
||||
pageSelectorFor(currentXml, aiPageId()),
|
||||
)
|
||||
if (!outcome.ok) {
|
||||
const reason = outcome.pageError
|
||||
? outcome.errors[0]
|
||||
@@ -518,7 +543,7 @@ ${finalXml}
|
||||
|
||||
Current diagram XML:
|
||||
\`\`\`xml
|
||||
${foldCells(currentXml)}
|
||||
${shownToModel(currentXml)}
|
||||
\`\`\`
|
||||
|
||||
Please check the cell IDs and retry.`,
|
||||
@@ -547,7 +572,7 @@ Please check the cell IDs and retry.`,
|
||||
|
||||
Current diagram XML:
|
||||
\`\`\`xml
|
||||
${currentXml ? foldCells(currentXml) : "No XML available"}
|
||||
${currentXml ? shownToModel(currentXml) : "No XML available"}
|
||||
\`\`\`
|
||||
|
||||
Please check cell IDs and retry, or use display_diagram to regenerate.`,
|
||||
@@ -620,10 +645,7 @@ Start your continuation with the NEXT character after where it stopped.`,
|
||||
// originals, so the preview code undoes none of them later
|
||||
const originalXml = takeOriginals()
|
||||
const validationError = prepared.ok
|
||||
? commit(
|
||||
keepFileVars(prepared.xml, chartXMLRef.current),
|
||||
toolCall.toolCallId,
|
||||
)
|
||||
? commit(onAiPage(prepared.xml), toolCall.toolCallId)
|
||||
: prepared.error
|
||||
|
||||
if (validationError) {
|
||||
|
||||
Reference in New Issue
Block a user