Changed frontend to not reference local storage

This commit is contained in:
Lucas Oliveira 2025-12-23 13:22:48 -03:00
parent 633afde224
commit 527bc7f67e
9 changed files with 157 additions and 202 deletions

View file

@ -12,6 +12,7 @@ import { FILE_CONFIRMATION, FILES_REGEX } from "@/lib/constants";
import { useLoadingStore } from "@/stores/loadingStore"; import { useLoadingStore } from "@/stores/loadingStore";
import { useGetConversationsQuery } from "../api/queries/useGetConversationsQuery"; import { useGetConversationsQuery } from "../api/queries/useGetConversationsQuery";
import { useGetNudgesQuery } from "../api/queries/useGetNudgesQuery"; import { useGetNudgesQuery } from "../api/queries/useGetNudgesQuery";
import { useGetSettingsQuery } from "../api/queries/useGetSettingsQuery";
import { AssistantMessage } from "./_components/assistant-message"; import { AssistantMessage } from "./_components/assistant-message";
import { ChatInput, type ChatInputHandle } from "./_components/chat-input"; import { ChatInput, type ChatInputHandle } from "./_components/chat-input";
import Nudges from "./_components/nudges"; import Nudges from "./_components/nudges";
@ -638,27 +639,14 @@ function ChatPage() {
}; };
}, [endpoint, setPreviousResponseIds, setLoading]); }, [endpoint, setPreviousResponseIds, setLoading]);
// Check if onboarding is complete by looking at local storage // Get settings to check onboarding completion
const [isOnboardingComplete, setIsOnboardingComplete] = useState(() => { const { data: settings } = useGetSettingsQuery();
if (typeof window === "undefined") return false;
return localStorage.getItem("onboarding-step") === null;
});
// Listen for storage changes to detect when onboarding completes // Check if onboarding is complete (current_step >= 4 means complete)
useEffect(() => { const TOTAL_ONBOARDING_STEPS = 4;
const checkOnboarding = () => { const isOnboardingComplete =
if (typeof window !== "undefined") { settings?.onboarding?.current_step !== undefined &&
setIsOnboardingComplete( settings.onboarding.current_step >= TOTAL_ONBOARDING_STEPS;
localStorage.getItem("onboarding-step") === null,
);
}
};
// Check periodically since storage events don't fire in the same tab
const interval = setInterval(checkOnboarding, 500);
return () => clearInterval(interval);
}, []);
// Prepare filters for nudges (same as chat) // Prepare filters for nudges (same as chat)
const processedFiltersForNudges = parsedFilterData?.filters const processedFiltersForNudges = parsedFilterData?.filters

View file

