import type { WebSocketMessage } from "./websocket"; import { chatStore } from "../stores/chat-store"; import { contentManager } from "./content-manager"; import type { MessageSegment } from "../models/chat-data"; export class MessageHandler { private currentMessageId: string | null = null; private currentSegments: Map = new Map(); private completedSegments: MessageSegment[] = []; private latestTodoSegmentId: string | null = null; handleMessage(message: WebSocketMessage): void { switch (message.type) { case "status": this.handleStatus(message); break; case "stream_start": this.handleStreamStart(message); break; case "stream_token": this.handleStreamToken(message); break; case "stream_end": this.handleStreamEnd(); break; case "chat": this.handleChat(message); break; case "error": this.handleError(message); break; case "editor_update": this.handleEditorUpdate(message); break; case "segment_start": this.handleSegmentStart(message); break; case "segment_token": this.handleSegmentToken(message); break; case "segment_end": this.handleSegmentEnd(message); break; case "tool_start": case "tool_end": break; } } private handleStatus(message: WebSocketMessage): void { const { processing, connected } = message.payload; if (processing !== undefined) { chatStore.setProcessing(processing as boolean); } if (connected !== undefined) { chatStore.setConnected(connected as boolean); } } private handleStreamStart(message: WebSocketMessage): void { const messageId = (message.payload.messageId as string) || `assistant_${Date.now()}`; this.currentMessageId = messageId; this.currentSegments.clear(); this.completedSegments = []; this.latestTodoSegmentId = null; chatStore.addMessage({ id: messageId, role: "assistant", content: "", timestamp: Date.now(), segments: [], }); } private handleStreamToken(message: WebSocketMessage): void { const token = (message.payload.token as string) || ""; if (this.currentMessageId && token) { chatStore.appendToLastMessage(token); } } private handleStreamEnd(): void { if (this.currentMessageId) { this.updateMessageSegments(); } this.currentMessageId = null; this.currentSegments.clear(); this.completedSegments = []; } private handleChat(message: WebSocketMessage): void { const { content } = message.payload; if (content) { const messageId = `assistant_${Date.now()}`; chatStore.addMessage({ id: messageId, role: "assistant", content: content as string, timestamp: Date.now(), }); } } private handleError(message: WebSocketMessage): void { chatStore.setError((message.payload.error as string) || null); } private handleEditorUpdate(message: WebSocketMessage): void { const content = message.payload.content as string; if (content) { contentManager.updateFromAgent(content); } } private handleSegmentStart(message: WebSocketMessage): void { const { segmentId, segmentType, toolName, toolArgs } = message.payload; if (!segmentId || !this.currentMessageId) return; const segment: MessageSegment = { id: segmentId as string, type: segmentType as MessageSegment["type"], content: "", toolName: toolName as string | undefined, toolArgs: toolArgs as Record | undefined, startTime: Date.now(), streaming: segmentType === "text", }; this.currentSegments.set(segmentId as string, segment); this.updateMessageSegments(); } private handleSegmentToken(message: WebSocketMessage): void { const { segmentId, token } = message.payload; if (!segmentId || !token) return; const segment = this.currentSegments.get(segmentId as string); if (segment) { const updatedSegment = { ...segment, content: segment.content + (token as string), }; this.currentSegments.set(segmentId as string, updatedSegment); this.updateMessageSegments(); } } private handleSegmentEnd(message: WebSocketMessage): void { const { segmentId, content, toolStatus, toolOutput, toolError, consoleOutput, } = message.payload; if (!segmentId) return; const segment = this.currentSegments.get(segmentId as string); if (segment) { const completedSegment: MessageSegment = { ...segment, content: content ? (content as string) : segment.content, toolStatus: toolStatus ? (toolStatus as MessageSegment["toolStatus"]) : segment.toolStatus, toolOutput: toolOutput ? (toolOutput as string) : segment.toolOutput, toolError: toolError ? (toolError as string) : segment.toolError, consoleOutput: consoleOutput ? (consoleOutput as string[]) : segment.consoleOutput, endTime: Date.now(), streaming: false, }; // Special handling for todo tools - merge with existing if (this.isTodoTool(completedSegment)) { this.handleTodoSegment(completedSegment); } else { this.completedSegments.push(completedSegment); } this.currentSegments.delete(segmentId as string); this.updateMessageSegments(); } } private isTodoTool(segment: MessageSegment): boolean { return !!segment.toolName?.includes("task"); } private handleTodoSegment(segment: MessageSegment): void { // Remove previous todo segment and replace with new one if (this.latestTodoSegmentId) { this.completedSegments = this.completedSegments.filter( (s) => s.id !== this.latestTodoSegmentId, ); } this.latestTodoSegmentId = segment.id; this.completedSegments.push(segment); } private updateMessageSegments(): void { if (!this.currentMessageId) return; const allSegments = [ ...this.completedSegments, ...Array.from(this.currentSegments.values()), ]; const content = this.buildContentFromSegments(allSegments); chatStore.setLastMessageContent(content); chatStore.setLastMessageSegments(allSegments); } private buildContentFromSegments(segments: MessageSegment[]): string { let content = ""; for (const segment of segments) { if (segment.type === "text" && segment.content) { content += segment.content; } else if (segment.type === "tool-invocation") { // Tool invocations are now handled in the UI continue; } else if (segment.type === "tool-result") { // Tool results are now handled in the UI continue; } } return content.trim(); } } export const messageHandler = new MessageHandler();