mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 14:22:28 +08:00
fix: recover from invalid XML in localStorage on startup (#261)
When LLM generates invalid XML, the app previously saved corrupted messages to localStorage, causing an unrecoverable crash loop on restart. This fix validates messages when restoring from localStorage and filters out any with invalid diagram XML. Users see a toast notification when corrupted messages are removed. Fixes #240
This commit is contained in:
@@ -40,6 +40,7 @@ interface MessagePart {
|
|||||||
type: string
|
type: string
|
||||||
state?: string
|
state?: string
|
||||||
toolName?: string
|
toolName?: string
|
||||||
|
input?: { xml?: string; [key: string]: unknown }
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +89,37 @@ function hasToolErrors(messages: ChatMessage[]): boolean {
|
|||||||
return lastToolPart?.state === TOOL_ERROR_STATE
|
return lastToolPart?.state === TOOL_ERROR_STATE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a message contains valid diagram XML.
|
||||||
|
* Used to filter out corrupted messages when restoring from localStorage.
|
||||||
|
* Validates both display_diagram and append_diagram tool calls.
|
||||||
|
*/
|
||||||
|
function hasValidDiagramXml(message: {
|
||||||
|
parts?: Array<{ type?: string; input?: unknown }>
|
||||||
|
}): boolean {
|
||||||
|
if (!message.parts) return true // No parts = valid (user messages, text-only)
|
||||||
|
|
||||||
|
const parser = new DOMParser()
|
||||||
|
for (const part of message.parts) {
|
||||||
|
// Check both display_diagram and append_diagram tools
|
||||||
|
const isDiagramTool =
|
||||||
|
part.type === "tool-display_diagram" ||
|
||||||
|
part.type === "tool-append_diagram"
|
||||||
|
const input = part.input as { xml?: string } | undefined
|
||||||
|
if (isDiagramTool && input?.xml) {
|
||||||
|
try {
|
||||||
|
const doc = parser.parseFromString(input.xml, "text/xml")
|
||||||
|
if (doc.querySelector("parsererror")) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
export default function ChatPanel({
|
export default function ChatPanel({
|
||||||
isVisible,
|
isVisible,
|
||||||
onToggleVisibility,
|
onToggleVisibility,
|
||||||
@@ -598,6 +630,7 @@ Continue from EXACTLY where you stopped.`,
|
|||||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
// Restore messages and XML snapshots from localStorage on mount
|
// Restore messages and XML snapshots from localStorage on mount
|
||||||
|
// Validates and filters out corrupted messages to prevent crash loops
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasRestoredRef.current) return
|
if (hasRestoredRef.current) return
|
||||||
hasRestoredRef.current = true
|
hasRestoredRef.current = true
|
||||||
@@ -608,7 +641,32 @@ Continue from EXACTLY where you stopped.`,
|
|||||||
if (savedMessages) {
|
if (savedMessages) {
|
||||||
const parsed = JSON.parse(savedMessages)
|
const parsed = JSON.parse(savedMessages)
|
||||||
if (Array.isArray(parsed) && parsed.length > 0) {
|
if (Array.isArray(parsed) && parsed.length > 0) {
|
||||||
setMessages(parsed)
|
// Filter out messages with invalid XML to prevent crash loops
|
||||||
|
const validMessages = parsed.filter((msg: ChatMessage) => {
|
||||||
|
try {
|
||||||
|
return hasValidDiagramXml(msg)
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (validMessages.length < parsed.length) {
|
||||||
|
const removedCount =
|
||||||
|
parsed.length - validMessages.length
|
||||||
|
console.warn(
|
||||||
|
`[ChatPanel] Filtered ${removedCount} corrupted message(s) from storage`,
|
||||||
|
)
|
||||||
|
toast.warning(
|
||||||
|
`Removed ${removedCount} message(s) with invalid diagrams to recover session.`,
|
||||||
|
)
|
||||||
|
// Update storage with cleaned messages
|
||||||
|
localStorage.setItem(
|
||||||
|
STORAGE_MESSAGES_KEY,
|
||||||
|
JSON.stringify(validMessages),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessages(validMessages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,6 +680,10 @@ Continue from EXACTLY where you stopped.`,
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to restore from localStorage:", error)
|
console.error("Failed to restore from localStorage:", error)
|
||||||
|
// On complete failure, clear storage to allow recovery
|
||||||
|
localStorage.removeItem(STORAGE_MESSAGES_KEY)
|
||||||
|
localStorage.removeItem(STORAGE_XML_SNAPSHOTS_KEY)
|
||||||
|
toast.error("Session data was corrupted. Starting fresh.")
|
||||||
}
|
}
|
||||||
}, [setMessages])
|
}, [setMessages])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user