Compare commits
7 commits
main
...
fix/onboar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd245f97a0 | ||
|
|
d839e235e5 | ||
|
|
527bc7f67e | ||
|
|
633afde224 | ||
|
|
98ff3e2f8b | ||
|
|
345a3861b0 | ||
|
|
f7f1553f1d |
15 changed files with 341 additions and 215 deletions
|
|
@ -3,7 +3,7 @@ import {
|
||||||
useMutation,
|
useMutation,
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import { ONBOARDING_OPENRAG_DOCS_FILTER_ID_KEY } from "@/lib/constants";
|
import { useUpdateOnboardingStateMutation } from "./useUpdateOnboardingStateMutation";
|
||||||
|
|
||||||
export interface OnboardingVariables {
|
export interface OnboardingVariables {
|
||||||
// Provider selection
|
// Provider selection
|
||||||
|
|
@ -36,12 +36,14 @@ export const useOnboardingMutation = (
|
||||||
options?: Omit<
|
options?: Omit<
|
||||||
UseMutationOptions<OnboardingResponse, Error, OnboardingVariables>,
|
UseMutationOptions<OnboardingResponse, Error, OnboardingVariables>,
|
||||||
"mutationFn"
|
"mutationFn"
|
||||||
>,
|
>
|
||||||
) => {
|
) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const updateOnboardingMutation = useUpdateOnboardingStateMutation();
|
||||||
|
|
||||||
async function submitOnboarding(
|
async function submitOnboarding(
|
||||||
variables: OnboardingVariables,
|
variables: OnboardingVariables
|
||||||
): Promise<OnboardingResponse> {
|
): Promise<OnboardingResponse> {
|
||||||
const response = await fetch("/api/onboarding", {
|
const response = await fetch("/api/onboarding", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -62,10 +64,15 @@ export const useOnboardingMutation = (
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: submitOnboarding,
|
mutationFn: submitOnboarding,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
// Store OpenRAG Docs filter ID if returned
|
// 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_KEY,
|
updateOnboardingMutation.mutateAsync({
|
||||||
|
openrag_docs_filter_id: data.openrag_docs_filter_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Saved OpenRAG docs filter ID:",
|
||||||
data.openrag_docs_filter_id
|
data.openrag_docs_filter_id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
interface UpdateOnboardingStateVariables {
|
||||||
|
current_step?: number;
|
||||||
|
assistant_message?: {
|
||||||
|
role: string;
|
||||||
|
content: string;
|
||||||
|
timestamp: string;
|
||||||
|
} | null;
|
||||||
|
selected_nudge?: string | null;
|
||||||
|
card_steps?: Record<string, unknown> | null;
|
||||||
|
upload_steps?: Record<string, unknown> | null;
|
||||||
|
openrag_docs_filter_id?: string | null;
|
||||||
|
user_doc_filter_id?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateOnboardingStateMutation = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (variables: UpdateOnboardingStateVariables) => {
|
||||||
|
const response = await fetch("/api/onboarding/state", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(variables),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || "Failed to update onboarding state");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
// Invalidate settings query to refetch updated onboarding state
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["settings"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Made with Bob
|
||||||
|
|
@ -41,12 +41,27 @@ export interface ProviderSettings {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OnboardingState {
|
||||||
|
current_step?: number;
|
||||||
|
assistant_message?: {
|
||||||
|
role: string;
|
||||||
|
content: string;
|
||||||
|
timestamp: string;
|
||||||
|
} | null;
|
||||||
|
selected_nudge?: string | null;
|
||||||
|
card_steps?: Record<string, unknown> | null;
|
||||||
|
upload_steps?: Record<string, unknown> | null;
|
||||||
|
openrag_docs_filter_id?: string | null;
|
||||||
|
user_doc_filter_id?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
langflow_url?: string;
|
langflow_url?: string;
|
||||||
flow_id?: string;
|
flow_id?: string;
|
||||||
ingest_flow_id?: string;
|
ingest_flow_id?: string;
|
||||||
langflow_public_url?: string;
|
langflow_public_url?: string;
|
||||||
edited?: boolean;
|
edited?: boolean;
|
||||||
|
onboarding?: OnboardingState;
|
||||||
providers?: ProviderSettings;
|
providers?: ProviderSettings;
|
||||||
knowledge?: KnowledgeSettings;
|
knowledge?: KnowledgeSettings;
|
||||||
agent?: AgentSettings;
|
agent?: AgentSettings;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
// Check if onboarding is complete (current_step >= 4 means complete)
|
||||||
});
|
const TOTAL_ONBOARDING_STEPS = 4;
|
||||||
|
const isOnboardingComplete =
|
||||||
// Listen for storage changes to detect when onboarding completes
|
settings?.onboarding?.current_step !== undefined &&
|
||||||
useEffect(() => {
|
settings.onboarding.current_step >= TOTAL_ONBOARDING_STEPS;
|
||||||
const checkOnboarding = () => {
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
setIsOnboardingComplete(
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
@ -306,15 +306,6 @@ const OnboardingCard = ({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
console.log("Onboarding completed successfully", data);
|
console.log("Onboarding completed successfully", data);
|
||||||
|
|
||||||
// Save OpenRAG docs filter ID if sample data was ingested
|
|
||||||
if (data.openrag_docs_filter_id && typeof window !== "undefined") {
|
|
||||||
localStorage.setItem(
|
|
||||||
"onboarding_openrag_docs_filter_id",
|
|
||||||
data.openrag_docs_filter_id
|
|
||||||
);
|
|
||||||
console.log("Saved OpenRAG docs filter ID:", data.openrag_docs_filter_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update provider health cache to healthy since backend just validated
|
// Update provider health cache to healthy since backend just validated
|
||||||
const provider =
|
const provider =
|
||||||
(isEmbedding ? settings.embedding_provider : settings.llm_provider) ||
|
(isEmbedding ? settings.embedding_provider : settings.llm_provider) ||
|
||||||
|
|
@ -674,7 +665,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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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:)/;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,16 @@ async def get_settings(request, session_manager):
|
||||||
"ingest_flow_id": LANGFLOW_INGEST_FLOW_ID,
|
"ingest_flow_id": LANGFLOW_INGEST_FLOW_ID,
|
||||||
"langflow_public_url": LANGFLOW_PUBLIC_URL,
|
"langflow_public_url": LANGFLOW_PUBLIC_URL,
|
||||||
"edited": openrag_config.edited,
|
"edited": openrag_config.edited,
|
||||||
|
# Onboarding state
|
||||||
|
"onboarding": {
|
||||||
|
"current_step": openrag_config.onboarding.current_step,
|
||||||
|
"assistant_message": openrag_config.onboarding.assistant_message,
|
||||||
|
"selected_nudge": openrag_config.onboarding.selected_nudge,
|
||||||
|
"card_steps": openrag_config.onboarding.card_steps,
|
||||||
|
"upload_steps": openrag_config.onboarding.upload_steps,
|
||||||
|
"openrag_docs_filter_id": openrag_config.onboarding.openrag_docs_filter_id,
|
||||||
|
"user_doc_filter_id": openrag_config.onboarding.user_doc_filter_id,
|
||||||
|
},
|
||||||
# OpenRAG configuration
|
# OpenRAG configuration
|
||||||
"providers": {
|
"providers": {
|
||||||
"openai": {
|
"openai": {
|
||||||
|
|
@ -1353,6 +1363,65 @@ async def _update_langflow_chunk_settings(config, flows_service):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
async def update_onboarding_state(request):
|
||||||
|
"""Update onboarding state in configuration"""
|
||||||
|
try:
|
||||||
|
await TelemetryClient.send_event(Category.ONBOARDING, MessageId.ORB_ONBOARD_START)
|
||||||
|
|
||||||
|
# Parse request body
|
||||||
|
body = await request.json()
|
||||||
|
|
||||||
|
# Validate allowed fields
|
||||||
|
allowed_fields = {
|
||||||
|
"current_step",
|
||||||
|
"assistant_message",
|
||||||
|
"selected_nudge",
|
||||||
|
"card_steps",
|
||||||
|
"upload_steps",
|
||||||
|
"openrag_docs_filter_id",
|
||||||
|
"user_doc_filter_id",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for invalid fields
|
||||||
|
invalid_fields = set(body.keys()) - allowed_fields
|
||||||
|
if invalid_fields:
|
||||||
|
return JSONResponse(
|
||||||
|
{
|
||||||
|
"error": f"Invalid fields: {', '.join(invalid_fields)}. Allowed fields: {', '.join(allowed_fields)}"
|
||||||
|
},
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update onboarding state using config manager
|
||||||
|
success = config_manager.update_onboarding_state(**body)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
return JSONResponse(
|
||||||
|
{"error": "Failed to update onboarding state"},
|
||||||
|
status_code=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Onboarding state updated: {body}")
|
||||||
|
|
||||||
|
return JSONResponse(
|
||||||
|
{
|
||||||
|
"message": "Onboarding state updated successfully",
|
||||||
|
"updated_fields": list(body.keys()),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return JSONResponse(
|
||||||
|
{"error": "Invalid JSON in request body"}, status_code=400
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating onboarding state: {str(e)}")
|
||||||
|
return JSONResponse(
|
||||||
|
{"error": f"Failed to update onboarding state: {str(e)}"},
|
||||||
|
status_code=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def reapply_all_settings(session_manager = None):
|
async def reapply_all_settings(session_manager = None):
|
||||||
"""
|
"""
|
||||||
Reapply all current configuration settings to Langflow flows and global variables.
|
Reapply all current configuration settings to Langflow flows and global variables.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import os
|
||||||
import yaml
|
import yaml
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
from dataclasses import dataclass, asdict
|
from dataclasses import dataclass, asdict, field
|
||||||
from utils.logging_config import get_logger
|
from utils.logging_config import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
@ -85,6 +85,19 @@ class AgentConfig:
|
||||||
system_prompt: str = "You are the OpenRAG Agent. You answer questions using retrieval, reasoning, and tool use.\nYou have access to several tools. Your job is to determine **which tool to use and when**.\n### Available Tools\n- OpenSearch Retrieval Tool:\n Use this to search the indexed knowledge base. Use when the user asks about product details, internal concepts, processes, architecture, documentation, roadmaps, or anything that may be stored in the index.\n- Conversation History:\n Use this to maintain continuity when the user is referring to previous turns. \n Do not treat history as a factual source.\n- Conversation File Context:\n Use this when the user asks about a document they uploaded or refers directly to its contents.\n- URL Ingestion Tool:\n Use this **only** when the user explicitly asks you to read, summarize, or analyze the content of a URL.\n Do not ingest URLs automatically.\n- Calculator / Expression Evaluation Tool:\n Use this when the user asks to compare numbers, compute estimates, calculate totals, analyze pricing, or answer any question requiring mathematics or quantitative reasoning.\n If the answer requires arithmetic, call the calculator tool rather than calculating internally.\n### Retrieval Decision Rules\nUse OpenSearch **whenever**:\n1. The question may be answered from internal or indexed data.\n2. The user references team names, product names, release plans, configurations, requirements, or official information.\n3. The user needs a factual, grounded answer.\nDo **not** use retrieval if:\n- The question is purely creative (e.g., storytelling, analogies) or personal preference.\n- The user simply wants text reformatted or rewritten from what is already present in the conversation.\nWhen uncertain → **Retrieve.** Retrieval is low risk and improves grounding.\n### URL Ingestion Rules\nOnly ingest URLs when the user explicitly says:\n- \"Read this link\"\n- \"Summarize this webpage\"\n- \"What does this site say?\"\n- \"Ingest this URL\"\nIf unclear → ask a clarifying question.\n### Calculator Usage Rules\nUse the calculator when:\n- Performing arithmetic\n- Estimating totals\n- Comparing values\n- Modeling cost, time, effort, scale, or projections\nDo not perform math internally. **Call the calculator tool instead.**\n### Answer Construction Rules\n1. When asked: \"What is OpenRAG\", answer the following:\n\"OpenRAG is an open-source package for building agentic RAG systems. It supports integration with a wide range of orchestration tools, vector databases, and LLM providers. OpenRAG connects and amplifies three popular, proven open-source projects into one powerful platform:\n**Langflow** – Langflow is a powerful tool to build and deploy AI agents and MCP servers [Read more](https://www.langflow.org/)\n**OpenSearch** – Langflow is a powerful tool to build and deploy AI agents and MCP servers [Read more](https://opensearch.org/)\n**Docling** – Langflow is a powerful tool to build and deploy AI agents and MCP servers [Read more](https://www.docling.ai/)\"\n2. Synthesize retrieved or ingested content in your own words.\n3. Support factual claims with citations in the format:\n (Source: <document_name_or_id>)\n4. If no supporting evidence is found:\n Say: \"No relevant supporting sources were found for that request.\"\n5. Never invent facts or hallucinate details.\n6. Be concise, direct, and confident. \n7. Do not reveal internal chain-of-thought."
|
system_prompt: str = "You are the OpenRAG Agent. You answer questions using retrieval, reasoning, and tool use.\nYou have access to several tools. Your job is to determine **which tool to use and when**.\n### Available Tools\n- OpenSearch Retrieval Tool:\n Use this to search the indexed knowledge base. Use when the user asks about product details, internal concepts, processes, architecture, documentation, roadmaps, or anything that may be stored in the index.\n- Conversation History:\n Use this to maintain continuity when the user is referring to previous turns. \n Do not treat history as a factual source.\n- Conversation File Context:\n Use this when the user asks about a document they uploaded or refers directly to its contents.\n- URL Ingestion Tool:\n Use this **only** when the user explicitly asks you to read, summarize, or analyze the content of a URL.\n Do not ingest URLs automatically.\n- Calculator / Expression Evaluation Tool:\n Use this when the user asks to compare numbers, compute estimates, calculate totals, analyze pricing, or answer any question requiring mathematics or quantitative reasoning.\n If the answer requires arithmetic, call the calculator tool rather than calculating internally.\n### Retrieval Decision Rules\nUse OpenSearch **whenever**:\n1. The question may be answered from internal or indexed data.\n2. The user references team names, product names, release plans, configurations, requirements, or official information.\n3. The user needs a factual, grounded answer.\nDo **not** use retrieval if:\n- The question is purely creative (e.g., storytelling, analogies) or personal preference.\n- The user simply wants text reformatted or rewritten from what is already present in the conversation.\nWhen uncertain → **Retrieve.** Retrieval is low risk and improves grounding.\n### URL Ingestion Rules\nOnly ingest URLs when the user explicitly says:\n- \"Read this link\"\n- \"Summarize this webpage\"\n- \"What does this site say?\"\n- \"Ingest this URL\"\nIf unclear → ask a clarifying question.\n### Calculator Usage Rules\nUse the calculator when:\n- Performing arithmetic\n- Estimating totals\n- Comparing values\n- Modeling cost, time, effort, scale, or projections\nDo not perform math internally. **Call the calculator tool instead.**\n### Answer Construction Rules\n1. When asked: \"What is OpenRAG\", answer the following:\n\"OpenRAG is an open-source package for building agentic RAG systems. It supports integration with a wide range of orchestration tools, vector databases, and LLM providers. OpenRAG connects and amplifies three popular, proven open-source projects into one powerful platform:\n**Langflow** – Langflow is a powerful tool to build and deploy AI agents and MCP servers [Read more](https://www.langflow.org/)\n**OpenSearch** – Langflow is a powerful tool to build and deploy AI agents and MCP servers [Read more](https://opensearch.org/)\n**Docling** – Langflow is a powerful tool to build and deploy AI agents and MCP servers [Read more](https://www.docling.ai/)\"\n2. Synthesize retrieved or ingested content in your own words.\n3. Support factual claims with citations in the format:\n (Source: <document_name_or_id>)\n4. If no supporting evidence is found:\n Say: \"No relevant supporting sources were found for that request.\"\n5. Never invent facts or hallucinate details.\n6. Be concise, direct, and confident. \n7. Do not reveal internal chain-of-thought."
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class OnboardingState:
|
||||||
|
"""Onboarding state configuration."""
|
||||||
|
|
||||||
|
current_step: int = 0
|
||||||
|
assistant_message: Optional[Dict[str, Any]] = field(default=None)
|
||||||
|
selected_nudge: Optional[str] = field(default=None)
|
||||||
|
card_steps: Optional[Dict[str, Any]] = field(default=None)
|
||||||
|
upload_steps: Optional[Dict[str, Any]] = field(default=None)
|
||||||
|
openrag_docs_filter_id: Optional[str] = field(default=None)
|
||||||
|
user_doc_filter_id: Optional[str] = field(default=None)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OpenRAGConfig:
|
class OpenRAGConfig:
|
||||||
"""Complete OpenRAG configuration."""
|
"""Complete OpenRAG configuration."""
|
||||||
|
|
@ -92,6 +105,7 @@ class OpenRAGConfig:
|
||||||
providers: ProvidersConfig
|
providers: ProvidersConfig
|
||||||
knowledge: KnowledgeConfig
|
knowledge: KnowledgeConfig
|
||||||
agent: AgentConfig
|
agent: AgentConfig
|
||||||
|
onboarding: OnboardingState
|
||||||
edited: bool = False # Track if manually edited
|
edited: bool = False # Track if manually edited
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -107,6 +121,7 @@ class OpenRAGConfig:
|
||||||
),
|
),
|
||||||
knowledge=KnowledgeConfig(**data.get("knowledge", {})),
|
knowledge=KnowledgeConfig(**data.get("knowledge", {})),
|
||||||
agent=AgentConfig(**data.get("agent", {})),
|
agent=AgentConfig(**data.get("agent", {})),
|
||||||
|
onboarding=OnboardingState(**data.get("onboarding", {})),
|
||||||
edited=data.get("edited", False),
|
edited=data.get("edited", False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -156,6 +171,7 @@ class ConfigManager:
|
||||||
},
|
},
|
||||||
"knowledge": {},
|
"knowledge": {},
|
||||||
"agent": {},
|
"agent": {},
|
||||||
|
"onboarding": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Load from config file if it exists
|
# Load from config file if it exists
|
||||||
|
|
@ -172,7 +188,7 @@ class ConfigManager:
|
||||||
file_config["providers"][provider]
|
file_config["providers"][provider]
|
||||||
)
|
)
|
||||||
|
|
||||||
for section in ["knowledge", "agent"]:
|
for section in ["knowledge", "agent", "onboarding"]:
|
||||||
if section in file_config:
|
if section in file_config:
|
||||||
config_data[section].update(file_config[section])
|
config_data[section].update(file_config[section])
|
||||||
|
|
||||||
|
|
@ -294,6 +310,31 @@ class ConfigManager:
|
||||||
logger.error(f"Failed to save configuration to {self.config_file}: {e}")
|
logger.error(f"Failed to save configuration to {self.config_file}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def update_onboarding_state(self, **kwargs) -> bool:
|
||||||
|
"""Update onboarding state fields.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs: Onboarding state fields to update (current_step, assistant_message, etc.)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if updated successfully, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
config = self.get_config()
|
||||||
|
|
||||||
|
# Update only the provided fields
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if hasattr(config.onboarding, key):
|
||||||
|
setattr(config.onboarding, key, value)
|
||||||
|
else:
|
||||||
|
logger.warning(f"Unknown onboarding field: {key}")
|
||||||
|
|
||||||
|
# Save the updated config
|
||||||
|
return self.save_config_file(config)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to update onboarding state: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# Global config manager instance
|
# Global config manager instance
|
||||||
config_manager = ConfigManager()
|
config_manager = ConfigManager()
|
||||||
|
|
|
||||||
|
|
@ -1140,6 +1140,13 @@ async def create_app():
|
||||||
),
|
),
|
||||||
methods=["POST"],
|
methods=["POST"],
|
||||||
),
|
),
|
||||||
|
Route(
|
||||||
|
"/onboarding/state",
|
||||||
|
require_auth(services["session_manager"])(
|
||||||
|
settings.update_onboarding_state
|
||||||
|
),
|
||||||
|
methods=["POST"],
|
||||||
|
),
|
||||||
# Provider health check endpoint
|
# Provider health check endpoint
|
||||||
Route(
|
Route(
|
||||||
"/provider/health",
|
"/provider/health",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue