Merge pull request #618 from langflow-ai/fix/chat_filter_clearing
fix: chat filter being cleared
This commit is contained in:
commit
d908174758
3 changed files with 252 additions and 368 deletions
|
|
@ -25,10 +25,6 @@ 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();
|
||||||
|
|
||||||
|
|
@ -45,6 +41,8 @@ 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) {
|
||||||
|
|
@ -59,109 +57,14 @@ 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 successfully (no failures) and has processed files, complete the onboarding step
|
// If task is completed or has processed files, complete the onboarding step
|
||||||
if (
|
if (!isTaskActive || (matchingTask.processed_files ?? 0) > 0) {
|
||||||
(!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);
|
||||||
|
|
||||||
|
|
@ -217,6 +120,8 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsCreatingFilter(false);
|
setIsCreatingFilter(false);
|
||||||
|
// Refetch nudges to get new ones
|
||||||
|
refetchNudges();
|
||||||
|
|
||||||
// Wait a bit before completing (after filter is created)
|
// Wait a bit before completing (after filter is created)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -225,6 +130,8 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// No filter to create, just complete
|
// No filter to create, just complete
|
||||||
|
// Refetch nudges to get new ones
|
||||||
|
refetchNudges();
|
||||||
|
|
||||||
// Wait a bit before completing
|
// Wait a bit before completing
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -236,6 +143,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
|
||||||
tasks,
|
tasks,
|
||||||
currentStep,
|
currentStep,
|
||||||
onComplete,
|
onComplete,
|
||||||
|
refetchNudges,
|
||||||
shouldCreateFilter,
|
shouldCreateFilter,
|
||||||
uploadedFilename,
|
uploadedFilename,
|
||||||
uploadedTaskId,
|
uploadedTaskId,
|
||||||
|
|
@ -250,23 +158,11 @@ 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
|
||||||
|
|
@ -288,8 +184,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
|
||||||
setCurrentStep(1);
|
setCurrentStep(1);
|
||||||
}, 1500);
|
}, 1500);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage =
|
const errorMessage = error instanceof Error ? error.message : "Upload failed";
|
||||||
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
|
||||||
|
|
@ -311,9 +206,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
@ -347,24 +239,6 @@ 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"
|
||||||
|
|
@ -373,7 +247,6 @@ 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"
|
||||||
|
|
@ -381,7 +254,6 @@ 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
|
||||||
|
|
@ -396,7 +268,6 @@ 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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -172,12 +172,14 @@ export function ChatRenderer({
|
||||||
// Mark onboarding as complete in context
|
// Mark onboarding as complete in context
|
||||||
setOnboardingComplete(true);
|
setOnboardingComplete(true);
|
||||||
|
|
||||||
// Clear ALL conversation state so next message starts fresh
|
// Store the user document filter as default for new conversations FIRST
|
||||||
await startNewConversation();
|
// This must happen before startNewConversation() so the filter is available
|
||||||
|
|
||||||
// Store the user document filter as default for new conversations and load it
|
|
||||||
await storeDefaultFilterForNewConversations(true);
|
await storeDefaultFilterForNewConversations(true);
|
||||||
|
|
||||||
|
// Clear ALL conversation state so next message starts fresh
|
||||||
|
// This will pick up the default filter we just set
|
||||||
|
await startNewConversation();
|
||||||
|
|
||||||
// Clean up onboarding filter IDs now that we've set the default
|
// Clean up onboarding filter IDs now that we've set the default
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.removeItem(ONBOARDING_OPENRAG_DOCS_FILTER_ID_KEY);
|
localStorage.removeItem(ONBOARDING_OPENRAG_DOCS_FILTER_ID_KEY);
|
||||||
|
|
|
||||||
|
|
@ -262,6 +262,10 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
||||||
const startNewConversation = useCallback(async () => {
|
const startNewConversation = useCallback(async () => {
|
||||||
console.log("[CONVERSATION] Starting new conversation");
|
console.log("[CONVERSATION] Starting new conversation");
|
||||||
|
|
||||||
|
// Check if there's existing conversation data - if so, this is a manual "new conversation" action
|
||||||
|
// Check state values before clearing them
|
||||||
|
const hasExistingConversation = conversationData !== null || placeholderConversation !== null;
|
||||||
|
|
||||||
// Clear current conversation data and reset state
|
// Clear current conversation data and reset state
|
||||||
setCurrentConversationId(null);
|
setCurrentConversationId(null);
|
||||||
setPreviousResponseIds({ chat: null, langflow: null });
|
setPreviousResponseIds({ chat: null, langflow: null });
|
||||||
|
|
@ -295,15 +299,22 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
||||||
setConversationFilterState(null);
|
setConversationFilterState(null);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("[CONVERSATION] No default filter set");
|
// No default filter in localStorage
|
||||||
|
if (hasExistingConversation) {
|
||||||
|
// User is manually starting a new conversation - clear the filter
|
||||||
|
console.log("[CONVERSATION] Manual new conversation - clearing filter");
|
||||||
setConversationFilterState(null);
|
setConversationFilterState(null);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setConversationFilterState(null);
|
// First time after onboarding - preserve existing filter if set
|
||||||
|
// This prevents clearing the filter when startNewConversation is called multiple times during onboarding
|
||||||
|
console.log("[CONVERSATION] No default filter set, preserving existing filter if any");
|
||||||
|
// Don't clear the filter - it may have been set by storeDefaultFilterForNewConversations
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a temporary placeholder conversation to show in sidebar
|
// Create a temporary placeholder conversation to show in sidebar
|
||||||
const placeholderConversation: ConversationData = {
|
const newPlaceholderConversation: ConversationData = {
|
||||||
response_id: "new-conversation-" + Date.now(),
|
response_id: "new-conversation-" + Date.now(),
|
||||||
title: "New conversation",
|
title: "New conversation",
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
|
|
@ -318,10 +329,10 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
||||||
last_activity: new Date().toISOString(),
|
last_activity: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
setPlaceholderConversation(placeholderConversation);
|
setPlaceholderConversation(newPlaceholderConversation);
|
||||||
// Force immediate refresh to ensure sidebar shows correct state
|
// Force immediate refresh to ensure sidebar shows correct state
|
||||||
refreshConversations(true);
|
refreshConversations(true);
|
||||||
}, [endpoint, refreshConversations]);
|
}, [endpoint, refreshConversations, conversationData, placeholderConversation]);
|
||||||
|
|
||||||
const addConversationDoc = useCallback((filename: string) => {
|
const addConversationDoc = useCallback((filename: string) => {
|
||||||
setConversationDocs((prev) => [
|
setConversationDocs((prev) => [
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue