From 1fab261cd05f066e2ab39bd28fe68296da32e556 Mon Sep 17 00:00:00 2001 From: Dayuan Jiang <34411969+DayuanJiang@users.noreply.github.com> Date: Wed, 24 Dec 2025 09:52:50 +0900 Subject: [PATCH] refactor: extract dev XML streaming simulator to separate component (#388) - Move DEV_XML_PRESETS constants to new file - Create DevXmlSimulator component with all simulator logic - Add preset dropdown with 5 test cases including HTML escape test - Set default interval to 1ms and chunk size to 10 chars - Simplify chat-panel.tsx by removing ~130 lines of inline code --- components/chat-panel.tsx | 188 +---------------- components/dev-xml-simulator.tsx | 350 +++++++++++++++++++++++++++++++ 2 files changed, 356 insertions(+), 182 deletions(-) create mode 100644 components/dev-xml-simulator.tsx diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index 1e609aa..1d8fbf1 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -36,6 +36,7 @@ import { type FileData, useFileProcessor } from "@/lib/use-file-processor" import { useQuotaManager } from "@/lib/use-quota-manager" import { formatXML, isMxCellXmlComplete, wrapWithMxFile } from "@/lib/utils" import { ChatMessageDisplay } from "./chat-message-display" +import { DevXmlSimulator } from "./dev-xml-simulator" // localStorage keys for persistence const STORAGE_MESSAGES_KEY = "next-ai-draw-io-messages" @@ -76,6 +77,7 @@ interface ChatPanelProps { const TOOL_ERROR_STATE = "output-error" as const const DEBUG = process.env.NODE_ENV === "development" const MAX_AUTO_RETRY_COUNT = 1 + const MAX_CONTINUATION_RETRY_COUNT = 2 // Limit for truncation continuation retries /** @@ -164,28 +166,6 @@ export default function ChatPanel({ const [showNewChatDialog, setShowNewChatDialog] = useState(false) const [minimalStyle, setMinimalStyle] = useState(false) - // Dev simulation state (only used in development) - const [devXml, setDevXml] = useState("") - const [isSimulating, setIsSimulating] = useState(false) - const [devIntervalMs, setDevIntervalMs] = useState(20) - const [devChunkSize, setDevChunkSize] = useState(5) - const devStopRef = useRef(false) - const devXmlInitializedRef = useRef(false) - - // Restore dev XML from localStorage on mount (after hydration) - useEffect(() => { - const saved = localStorage.getItem("dev-xml-simulator") - if (saved) setDevXml(saved) - devXmlInitializedRef.current = true - }, []) - - // Save dev XML to localStorage (only after initial load) - useEffect(() => { - if (devXmlInitializedRef.current) { - localStorage.setItem("dev-xml-simulator", devXml) - } - }, [devXml]) - // Restore input from sessionStorage on mount (when ChatPanel remounts due to key change) useEffect(() => { const savedInput = sessionStorage.getItem(SESSION_STORAGE_INPUT_KEY) @@ -1212,85 +1192,6 @@ Continue from EXACTLY where you stopped.`, sendChatMessage(newParts, savedXml, previousXml, sessionId) } - // Dev: Simulate display_diagram streaming - const handleDevSimulate = async () => { - if (!devXml.trim() || isSimulating) return - - setIsSimulating(true) - devStopRef.current = false - const toolCallId = `dev-sim-${Date.now()}` - const xml = devXml.trim() - - // Add user message and initial assistant message with empty XML - const userMsg = { - id: `user-${Date.now()}`, - role: "user" as const, - parts: [ - { - type: "text" as const, - text: "[Dev] Simulating XML streaming", - }, - ], - } - const assistantMsg = { - id: `assistant-${Date.now()}`, - role: "assistant" as const, - parts: [ - { - type: "tool-display_diagram" as const, - toolCallId, - state: "input-streaming" as const, - input: { xml: "" }, - }, - ], - } - setMessages((prev) => [...prev, userMsg, assistantMsg] as any) - - // Stream characters progressively - for (let i = 0; i < xml.length; i += devChunkSize) { - if (devStopRef.current) { - setIsSimulating(false) - return - } - - const chunk = xml.slice(0, i + devChunkSize) - - setMessages((prev) => { - const updated = [...prev] - const lastMsg = updated[updated.length - 1] as any - if (lastMsg?.role === "assistant" && lastMsg.parts?.[0]) { - lastMsg.parts[0].input = { xml: chunk } - } - return updated - }) - - await new Promise((r) => setTimeout(r, devIntervalMs)) - } - - if (devStopRef.current) { - setIsSimulating(false) - return - } - - // Finalize: set state to output-available - setMessages((prev) => { - const updated = [...prev] - const lastMsg = updated[updated.length - 1] as any - if (lastMsg?.role === "assistant" && lastMsg.parts?.[0]) { - lastMsg.parts[0].state = "output-available" - lastMsg.parts[0].output = "Successfully displayed the diagram." - lastMsg.parts[0].input = { xml } - } - return updated - }) - - // Display the final diagram - const fullXml = wrapWithMxFile(xml) - onDisplayChart(fullXml) - - setIsSimulating(false) - } - // Collapsed view (desktop only) if (!isVisible && !isMobile) { return ( @@ -1440,87 +1341,10 @@ Continue from EXACTLY where you stopped.`, {/* Dev XML Streaming Simulator - only in development */} {DEBUG && ( -
-
- - Dev: XML Streaming Simulator - -
-