fix:fixed and finished chatbot integration

This commit is contained in:
Ida Dittrich 2026-01-09 09:22:28 +01:00
parent 23508eaa74
commit 836b8032ae
3 changed files with 90 additions and 71 deletions

View file

@ -181,11 +181,7 @@ export function FormGeneratorControls({
size="sm" size="sm"
icon={FaTrash} icon={FaTrash}
> >
<<<<<<< HEAD
{allItemsSelected {allItemsSelected
=======
{selectedCount === displayData.length && displayData.length > 0
>>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7
? t('formgen.delete.all', `Delete all ${selectedCount} items`).replace('{count}', selectedCount.toString()) ? t('formgen.delete.all', `Delete all ${selectedCount} items`).replace('{count}', selectedCount.toString())
: t('formgen.delete.multiple', `Delete ${selectedCount} selected items`).replace('{count}', selectedCount.toString())} : t('formgen.delete.multiple', `Delete ${selectedCount} selected items`).replace('{count}', selectedCount.toString())}
</Button> </Button>

View file

@ -1006,7 +1006,12 @@ const PageRenderer: React.FC<PageRendererProps> = ({
const buttonVariant = isRunning const buttonVariant = isRunning
? (config.stopButtonVariant || config.buttonVariant || 'primary') ? (config.stopButtonVariant || config.buttonVariant || 'primary')
: (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 // Handle Enter key press
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {

View file

@ -54,6 +54,7 @@ export function useChatbot() {
const thinkingLogsRef = useRef<string[]>([]); // Use ref instead of state to avoid batching const thinkingLogsRef = useRef<string[]>([]); // Use ref instead of state to avoid batching
const logQueueRef = useRef<string[]>([]); // Queue for logs to process one by one const logQueueRef = useRef<string[]>([]); // Queue for logs to process one by one
const isProcessingLogsRef = useRef<boolean>(false); // Flag to prevent concurrent processing const isProcessingLogsRef = useRef<boolean>(false); // Flag to prevent concurrent processing
const processedLogsRef = useRef<Set<string>>(new Set()); // Track processed logs to prevent duplicates
// Clear processed message IDs when workflow changes // Clear processed message IDs when workflow changes
const clearProcessedMessages = useCallback(() => { const clearProcessedMessages = useCallback(() => {
@ -65,16 +66,18 @@ export function useChatbot() {
// Clear log queue and stop processing // Clear log queue and stop processing
logQueueRef.current = []; logQueueRef.current = [];
isProcessingLogsRef.current = false; isProcessingLogsRef.current = false;
processedLogsRef.current.clear(); // Clear processed logs tracking
if (thinkingMessageIdRef.current) { // Reset thinking message refs
const thinkingId = thinkingMessageIdRef.current; const thinkingId = thinkingMessageIdRef.current;
thinkingMessageIdRef.current = null; thinkingMessageIdRef.current = null;
thinkingLogsRef.current = []; thinkingLogsRef.current = [];
setMessages(prevMessages => { // Remove ALL thinking messages (not just the one with current ID)
return prevMessages.filter(m => m.id !== thinkingId); // 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) // Process logs from queue one by one (progressive display)
@ -110,8 +113,8 @@ export function useChatbot() {
// Update messages immediately // Update messages immediately
setMessages(prevMessages => { setMessages(prevMessages => {
// Remove old thinking message if it exists // Remove ALL thinking messages first (to prevent duplicates from previous workflows)
const filtered = prevMessages.filter(m => m.id !== thinkingId); const filtered = prevMessages.filter(m => m.status !== 'thinking');
// Add updated thinking message // Add updated thinking message
const updated = [...filtered, thinkingMessage]; const updated = [...filtered, thinkingMessage];
return updated.sort(sortMessages); return updated.sort(sortMessages);
@ -129,7 +132,21 @@ export function useChatbot() {
}, [workflowId]); }, [workflowId]);
// Add a single log to thinking message (progressive display) // 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 // Add log to queue
logQueueRef.current.push(logMessage); logQueueRef.current.push(logMessage);
@ -141,14 +158,27 @@ export function useChatbot() {
// Process SSE event and update messages // Process SSE event and update messages
const processChatDataItem = useCallback((item: ChatDataItem) => { 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) { if (item.type === 'log' && item.item) {
// Process log item - add to thinking message one at a time // Process log item - add to thinking message one at a time
const logData = item.item as any; const logData = item.item as any;
const logMessage = logData.message || logData.text || ''; 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) { if (logMessage) {
// Add log immediately (progressive display) // Add log immediately (progressive display) with createdAt for deduplication
addLogToThinkingMessage(logMessage); addLogToThinkingMessage(logMessage, item.createdAt);
} }
} else if (item.type === 'message' && item.item) { } else if (item.type === 'message' && item.item) {
const messageData = item.item as any; const messageData = item.item as any;
@ -170,38 +200,26 @@ export function useChatbot() {
// Check if we've already processed this message // Check if we've already processed this message
const messageId = messageData.id; const messageId = messageData.id;
// Always clear thinking messages when a real message arrives
clearThinkingMessage();
if (processedMessageIdsRef.current.has(messageId)) { if (processedMessageIdsRef.current.has(messageId)) {
// Update existing message - clear thinking message first // Update existing message
setMessages(prevMessages => { setMessages(prevMessages => {
let filtered = prevMessages; const existingIndex = prevMessages.findIndex(m => m.id === messageId);
if (thinkingId) {
filtered = prevMessages.filter(m => m.id !== thinkingId);
thinkingMessageIdRef.current = null;
thinkingLogsRef.current = [];
}
const existingIndex = filtered.findIndex(m => m.id === messageId);
if (existingIndex >= 0) { if (existingIndex >= 0) {
const updated = [...filtered]; const updated = [...prevMessages];
updated[existingIndex] = messageData as Message; updated[existingIndex] = messageData as Message;
return updated.sort(sortMessages); return updated.sort(sortMessages);
} }
return filtered; return prevMessages;
}); });
} else { } else {
// Add new message - clear thinking message first // Add new message
processedMessageIdsRef.current.add(messageId); processedMessageIdsRef.current.add(messageId);
setMessages(prevMessages => { setMessages(prevMessages => {
// Remove thinking message BEFORE adding new message (same state update) // Add new message (thinking messages already cleared by clearThinkingMessage)
let filtered = prevMessages; const updated = [...prevMessages, messageData as Message];
if (thinkingId) {
filtered = prevMessages.filter(m => m.id !== thinkingId);
thinkingMessageIdRef.current = null;
thinkingLogsRef.current = [];
}
// Add new message
const updated = [...filtered, messageData as Message];
return updated.sort(sortMessages); return updated.sort(sortMessages);
}); });
} }
@ -348,7 +366,6 @@ export function useChatbot() {
setInputValue(value); setInputValue(value);
}, []); }, []);
<<<<<<< HEAD
// Handle file upload // Handle file upload
const handleFileUpload = useCallback(async (file: File): Promise<{ success: boolean; data?: any }> => { const handleFileUpload = useCallback(async (file: File): Promise<{ success: boolean; data?: any }> => {
setUploadError(null); setUploadError(null);
@ -416,8 +433,6 @@ export function useChatbot() {
setUploadedFiles(prev => prev.filter(f => f.fileId !== fileId)); setUploadedFiles(prev => prev.filter(f => f.fileId !== fileId));
}, []); }, []);
=======
>>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7
// Stop chatbot workflow // Stop chatbot workflow
const stopChatbot = useCallback(async () => { const stopChatbot = useCallback(async () => {
if (!workflowId || !isRunning) { if (!workflowId || !isRunning) {
@ -476,7 +491,6 @@ export function useChatbot() {
const abortController = new AbortController(); const abortController = new AbortController();
streamAbortControllerRef.current = abortController; streamAbortControllerRef.current = abortController;
<<<<<<< HEAD
// Use ref to get current file IDs (avoids closure issues) // Use ref to get current file IDs (avoids closure issues)
const fileIdsToSend = pendingFileIdsRef.current.length > 0 const fileIdsToSend = pendingFileIdsRef.current.length > 0
? pendingFileIdsRef.current ? pendingFileIdsRef.current
@ -487,15 +501,11 @@ export function useChatbot() {
console.log('[handleSubmit] pendingFileIds from ref:', pendingFileIdsRef.current); console.log('[handleSubmit] pendingFileIds from ref:', pendingFileIdsRef.current);
console.log('[handleSubmit] fileIdsToSend:', fileIdsToSend); console.log('[handleSubmit] fileIdsToSend:', fileIdsToSend);
=======
// Prepare request body
>>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7
const requestBody: StartChatbotRequest = { const requestBody: StartChatbotRequest = {
prompt: trimmedInput, prompt: trimmedInput,
userLanguage: 'en', userLanguage: 'en',
...(workflowId && { workflowId }) ...(workflowId && { workflowId })
}; };
<<<<<<< HEAD
// Always include listFileId if there are any files // Always include listFileId if there are any files
if (fileIdsToSend.length > 0) { if (fileIdsToSend.length > 0) {
@ -506,14 +516,16 @@ export function useChatbot() {
} }
console.log('[handleSubmit] Final requestBody:', JSON.stringify(requestBody, null, 2)); console.log('[handleSubmit] Final requestBody:', JSON.stringify(requestBody, null, 2));
=======
>>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7
// Track if workflow was created in this request // Track if workflow was created in this request
let workflowCreated = false; let workflowCreated = false;
// Clear thinking message when starting a new request // Clear thinking message when starting a new request
clearThinkingMessage(); 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 // Start SSE stream
await startChatbotStreamApi( await startChatbotStreamApi(
@ -524,6 +536,12 @@ export function useChatbot() {
return; return;
} }
// Reset isSubmitting after first event to enable stop button
if (!firstEventReceived) {
firstEventReceived = true;
setIsSubmitting(false);
}
// Process the chat data item // Process the chat data item
processChatDataItem(item); processChatDataItem(item);
@ -549,6 +567,15 @@ export function useChatbot() {
console.error('SSE stream error:', err); console.error('SSE stream error:', err);
setError(err.message || 'Stream error occurred'); setError(err.message || 'Stream error occurred');
setIsRunning(false); 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) { if (!abortController.signal.aborted) {
setIsRunning(false); setIsRunning(false);
setInputValue(''); // Clear input on completion setInputValue(''); // Clear input on completion
<<<<<<< HEAD
// Clear pending file IDs after successful submission (files are now part of conversation) // Clear pending file IDs after successful submission (files are now part of conversation)
setPendingFileIds([]); setPendingFileIds([]);
pendingFileIdsRef.current = []; // Clear ref too pendingFileIdsRef.current = []; // Clear ref too
setUploadedFiles([]); setUploadedFiles([]);
======= // Clear thinking message on completion (final message should have cleared it, but ensure cleanup)
>>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7 clearThinkingMessage();
// Clear thinking message on completion if no final message was received // Refresh threads list after message completion (silently, without loading state)
setTimeout(() => { setTimeout(() => {
clearThinkingMessage();
// Refresh threads list after message completion (silently, without loading state)
loadThreadsSilently(); loadThreadsSilently();
}, 100); }, 100);
} else {
// Stream was aborted (stopped) - clear thinking messages
clearThinkingMessage();
} }
} }
); );
@ -581,15 +608,13 @@ export function useChatbot() {
console.error('Error starting chatbot:', err); console.error('Error starting chatbot:', err);
setError(err.message || 'Failed to start chatbot'); setError(err.message || 'Failed to start chatbot');
setIsRunning(false); setIsRunning(false);
// Clear thinking messages on error
clearThinkingMessage();
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
streamAbortControllerRef.current = null; streamAbortControllerRef.current = null;
} }
<<<<<<< HEAD
}, [inputValue, workflowId, isRunning, isSubmitting, stopChatbot, processChatDataItem, clearThinkingMessage, loadThreads, pendingFileIds]); }, [inputValue, workflowId, isRunning, isSubmitting, stopChatbot, processChatDataItem, clearThinkingMessage, loadThreads, pendingFileIds]);
=======
}, [inputValue, workflowId, isRunning, isSubmitting, stopChatbot, processChatDataItem, clearThinkingMessage, loadThreads]);
>>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7
// Delete a chatbot workflow // Delete a chatbot workflow
const handleDeleteThread = useCallback(async (workflowIdToDelete: string): Promise<boolean> => { const handleDeleteThread = useCallback(async (workflowIdToDelete: string): Promise<boolean> => {
@ -649,14 +674,12 @@ export function useChatbot() {
setSelectedThreadId(null); setSelectedThreadId(null);
setError(null); setError(null);
setInputValue(''); setInputValue('');
<<<<<<< HEAD
setPendingFileIds([]); setPendingFileIds([]);
pendingFileIdsRef.current = []; pendingFileIdsRef.current = [];
setUploadedFiles([]); setUploadedFiles([]);
=======
>>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7
thinkingLogsRef.current = []; thinkingLogsRef.current = [];
thinkingMessageIdRef.current = null; thinkingMessageIdRef.current = null;
processedLogsRef.current.clear();
clearProcessedMessages(); clearProcessedMessages();
}, [clearProcessedMessages]); }, [clearProcessedMessages]);
@ -675,14 +698,12 @@ export function useChatbot() {
setIsSubmitting(false); setIsSubmitting(false);
setError(null); setError(null);
setInputValue(''); setInputValue('');
<<<<<<< HEAD
setPendingFileIds([]); setPendingFileIds([]);
pendingFileIdsRef.current = []; pendingFileIdsRef.current = [];
setUploadedFiles([]); setUploadedFiles([]);
=======
>>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7
thinkingLogsRef.current = []; thinkingLogsRef.current = [];
thinkingMessageIdRef.current = null; thinkingMessageIdRef.current = null;
processedLogsRef.current.clear();
clearProcessedMessages(); clearProcessedMessages();
}, [clearProcessedMessages]); }, [clearProcessedMessages]);
@ -694,6 +715,7 @@ export function useChatbot() {
} }
logQueueRef.current = []; logQueueRef.current = [];
isProcessingLogsRef.current = false; isProcessingLogsRef.current = false;
processedLogsRef.current.clear();
}, []); }, []);
// Memoized display messages // Memoized display messages
@ -740,7 +762,6 @@ export function useChatbot() {
stopChatbot, stopChatbot,
resetChatbot, resetChatbot,
startNewChat, startNewChat,
<<<<<<< HEAD
cleanup, cleanup,
// File upload interface // File upload interface
@ -751,9 +772,6 @@ export function useChatbot() {
uploadedFiles, uploadedFiles,
uploadingFile, uploadingFile,
uploadError uploadError
=======
cleanup
>>>>>>> c76e7efd28210f45737b5afbdddff2712b2c0cc7
}; };
} }