mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 22:32:27 +08:00
fix: handle malformed XML from DeepSeek gracefully (#235)
* fix: handle malformed XML from DeepSeek gracefully Add early XML validation with parsererror check before calling replaceNodes to prevent application crashes when AI models generate malformed XML with unescaped special characters. Changes: - Add toast import from sonner - Parse and validate XML before processing - Add parsererror detection to catch malformed XML early - Wrap replaceNodes in try-catch for additional safety - Add user-friendly toast notifications for all error cases - Change console.log to console.error for validation failures Fixes #220 #230 #231 * fix: prevent toast spam during streaming and merge silent failure fixes - Only show error toasts after streaming completes (not during partial updates) - Track which tool calls have shown errors to prevent duplicate toasts - Merge clipboard copy error handling from PR #236 - Merge feedback submission error handling from PR #237 - Add comments explaining streaming vs completion behavior * refactor: simplify toast deduplication with boolean flag Based on code review feedback, simplified the approach from tracking per-tool-call IDs in a Set to using a single boolean flag. Changes: - Replaced Set<string> with boolean ref for toast tracking - Removed toolCallId and showToast parameters from handleDisplayChart - Reset flag when streaming starts (simpler mental model) - Same behavior: one toast per streaming session, no spam Benefits: - Fewer concepts (1 boolean vs Set + 2 parameters) - No manual coordination between call sites - Easier to understand and maintain - ~15 fewer lines of tracking logic * fix: only show toast for final malformed XML, not during streaming - Remove errorToastShownRef tracking (no longer needed) - Add showToast parameter to handleDisplayChart (default false) - Pass false during streaming (XML may be incomplete) - Pass true at completion (show toast if final XML is malformed) - Simpler and more explicit error handling
This commit is contained in:
@@ -282,27 +282,64 @@ export function ChatMessageDisplay({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDisplayChart = useCallback(
|
const handleDisplayChart = useCallback(
|
||||||
(xml: string) => {
|
(xml: string, showToast = false) => {
|
||||||
const currentXml = xml || ""
|
const currentXml = xml || ""
|
||||||
const convertedXml = convertToLegalXml(currentXml)
|
const convertedXml = convertToLegalXml(currentXml)
|
||||||
if (convertedXml !== previousXML.current) {
|
if (convertedXml !== previousXML.current) {
|
||||||
// If chartXML is empty, create a default mxfile structure to use with replaceNodes
|
// Parse and validate XML BEFORE calling replaceNodes
|
||||||
// This ensures the XML is properly wrapped in mxfile/diagram/mxGraphModel format
|
const parser = new DOMParser()
|
||||||
const baseXML =
|
const testDoc = parser.parseFromString(convertedXml, "text/xml")
|
||||||
chartXML ||
|
const parseError = testDoc.querySelector("parsererror")
|
||||||
`<mxfile><diagram name="Page-1" id="page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>`
|
|
||||||
const replacedXML = replaceNodes(baseXML, convertedXml)
|
|
||||||
|
|
||||||
const validationError = validateMxCellStructure(replacedXML)
|
if (parseError) {
|
||||||
if (!validationError) {
|
console.error(
|
||||||
previousXML.current = convertedXml
|
"[ChatMessageDisplay] Malformed XML detected - skipping update",
|
||||||
// Skip validation in loadDiagram since we already validated above
|
|
||||||
onDisplayChart(replacedXML, true)
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
"[ChatMessageDisplay] XML validation failed:",
|
|
||||||
validationError,
|
|
||||||
)
|
)
|
||||||
|
// Only show toast if this is the final XML (not during streaming)
|
||||||
|
if (showToast) {
|
||||||
|
toast.error(
|
||||||
|
"AI generated invalid diagram XML. Please try regenerating.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return // Skip this update
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If chartXML is empty, create a default mxfile structure to use with replaceNodes
|
||||||
|
// This ensures the XML is properly wrapped in mxfile/diagram/mxGraphModel format
|
||||||
|
const baseXML =
|
||||||
|
chartXML ||
|
||||||
|
`<mxfile><diagram name="Page-1" id="page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>`
|
||||||
|
const replacedXML = replaceNodes(baseXML, convertedXml)
|
||||||
|
|
||||||
|
const validationError = validateMxCellStructure(replacedXML)
|
||||||
|
if (!validationError) {
|
||||||
|
previousXML.current = convertedXml
|
||||||
|
// Skip validation in loadDiagram since we already validated above
|
||||||
|
onDisplayChart(replacedXML, true)
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"[ChatMessageDisplay] XML validation failed:",
|
||||||
|
validationError,
|
||||||
|
)
|
||||||
|
// Only show toast if this is the final XML (not during streaming)
|
||||||
|
if (showToast) {
|
||||||
|
toast.error(
|
||||||
|
"Diagram validation failed. Please try regenerating.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"[ChatMessageDisplay] Error processing XML:",
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
// Only show toast if this is the final XML (not during streaming)
|
||||||
|
if (showToast) {
|
||||||
|
toast.error(
|
||||||
|
"Failed to process diagram. Please try regenerating.",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -345,12 +382,14 @@ export function ChatMessageDisplay({
|
|||||||
state === "input-streaming" ||
|
state === "input-streaming" ||
|
||||||
state === "input-available"
|
state === "input-available"
|
||||||
) {
|
) {
|
||||||
handleDisplayChart(xml)
|
// During streaming, don't show toast (XML may be incomplete)
|
||||||
|
handleDisplayChart(xml, false)
|
||||||
} else if (
|
} else if (
|
||||||
state === "output-available" &&
|
state === "output-available" &&
|
||||||
!processedToolCalls.current.has(toolCallId)
|
!processedToolCalls.current.has(toolCallId)
|
||||||
) {
|
) {
|
||||||
handleDisplayChart(xml)
|
// Show toast only if final XML is malformed
|
||||||
|
handleDisplayChart(xml, true)
|
||||||
processedToolCalls.current.add(toolCallId)
|
processedToolCalls.current.add(toolCallId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user