@ -10,7 +10,6 @@ import {
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
} from "@/components/ui/accordion"; } from "@/components/ui/accordion";
import { ONBOARDING_CARD_STEPS_KEY } from "@/lib/constants";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export function AnimatedProviderSteps({ export function AnimatedProviderSteps({
@ -18,7 +17,6 @@ export function AnimatedProviderSteps({
isCompleted, isCompleted,
setCurrentStep, setCurrentStep,
steps, steps,
storageKey = ONBOARDING_CARD_STEPS_KEY,
processingStartTime, processingStartTime,
hasError = false, hasError = false,
}: { }: {
@ -26,25 +24,19 @@ export function AnimatedProviderSteps({
isCompleted: boolean; isCompleted: boolean;
setCurrentStep: (step: number) => void; setCurrentStep: (step: number) => void;
steps: string[]; steps: string[];
storageKey?: string;
processingStartTime?: number | null; processingStartTime?: number | null;
hasError?: boolean; hasError?: boolean;
}) { }) {
const [startTime, setStartTime] = useState<number | null>(null); const [startTime, setStartTime] = useState<number | null>(null);
const [elapsedTime, setElapsedTime] = useState<number>(0); const [elapsedTime, setElapsedTime] = useState<number>(0);
// Initialize start time from prop or local storage // Initialize start time from prop
useEffect(() => { useEffect(() => {
const storedElapsedTime = localStorage.getItem(storageKey); if (processingStartTime) {
if (isCompleted && storedElapsedTime) {
// If completed, use stored elapsed time
setElapsedTime(parseFloat(storedElapsedTime));
} else if (processingStartTime) {
// Use the start time passed from parent (when user clicked Complete) // Use the start time passed from parent (when user clicked Complete)
setStartTime(processingStartTime); setStartTime(processingStartTime);
} }
}, [storageKey, isCompleted, processingStartTime]); }, [processingStartTime]);
// Progress through steps // Progress through steps
useEffect(() => { useEffect(() => {
@ -56,14 +48,13 @@ export function AnimatedProviderSteps({
} }
}, [currentStep, setCurrentStep, steps, isCompleted]); }, [currentStep, setCurrentStep, steps, isCompleted]);
// Calculate and store elapsed time when completed // Calculate elapsed time when completed
useEffect(() => { useEffect(() => {
if (isCompleted && startTime) { if (isCompleted && startTime) {
const elapsed = Date.now() - startTime; const elapsed = Date.now() - startTime;
setElapsedTime(elapsed); setElapsedTime(elapsed);
localStorage.setItem(storageKey, elapsed.toString());
} }
}, [isCompleted, startTime, storageKey]); }, [isCompleted, startTime]);
const isDone = currentStep >= steps.length && !isCompleted && !hasError; const isDone = currentStep >= steps.length && !isCompleted && !hasError;

View file

@ -10,6 +10,7 @@ import {
useOnboardingMutation, useOnboardingMutation,
} from "@/app/api/mutations/useOnboardingMutation"; } from "@/app/api/mutations/useOnboardingMutation";
import { useOnboardingRollbackMutation } from "@/app/api/mutations/useOnboardingRollbackMutation"; import { useOnboardingRollbackMutation } from "@/app/api/mutations/useOnboardingRollbackMutation";
import { useUpdateOnboardingStateMutation } from "@/app/api/mutations/useUpdateOnboardingStateMutation";
import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery"; import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery";
import { useGetTasksQuery } from "@/app/api/queries/useGetTasksQuery"; import { useGetTasksQuery } from "@/app/api/queries/useGetTasksQuery";
import type { ProviderHealthResponse } from "@/app/api/queries/useProviderHealthQuery"; import type { ProviderHealthResponse } from "@/app/api/queries/useProviderHealthQuery";
@ -25,7 +26,6 @@ import {
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { ONBOARDING_CARD_STEPS_KEY } from "@/lib/constants";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { AnimatedProviderSteps } from "./animated-provider-steps"; import { AnimatedProviderSteps } from "./animated-provider-steps";
import { AnthropicOnboarding } from "./anthropic-onboarding"; import { AnthropicOnboarding } from "./anthropic-onboarding";
@ -174,6 +174,8 @@ const OnboardingCard = ({
// Track which tasks we've already handled to prevent infinite loops // Track which tasks we've already handled to prevent infinite loops
const handledFailedTasksRef = useRef<Set<string>>(new Set()); const handledFailedTasksRef = useRef<Set<string>>(new Set());
const updateOnboardingMutation = useUpdateOnboardingStateMutation();
// Query tasks to track completion // Query tasks to track completion
const { data: tasks } = useGetTasksQuery({ const { data: tasks } = useGetTasksQuery({
enabled: currentStep !== null, // Only poll when onboarding has started enabled: currentStep !== null, // Only poll when onboarding has started
@ -307,11 +309,12 @@ const OnboardingCard = ({
console.log("Onboarding completed successfully", data); console.log("Onboarding completed successfully", data);
// Save OpenRAG docs filter ID if sample data was ingested // Save OpenRAG docs filter ID if sample data was ingested
if (data.openrag_docs_filter_id && typeof window !== "undefined") { if (data.openrag_docs_filter_id) {
localStorage.setItem( // Save to backend
"onboarding_openrag_docs_filter_id", updateOnboardingMutation.mutateAsync({
data.openrag_docs_filter_id openrag_docs_filter_id: data.openrag_docs_filter_id,
); });
console.log("Saved OpenRAG docs filter ID:", data.openrag_docs_filter_id); console.log("Saved OpenRAG docs filter ID:", data.openrag_docs_filter_id);
} }
@ -674,7 +677,6 @@ const OnboardingCard = ({
setCurrentStep={setCurrentStep} setCurrentStep={setCurrentStep}
steps={isEmbedding ? EMBEDDING_STEP_LIST : STEP_LIST} steps={isEmbedding ? EMBEDDING_STEP_LIST : STEP_LIST}
processingStartTime={processingStartTime} processingStartTime={processingStartTime}
storageKey={ONBOARDING_CARD_STEPS_KEY}
hasError={!!error} hasError={!!error}
/> />
</motion.div> </motion.div>

View file

@ -3,6 +3,8 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { StickToBottom } from "use-stick-to-bottom"; import { StickToBottom } from "use-stick-to-bottom";
import { getFilterById } from "@/app/api/queries/useGetFilterByIdQuery"; import { getFilterById } from "@/app/api/queries/useGetFilterByIdQuery";
import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery";
import { useUpdateOnboardingStateMutation } from "@/app/api/mutations/useUpdateOnboardingStateMutation";
import { AssistantMessage } from "@/app/chat/_components/assistant-message"; import { AssistantMessage } from "@/app/chat/_components/assistant-message";
import Nudges from "@/app/chat/_components/nudges"; import Nudges from "@/app/chat/_components/nudges";
import { UserMessage } from "@/app/chat/_components/user-message"; import { UserMessage } from "@/app/chat/_components/user-message";
@ -10,11 +12,6 @@ import type { Message, SelectedFilters } from "@/app/chat/_types/types";
import OnboardingCard from "@/app/onboarding/_components/onboarding-card"; import OnboardingCard from "@/app/onboarding/_components/onboarding-card";
import { useChat } from "@/contexts/chat-context"; import { useChat } from "@/contexts/chat-context";
import { useChatStreaming } from "@/hooks/useChatStreaming"; import { useChatStreaming } from "@/hooks/useChatStreaming";
import {
ONBOARDING_ASSISTANT_MESSAGE_KEY,
ONBOARDING_OPENRAG_DOCS_FILTER_ID_KEY,
ONBOARDING_SELECTED_NUDGE_KEY,
} from "@/lib/constants";
import { OnboardingStep } from "./onboarding-step"; import { OnboardingStep } from "./onboarding-step";
import OnboardingUpload from "./onboarding-upload"; import OnboardingUpload from "./onboarding-upload";
@ -36,43 +33,46 @@ export function OnboardingContent({
currentStep: number; currentStep: number;
}) { }) {
const { setConversationFilter, setCurrentConversationId } = useChat(); const { setConversationFilter, setCurrentConversationId } = useChat();
const { data: settings } = useGetSettingsQuery();
const updateOnboardingMutation = useUpdateOnboardingStateMutation();
const parseFailedRef = useRef(false); const parseFailedRef = useRef(false);
const [responseId, setResponseId] = useState<string | null>(null); const [responseId, setResponseId] = useState<string | null>(null);
// Initialize from backend settings
const [selectedNudge, setSelectedNudge] = useState<string>(() => { const [selectedNudge, setSelectedNudge] = useState<string>(() => {
// Retrieve selected nudge from localStorage on mount return settings?.onboarding?.selected_nudge || "";
if (typeof window === "undefined") return "";
return localStorage.getItem(ONBOARDING_SELECTED_NUDGE_KEY) || "";
}); });
const [assistantMessage, setAssistantMessage] = useState<Message | null>( const [assistantMessage, setAssistantMessage] = useState<Message | null>(
() => { () => {
// Retrieve assistant message from localStorage on mount // Get from backend settings
if (typeof window === "undefined") return null; if (settings?.onboarding?.assistant_message) {
const savedMessage = localStorage.getItem( const msg = settings.onboarding.assistant_message;
ONBOARDING_ASSISTANT_MESSAGE_KEY, return {
); role: msg.role as "user" | "assistant",
if (savedMessage) { content: msg.content,
try { timestamp: new Date(msg.timestamp),
const parsed = JSON.parse(savedMessage); };
// Convert timestamp string back to Date object
return {
...parsed,
timestamp: new Date(parsed.timestamp),
};
} catch (error) {
console.error("Failed to parse saved assistant message:", error);
parseFailedRef.current = true;
// Clear corrupted data - will go back a step in useEffect
if (typeof window !== "undefined") {
localStorage.removeItem(ONBOARDING_ASSISTANT_MESSAGE_KEY);
localStorage.removeItem(ONBOARDING_SELECTED_NUDGE_KEY);
}
return null;
}
} }
return null; return null;
}, },
); );
// Sync state when settings change
useEffect(() => {
if (settings?.onboarding?.selected_nudge) {
setSelectedNudge(settings.onboarding.selected_nudge);
}
if (settings?.onboarding?.assistant_message) {
const msg = settings.onboarding.assistant_message;
setAssistantMessage({
role: msg.role as "user" | "assistant",
content: msg.content,
timestamp: new Date(msg.timestamp),
});
}
}, [settings?.onboarding]);
// Handle parse errors by going back a step // Handle parse errors by going back a step
useEffect(() => { useEffect(() => {
if (parseFailedRef.current && currentStep >= 2) { if (parseFailedRef.current && currentStep >= 2) {
@ -83,28 +83,23 @@ export function OnboardingContent({
const { streamingMessage, isLoading, sendMessage } = useChatStreaming({ const { streamingMessage, isLoading, sendMessage } = useChatStreaming({
onComplete: async (message, newResponseId) => { onComplete: async (message, newResponseId) => {
setAssistantMessage(message); setAssistantMessage(message);
// Save assistant message to localStorage when complete // Save assistant message to backend
if (typeof window !== "undefined") { await updateOnboardingMutation.mutateAsync({
try { assistant_message: {
localStorage.setItem( role: message.role,
ONBOARDING_ASSISTANT_MESSAGE_KEY, content: message.content,
JSON.stringify(message), timestamp: message.timestamp.toISOString(),
); },
} catch (error) { });
console.error(
"Failed to save assistant message to localStorage:",
error,
);
}
}
if (newResponseId) { if (newResponseId) {
setResponseId(newResponseId); setResponseId(newResponseId);
// Set the current conversation ID // Set the current conversation ID
setCurrentConversationId(newResponseId); setCurrentConversationId(newResponseId);
// Save the filter association for this conversation // Get filter ID from backend settings
const openragDocsFilterId = localStorage.getItem(ONBOARDING_OPENRAG_DOCS_FILTER_ID_KEY); const openragDocsFilterId = settings?.onboarding?.openrag_docs_filter_id;
if (openragDocsFilterId) { if (openragDocsFilterId) {
try { try {
// Load the filter and set it in the context with explicit responseId // Load the filter and set it in the context with explicit responseId
@ -136,21 +131,17 @@ export function OnboardingContent({
const handleNudgeClick = async (nudge: string) => { const handleNudgeClick = async (nudge: string) => {
setSelectedNudge(nudge); setSelectedNudge(nudge);
// Save selected nudge to localStorage
if (typeof window !== "undefined") {
localStorage.setItem(ONBOARDING_SELECTED_NUDGE_KEY, nudge);
}
setAssistantMessage(null); setAssistantMessage(null);
// Clear saved assistant message when starting a new conversation
if (typeof window !== "undefined") { // Save selected nudge to backend and clear assistant message
localStorage.removeItem(ONBOARDING_ASSISTANT_MESSAGE_KEY); await updateOnboardingMutation.mutateAsync({
} selected_nudge: nudge,
assistant_message: null,
});
setTimeout(async () => { setTimeout(async () => {
// Check if we have the OpenRAG docs filter ID (sample data was ingested) // Check if we have the OpenRAG docs filter ID (sample data was ingested)
const openragDocsFilterId = const openragDocsFilterId = settings?.onboarding?.openrag_docs_filter_id;
typeof window !== "undefined"
? localStorage.getItem(ONBOARDING_OPENRAG_DOCS_FILTER_ID_KEY)
: null;
// Load and set the OpenRAG docs filter if available // Load and set the OpenRAG docs filter if available
let filterToUse = null; let filterToUse = null;

View file

@ -3,14 +3,11 @@ 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";
import { useCreateFilter } from "@/app/api/mutations/useCreateFilter"; import { useCreateFilter } from "@/app/api/mutations/useCreateFilter";
import { useUpdateOnboardingStateMutation } from "@/app/api/mutations/useUpdateOnboardingStateMutation";
import { useGetNudgesQuery } from "@/app/api/queries/useGetNudgesQuery"; import { useGetNudgesQuery } from "@/app/api/queries/useGetNudgesQuery";
import { useGetTasksQuery } from "@/app/api/queries/useGetTasksQuery"; import { useGetTasksQuery } from "@/app/api/queries/useGetTasksQuery";
import { AnimatedProviderSteps } from "@/app/onboarding/_components/animated-provider-steps"; import { AnimatedProviderSteps } from "@/app/onboarding/_components/animated-provider-steps";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
ONBOARDING_UPLOAD_STEPS_KEY,
ONBOARDING_USER_DOC_FILTER_ID_KEY,
} from "@/lib/constants";
import { uploadFile } from "@/lib/upload-utils"; import { uploadFile } from "@/lib/upload-utils";
interface OnboardingUploadProps { interface OnboardingUploadProps {
@ -27,6 +24,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
const [isCreatingFilter, setIsCreatingFilter] = useState(false); const [isCreatingFilter, setIsCreatingFilter] = useState(false);
const createFilterMutation = useCreateFilter(); const createFilterMutation = useCreateFilter();
const updateOnboardingMutation = useUpdateOnboardingStateMutation();
const STEP_LIST = [ const STEP_LIST = [
"Uploading your document", "Uploading your document",
@ -103,12 +101,13 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
description: `Filter for ${filename}`, description: `Filter for ${filename}`,
queryData: queryData, queryData: queryData,
}) })
.then((result) => { .then(async (result) => {
if (result.filter?.id && typeof window !== "undefined") { if (result.filter?.id) {
localStorage.setItem( // Save to backend
ONBOARDING_USER_DOC_FILTER_ID_KEY, await updateOnboardingMutation.mutateAsync({
result.filter.id, user_doc_filter_id: result.filter.id,
); });
console.log( console.log(
"Created knowledge filter for uploaded document", "Created knowledge filter for uploaded document",
result.filter.id, result.filter.id,
@ -267,7 +266,6 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
setCurrentStep={setCurrentStep} setCurrentStep={setCurrentStep}
isCompleted={false} isCompleted={false}
steps={STEP_LIST} steps={STEP_LIST}
storageKey={ONBOARDING_UPLOAD_STEPS_KEY}
/> />
</motion.div> </motion.div>
)} )}

View file

@ -9,6 +9,7 @@ import {
} from "@/app/api/queries/useGetConversationsQuery"; } from "@/app/api/queries/useGetConversationsQuery";
import { getFilterById } from "@/app/api/queries/useGetFilterByIdQuery"; import { getFilterById } from "@/app/api/queries/useGetFilterByIdQuery";
import type { Settings } from "@/app/api/queries/useGetSettingsQuery"; import type { Settings } from "@/app/api/queries/useGetSettingsQuery";
import { useUpdateOnboardingStateMutation } from "@/app/api/mutations/useUpdateOnboardingStateMutation";
import { OnboardingContent } from "@/app/onboarding/_components/onboarding-content"; import { OnboardingContent } from "@/app/onboarding/_components/onboarding-content";
import { ProgressBar } from "@/app/onboarding/_components/progress-bar"; import { ProgressBar } from "@/app/onboarding/_components/progress-bar";
import { AnimatedConditional } from "@/components/animated-conditional"; import { AnimatedConditional } from "@/components/animated-conditional";
@ -19,13 +20,6 @@ import { useChat } from "@/contexts/chat-context";
import { import {
ANIMATION_DURATION, ANIMATION_DURATION,
HEADER_HEIGHT, HEADER_HEIGHT,
ONBOARDING_ASSISTANT_MESSAGE_KEY,
ONBOARDING_CARD_STEPS_KEY,
ONBOARDING_OPENRAG_DOCS_FILTER_ID_KEY,
ONBOARDING_SELECTED_NUDGE_KEY,
ONBOARDING_STEP_KEY,
ONBOARDING_UPLOAD_STEPS_KEY,
ONBOARDING_USER_DOC_FILTER_ID_KEY,
SIDEBAR_WIDTH, SIDEBAR_WIDTH,
TOTAL_ONBOARDING_STEPS, TOTAL_ONBOARDING_STEPS,
} from "@/lib/constants"; } from "@/lib/constants";
@ -50,21 +44,27 @@ export function ChatRenderer({
setOnboardingComplete, setOnboardingComplete,
} = useChat(); } = useChat();
// Initialize onboarding state based on local storage and settings // Initialize onboarding state from backend settings
const [currentStep, setCurrentStep] = useState<number>(() => { const [currentStep, setCurrentStep] = useState<number>(() => {
if (typeof window === "undefined") return 0; return settings?.onboarding?.current_step ?? 0;
const savedStep = localStorage.getItem(ONBOARDING_STEP_KEY);
return savedStep !== null ? parseInt(savedStep, 10) : 0;
}); });
const [showLayout, setShowLayout] = useState<boolean>(() => { const [showLayout, setShowLayout] = useState<boolean>(() => {
if (typeof window === "undefined") return false; // Show layout only if onboarding is complete (current_step >= TOTAL_ONBOARDING_STEPS)
const savedStep = localStorage.getItem(ONBOARDING_STEP_KEY); // This means onboarding will show even if edited=true, as long as it's not complete
// Show layout if settings.edited is true and if no onboarding step is saved const onboardingStep = settings?.onboarding?.current_step ?? 0;
const isEdited = settings?.edited ?? true; return onboardingStep >= TOTAL_ONBOARDING_STEPS;
return isEdited ? savedStep === null : false;
}); });
// Update currentStep and showLayout when settings change
useEffect(() => {
if (settings?.onboarding?.current_step !== undefined) {
setCurrentStep(settings.onboarding.current_step);
// Update showLayout based on whether onboarding is complete
setShowLayout(settings.onboarding.current_step >= TOTAL_ONBOARDING_STEPS);
}
}, [settings?.onboarding?.current_step]);
// Only fetch conversations on chat page // Only fetch conversations on chat page
const isOnChatPage = pathname === "/" || pathname === "/chat"; const isOnChatPage = pathname === "/" || pathname === "/chat";
const { data: conversations = [], isLoading: isConversationsLoading } = const { data: conversations = [], isLoading: isConversationsLoading } =
@ -108,18 +108,18 @@ export function ChatRenderer({
} }
} }
// Try to get the appropriate filter ID // Try to get the appropriate filter ID from settings
let filterId: string | null = null; let filterId: string | null = null;
if (preferUserDoc) { if (preferUserDoc) {
// Completed full onboarding - prefer user document filter // Completed full onboarding - prefer user document filter
filterId = localStorage.getItem(ONBOARDING_USER_DOC_FILTER_ID_KEY); filterId = settings?.onboarding?.user_doc_filter_id || null;
console.log("[FILTER] User doc filter ID:", filterId); console.log("[FILTER] User doc filter ID:", filterId);
} }
// Fall back to OpenRAG docs filter // Fall back to OpenRAG docs filter
if (!filterId) { if (!filterId) {
filterId = localStorage.getItem(ONBOARDING_OPENRAG_DOCS_FILTER_ID_KEY); filterId = settings?.onboarding?.openrag_docs_filter_id || null;
console.log("[FILTER] OpenRAG docs filter ID:", filterId); console.log("[FILTER] OpenRAG docs filter ID:", filterId);
} }
@ -149,25 +149,29 @@ export function ChatRenderer({
[setConversationFilter] [setConversationFilter]
); );
// Save current step to local storage whenever it changes // Note: Current step is now saved to backend via handleStepComplete
useEffect(() => { // No need to save on every change, only on completion
if (typeof window !== "undefined" && !showLayout) {
localStorage.setItem(ONBOARDING_STEP_KEY, currentStep.toString()); const updateOnboardingMutation = useUpdateOnboardingStateMutation();
}
}, [currentStep, showLayout]);
const handleStepComplete = async () => { const handleStepComplete = async () => {
if (currentStep < TOTAL_ONBOARDING_STEPS - 1) { if (currentStep < TOTAL_ONBOARDING_STEPS - 1) {
setCurrentStep(currentStep + 1); const nextStep = currentStep + 1;
setCurrentStep(nextStep);
// Save step to backend
await updateOnboardingMutation.mutateAsync({ current_step: nextStep });
} else { } else {
// Onboarding is complete - remove from local storage and show layout // Onboarding is complete - set step to TOTAL_ONBOARDING_STEPS to indicate completion
if (typeof window !== "undefined") { // and clear intermediate state in backend
localStorage.removeItem(ONBOARDING_STEP_KEY); await updateOnboardingMutation.mutateAsync({
localStorage.removeItem(ONBOARDING_ASSISTANT_MESSAGE_KEY); current_step: TOTAL_ONBOARDING_STEPS,
localStorage.removeItem(ONBOARDING_SELECTED_NUDGE_KEY); assistant_message: null,
localStorage.removeItem(ONBOARDING_CARD_STEPS_KEY); selected_nudge: null,
localStorage.removeItem(ONBOARDING_UPLOAD_STEPS_KEY); card_steps: null,
} upload_steps: null,
openrag_docs_filter_id: null,
user_doc_filter_id: null,
});
// Mark onboarding as complete in context // Mark onboarding as complete in context
setOnboardingComplete(true); setOnboardingComplete(true);
@ -180,36 +184,35 @@ export function ChatRenderer({
// This will pick up the default filter we just set // This will pick up the default filter we just set
await startNewConversation(); await startNewConversation();
// Clean up onboarding filter IDs now that we've set the default
if (typeof window !== "undefined") {
localStorage.removeItem(ONBOARDING_OPENRAG_DOCS_FILTER_ID_KEY);
localStorage.removeItem(ONBOARDING_USER_DOC_FILTER_ID_KEY);
console.log("[FILTER] Cleaned up onboarding filter IDs");
}
setShowLayout(true); setShowLayout(true);
} }
}; };
const handleStepBack = () => { const handleStepBack = async () => {
if (currentStep > 0) { if (currentStep > 0) {
setCurrentStep(currentStep - 1); const prevStep = currentStep - 1;
setCurrentStep(prevStep);
// Save step to backend
await updateOnboardingMutation.mutateAsync({ current_step: prevStep });
} }
}; };
const handleSkipOnboarding = () => { const handleSkipOnboarding = async () => {
// Skip onboarding by marking it as complete // Skip onboarding by marking it as complete in backend
if (typeof window !== "undefined") { await updateOnboardingMutation.mutateAsync({
localStorage.removeItem(ONBOARDING_STEP_KEY); current_step: TOTAL_ONBOARDING_STEPS,
localStorage.removeItem(ONBOARDING_ASSISTANT_MESSAGE_KEY); assistant_message: null,
localStorage.removeItem(ONBOARDING_SELECTED_NUDGE_KEY); selected_nudge: null,
localStorage.removeItem(ONBOARDING_CARD_STEPS_KEY); card_steps: null,
localStorage.removeItem(ONBOARDING_UPLOAD_STEPS_KEY); upload_steps: null,
} openrag_docs_filter_id: null,
user_doc_filter_id: null,
});
// Mark onboarding as complete in context // Mark onboarding as complete in context
setOnboardingComplete(true); setOnboardingComplete(true);
// Store the OpenRAG docs filter as default for new conversations // Store the OpenRAG docs filter as default for new conversations
storeDefaultFilterForNewConversations(false); await storeDefaultFilterForNewConversations(false);
setShowLayout(true); setShowLayout(true);
}; };

View file

@ -10,7 +10,6 @@ import {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import { ONBOARDING_STEP_KEY } from "@/lib/constants";
import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery"; import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery";
export type EndpointType = "chat" | "langflow"; export type EndpointType = "chat" | "langflow";
@ -129,28 +128,15 @@ export function ChatProvider({ children }: ChatProviderProps) {
return false; return false;
}); });
// Sync onboarding completion state with settings.edited and localStorage // Sync onboarding completion state with settings from backend
useEffect(() => { useEffect(() => {
const checkOnboarding = () => { const TOTAL_ONBOARDING_STEPS = 4;
if (typeof window !== "undefined") { // Onboarding is complete if current_step >= 4
// Onboarding is complete if settings.edited is true AND step key is null const isComplete =
const stepKeyExists = localStorage.getItem(ONBOARDING_STEP_KEY) !== null; settings?.onboarding?.current_step !== undefined &&
const isEdited = settings?.edited === true; settings.onboarding.current_step >= TOTAL_ONBOARDING_STEPS;
// Complete if edited is true and step key doesn't exist (onboarding flow finished) setIsOnboardingComplete(isComplete);
setIsOnboardingComplete(isEdited && !stepKeyExists); }, [settings?.onboarding?.current_step]);
}
};
// Check on mount and when settings change
checkOnboarding();
// Listen for storage events (for cross-tab sync)
window.addEventListener("storage", checkOnboarding);
return () => {
window.removeEventListener("storage", checkOnboarding);
};
}, [settings?.edited]);
const setOnboardingComplete = useCallback((complete: boolean) => { const setOnboardingComplete = useCallback((complete: boolean) => {
setIsOnboardingComplete(complete); setIsOnboardingComplete(complete);

View file

@ -17,8 +17,8 @@ import {
type TaskFileEntry, type TaskFileEntry,
useGetTasksQuery, useGetTasksQuery,
} from "@/app/api/queries/useGetTasksQuery"; } from "@/app/api/queries/useGetTasksQuery";
import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery";
import { useAuth } from "@/contexts/auth-context"; import { useAuth } from "@/contexts/auth-context";
import { ONBOARDING_STEP_KEY } from "@/lib/constants";
// Task interface is now imported from useGetTasksQuery // Task interface is now imported from useGetTasksQuery
export type { Task }; export type { Task };
@ -90,11 +90,18 @@ export function TaskProvider({ children }: { children: React.ReactNode }) {
}, },
}); });
// Get settings to check if onboarding is active
const { data: settings } = useGetSettingsQuery();
// Helper function to check if onboarding is active // Helper function to check if onboarding is active
const isOnboardingActive = useCallback(() => { const isOnboardingActive = useCallback(() => {
if (typeof window === "undefined") return false; const TOTAL_ONBOARDING_STEPS = 4;
return localStorage.getItem(ONBOARDING_STEP_KEY) !== null; // Onboarding is active if current_step < 4
}, []); return (
settings?.onboarding?.current_step !== undefined &&
settings.onboarding.current_step < TOTAL_ONBOARDING_STEPS
);
}, [settings?.onboarding?.current_step]);
const refetchSearch = useCallback(() => { const refetchSearch = useCallback(() => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({

View file

@ -37,17 +37,6 @@ export const SIDEBAR_WIDTH = 280;
export const HEADER_HEIGHT = 54; export const HEADER_HEIGHT = 54;
export const TOTAL_ONBOARDING_STEPS = 4; export const TOTAL_ONBOARDING_STEPS = 4;
/**
* Local Storage Keys
*/
export const ONBOARDING_STEP_KEY = "onboarding_current_step";
export const ONBOARDING_ASSISTANT_MESSAGE_KEY = "onboarding_assistant_message";
export const ONBOARDING_SELECTED_NUDGE_KEY = "onboarding_selected_nudge";
export const ONBOARDING_CARD_STEPS_KEY = "onboarding_card_steps";
export const ONBOARDING_UPLOAD_STEPS_KEY = "onboarding_upload_steps";
export const ONBOARDING_OPENRAG_DOCS_FILTER_ID_KEY = "onboarding_openrag_docs_filter_id";
export const ONBOARDING_USER_DOC_FILTER_ID_KEY = "onboarding_user_doc_filter_id";
export const FILES_REGEX = export const FILES_REGEX =
/(?<=I'm uploading a document called ['"])[^'"]+\.[^.]+(?=['"]\. Here is its content:)/; /(?<=I'm uploading a document called ['"])[^'"]+\.[^.]+(?=['"]\. Here is its content:)/;