mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-03-21 07:32:41 +00:00
Compare commits
6 Commits
v3.0.3
...
refactor/o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ca8050540 | ||
|
|
85186fc58a | ||
|
|
7e37c48fb7 | ||
|
|
7122e5a9a7 | ||
|
|
5ad3dd370f | ||
|
|
66c464c29a |
@@ -23,7 +23,7 @@ import { LLM_PROVIDERS_ADMIN_URL } from "@/lib/llmConfig/constants";
|
||||
import {
|
||||
buildInitialValues,
|
||||
testApiKeyHelper,
|
||||
} from "@/refresh-components/onboarding/components/llmConnectionHelpers";
|
||||
} from "@/sections/onboarding/components/llmConnectionHelpers";
|
||||
import OnboardingInfoPages from "@/app/craft/onboarding/components/OnboardingInfoPages";
|
||||
import OnboardingUserInfo from "@/app/craft/onboarding/components/OnboardingUserInfo";
|
||||
import OnboardingLlmSetup, {
|
||||
|
||||
@@ -2,39 +2,36 @@ import React from "react";
|
||||
import { renderHook, act } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom";
|
||||
import { useShowOnboarding } from "@/hooks/useShowOnboarding";
|
||||
import { OnboardingStep } from "../types";
|
||||
import { OnboardingStep } from "@/interfaces/onboarding";
|
||||
|
||||
// Mock useOnboardingState to isolate useShowOnboarding logic
|
||||
const mockActions = {
|
||||
nextStep: jest.fn(),
|
||||
prevStep: jest.fn(),
|
||||
goToStep: jest.fn(),
|
||||
setButtonActive: jest.fn(),
|
||||
updateName: jest.fn(),
|
||||
updateData: jest.fn(),
|
||||
setLoading: jest.fn(),
|
||||
setError: jest.fn(),
|
||||
reset: jest.fn(),
|
||||
};
|
||||
|
||||
let mockStepIndex = 0;
|
||||
|
||||
jest.mock("@/refresh-components/onboarding/useOnboardingState", () => ({
|
||||
useOnboardingState: () => ({
|
||||
state: {
|
||||
currentStep: OnboardingStep.Welcome,
|
||||
stepIndex: mockStepIndex,
|
||||
totalSteps: 3,
|
||||
data: {},
|
||||
isButtonActive: true,
|
||||
isLoading: false,
|
||||
},
|
||||
llmDescriptors: [],
|
||||
actions: mockActions,
|
||||
isLoading: false,
|
||||
// Mock the underlying dependencies that useOnboardingState relies on
|
||||
jest.mock("@/providers/UserProvider", () => ({
|
||||
useUser: () => ({
|
||||
user: null,
|
||||
refreshUser: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("@/components/chat/ProviderContext", () => ({
|
||||
useProviderStatus: () => ({
|
||||
llmProviders: [],
|
||||
isLoadingProviders: false,
|
||||
hasProviders: false,
|
||||
providerOptions: [],
|
||||
refreshProviderInfo: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("@/hooks/useLLMProviders", () => ({
|
||||
useLLMProviders: () => ({
|
||||
refetch: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("@/lib/userSettings", () => ({
|
||||
updateUserPersonalization: jest.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
function renderUseShowOnboarding(
|
||||
overrides: {
|
||||
isLoadingProviders?: boolean;
|
||||
@@ -63,7 +60,6 @@ describe("useShowOnboarding", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
localStorage.clear();
|
||||
mockStepIndex = 0;
|
||||
});
|
||||
|
||||
it("returns showOnboarding=false while providers are loading", () => {
|
||||
@@ -133,31 +129,6 @@ describe("useShowOnboarding", () => {
|
||||
expect(result.current.showOnboarding).toBe(false);
|
||||
});
|
||||
|
||||
it("does not self-correct when user has advanced past Welcome step", () => {
|
||||
const { result, rerender } = renderUseShowOnboarding({
|
||||
hasAnyProvider: false,
|
||||
chatSessionsCount: 0,
|
||||
userId: "user-1",
|
||||
});
|
||||
expect(result.current.showOnboarding).toBe(true);
|
||||
|
||||
// Simulate user advancing past Welcome (e.g. they configured an LLM provider)
|
||||
mockStepIndex = 1;
|
||||
|
||||
// Re-render with same userId but provider data now available
|
||||
rerender({
|
||||
liveAgent: undefined,
|
||||
isLoadingProviders: false,
|
||||
hasAnyProvider: true,
|
||||
isLoadingChatSessions: false,
|
||||
chatSessionsCount: 0,
|
||||
userId: "user-1",
|
||||
});
|
||||
|
||||
// Should stay true — user is actively using onboarding
|
||||
expect(result.current.showOnboarding).toBe(true);
|
||||
});
|
||||
|
||||
it("re-evaluates when userId changes", () => {
|
||||
const { result, rerender } = renderUseShowOnboarding({
|
||||
hasAnyProvider: false,
|
||||
@@ -207,7 +178,7 @@ describe("useShowOnboarding", () => {
|
||||
expect(result.current.showOnboarding).toBe(false);
|
||||
});
|
||||
|
||||
it("returns onboardingState and actions from useOnboardingState", () => {
|
||||
it("returns onboardingState and actions", () => {
|
||||
const { result } = renderUseShowOnboarding();
|
||||
expect(result.current.onboardingState.currentStep).toBe(
|
||||
OnboardingStep.Welcome
|
||||
@@ -1,13 +1,250 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useReducer, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { onboardingReducer, initialState } from "@/sections/onboarding/reducer";
|
||||
import {
|
||||
OnboardingActions,
|
||||
OnboardingActionType,
|
||||
OnboardingData,
|
||||
OnboardingState,
|
||||
OnboardingStep,
|
||||
} from "@/interfaces/onboarding";
|
||||
import { WellKnownLLMProviderDescriptor } from "@/interfaces/llm";
|
||||
import { updateUserPersonalization } from "@/lib/userSettings";
|
||||
import { useUser } from "@/providers/UserProvider";
|
||||
import { MinimalPersonaSnapshot } from "@/app/admin/agents/interfaces";
|
||||
import { useOnboardingState } from "@/refresh-components/onboarding/useOnboardingState";
|
||||
import { useLLMProviders } from "@/hooks/useLLMProviders";
|
||||
import { useProviderStatus } from "@/components/chat/ProviderContext";
|
||||
|
||||
function getOnboardingCompletedKey(userId: string): string {
|
||||
return `onyx:onboardingCompleted:${userId}`;
|
||||
}
|
||||
|
||||
function useOnboardingState(liveAgent?: MinimalPersonaSnapshot): {
|
||||
state: OnboardingState;
|
||||
llmDescriptors: WellKnownLLMProviderDescriptor[];
|
||||
actions: OnboardingActions;
|
||||
isLoading: boolean;
|
||||
} {
|
||||
const [state, dispatch] = useReducer(onboardingReducer, initialState);
|
||||
const { user, refreshUser } = useUser();
|
||||
|
||||
// Get provider data from ProviderContext instead of duplicating the call
|
||||
const {
|
||||
llmProviders,
|
||||
isLoadingProviders,
|
||||
hasProviders: hasLlmProviders,
|
||||
providerOptions,
|
||||
refreshProviderInfo,
|
||||
} = useProviderStatus();
|
||||
|
||||
// Only fetch persona-specific providers (different endpoint)
|
||||
const { refetch: refreshPersonaProviders } = useLLMProviders(liveAgent?.id);
|
||||
|
||||
const userName = user?.personalization?.name;
|
||||
const llmDescriptors = providerOptions;
|
||||
|
||||
const nameUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
|
||||
null
|
||||
);
|
||||
|
||||
// Navigate to the earliest incomplete step in the onboarding flow.
|
||||
// Step order: Welcome -> Name -> LlmSetup -> Complete
|
||||
// We check steps in order and stop at the first incomplete one.
|
||||
useEffect(() => {
|
||||
// Don't run logic until data has loaded
|
||||
if (isLoadingProviders) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pre-populate state with existing data
|
||||
if (userName) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.UPDATE_DATA,
|
||||
payload: { userName },
|
||||
});
|
||||
}
|
||||
if (hasLlmProviders) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.UPDATE_DATA,
|
||||
payload: { llmProviders: (llmProviders ?? []).map((p) => p.provider) },
|
||||
});
|
||||
}
|
||||
|
||||
// Determine the earliest incomplete step
|
||||
// Name step is incomplete if userName is not set
|
||||
if (!userName) {
|
||||
// Stay at Welcome/Name step (no dispatch needed, this is the initial state)
|
||||
return;
|
||||
}
|
||||
|
||||
// LlmSetup step is incomplete if no LLM providers are configured
|
||||
if (!hasLlmProviders) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
dispatch({
|
||||
type: OnboardingActionType.GO_TO_STEP,
|
||||
step: OnboardingStep.LlmSetup,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// All steps complete - go to Complete step
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: true,
|
||||
});
|
||||
dispatch({
|
||||
type: OnboardingActionType.GO_TO_STEP,
|
||||
step: OnboardingStep.Complete,
|
||||
});
|
||||
}, [llmProviders, isLoadingProviders]);
|
||||
|
||||
const nextStep = useCallback(() => {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
|
||||
if (state.currentStep === OnboardingStep.Name) {
|
||||
const hasProviders = (state.data.llmProviders?.length ?? 0) > 0;
|
||||
if (hasProviders) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: true,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (state.currentStep === OnboardingStep.LlmSetup) {
|
||||
refreshProviderInfo();
|
||||
if (liveAgent) {
|
||||
refreshPersonaProviders();
|
||||
}
|
||||
}
|
||||
dispatch({ type: OnboardingActionType.NEXT_STEP });
|
||||
}, [state, refreshProviderInfo, llmProviders, refreshPersonaProviders]);
|
||||
|
||||
const prevStep = useCallback(() => {
|
||||
dispatch({ type: OnboardingActionType.PREV_STEP });
|
||||
}, []);
|
||||
|
||||
const goToStep = useCallback(
|
||||
(step: OnboardingStep) => {
|
||||
const hasProviders = state.data.llmProviders?.length || 0 > 0;
|
||||
if (step === OnboardingStep.LlmSetup && hasProviders) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: true,
|
||||
});
|
||||
} else if (step === OnboardingStep.LlmSetup) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
}
|
||||
dispatch({ type: OnboardingActionType.GO_TO_STEP, step });
|
||||
},
|
||||
[llmProviders]
|
||||
);
|
||||
|
||||
const updateName = useCallback(
|
||||
(name: string) => {
|
||||
dispatch({
|
||||
type: OnboardingActionType.UPDATE_DATA,
|
||||
payload: { userName: name },
|
||||
});
|
||||
|
||||
if (nameUpdateTimeoutRef.current) {
|
||||
clearTimeout(nameUpdateTimeoutRef.current);
|
||||
}
|
||||
|
||||
if (name === "") {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: true,
|
||||
});
|
||||
}
|
||||
|
||||
nameUpdateTimeoutRef.current = setTimeout(async () => {
|
||||
try {
|
||||
await updateUserPersonalization({ name });
|
||||
await refreshUser();
|
||||
} catch (_e) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
console.error("Error updating user name:", _e);
|
||||
} finally {
|
||||
nameUpdateTimeoutRef.current = null;
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
[refreshUser]
|
||||
);
|
||||
|
||||
const updateData = useCallback((data: Partial<OnboardingData>) => {
|
||||
dispatch({ type: OnboardingActionType.UPDATE_DATA, payload: data });
|
||||
}, []);
|
||||
|
||||
const setLoading = useCallback((isLoading: boolean) => {
|
||||
dispatch({ type: OnboardingActionType.SET_LOADING, isLoading });
|
||||
}, []);
|
||||
|
||||
const setButtonActive = useCallback((active: boolean) => {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: active,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setError = useCallback((error: string | undefined) => {
|
||||
dispatch({ type: OnboardingActionType.SET_ERROR, error });
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
dispatch({ type: OnboardingActionType.RESET });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (nameUpdateTimeoutRef.current) {
|
||||
clearTimeout(nameUpdateTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
state,
|
||||
llmDescriptors,
|
||||
actions: {
|
||||
nextStep,
|
||||
prevStep,
|
||||
goToStep,
|
||||
setButtonActive,
|
||||
updateName,
|
||||
updateData,
|
||||
setLoading,
|
||||
setError,
|
||||
reset,
|
||||
},
|
||||
isLoading: isLoadingProviders || !!liveAgent,
|
||||
};
|
||||
}
|
||||
|
||||
interface UseShowOnboardingParams {
|
||||
liveAgent: MinimalPersonaSnapshot | undefined;
|
||||
isLoadingProviders: boolean;
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
import { useReducer, useCallback, useEffect, useRef } from "react";
|
||||
import { onboardingReducer, initialState } from "./reducer";
|
||||
import {
|
||||
OnboardingActions,
|
||||
OnboardingActionType,
|
||||
OnboardingData,
|
||||
OnboardingState,
|
||||
OnboardingStep,
|
||||
} from "./types";
|
||||
import { WellKnownLLMProviderDescriptor } from "@/interfaces/llm";
|
||||
import { updateUserPersonalization } from "@/lib/userSettings";
|
||||
import { useUser } from "@/providers/UserProvider";
|
||||
import { MinimalPersonaSnapshot } from "@/app/admin/agents/interfaces";
|
||||
import { useLLMProviders } from "@/hooks/useLLMProviders";
|
||||
import { useProviderStatus } from "@/components/chat/ProviderContext";
|
||||
|
||||
export function useOnboardingState(liveAgent?: MinimalPersonaSnapshot): {
|
||||
state: OnboardingState;
|
||||
llmDescriptors: WellKnownLLMProviderDescriptor[];
|
||||
actions: OnboardingActions;
|
||||
isLoading: boolean;
|
||||
} {
|
||||
const [state, dispatch] = useReducer(onboardingReducer, initialState);
|
||||
const { user, refreshUser } = useUser();
|
||||
|
||||
// Get provider data from ProviderContext instead of duplicating the call
|
||||
const {
|
||||
llmProviders,
|
||||
isLoadingProviders,
|
||||
hasProviders: hasLlmProviders,
|
||||
providerOptions,
|
||||
refreshProviderInfo,
|
||||
} = useProviderStatus();
|
||||
|
||||
// Only fetch persona-specific providers (different endpoint)
|
||||
const { refetch: refreshPersonaProviders } = useLLMProviders(liveAgent?.id);
|
||||
|
||||
const userName = user?.personalization?.name;
|
||||
const llmDescriptors = providerOptions;
|
||||
|
||||
const nameUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
|
||||
null
|
||||
);
|
||||
|
||||
// Navigate to the earliest incomplete step in the onboarding flow.
|
||||
// Step order: Welcome -> Name -> LlmSetup -> Complete
|
||||
// We check steps in order and stop at the first incomplete one.
|
||||
useEffect(() => {
|
||||
// Don't run logic until data has loaded
|
||||
if (isLoadingProviders) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pre-populate state with existing data
|
||||
if (userName) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.UPDATE_DATA,
|
||||
payload: { userName },
|
||||
});
|
||||
}
|
||||
if (hasLlmProviders) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.UPDATE_DATA,
|
||||
payload: { llmProviders: (llmProviders ?? []).map((p) => p.provider) },
|
||||
});
|
||||
}
|
||||
|
||||
// Determine the earliest incomplete step
|
||||
// Name step is incomplete if userName is not set
|
||||
if (!userName) {
|
||||
// Stay at Welcome/Name step (no dispatch needed, this is the initial state)
|
||||
return;
|
||||
}
|
||||
|
||||
// LlmSetup step is incomplete if no LLM providers are configured
|
||||
if (!hasLlmProviders) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
dispatch({
|
||||
type: OnboardingActionType.GO_TO_STEP,
|
||||
step: OnboardingStep.LlmSetup,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// All steps complete - go to Complete step
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: true,
|
||||
});
|
||||
dispatch({
|
||||
type: OnboardingActionType.GO_TO_STEP,
|
||||
step: OnboardingStep.Complete,
|
||||
});
|
||||
}, [llmProviders, isLoadingProviders]);
|
||||
|
||||
const nextStep = useCallback(() => {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
|
||||
if (state.currentStep === OnboardingStep.Name) {
|
||||
const hasProviders = (state.data.llmProviders?.length ?? 0) > 0;
|
||||
if (hasProviders) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: true,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (state.currentStep === OnboardingStep.LlmSetup) {
|
||||
refreshProviderInfo();
|
||||
if (liveAgent) {
|
||||
refreshPersonaProviders();
|
||||
}
|
||||
}
|
||||
dispatch({ type: OnboardingActionType.NEXT_STEP });
|
||||
}, [state, refreshProviderInfo, llmProviders, refreshPersonaProviders]);
|
||||
|
||||
const prevStep = useCallback(() => {
|
||||
dispatch({ type: OnboardingActionType.PREV_STEP });
|
||||
}, []);
|
||||
|
||||
const goToStep = useCallback(
|
||||
(step: OnboardingStep) => {
|
||||
const hasProviders = state.data.llmProviders?.length || 0 > 0;
|
||||
if (step === OnboardingStep.LlmSetup && hasProviders) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: true,
|
||||
});
|
||||
} else if (step === OnboardingStep.LlmSetup) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
}
|
||||
dispatch({ type: OnboardingActionType.GO_TO_STEP, step });
|
||||
},
|
||||
[llmProviders]
|
||||
);
|
||||
|
||||
const updateName = useCallback(
|
||||
(name: string) => {
|
||||
dispatch({
|
||||
type: OnboardingActionType.UPDATE_DATA,
|
||||
payload: { userName: name },
|
||||
});
|
||||
|
||||
if (nameUpdateTimeoutRef.current) {
|
||||
clearTimeout(nameUpdateTimeoutRef.current);
|
||||
}
|
||||
|
||||
if (name === "") {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: true,
|
||||
});
|
||||
}
|
||||
|
||||
nameUpdateTimeoutRef.current = setTimeout(async () => {
|
||||
try {
|
||||
await updateUserPersonalization({ name });
|
||||
await refreshUser();
|
||||
} catch (_e) {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: false,
|
||||
});
|
||||
console.error("Error updating user name:", _e);
|
||||
} finally {
|
||||
nameUpdateTimeoutRef.current = null;
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
[refreshUser]
|
||||
);
|
||||
|
||||
const updateData = useCallback((data: Partial<OnboardingData>) => {
|
||||
dispatch({ type: OnboardingActionType.UPDATE_DATA, payload: data });
|
||||
}, []);
|
||||
|
||||
const setLoading = useCallback((isLoading: boolean) => {
|
||||
dispatch({ type: OnboardingActionType.SET_LOADING, isLoading });
|
||||
}, []);
|
||||
|
||||
const setButtonActive = useCallback((active: boolean) => {
|
||||
dispatch({
|
||||
type: OnboardingActionType.SET_BUTTON_ACTIVE,
|
||||
isButtonActive: active,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setError = useCallback((error: string | undefined) => {
|
||||
dispatch({ type: OnboardingActionType.SET_ERROR, error });
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
dispatch({ type: OnboardingActionType.RESET });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (nameUpdateTimeoutRef.current) {
|
||||
clearTimeout(nameUpdateTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
state,
|
||||
llmDescriptors,
|
||||
actions: {
|
||||
nextStep,
|
||||
prevStep,
|
||||
goToStep,
|
||||
setButtonActive,
|
||||
updateName,
|
||||
updateData,
|
||||
setLoading,
|
||||
setError,
|
||||
reset,
|
||||
},
|
||||
isLoading: isLoadingProviders || !!liveAgent,
|
||||
};
|
||||
}
|
||||
@@ -60,8 +60,8 @@ import {
|
||||
import ProjectChatSessionList from "@/app/app/components/projects/ProjectChatSessionList";
|
||||
import { cn } from "@/lib/utils";
|
||||
import Suggestions from "@/sections/Suggestions";
|
||||
import OnboardingFlow from "@/refresh-components/onboarding/OnboardingFlow";
|
||||
import { OnboardingStep } from "@/refresh-components/onboarding/types";
|
||||
import OnboardingFlow from "@/sections/onboarding/OnboardingFlow";
|
||||
import { OnboardingStep } from "@/interfaces/onboarding";
|
||||
import { useShowOnboarding } from "@/hooks/useShowOnboarding";
|
||||
import * as AppLayouts from "@/layouts/app-layouts";
|
||||
import { SvgChevronDown, SvgFileText } from "@opal/icons";
|
||||
|
||||
@@ -51,7 +51,7 @@ import {
|
||||
SvgStop,
|
||||
SvgX,
|
||||
} from "@opal/icons";
|
||||
import { Button, OpenButton } from "@opal/components";
|
||||
import { Button } from "@opal/components";
|
||||
import Popover from "@/refresh-components/Popover";
|
||||
import SimpleLoader from "@/refresh-components/loaders/SimpleLoader";
|
||||
import { useQueryController } from "@/providers/QueryControllerProvider";
|
||||
|
||||
@@ -3,7 +3,11 @@ import OnboardingHeader from "./components/OnboardingHeader";
|
||||
import NameStep from "./steps/NameStep";
|
||||
import LLMStep from "./steps/LLMStep";
|
||||
import FinalStep from "./steps/FinalStep";
|
||||
import { OnboardingActions, OnboardingState, OnboardingStep } from "./types";
|
||||
import {
|
||||
OnboardingActions,
|
||||
OnboardingState,
|
||||
OnboardingStep,
|
||||
} from "@/interfaces/onboarding";
|
||||
import { WellKnownLLMProviderDescriptor } from "@/interfaces/llm";
|
||||
import { useUser } from "@/providers/UserProvider";
|
||||
import { UserRole } from "@/lib/types";
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
OnboardingActionType,
|
||||
OnboardingStep,
|
||||
OnboardingState,
|
||||
} from "../types";
|
||||
} from "@/interfaces/onboarding";
|
||||
|
||||
describe("onboardingReducer", () => {
|
||||
describe("initial state", () => {
|
||||
@@ -9,6 +9,7 @@ import { Button as OpalButton } from "@opal/components";
|
||||
import InputAvatar from "@/refresh-components/inputs/InputAvatar";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SvgCheckCircle, SvgEdit, SvgUser, SvgX } from "@opal/icons";
|
||||
import { ContentAction } from "@opal/layouts";
|
||||
|
||||
export default function NonAdminStep() {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -54,17 +55,21 @@ export default function NonAdminStep() {
|
||||
className="flex items-center justify-between w-full min-h-11 py-1 pl-3 pr-2 bg-background-tint-00 rounded-16 shadow-01 mb-2"
|
||||
aria-label="non-admin-confirmation"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<SvgCheckCircle className="w-4 h-4 stroke-status-success-05" />
|
||||
<Text as="p" text03 mainUiBody>
|
||||
You're all set!
|
||||
</Text>
|
||||
</div>
|
||||
<OpalButton
|
||||
prominence="tertiary"
|
||||
size="sm"
|
||||
icon={SvgX}
|
||||
onClick={handleDismissConfirmation}
|
||||
<ContentAction
|
||||
icon={SvgCheckCircle}
|
||||
title="You're all set!"
|
||||
sizePreset="main-ui"
|
||||
variant="body"
|
||||
prominence="muted"
|
||||
paddingVariant="fit"
|
||||
rightChildren={
|
||||
<OpalButton
|
||||
prominence="tertiary"
|
||||
size="sm"
|
||||
icon={SvgX}
|
||||
onClick={handleDismissConfirmation}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -75,39 +80,36 @@ export default function NonAdminStep() {
|
||||
role="group"
|
||||
aria-label="non-admin-name-prompt"
|
||||
>
|
||||
<div className="flex items-center gap-1 h-full">
|
||||
<div className="h-full p-0.5">
|
||||
<SvgUser className="w-4 h-4 stroke-text-03" />
|
||||
</div>
|
||||
<div>
|
||||
<Text as="p" text04 mainUiAction>
|
||||
What should Onyx call you?
|
||||
</Text>
|
||||
<Text as="p" text03 secondaryBody>
|
||||
We will display this name in the app.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<InputTypeIn
|
||||
ref={inputRef}
|
||||
placeholder="Your name"
|
||||
value={name || ""}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setName(e.target.value)
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && name && name.trim().length > 0) {
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
}
|
||||
}}
|
||||
className="w-[26%] min-w-40"
|
||||
/>
|
||||
<Button disabled={name === ""} onClick={handleSave}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
<ContentAction
|
||||
icon={SvgUser}
|
||||
title="What should Onyx call you?"
|
||||
description="We will display this name in the app."
|
||||
sizePreset="main-ui"
|
||||
variant="section"
|
||||
paddingVariant="fit"
|
||||
rightChildren={
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<InputTypeIn
|
||||
ref={inputRef}
|
||||
placeholder="Your name"
|
||||
value={name || ""}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setName(e.target.value)
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && name && name.trim().length > 0) {
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
}
|
||||
}}
|
||||
className="w-[26%] min-w-40"
|
||||
/>
|
||||
<Button disabled={name === ""} onClick={handleSave}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
import { STEP_CONFIG } from "@/refresh-components/onboarding/constants";
|
||||
import { STEP_CONFIG } from "@/sections/onboarding/constants";
|
||||
import {
|
||||
OnboardingActions,
|
||||
OnboardingState,
|
||||
OnboardingStep,
|
||||
} from "@/refresh-components/onboarding/types";
|
||||
} from "@/interfaces/onboarding";
|
||||
import Text from "@/refresh-components/texts/Text";
|
||||
import Button from "@/refresh-components/buttons/Button";
|
||||
import { Button as OpalButton } from "@opal/components";
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
OnboardingStep,
|
||||
FinalStepItemProps,
|
||||
} from "@/refresh-components/onboarding/types";
|
||||
import { OnboardingStep, FinalStepItemProps } from "@/interfaces/onboarding";
|
||||
import { SvgGlobe, SvgImage, SvgUsers } from "@opal/icons";
|
||||
|
||||
type StepConfig = {
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
OnboardingFormWrapper,
|
||||
OnboardingFormChildProps,
|
||||
} from "./OnboardingFormWrapper";
|
||||
import { OnboardingActions, OnboardingState } from "../types";
|
||||
import { OnboardingActions, OnboardingState } from "@/interfaces/onboarding";
|
||||
import { buildInitialValues } from "../components/llmConnectionHelpers";
|
||||
import ConnectionProviderIcon from "@/refresh-components/ConnectionProviderIcon";
|
||||
import InlineExternalLink from "@/refresh-components/InlineExternalLink";
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
OnboardingFormWrapper,
|
||||
OnboardingFormChildProps,
|
||||
} from "./OnboardingFormWrapper";
|
||||
import { OnboardingActions, OnboardingState } from "../types";
|
||||
import { OnboardingActions, OnboardingState } from "@/interfaces/onboarding";
|
||||
import { buildInitialValues } from "../components/llmConnectionHelpers";
|
||||
import ConnectionProviderIcon from "@/refresh-components/ConnectionProviderIcon";
|
||||
import InlineExternalLink from "@/refresh-components/InlineExternalLink";
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
OnboardingFormWrapper,
|
||||
OnboardingFormChildProps,
|
||||
} from "./OnboardingFormWrapper";
|
||||
import { OnboardingActions, OnboardingState } from "../types";
|
||||
import { OnboardingActions, OnboardingState } from "@/interfaces/onboarding";
|
||||
import { buildInitialValues } from "../components/llmConnectionHelpers";
|
||||
import ConnectionProviderIcon from "@/refresh-components/ConnectionProviderIcon";
|
||||
import InlineExternalLink from "@/refresh-components/InlineExternalLink";
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
OnboardingFormWrapper,
|
||||
OnboardingFormChildProps,
|
||||
} from "./OnboardingFormWrapper";
|
||||
import { OnboardingActions, OnboardingState } from "../types";
|
||||
import { OnboardingActions, OnboardingState } from "@/interfaces/onboarding";
|
||||
import { buildInitialValues } from "../components/llmConnectionHelpers";
|
||||
import ConnectionProviderIcon from "@/refresh-components/ConnectionProviderIcon";
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
OnboardingFormWrapper,
|
||||
OnboardingFormChildProps,
|
||||
} from "./OnboardingFormWrapper";
|
||||
import { OnboardingActions, OnboardingState } from "../types";
|
||||
import { OnboardingActions, OnboardingState } from "@/interfaces/onboarding";
|
||||
import { buildInitialValues } from "../components/llmConnectionHelpers";
|
||||
import ConnectionProviderIcon from "@/refresh-components/ConnectionProviderIcon";
|
||||
import InlineExternalLink from "@/refresh-components/InlineExternalLink";
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
LLM_ADMIN_URL,
|
||||
LLM_PROVIDERS_ADMIN_URL,
|
||||
} from "@/lib/llmConfig/constants";
|
||||
import { OnboardingActions, OnboardingState } from "../types";
|
||||
import { OnboardingActions, OnboardingState } from "@/interfaces/onboarding";
|
||||
import { APIFormFieldState } from "@/refresh-components/form/types";
|
||||
import {
|
||||
testApiKeyHelper,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
OnboardingFormWrapper,
|
||||
OnboardingFormChildProps,
|
||||
} from "./OnboardingFormWrapper";
|
||||
import { OnboardingActions, OnboardingState } from "../types";
|
||||
import { OnboardingActions, OnboardingState } from "@/interfaces/onboarding";
|
||||
import { buildInitialValues } from "../components/llmConnectionHelpers";
|
||||
import ConnectionProviderIcon from "@/refresh-components/ConnectionProviderIcon";
|
||||
import InlineExternalLink from "@/refresh-components/InlineExternalLink";
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
OnboardingFormWrapper,
|
||||
OnboardingFormChildProps,
|
||||
} from "./OnboardingFormWrapper";
|
||||
import { OnboardingActions, OnboardingState } from "../types";
|
||||
import { OnboardingActions, OnboardingState } from "@/interfaces/onboarding";
|
||||
import { buildInitialValues } from "../components/llmConnectionHelpers";
|
||||
import ConnectionProviderIcon from "@/refresh-components/ConnectionProviderIcon";
|
||||
import InlineExternalLink from "@/refresh-components/InlineExternalLink";
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
OnboardingFormWrapper,
|
||||
OnboardingFormChildProps,
|
||||
} from "./OnboardingFormWrapper";
|
||||
import { OnboardingActions, OnboardingState } from "../types";
|
||||
import { OnboardingActions, OnboardingState } from "@/interfaces/onboarding";
|
||||
import {
|
||||
buildInitialValues,
|
||||
testApiKeyHelper,
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
OnboardingState,
|
||||
OnboardingActions,
|
||||
OnboardingStep,
|
||||
} from "../../types";
|
||||
} from "@/interfaces/onboarding";
|
||||
|
||||
/**
|
||||
* Creates a mock WellKnownLLMProviderDescriptor for testing
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
WellKnownLLMProviderDescriptor,
|
||||
LLMProviderName,
|
||||
} from "@/interfaces/llm";
|
||||
import { OnboardingActions, OnboardingState } from "../types";
|
||||
import { OnboardingActions, OnboardingState } from "@/interfaces/onboarding";
|
||||
import { OpenAIOnboardingForm } from "./OpenAIOnboardingForm";
|
||||
import { AnthropicOnboardingForm } from "./AnthropicOnboardingForm";
|
||||
import { OllamaOnboardingForm } from "./OllamaOnboardingForm";
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
OnboardingAction,
|
||||
OnboardingActionType,
|
||||
OnboardingStep,
|
||||
} from "./types";
|
||||
} from "@/interfaces/onboarding";
|
||||
import { STEP_NAVIGATION, STEP_CONFIG, TOTAL_STEPS } from "./constants";
|
||||
|
||||
export const initialState: OnboardingState = {
|
||||
@@ -2,8 +2,8 @@ import React from "react";
|
||||
import Link from "next/link";
|
||||
import type { Route } from "next";
|
||||
import Button from "@/refresh-components/buttons/Button";
|
||||
import { FINAL_SETUP_CONFIG } from "@/refresh-components/onboarding/constants";
|
||||
import { FinalStepItemProps } from "@/refresh-components/onboarding/types";
|
||||
import { FINAL_SETUP_CONFIG } from "@/sections/onboarding/constants";
|
||||
import { FinalStepItemProps } from "@/interfaces/onboarding";
|
||||
import { SvgExternalLink } from "@opal/icons";
|
||||
import { Section } from "@/layouts/general-layouts";
|
||||
import { ContentAction } from "@opal/layouts";
|
||||
@@ -3,7 +3,11 @@ import Text from "@/refresh-components/texts/Text";
|
||||
import Button from "@/refresh-components/buttons/Button";
|
||||
import Separator from "@/refresh-components/Separator";
|
||||
import LLMProviderCard from "../components/LLMProviderCard";
|
||||
import { OnboardingActions, OnboardingState, OnboardingStep } from "../types";
|
||||
import {
|
||||
OnboardingActions,
|
||||
OnboardingState,
|
||||
OnboardingStep,
|
||||
} from "@/interfaces/onboarding";
|
||||
import { WellKnownLLMProviderDescriptor } from "@/interfaces/llm";
|
||||
import {
|
||||
getOnboardingForm,
|
||||
@@ -12,6 +16,7 @@ import {
|
||||
import { Disabled } from "@/refresh-components/Disabled";
|
||||
import { ProviderIcon } from "@/app/admin/configuration/llm/ProviderIcon";
|
||||
import { SvgCheckCircle, SvgCpu, SvgExternalLink } from "@opal/icons";
|
||||
import { ContentAction } from "@opal/layouts";
|
||||
|
||||
type LLMStepProps = {
|
||||
state: OnboardingState;
|
||||
@@ -122,31 +127,26 @@ const LLMStepInner = ({
|
||||
className="flex flex-col items-center justify-between w-full p-1 rounded-16 border border-border-01 bg-background-tint-00"
|
||||
aria-label="onboarding-llm-step"
|
||||
>
|
||||
<div className="flex gap-2 justify-between h-full w-full">
|
||||
<div className="flex mx-2 mt-2 gap-1">
|
||||
<div className="h-full p-0.5">
|
||||
<SvgCpu className="w-4 h-4 stroke-text-03" />
|
||||
<ContentAction
|
||||
icon={SvgCpu}
|
||||
title="Connect your LLM models"
|
||||
description="Onyx supports both self-hosted models and popular providers."
|
||||
sizePreset="main-ui"
|
||||
variant="section"
|
||||
paddingVariant="lg"
|
||||
rightChildren={
|
||||
<div className="p-0.5">
|
||||
<Button
|
||||
tertiary
|
||||
rightIcon={SvgExternalLink}
|
||||
disabled={disabled}
|
||||
href="admin/configuration/llm"
|
||||
>
|
||||
View in Admin Panel
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Text as="p" text04 mainUiAction>
|
||||
Connect your LLM models
|
||||
</Text>
|
||||
<Text as="p" text03 secondaryBody>
|
||||
Onyx supports both self-hosted models and popular providers.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-0.5">
|
||||
<Button
|
||||
tertiary
|
||||
rightIcon={SvgExternalLink}
|
||||
disabled={disabled}
|
||||
href="admin/configuration/llm"
|
||||
>
|
||||
View in Admin Panel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Separator />
|
||||
<div className="flex flex-wrap gap-1 [&>*:last-child:nth-child(odd)]:basis-full">
|
||||
{isLoading ? (
|
||||
@@ -3,11 +3,16 @@
|
||||
import React, { useRef } from "react";
|
||||
import Text from "@/refresh-components/texts/Text";
|
||||
import InputTypeIn from "@/refresh-components/inputs/InputTypeIn";
|
||||
import { OnboardingState, OnboardingActions, OnboardingStep } from "../types";
|
||||
import {
|
||||
OnboardingState,
|
||||
OnboardingActions,
|
||||
OnboardingStep,
|
||||
} from "@/interfaces/onboarding";
|
||||
import InputAvatar from "@/refresh-components/inputs/InputAvatar";
|
||||
import { cn } from "@/lib/utils";
|
||||
import IconButton from "@/refresh-components/buttons/IconButton";
|
||||
import { SvgCheckCircle, SvgEdit, SvgUser } from "@opal/icons";
|
||||
import { ContentAction } from "@opal/layouts";
|
||||
|
||||
export interface NameStepProps {
|
||||
state: OnboardingState;
|
||||
@@ -40,26 +45,23 @@ const NameStep = React.memo(
|
||||
role="group"
|
||||
aria-label="onboarding-name-step"
|
||||
>
|
||||
<div className="flex items-center gap-1 h-full">
|
||||
<div className="h-full p-0.5">
|
||||
<SvgUser className="w-4 h-4 stroke-text-03" />
|
||||
</div>
|
||||
<div>
|
||||
<Text as="p" text04 mainUiAction>
|
||||
What should Onyx call you?
|
||||
</Text>
|
||||
<Text as="p" text03 secondaryBody>
|
||||
We will display this name in the app.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<InputTypeIn
|
||||
ref={inputRef}
|
||||
placeholder="Your name"
|
||||
value={userName || ""}
|
||||
onChange={(e) => updateName(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="max-w-60"
|
||||
<ContentAction
|
||||
icon={SvgUser}
|
||||
title="What should Onyx call you?"
|
||||
description="We will display this name in the app."
|
||||
sizePreset="main-ui"
|
||||
variant="section"
|
||||
paddingVariant="fit"
|
||||
rightChildren={
|
||||
<InputTypeIn
|
||||
ref={inputRef}
|
||||
placeholder="Your name"
|
||||
value={userName || ""}
|
||||
onChange={(e) => updateName(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="max-w-60"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
Reference in New Issue
Block a user