Merge remote-tracking branch 'origin/fix/watsonx_fixes' into all-merges

This commit is contained in:
Lucas Oliveira 2025-12-05 17:50:41 -03:00
commit 8a456bc605
2 changed files with 366 additions and 233 deletions

View file

@ -73,8 +73,6 @@ function ChatPage() {
const lastLoadedConversationRef = useRef<string | null>(null); const lastLoadedConversationRef = useRef<string | null>(null);
const { addTask } = useTask(); const { addTask } = useTask();
console.log(endpoint, refreshTrigger);
// Check if chat history is loading // Check if chat history is loading
const { isLoading: isConversationsLoading } = useGetConversationsQuery( const { isLoading: isConversationsLoading } = useGetConversationsQuery(
endpoint, endpoint,

View file

@ -1,3 +1,4 @@
import { X } from "lucide-react";
import { AnimatePresence, motion } from "motion/react"; import { AnimatePresence, motion } from "motion/react";
import { type ChangeEvent, useEffect, useRef, useState } from "react"; import { type ChangeEvent, useEffect, useRef, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
@ -24,6 +25,10 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
const [uploadedTaskId, setUploadedTaskId] = useState<string | null>(null); const [uploadedTaskId, setUploadedTaskId] = useState<string | null>(null);
const [shouldCreateFilter, setShouldCreateFilter] = useState(false); const [shouldCreateFilter, setShouldCreateFilter] = useState(false);
const [isCreatingFilter, setIsCreatingFilter] = useState(false); const [isCreatingFilter, setIsCreatingFilter] = useState(false);
const [error, setError] = useState<string | null>(null);
// Track which tasks we've already handled to prevent infinite loops
const handledFailedTasksRef = useRef<Set<string>>(new Set());
const createFilterMutation = useCreateFilter(); const createFilterMutation = useCreateFilter();
@ -40,8 +45,6 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
refetchInterval: currentStep !== null ? 1000 : false, // Poll every 1 second during upload refetchInterval: currentStep !== null ? 1000 : false, // Poll every 1 second during upload
}); });
const { refetch: refetchNudges } = useGetNudgesQuery(null);
// Monitor tasks and call onComplete when file processing is done // Monitor tasks and call onComplete when file processing is done
useEffect(() => { useEffect(() => {
if (currentStep === null || !tasks || !uploadedTaskId) { if (currentStep === null || !tasks || !uploadedTaskId) {
@ -56,14 +59,109 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
return; return;
} }
// Skip if this task was already handled as a failed task (from a previous failed upload)
// This prevents processing old failed tasks when a new upload starts
if (handledFailedTasksRef.current.has(matchingTask.task_id)) {
// Check if it's a failed task that we've already handled
const hasFailedFile =
matchingTask.files &&
Object.values(matchingTask.files).some(
(file) => file.status === "failed" || file.status === "error",
);
if (hasFailedFile) {
// This is an old failed task that we've already handled, ignore it
console.log(
"Skipping already-handled failed task:",
matchingTask.task_id,
);
return;
}
// If it's not a failed task, remove it from handled list (it might have succeeded on retry)
handledFailedTasksRef.current.delete(matchingTask.task_id);
}
// Check if any file failed in the matching task
const hasFailedFile = (() => {
// Must have files object
if (!matchingTask.files || typeof matchingTask.files !== "object") {
return false;
}
const fileEntries = Object.values(matchingTask.files);
// Must have at least one file
if (fileEntries.length === 0) {
return false;
}
// Check if any file has failed status
return fileEntries.some(
(file) => file.status === "failed" || file.status === "error",
);
})();
// If any file failed, show error and jump back one step (like onboarding-card.tsx)
// Only handle if we haven't already handled this task
if (
hasFailedFile &&
!isCreatingFilter &&
!handledFailedTasksRef.current.has(matchingTask.task_id)
) {
console.error("File failed in task, jumping back one step", matchingTask);
// Mark this task as handled to prevent infinite loops
handledFailedTasksRef.current.add(matchingTask.task_id);
// Extract error messages from failed files
const errorMessages: string[] = [];
if (matchingTask.files) {
Object.values(matchingTask.files).forEach((file) => {
if (
(file.status === "failed" || file.status === "error") &&
file.error
) {
errorMessages.push(file.error);
}
});
}
// Also check task-level error
if (matchingTask.error) {
errorMessages.push(matchingTask.error);
}
// Use the first error message, or a generic message if no errors found
const errorMessage =
errorMessages.length > 0
? errorMessages[0]
: "Document failed to ingest. Please try again with a different file.";
// Set error message and jump back one step
setError(errorMessage);
setCurrentStep(STEP_LIST.length);
// Clear filter creation flags since ingestion failed
setShouldCreateFilter(false);
setUploadedFilename(null);
// Jump back one step after 1 second (go back to upload step)
setTimeout(() => {
setCurrentStep(null);
}, 1000);
return;
}
// Check if the matching task is still active (pending, running, or processing) // Check if the matching task is still active (pending, running, or processing)
const isTaskActive = const isTaskActive =
matchingTask.status === "pending" || matchingTask.status === "pending" ||
matchingTask.status === "running" || matchingTask.status === "running" ||
matchingTask.status === "processing"; matchingTask.status === "processing";
// If task is completed or has processed files, complete the onboarding step // If task is completed successfully (no failures) and has processed files, complete the onboarding step
if (!isTaskActive || (matchingTask.processed_files ?? 0) > 0) { if (
(!isTaskActive || (matchingTask.processed_files ?? 0) > 0) &&
!hasFailedFile
) {
// Set to final step to show "Done" // Set to final step to show "Done"
setCurrentStep(STEP_LIST.length); setCurrentStep(STEP_LIST.length);
@ -157,11 +255,23 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
}; };
const handleUploadClick = () => { const handleUploadClick = () => {
// Clear any previous error when user clicks to upload again
setError(null);
fileInputRef.current?.click(); fileInputRef.current?.click();
}; };
const performUpload = async (file: File) => { const performUpload = async (file: File) => {
setIsUploading(true); setIsUploading(true);
// Clear any previous error when starting a new upload
setError(null);
// Clear handled tasks ref to allow retry
handledFailedTasksRef.current.clear();
// Reset task ID to prevent matching old failed tasks
setUploadedTaskId(null);
// Clear filter creation flags
setShouldCreateFilter(false);
setUploadedFilename(null);
try { try {
setCurrentStep(0); setCurrentStep(0);
const result = await uploadFile(file, true, true); // Pass createFilter=true const result = await uploadFile(file, true, true); // Pass createFilter=true
@ -183,7 +293,8 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
setCurrentStep(1); setCurrentStep(1);
}, 1500); }, 1500);
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : "Upload failed"; const errorMessage =
error instanceof Error ? error.message : "Upload failed";
console.error("Upload failed", errorMessage); console.error("Upload failed", errorMessage);
// Dispatch event that chat context can listen to // Dispatch event that chat context can listen to
@ -205,6 +316,9 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
// Reset on error // Reset on error
setCurrentStep(null); setCurrentStep(null);
setUploadedTaskId(null); setUploadedTaskId(null);
setError(errorMessage);
setShouldCreateFilter(false);
setUploadedFilename(null);
} finally { } finally {
setIsUploading(false); setIsUploading(false);
} }
@ -238,6 +352,24 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
exit={{ opacity: 0, y: -24 }} exit={{ opacity: 0, y: -24 }}
transition={{ duration: 0.4, ease: "easeInOut" }} transition={{ duration: 0.4, ease: "easeInOut" }}
> >
<div className="w-full flex flex-col gap-4">
<AnimatePresence mode="wait">
{error && (
<motion.div
key="error"
initial={{ opacity: 1, y: 0, height: "auto" }}
exit={{ opacity: 0, y: -10, height: 0 }}
>
<div className="pb-2 flex items-center gap-4">
<X className="w-4 h-4 text-destructive shrink-0" />
<span className="text-sm text-muted-foreground">
{error}
</span>
</div>
</motion.div>
)}
</AnimatePresence>
<div>
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
@ -246,6 +378,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
> >
<div>{isUploading ? "Uploading..." : "Add a document"}</div> <div>{isUploading ? "Uploading..." : "Add a document"}</div>
</Button> </Button>
</div>
<input <input
ref={fileInputRef} ref={fileInputRef}
type="file" type="file"
@ -253,6 +386,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
className="hidden" className="hidden"
accept=".pdf,.doc,.docx,.txt,.md,.rtf,.odt" accept=".pdf,.doc,.docx,.txt,.md,.rtf,.odt"
/> />
</div>
</motion.div> </motion.div>
) : ( ) : (
<motion.div <motion.div
@ -267,6 +401,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
isCompleted={false} isCompleted={false}
steps={STEP_LIST} steps={STEP_LIST}
storageKey={ONBOARDING_UPLOAD_STEPS_KEY} storageKey={ONBOARDING_UPLOAD_STEPS_KEY}
hasError={!!error}
/> />
</motion.div> </motion.div>
)} )}