diff --git a/src/api/chatbotApi.ts b/src/api/chatbotApi.ts index 90f515e..61280a9 100644 --- a/src/api/chatbotApi.ts +++ b/src/api/chatbotApi.ts @@ -41,10 +41,11 @@ export interface StartChatbotResponse extends ChatbotWorkflow { } export interface ChatDataItem { - type: 'message' | 'log' | 'stat' | 'document' | 'stopped' | 'status'; - createdAt: number; - item: Message | any; + type: 'message' | 'log' | 'stat' | 'document' | 'stopped' | 'status' | 'chunk'; + createdAt?: number; + item?: Message | any; label?: string; // For status events + content?: string; // For chunk events (token-by-token streaming) } // Type for the request function passed to API functions diff --git a/src/hooks/useChatbot.ts b/src/hooks/useChatbot.ts index f2c0a2e..44084eb 100644 --- a/src/hooks/useChatbot.ts +++ b/src/hooks/useChatbot.ts @@ -212,6 +212,27 @@ export function useChatbot(): ChatbotHookReturn { return; } + // Handle chunk events (ChatGPT-like token-by-token streaming) + if (item.type === 'chunk' && item.content) { + setMessages(prev => { + const last = prev[prev.length - 1]; + if (last?.role === 'assistant') { + return prev.map(m => + m.id === last!.id ? { ...m, message: (m.message || '') + item.content } : m + ); + } + return [...prev, { + id: `streaming-${currentWorkflowId || 'new'}-${Date.now()}`, + workflowId: currentWorkflowId || undefined, + conversationId: currentWorkflowId || undefined, + role: 'assistant' as const, + message: item.content, + publishedAt: Date.now() + }]; + }); + return; + } + // Handle workflow update (includes name updates from background task) if (item.type === 'stat' && item.item?.id) { newWorkflowId = item.item.id; @@ -269,6 +290,16 @@ export function useChatbot(): ChatbotHookReturn { ); } + // Final assistant message: replace streaming placeholder if we have one + if (message.role === 'assistant') { + const streamingIdx = prev.findIndex(m => m.id?.startsWith('streaming-')); + if (streamingIdx >= 0) { + const before = prev.slice(0, streamingIdx); + const after = prev.slice(streamingIdx + 1); + return [...before, message, ...after]; + } + } + // For other messages, check for duplicates by role and content (more lenient check) const isDuplicate = prev.some(m => { // Exact ID match