Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes an issue where the chat filter was being cleared immediately after onboarding by adjusting the order of operations and improving the onboarding completion tracking across the application.
Key Changes:
- Reordered filter setup to occur before starting a new conversation in the onboarding flow
- Added onboarding completion tracking in the chat context to better control query execution
- Improved error message handling in provider validation to show more helpful error details
- Enhanced task tracking in file upload to prevent premature completion
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/contexts/chat-context.tsx | Added onboarding completion state tracking and improved filter preservation logic in startNewConversation |
| frontend/components/chat-renderer.tsx | Reordered operations to set default filter before calling startNewConversation |
| frontend/app/onboarding/_components/onboarding-upload.tsx | Enhanced task tracking by matching specific upload task IDs instead of counting all tasks |
| frontend/app/onboarding/_components/onboarding-card.tsx | Added provider-specific checks to alreadyConfigured prop for OpenAI, WatsonX, and Ollama |
| frontend/app/chat/page.tsx | Added conversation loading check and onboarding completion gating for nudges query |
| frontend/lib/upload-utils.ts | Added taskId field to upload result for better task tracking |
| frontend/app/api/queries/useProviderHealthQuery.ts | Added onboarding completion and active ingestion checks to prevent health checks during setup |
| frontend/app/api/queries/useGetNudgesQuery.ts | Added onboarding completion and LLM health checks to prevent nudges during errors |
| frontend/app/api/queries/useGetConversationsQuery.ts | Added onboarding completion check to prevent conversation fetching during setup |
| frontend/components/provider-health-banner.tsx | Improved error message display to avoid showing duplicates when LLM and embedding have same error |
| frontend/app/onboarding/_components/ibm-onboarding.tsx | Code formatting changes only (tabs instead of spaces) |
| src/api/provider_validation.py | Added helper functions for parsing error messages and improved error handling to preserve original error details |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, []); | ||
|
|
||
| const setOnboardingComplete = useCallback((complete: boolean) => { | ||
| setIsOnboardingComplete(complete); |
There was a problem hiding this comment.
The setOnboardingComplete callback (lines 145-147) is defined with useCallback but has an empty dependency array. This is correct since it only calls setIsOnboardingComplete which is a state setter. However, this callback is exposed in the context (line 424) but is not actually used to update localStorage. When setOnboardingComplete(true) is called (e.g., line 173 in chat-renderer.tsx), it only updates the local state but doesn't clear the ONBOARDING_STEP_KEY from localStorage. This could cause inconsistency between the context state and localStorage. Consider having this callback also update localStorage when appropriate.
| setIsOnboardingComplete(complete); | |
| setIsOnboardingComplete(complete); | |
| if (typeof window !== "undefined") { | |
| if (complete) { | |
| localStorage.removeItem(ONBOARDING_STEP_KEY); | |
| } else { | |
| localStorage.setItem(ONBOARDING_STEP_KEY, "incomplete"); | |
| } | |
| } |
| const lastLoadedConversationRef = useRef<string | null>(null); | ||
| const { addTask } = useTask(); | ||
|
|
||
| console.log(endpoint, refreshTrigger); |
There was a problem hiding this comment.
This console.log statement appears to be leftover debug code and should be removed. The parameters endpoint and refreshTrigger are not being used in any meaningful way here.
| console.log(endpoint, refreshTrigger); |
| }, [ | ||
| tasks, | ||
| currentStep, | ||
| onComplete, | ||
| refetchNudges, | ||
| shouldCreateFilter, | ||
| uploadedFilename, | ||
| uploadedTaskId, | ||
| createFilterMutation, | ||
| isCreatingFilter, | ||
| ]); |
There was a problem hiding this comment.
The dependency array is missing isCreatingFilter as a dependency. While createFilterMutation is included, the isCreatingFilter state is being checked within the effect (line 122), so it should be included in the dependencies to ensure the effect has access to the current value.
| // Include hasChatError in queryKey so React Query refetches when it changes | ||
| // This ensures the health check runs with test_completion=true when chat errors occur | ||
| const testCompletion = params?.test_completion ?? hasChatError; | ||
| const queryKey = ["provider", "health", testCompletion, hasChatError]; |
There was a problem hiding this comment.
The query key includes hasChatError (line 118) but this value is also used in the enabled condition (line 160-164). When hasChatError changes, React Query will refetch due to the changed key, but the component will also re-render due to the context update. This could cause redundant fetches. Consider removing hasChatError from the query key since testCompletion already captures its value.
| const queryKey = ["provider", "health", testCompletion, hasChatError]; | |
| const queryKey = ["provider", "health", testCompletion]; |
| (!!debouncedEndpoint && !!debouncedApiKey && !!debouncedProjectId) || | ||
| getFromEnv || |
There was a problem hiding this comment.
The enabled property in the query options (line 102-105) requires that all 3 fields (endpoint, apiKey, projectId) are populated OR getFromEnv is true OR alreadyConfigured is true. However, when getFromEnv is true, the apiKey will be empty (line 98), so the condition !!debouncedApiKey will be false. This means the query will only be enabled when getFromEnv || alreadyConfigured is true, not when all 3 fields are filled. Consider revising the condition to: (!!debouncedEndpoint && (getFromEnv || !!debouncedApiKey) && !!debouncedProjectId) || alreadyConfigured
| (!!debouncedEndpoint && !!debouncedApiKey && !!debouncedProjectId) || | |
| getFromEnv || | |
| (!!debouncedEndpoint && (getFromEnv || !!debouncedApiKey) && !!debouncedProjectId) || |
| # If the error message contains JSON, parse it to extract just the message | ||
| error_str = str(e) | ||
| if "IBM Watson API error: " in error_str: | ||
| json_part = error_str.split("IBM Watson API error: ", 1)[1] | ||
| parsed_message = _parse_json_error_message(json_part) | ||
| if parsed_message != json_part: | ||
| raise Exception(f"IBM Watson API error: {parsed_message}") |
There was a problem hiding this comment.
Lines 573-574 and 588-594 contain duplicate logic for parsing JSON error messages, similar to the pattern in the completion test. The exception handler re-parses error messages that were already parsed in line 574. Consider removing this duplicate parsing logic.
| # If the error message contains JSON, parse it to extract just the message | |
| error_str = str(e) | |
| if "IBM Watson API error: " in error_str: | |
| json_part = error_str.split("IBM Watson API error: ", 1)[1] | |
| parsed_message = _parse_json_error_message(json_part) | |
| if parsed_message != json_part: | |
| raise Exception(f"IBM Watson API error: {parsed_message}") |
| // Force immediate refresh to ensure sidebar shows correct state | ||
| refreshConversations(true); | ||
| }, [endpoint, refreshConversations]); | ||
| }, [endpoint, refreshConversations, conversationData, placeholderConversation]); |
There was a problem hiding this comment.
[nitpick] The dependency array for startNewConversation includes conversationData and placeholderConversation (line 335), but these are checked inside the function (lines 265-267). This means the callback will be recreated whenever these values change, which could cause unnecessary re-renders of child components that use this callback. Since these are only used for conditional logic inside the function, they don't need to be in the dependency array unless the callback's behavior should change when they change. Consider if this is intentional or if the dependency array should be simplified.
| }, [endpoint, refreshConversations, conversationData, placeholderConversation]); | |
| }, [endpoint, refreshConversations]); |
| console.log("[CONVERSATION] No default filter set, preserving existing filter if any"); | ||
| // Don't clear the filter - it may have been set by storeDefaultFilterForNewConversations | ||
| } | ||
| } |
There was a problem hiding this comment.
The logic on lines 302-313 has an issue: when there's no default filter and no existing conversation (first time after onboarding), the filter is preserved. However, this is inside the if (typeof window !== "undefined") block which checks localStorage. If window is undefined (server-side rendering), the entire block is skipped and the filter behavior is undefined. Consider adding an else clause after line 314 to handle the server-side case explicitly, or document that this only runs client-side.
| } | |
| } | |
| } else { | |
| // Server-side rendering: filter logic is skipped. | |
| // Explicitly preserve existing filter state or set to null as needed. | |
| // For SSR, we choose to preserve the existing filter state. | |
| console.log("[CONVERSATION] SSR: preserving existing filter state"); | |
| // Optionally, you could setConversationFilterState(null) here if desired. |
| (messages[index - 2]?.content.match( | ||
| FILES_REGEX, | ||
| )?.[0] ?? | ||
| undefined) && | ||
| message.content === FILE_CONFIRMATION | ||
| ? undefined | ||
| : message.content | ||
| } | ||
| files={ | ||
| index >= 2 | ||
| ? (messages[index - 2]?.content.match( | ||
| FILES_REGEX, | ||
| )?.[0] ?? undefined) |
There was a problem hiding this comment.
This use of variable 'undefined' always evaluates to false.
| (messages[index - 2]?.content.match( | |
| FILES_REGEX, | |
| )?.[0] ?? | |
| undefined) && | |
| message.content === FILE_CONFIRMATION | |
| ? undefined | |
| : message.content | |
| } | |
| files={ | |
| index >= 2 | |
| ? (messages[index - 2]?.content.match( | |
| FILES_REGEX, | |
| )?.[0] ?? undefined) | |
| messages[index - 2]?.content.match(FILES_REGEX)?.[0] != null && | |
| message.content === FILE_CONFIRMATION | |
| ? undefined | |
| : message.content | |
| } | |
| files={ | |
| index >= 2 | |
| ? messages[index - 2]?.content.match(FILES_REGEX)?.[0] |
| # Generic format: {"detail": "..."} | ||
| if "detail" in error_data: | ||
| return error_data["detail"] | ||
| except (json.JSONDecodeError, ValueError, TypeError): |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| except (json.JSONDecodeError, ValueError, TypeError): | |
| except (json.JSONDecodeError, ValueError, TypeError): | |
| # JSON parsing errors are expected if the input is not valid JSON. | |
| # In this case, we fall back to returning the original error text. |
|
@phact can you take a stab at the conflict? |
This PR fixes the problem where the chat filter is cleared right after onboarding.
Closes #612