From 836b8032ae97a93bd34108221d0c39282de391be Mon Sep 17 00:00:00 2001 From: Ida Dittrich Date: Fri, 9 Jan 2026 09:22:28 +0100 Subject: [PATCH] fix:fixed and finished chatbot integration --- .../FormGeneratorControls.tsx | 4 - src/core/PageManager/PageRenderer.tsx | 7 +- src/hooks/useChatbot.ts | 150 ++++++++++-------- 3 files changed, 90 insertions(+), 71 deletions(-) diff --git a/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx b/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx index c08e7ee..0299707 100644 --- a/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx +++ b/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx @@ -181,11 +181,7 @@ export function FormGeneratorControls({ size="sm" icon={FaTrash} > -<<<<<<< HEAD {allItemsSelected -======= - {selectedCount === displayData.length && displayData.length > 0 ->>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7 ? t('formgen.delete.all', `Delete all ${selectedCount} items`).replace('{count}', selectedCount.toString()) : t('formgen.delete.multiple', `Delete ${selectedCount} selected items`).replace('{count}', selectedCount.toString())} diff --git a/src/core/PageManager/PageRenderer.tsx b/src/core/PageManager/PageRenderer.tsx index 4aae85f..d6ec0df 100644 --- a/src/core/PageManager/PageRenderer.tsx +++ b/src/core/PageManager/PageRenderer.tsx @@ -1006,7 +1006,12 @@ const PageRenderer: React.FC = ({ const buttonVariant = isRunning ? (config.stopButtonVariant || config.buttonVariant || 'primary') : (config.buttonVariant || 'primary'); - const buttonDisabled = hookData.isSubmitting || (!isRunning && !hookData.inputValue?.trim()); + // Button disabled logic: + // - Always enabled when running (to allow stopping), unless submitting + // - When not running, disabled if submitting or input is empty + const buttonDisabled = isRunning + ? hookData.isSubmitting // When running, only disable if submitting + : (hookData.isSubmitting || !hookData.inputValue?.trim()); // When not running, disable if submitting or input empty // Handle Enter key press const handleKeyDown = (e: React.KeyboardEvent) => { diff --git a/src/hooks/useChatbot.ts b/src/hooks/useChatbot.ts index 60c6797..ee4d129 100644 --- a/src/hooks/useChatbot.ts +++ b/src/hooks/useChatbot.ts @@ -54,6 +54,7 @@ export function useChatbot() { const thinkingLogsRef = useRef([]); // Use ref instead of state to avoid batching const logQueueRef = useRef([]); // Queue for logs to process one by one const isProcessingLogsRef = useRef(false); // Flag to prevent concurrent processing + const processedLogsRef = useRef>(new Set()); // Track processed logs to prevent duplicates // Clear processed message IDs when workflow changes const clearProcessedMessages = useCallback(() => { @@ -65,16 +66,18 @@ export function useChatbot() { // Clear log queue and stop processing logQueueRef.current = []; isProcessingLogsRef.current = false; + processedLogsRef.current.clear(); // Clear processed logs tracking - if (thinkingMessageIdRef.current) { - const thinkingId = thinkingMessageIdRef.current; - thinkingMessageIdRef.current = null; - thinkingLogsRef.current = []; - - setMessages(prevMessages => { - return prevMessages.filter(m => m.id !== thinkingId); - }); - } + // Reset thinking message refs + const thinkingId = thinkingMessageIdRef.current; + thinkingMessageIdRef.current = null; + thinkingLogsRef.current = []; + + // Remove ALL thinking messages (not just the one with current ID) + // This handles cases where multiple thinking messages might exist + setMessages(prevMessages => { + return prevMessages.filter(m => m.status !== 'thinking'); + }); }, []); // Process logs from queue one by one (progressive display) @@ -110,8 +113,8 @@ export function useChatbot() { // Update messages immediately setMessages(prevMessages => { - // Remove old thinking message if it exists - const filtered = prevMessages.filter(m => m.id !== thinkingId); + // Remove ALL thinking messages first (to prevent duplicates from previous workflows) + const filtered = prevMessages.filter(m => m.status !== 'thinking'); // Add updated thinking message const updated = [...filtered, thinkingMessage]; return updated.sort(sortMessages); @@ -129,7 +132,21 @@ export function useChatbot() { }, [workflowId]); // Add a single log to thinking message (progressive display) - const addLogToThinkingMessage = useCallback((logMessage: string) => { + const addLogToThinkingMessage = useCallback((logMessage: string, createdAt?: number) => { + // Create a unique key for this log message to detect duplicates + // Use content + createdAt timestamp if available, otherwise use current time + const timestamp = createdAt || Date.now(); + const logKey = `${logMessage.trim()}_${timestamp}`; + + // Skip if this log was already processed + if (processedLogsRef.current.has(logKey)) { + console.log('[useChatbot] Skipping duplicate log:', logMessage.substring(0, 50) + '...'); + return; + } + + // Mark as processed + processedLogsRef.current.add(logKey); + // Add log to queue logQueueRef.current.push(logMessage); @@ -141,14 +158,27 @@ export function useChatbot() { // Process SSE event and update messages const processChatDataItem = useCallback((item: ChatDataItem) => { + // Log the actual streamed response for debugging + console.log('[useChatbot] Streamed item:', { + type: item.type, + createdAt: item.createdAt, + item: item.item + }); + if (item.type === 'log' && item.item) { // Process log item - add to thinking message one at a time const logData = item.item as any; const logMessage = logData.message || logData.text || ''; + console.log('[useChatbot] Processing log:', { + message: logMessage.substring(0, 100) + (logMessage.length > 100 ? '...' : ''), + createdAt: item.createdAt, + fullItem: item + }); + if (logMessage) { - // Add log immediately (progressive display) - addLogToThinkingMessage(logMessage); + // Add log immediately (progressive display) with createdAt for deduplication + addLogToThinkingMessage(logMessage, item.createdAt); } } else if (item.type === 'message' && item.item) { const messageData = item.item as any; @@ -170,38 +200,26 @@ export function useChatbot() { // Check if we've already processed this message const messageId = messageData.id; + // Always clear thinking messages when a real message arrives + clearThinkingMessage(); + if (processedMessageIdsRef.current.has(messageId)) { - // Update existing message - clear thinking message first + // Update existing message setMessages(prevMessages => { - let filtered = prevMessages; - if (thinkingId) { - filtered = prevMessages.filter(m => m.id !== thinkingId); - thinkingMessageIdRef.current = null; - thinkingLogsRef.current = []; - } - - const existingIndex = filtered.findIndex(m => m.id === messageId); + const existingIndex = prevMessages.findIndex(m => m.id === messageId); if (existingIndex >= 0) { - const updated = [...filtered]; + const updated = [...prevMessages]; updated[existingIndex] = messageData as Message; return updated.sort(sortMessages); } - return filtered; + return prevMessages; }); } else { - // Add new message - clear thinking message first + // Add new message processedMessageIdsRef.current.add(messageId); setMessages(prevMessages => { - // Remove thinking message BEFORE adding new message (same state update) - let filtered = prevMessages; - if (thinkingId) { - filtered = prevMessages.filter(m => m.id !== thinkingId); - thinkingMessageIdRef.current = null; - thinkingLogsRef.current = []; - } - - // Add new message - const updated = [...filtered, messageData as Message]; + // Add new message (thinking messages already cleared by clearThinkingMessage) + const updated = [...prevMessages, messageData as Message]; return updated.sort(sortMessages); }); } @@ -348,7 +366,6 @@ export function useChatbot() { setInputValue(value); }, []); -<<<<<<< HEAD // Handle file upload const handleFileUpload = useCallback(async (file: File): Promise<{ success: boolean; data?: any }> => { setUploadError(null); @@ -416,8 +433,6 @@ export function useChatbot() { setUploadedFiles(prev => prev.filter(f => f.fileId !== fileId)); }, []); -======= ->>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7 // Stop chatbot workflow const stopChatbot = useCallback(async () => { if (!workflowId || !isRunning) { @@ -476,7 +491,6 @@ export function useChatbot() { const abortController = new AbortController(); streamAbortControllerRef.current = abortController; -<<<<<<< HEAD // Use ref to get current file IDs (avoids closure issues) const fileIdsToSend = pendingFileIdsRef.current.length > 0 ? pendingFileIdsRef.current @@ -487,15 +501,11 @@ export function useChatbot() { console.log('[handleSubmit] pendingFileIds from ref:', pendingFileIdsRef.current); console.log('[handleSubmit] fileIdsToSend:', fileIdsToSend); -======= - // Prepare request body ->>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7 const requestBody: StartChatbotRequest = { prompt: trimmedInput, userLanguage: 'en', ...(workflowId && { workflowId }) }; -<<<<<<< HEAD // Always include listFileId if there are any files if (fileIdsToSend.length > 0) { @@ -506,14 +516,16 @@ export function useChatbot() { } console.log('[handleSubmit] Final requestBody:', JSON.stringify(requestBody, null, 2)); -======= ->>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7 // Track if workflow was created in this request let workflowCreated = false; // Clear thinking message when starting a new request clearThinkingMessage(); + processedLogsRef.current.clear(); // Clear processed logs for new request + + // Track if this is the first event (to reset isSubmitting) + let firstEventReceived = false; // Start SSE stream await startChatbotStreamApi( @@ -524,6 +536,12 @@ export function useChatbot() { return; } + // Reset isSubmitting after first event to enable stop button + if (!firstEventReceived) { + firstEventReceived = true; + setIsSubmitting(false); + } + // Process the chat data item processChatDataItem(item); @@ -549,6 +567,15 @@ export function useChatbot() { console.error('SSE stream error:', err); setError(err.message || 'Stream error occurred'); setIsRunning(false); + // Reset isSubmitting if stream fails before first event + if (!firstEventReceived) { + setIsSubmitting(false); + } + // Clear thinking messages on error + clearThinkingMessage(); + } else { + // Stream was aborted (stopped) - clear thinking messages + clearThinkingMessage(); } }, () => { @@ -556,19 +583,19 @@ export function useChatbot() { if (!abortController.signal.aborted) { setIsRunning(false); setInputValue(''); // Clear input on completion -<<<<<<< HEAD // Clear pending file IDs after successful submission (files are now part of conversation) setPendingFileIds([]); pendingFileIdsRef.current = []; // Clear ref too setUploadedFiles([]); -======= ->>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7 - // Clear thinking message on completion if no final message was received + // Clear thinking message on completion (final message should have cleared it, but ensure cleanup) + clearThinkingMessage(); + // Refresh threads list after message completion (silently, without loading state) setTimeout(() => { - clearThinkingMessage(); - // Refresh threads list after message completion (silently, without loading state) loadThreadsSilently(); }, 100); + } else { + // Stream was aborted (stopped) - clear thinking messages + clearThinkingMessage(); } } ); @@ -581,15 +608,13 @@ export function useChatbot() { console.error('Error starting chatbot:', err); setError(err.message || 'Failed to start chatbot'); setIsRunning(false); + // Clear thinking messages on error + clearThinkingMessage(); } finally { setIsSubmitting(false); streamAbortControllerRef.current = null; } -<<<<<<< HEAD }, [inputValue, workflowId, isRunning, isSubmitting, stopChatbot, processChatDataItem, clearThinkingMessage, loadThreads, pendingFileIds]); -======= - }, [inputValue, workflowId, isRunning, isSubmitting, stopChatbot, processChatDataItem, clearThinkingMessage, loadThreads]); ->>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7 // Delete a chatbot workflow const handleDeleteThread = useCallback(async (workflowIdToDelete: string): Promise => { @@ -649,14 +674,12 @@ export function useChatbot() { setSelectedThreadId(null); setError(null); setInputValue(''); -<<<<<<< HEAD setPendingFileIds([]); pendingFileIdsRef.current = []; setUploadedFiles([]); -======= ->>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7 thinkingLogsRef.current = []; thinkingMessageIdRef.current = null; + processedLogsRef.current.clear(); clearProcessedMessages(); }, [clearProcessedMessages]); @@ -675,14 +698,12 @@ export function useChatbot() { setIsSubmitting(false); setError(null); setInputValue(''); -<<<<<<< HEAD setPendingFileIds([]); pendingFileIdsRef.current = []; setUploadedFiles([]); -======= ->>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7 thinkingLogsRef.current = []; thinkingMessageIdRef.current = null; + processedLogsRef.current.clear(); clearProcessedMessages(); }, [clearProcessedMessages]); @@ -694,6 +715,7 @@ export function useChatbot() { } logQueueRef.current = []; isProcessingLogsRef.current = false; + processedLogsRef.current.clear(); }, []); // Memoized display messages @@ -740,7 +762,6 @@ export function useChatbot() { stopChatbot, resetChatbot, startNewChat, -<<<<<<< HEAD cleanup, // File upload interface @@ -751,9 +772,6 @@ export function useChatbot() { uploadedFiles, uploadingFile, uploadError -======= - cleanup ->>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7 }; }