feat: user onboarding

This commit is contained in:
YuTengjing
2025-12-20 21:32:34 +08:00
committed by arvinxx
parent c305889ac4
commit 5e59388317
27 changed files with 311 additions and 46 deletions
-1
View File
@@ -24,7 +24,6 @@ export const DEFAULT_AGENT_SEARCH_FC_MODEL = {
export const DEFAULT_AGENT_CHAT_CONFIG: LobeAgentChatConfig = {
autoCreateTopicThreshold: 2,
displayMode: 'chat',
enableAutoCreateTopic: true,
enableCompressHistory: true,
enableHistoryCount: true,
+3
View File
@@ -5,6 +5,9 @@ export const DEFAULT_COMMON_SETTINGS: UserGeneralConfig = {
// contextMenuMode not set default value, use env to calc
fontSize: 14,
highlighterTheme: 'lobe-theme',
isDevMode: false,
isLiteMode: false,
mermaidTheme: 'lobe-theme',
telemetry: true,
transitionMode: 'fadeIn',
};
+2
View File
@@ -10,6 +10,8 @@ export const DEFAULT_CHAT_GROUP_CHAT_CONFIG: LobeChatGroupChatConfig = {
allowDM: true,
enableSupervisor: true,
maxResponseInRow: 10,
openingMessage: '',
openingQuestions: [],
orchestratorModel: DEFAULT_MODEL,
orchestratorProvider: DEFAULT_PROVIDER,
responseOrder: 'natural',
+3
View File
@@ -5,6 +5,7 @@ import { DEFAULT_COMMON_SETTINGS } from './common';
import { DEFAULT_HOTKEY_CONFIG } from './hotkey';
import { DEFAULT_IMAGE_CONFIG } from './image';
import { DEFAULT_LLM_CONFIG } from './llm';
import { DEFAULT_MEMORY_SETTINGS } from './memory';
import { DEFAULT_SYSTEM_AGENT_CONFIG } from './systemAgent';
import { DEFAULT_TOOL_CONFIG } from './tool';
import { DEFAULT_TTS_CONFIG } from './tts';
@@ -16,6 +17,7 @@ export * from './hotkey';
export * from './image';
export * from './knowledge';
export * from './llm';
export * from './memory';
export * from './systemAgent';
export * from './tool';
export * from './tts';
@@ -27,6 +29,7 @@ export const DEFAULT_SETTINGS: UserSettings = {
image: DEFAULT_IMAGE_CONFIG,
keyVaults: {},
languageModel: DEFAULT_LLM_CONFIG,
memory: DEFAULT_MEMORY_SETTINGS,
systemAgent: DEFAULT_SYSTEM_AGENT_CONFIG,
tool: DEFAULT_TOOL_CONFIG,
tts: DEFAULT_TTS_CONFIG,
+5
View File
@@ -0,0 +1,5 @@
import { UserMemorySettings } from '@lobechat/types';
export const DEFAULT_MEMORY_SETTINGS: UserMemorySettings = {
enabled: true,
};
+9 -1
View File
@@ -3,6 +3,7 @@ import { z } from 'zod';
import { Plans } from '../subscription';
import { TopicDisplayMode } from '../topic';
import { UserOnboarding } from './onboarding';
import { UserSettings } from './settings';
export interface LobeUser {
@@ -11,6 +12,7 @@ export interface LobeUser {
firstName?: string | null;
fullName?: string | null;
id: string;
interests?: string[];
latestName?: string | null;
username?: string | null;
}
@@ -59,7 +61,10 @@ export interface UserPreference {
* lab experimental features
*/
lab?: UserLab;
telemetry: boolean | null;
/**
* @deprecated Use settings.general.telemetry instead
*/
telemetry?: boolean | null;
topicDisplayMode?: TopicDisplayMode;
/**
* whether to use cmd + enter to send message
@@ -75,8 +80,11 @@ export interface UserInitializationState {
firstName?: string;
fullName?: string;
hasConversation?: boolean;
interests?: string[];
/** @deprecated Use onboarding field instead */
isOnboard?: boolean;
lastName?: string;
onboarding?: UserOnboarding;
preference: UserPreference;
settings: PartialDeep<UserSettings>;
subscriptionPlan?: Plans;
@@ -11,8 +11,12 @@ export interface UserGeneralConfig {
contextMenuMode?: ContextMenuMode;
fontSize: number;
highlighterTheme?: HighlighterProps['theme'];
isDevMode: boolean;
isLiteMode: boolean;
mermaidTheme?: MermaidProps['theme'];
neutralColor?: NeutralColors;
primaryColor?: PrimaryColors;
responseLanguage?: string;
telemetry: boolean;
transitionMode?: ResponseAnimationStyle;
}
@@ -6,6 +6,7 @@ import { UserHotkeyConfig } from './hotkey';
import { UserImageConfig } from './image';
import { UserKeyVaults } from './keyVaults';
import { MarketAuthTokens } from './market';
import { UserMemorySettings } from './memory';
import { UserModelProviderConfig } from './modelProvider';
import { UserSystemAgentConfig } from './systemAgent';
import { UserToolConfig } from './tool';
@@ -19,6 +20,7 @@ export * from './hotkey';
export * from './image';
export * from './keyVaults';
export * from './market';
export * from './memory';
export * from './modelProvider';
export * from './sync';
export * from './systemAgent';
@@ -36,6 +38,7 @@ export interface UserSettings {
keyVaults: UserKeyVaults;
languageModel: UserModelProviderConfig;
market?: MarketAuthTokens;
memory?: UserMemorySettings;
systemAgent: UserSystemAgentConfig;
tool: UserToolConfig;
tts: UserTTSConfig;
@@ -54,6 +57,7 @@ export const UserSettingsSchema = z
keyVaults: z.any().optional(),
languageModel: z.any().optional(),
market: z.any().optional(),
memory: z.any().optional(),
systemAgent: z.any().optional(),
tool: z.any().optional(),
tts: z.any().optional(),
@@ -0,0 +1,3 @@
export interface UserMemorySettings {
enabled?: boolean;
}
+7 -1
View File
@@ -1,13 +1,19 @@
import { UserAuthState, initialAuthState } from './slices/auth/initialState';
import { CommonState, initialCommonState } from './slices/common/initialState';
import { OnboardingState, initialOnboardingState } from './slices/onboarding/initialState';
import { UserPreferenceState, initialPreferenceState } from './slices/preference/initialState';
import { UserSettingsState, initialSettingsState } from './slices/settings/initialState';
export type UserState = UserSettingsState & UserPreferenceState & UserAuthState & CommonState;
export type UserState = UserSettingsState &
UserPreferenceState &
UserAuthState &
CommonState &
OnboardingState;
export const initialState: UserState = {
...initialSettingsState,
...initialPreferenceState,
...initialAuthState,
...initialCommonState,
...initialOnboardingState,
};
+1
View File
@@ -1,4 +1,5 @@
export { authSelectors, userProfileSelectors } from './slices/auth/selectors';
export { onboardingSelectors } from './slices/onboarding/selectors';
export { labPreferSelectors, preferenceSelectors } from './slices/preference/selectors';
export {
keyVaultsConfigSelectors,
+3 -2
View File
@@ -243,7 +243,7 @@ describe('userProfileSelectors', () => {
describe('authSelectors', () => {
describe('isLogin', () => {
it('should return true when auth is disabled', () => {
it('should return false when not signed in (regardless of auth enabled state)', () => {
enableAuth = false;
const store: UserStore = {
@@ -251,7 +251,8 @@ describe('authSelectors', () => {
enableAuth: () => false,
} as UserStore;
expect(authSelectors.isLogin(store)).toBe(true);
// isLogin now only checks isSignedIn, not enableAuth
expect(authSelectors.isLogin(store)).toBe(false);
});
it('should return true when signed in', () => {
+2 -11
View File
@@ -38,6 +38,7 @@ export const userProfileSelectors = {
displayUserName: (s: UserStore): string => username(s) || s.user?.email || '',
email: (s: UserStore): string => s.user?.email || '',
fullName: (s: UserStore): string => s.user?.fullName || '',
interests: (s: UserStore): string[] => s.user?.interests || [],
nickName,
userAvatar: (s: UserStore): string => s.user?.avatar || '',
userId: (s: UserStore) => s.user?.id,
@@ -45,22 +46,12 @@ export const userProfileSelectors = {
username,
};
/**
* 使用此方法可以兼容不需要登录鉴权的情况
*/
const isLogin = (s: UserStore) => {
// 如果没有开启鉴权,说明不需要登录,默认是登录态
if (!enableAuth) return true;
return s.isSignedIn;
};
export const authSelectors = {
authProviders: (s: UserStore): SSOProvider[] => s.authProviders || [],
hasPasswordAccount: (s: UserStore) => s.hasPasswordAccount ?? false,
isLoaded: (s: UserStore) => s.isLoaded,
isLoadedAuthProviders: (s: UserStore) => s.isLoadedAuthProviders ?? false,
isLogin,
isLogin: (s: UserStore) => s.isSignedIn,
isLoginWithAuth: (s: UserStore) => s.isSignedIn,
isLoginWithBetterAuth: (s: UserStore): boolean => (s.isSignedIn && enableBetterAuth) || false,
isLoginWithClerk: (s: UserStore): boolean => (s.isSignedIn && enableClerk) || false,
+4 -4
View File
@@ -5,7 +5,7 @@ import { withSWR } from '~test-utils';
import { DEFAULT_PREFERENCE } from '@/const/user';
import { userService } from '@/services/user';
import { useUserStore } from '@/store/user';
import { preferenceSelectors } from '@/store/user/selectors';
import { userGeneralSettingsSelectors } from '@/store/user/selectors';
import { GlobalServerConfig } from '@/types/serverConfig';
import { UserInitializationState, UserPreference } from '@/types/user';
@@ -232,8 +232,8 @@ describe('createCommonSlice', () => {
await waitFor(() => expect(result.current.data).toBeUndefined());
});
it('should return false when userAllowTrace is already set', async () => {
vi.spyOn(preferenceSelectors, 'userAllowTrace').mockReturnValueOnce(true);
it('should return false when telemetry is already set', async () => {
vi.spyOn(userGeneralSettingsSelectors, 'telemetry').mockReturnValueOnce(true);
const { result } = renderHook(() => useUserStore().useCheckTrace(true), {
wrapper: withSWR,
@@ -243,7 +243,7 @@ describe('createCommonSlice', () => {
});
it('should call messageService.messageCountToCheckTrace when needed', async () => {
vi.spyOn(preferenceSelectors, 'userAllowTrace').mockReturnValueOnce(null);
vi.spyOn(userGeneralSettingsSelectors, 'telemetry').mockReturnValueOnce(undefined as any);
act(() => {
useUserStore.setState({
+14 -5
View File
@@ -1,3 +1,4 @@
import { isDesktop } from '@lobechat/const';
import { getSingletonAnalyticsOptional } from '@lobehub/analytics';
import useSWR, { SWRResponse, mutate } from 'swr';
import type { PartialDeep } from 'type-fest';
@@ -13,7 +14,7 @@ import type { UserSettings } from '@/types/user/settings';
import { merge } from '@/utils/merge';
import { setNamespace } from '@/utils/storeDebug';
import { preferenceSelectors } from '../preference/selectors';
import { userGeneralSettingsSelectors } from '../settings/selectors';
const n = setNamespace('common');
@@ -25,6 +26,7 @@ export interface CommonAction {
refreshUserState: () => Promise<void>;
updateAvatar: (avatar: string) => Promise<void>;
updateFullName: (fullName: string) => Promise<void>;
updateInterests: (interests: string[]) => Promise<void>;
updateKeyVaultConfig: (provider: string, config: any) => Promise<void>;
updateUsername: (username: string) => Promise<void>;
useCheckTrace: (shouldFetch: boolean) => SWRResponse;
@@ -57,6 +59,11 @@ export const createCommonSlice: StateCreator<
await get().refreshUserState();
},
updateInterests: async (interests) => {
await userService.updateInterests(interests);
await get().refreshUserState();
},
updateKeyVaultConfig: async (provider, config) => {
await get().setSettings({ keyVaults: { [provider]: config } });
},
@@ -70,10 +77,10 @@ export const createCommonSlice: StateCreator<
useSWR<boolean>(
shouldFetch ? 'checkTrace' : null,
() => {
const userAllowTrace = preferenceSelectors.userAllowTrace(get());
const telemetry = userGeneralSettingsSelectors.telemetry(get());
// if user have set the trace, return false
if (typeof userAllowTrace === 'boolean') return Promise.resolve(false);
// if user have set the telemetry, return false
if (typeof telemetry === 'boolean') return Promise.resolve(false);
return Promise.resolve(get().isUserCanEnableTrace);
},
@@ -83,7 +90,7 @@ export const createCommonSlice: StateCreator<
),
useInitUserState: (isLogin, serverConfig, options) =>
useOnlyFetchOnceSWR<UserInitializationState>(
!!isLogin ? GET_USER_STATE_KEY : null,
!!isLogin || isDesktop ? GET_USER_STATE_KEY : null,
() => userService.getUserState(),
{
onError: (error) => {
@@ -115,6 +122,7 @@ export const createCommonSlice: StateCreator<
firstName: data.firstName,
fullName: data.fullName,
id: data.userId,
interests: data.interests,
latestName: data.lastName,
username: data.username,
} as LobeUser)
@@ -128,6 +136,7 @@ export const createCommonSlice: StateCreator<
isUserCanEnableTrace: data.canEnableTrace,
isUserHasConversation: data.hasConversation,
isUserStateInit: true,
onboarding: data.onboarding,
preference,
settings: data.settings || {},
subscriptionPlan: data.subscriptionPlan,
@@ -1,6 +1,7 @@
import { Plans } from '@/types/subscription';
export interface CommonState {
/** @deprecated Use onboarding field instead */
isOnboard: boolean;
isShowPWAGuide: boolean;
isUserCanEnableTrace: boolean;
+172
View File
@@ -0,0 +1,172 @@
import { CURRENT_ONBOARDING_VERSION, INBOX_SESSION_ID } from '@lobechat/const';
import type { StateCreator } from 'zustand/vanilla';
import { userService } from '@/services/user';
import { getAgentStoreState } from '@/store/agent';
import type { UserStore } from '@/store/user';
import { settingsSelectors } from '../settings/selectors';
import { onboardingSelectors } from './selectors';
export interface OnboardingAction {
finishOnboarding: () => Promise<void>;
goToNextStep: () => void;
goToPreviousStep: () => void;
/**
* Internal method to process the step update queue
*/
internal_processStepUpdateQueue: () => Promise<void>;
/**
* Internal method to queue a step update
*/
internal_queueStepUpdate: (step: number) => void;
setOnboardingStep: (step: number) => Promise<void>;
/**
* Toggle plugin in default agent config for onboarding
*/
toggleInboxAgentDefaultPlugin: (id: string, open?: boolean) => Promise<void>;
/**
* Update default model for both user settings and inbox agent
*/
updateDefaultModel: (model: string, provider: string) => Promise<void>;
}
export const createOnboardingSlice: StateCreator<
UserStore,
[['zustand/devtools', never]],
[],
OnboardingAction
> = (set, get) => ({
finishOnboarding: async () => {
const currentStep = onboardingSelectors.currentStep(get());
await userService.updateOnboarding({
currentStep,
finishedAt: new Date().toISOString(),
version: CURRENT_ONBOARDING_VERSION,
});
await get().refreshUserState();
},
goToNextStep: () => {
const currentStep = onboardingSelectors.currentStep(get());
const nextStep = currentStep + 1;
// Optimistic update: immediately update local state
set({ localOnboardingStep: nextStep }, false, 'goToNextStep/optimistic');
// Queue the server update
get().internal_queueStepUpdate(nextStep);
},
goToPreviousStep: () => {
const currentStep = onboardingSelectors.currentStep(get());
if (currentStep <= 1) return;
const prevStep = currentStep - 1;
// Optimistic update: immediately update local state
set({ localOnboardingStep: prevStep }, false, 'goToPreviousStep/optimistic');
// Queue the server update
get().internal_queueStepUpdate(prevStep);
},
internal_processStepUpdateQueue: async () => {
const { isProcessingStepQueue, stepUpdateQueue } = get();
if (isProcessingStepQueue || stepUpdateQueue.length === 0) return;
set({ isProcessingStepQueue: true }, false, 'processStepUpdateQueue/start');
while (get().stepUpdateQueue.length > 0) {
const step = get().stepUpdateQueue[0];
try {
await userService.updateOnboarding({
currentStep: step,
version: CURRENT_ONBOARDING_VERSION,
});
} catch (error) {
console.error('Failed to update onboarding step:', error);
}
// Remove the completed task
set(
{ stepUpdateQueue: get().stepUpdateQueue.slice(1) },
false,
'processStepUpdateQueue/shift',
);
}
set({ isProcessingStepQueue: false }, false, 'processStepUpdateQueue/end');
// Sync with server state after all updates complete
await get().refreshUserState();
},
internal_queueStepUpdate: (step) => {
const { stepUpdateQueue } = get();
if (stepUpdateQueue.length === 0) {
// Queue is empty, add task and start processing
set({ stepUpdateQueue: [step] }, false, 'queueStepUpdate/push');
get().internal_processStepUpdateQueue();
} else if (stepUpdateQueue.length === 1) {
// One task is executing, add as pending
set({ stepUpdateQueue: [...stepUpdateQueue, step] }, false, 'queueStepUpdate/push');
} else {
// Queue is full (length >= 2), replace the pending task
set({ stepUpdateQueue: [stepUpdateQueue[0], step] }, false, 'queueStepUpdate/replace');
}
},
setOnboardingStep: async (step) => {
// Optimistic update
set({ localOnboardingStep: step }, false, 'setOnboardingStep/optimistic');
await userService.updateOnboarding({
currentStep: step,
version: CURRENT_ONBOARDING_VERSION,
});
await get().refreshUserState();
},
toggleInboxAgentDefaultPlugin: async (id, open) => {
const currentSettings = settingsSelectors.currentSettings(get());
const currentPlugins = currentSettings.defaultAgent?.config?.plugins || [];
const index = currentPlugins.indexOf(id);
const shouldOpen = open !== undefined ? open : index === -1;
const agentStore = getAgentStoreState();
const inboxAgentId = agentStore.builtinAgentIdMap[INBOX_SESSION_ID];
// Calculate inbox agent's new plugins
const inboxPlugins = inboxAgentId ? agentStore.agentMap[inboxAgentId]?.plugins || [] : [];
const inboxIndex = inboxPlugins.indexOf(id);
let newInboxPlugins: string[];
if (shouldOpen) {
newInboxPlugins = inboxIndex === -1 ? [...inboxPlugins, id] : inboxPlugins;
} else {
newInboxPlugins = inboxIndex !== -1 ? inboxPlugins.filter((p) => p !== id) : inboxPlugins;
}
if (inboxAgentId) {
await agentStore.updateAgentConfigById(inboxAgentId, { plugins: newInboxPlugins });
}
},
updateDefaultModel: async (model, provider) => {
const agentStore = getAgentStoreState();
const inboxAgentId = agentStore.builtinAgentIdMap[INBOX_SESSION_ID];
await Promise.all([
// 1. Update user settings' defaultAgentConfig
get().updateDefaultAgent({ config: { model, provider } }),
// 2. Update inbox agent's model
inboxAgentId && agentStore.updateAgentConfigById(inboxAgentId, { model, provider }),
]);
},
});
@@ -0,0 +1,27 @@
import { UserOnboarding } from '@/types/user';
export interface OnboardingState {
/**
* Whether the step update queue is currently being processed.
*/
isProcessingStepQueue: boolean;
/**
* Local step for optimistic UI updates.
* When set, takes precedence over server state for immediate UI feedback.
*/
localOnboardingStep?: number;
onboarding?: UserOnboarding;
/**
* Queue for managing server updates with max length 2.
* - Position 0: Currently executing task
* - Position 1: Pending task (replaced if new task arrives while queue is full)
*/
stepUpdateQueue: number[];
}
export const initialOnboardingState: OnboardingState = {
isProcessingStepQueue: false,
localOnboardingStep: undefined,
onboarding: undefined,
stepUpdateQueue: [],
};
@@ -0,0 +1,33 @@
import { CURRENT_ONBOARDING_VERSION } from '@lobechat/const';
import type { UserStore } from '../../store';
/**
* Returns the current step for UI display.
* Prioritizes local optimistic state over server state for immediate feedback.
*/
const currentStep = (s: UserStore) => s.localOnboardingStep ?? s.onboarding?.currentStep ?? 1;
const version = (s: UserStore) => s.onboarding?.version ?? CURRENT_ONBOARDING_VERSION;
const finishedAt = (s: UserStore) => s.onboarding?.finishedAt;
const isFinished = (s: UserStore) => !!s.onboarding?.finishedAt;
/**
* Check if user needs to go through onboarding.
*/
const needsOnboarding = (s: Pick<UserStore, 'onboarding'>) => {
return (
!s.onboarding?.finishedAt ||
(s.onboarding?.version && s.onboarding.version < CURRENT_ONBOARDING_VERSION)
);
};
export const onboardingSelectors = {
currentStep,
finishedAt,
isFinished,
needsOnboarding,
version,
};
@@ -29,19 +29,6 @@ describe('preferenceSelectors', () => {
});
});
describe('userAllowTrace', () => {
it('should return the value of telemetry preference', () => {
store.preference.telemetry = true;
expect(preferenceSelectors.userAllowTrace(store)).toBe(true);
store.preference.telemetry = false;
expect(preferenceSelectors.userAllowTrace(store)).toBe(false);
store.preference.telemetry = null;
expect(preferenceSelectors.userAllowTrace(store)).toBe(null);
});
});
describe('hideSyncAlert', () => {
it('should return the value of hideSyncAlert preference', () => {
store.preference.hideSyncAlert = true;
@@ -3,8 +3,6 @@ import { DEFAULT_PREFERENCE } from '@lobechat/const';
import type { UserState } from '@/store/user/initialState';
export const labPreferSelectors = {
enableGroupChat: (s: UserState): boolean =>
s.preference.lab?.enableGroupChat ?? DEFAULT_PREFERENCE.lab!.enableGroupChat!,
enableInputMarkdown: (s: UserState): boolean =>
s.preference.lab?.enableInputMarkdown ?? DEFAULT_PREFERENCE.lab!.enableInputMarkdown!,
};
@@ -6,8 +6,6 @@ const useCmdEnterToSend = (s: UserStore): boolean => s.preference.useCmdEnterToS
const topicDisplayMode = (s: UserStore) =>
s.preference.topicDisplayMode || DEFAULT_PREFERENCE.topicDisplayMode;
const userAllowTrace = (s: UserStore) => s.preference.telemetry;
const hideSyncAlert = (s: UserStore) => s.preference.hideSyncAlert;
const hideSettingsMoveGuide = (s: UserStore) => s.preference.guide?.moveSettingsToAvatar;
@@ -28,5 +26,4 @@ export const preferenceSelectors = {
showUploadFileInKnowledgeBaseTip,
topicDisplayMode,
useCmdEnterToSend,
userAllowTrace,
};
@@ -100,7 +100,6 @@ exports[`settingsSelectors > defaultAgent > should merge DEFAULT_AGENT and s.set
"config": {
"chatConfig": {
"autoCreateTopicThreshold": 2,
"displayMode": "chat",
"enableAutoCreateTopic": true,
"enableCompressHistory": true,
"enableHistoryCount": true,
@@ -145,7 +144,6 @@ exports[`settingsSelectors > defaultAgentConfig > should merge DEFAULT_AGENT_CON
{
"chatConfig": {
"autoCreateTopicThreshold": 2,
"displayMode": "chat",
"enableAutoCreateTopic": true,
"enableCompressHistory": true,
"enableHistoryCount": true,
@@ -19,7 +19,10 @@ describe('settingsSelectors', () => {
animationMode: 'agile',
fontSize: 12,
highlighterTheme: 'lobe-theme',
isDevMode: false,
isLiteMode: false,
mermaidTheme: 'lobe-theme',
telemetry: true,
transitionMode: 'fadeIn',
});
});
@@ -17,6 +17,7 @@ const contextMenuMode = (s: UserStore) => {
if (config !== undefined) return config;
return isDesktop ? 'default' : 'disabled';
};
const telemetry = (s: UserStore) => generalConfig(s).telemetry;
export const userGeneralSettingsSelectors = {
animationMode,
@@ -27,5 +28,6 @@ export const userGeneralSettingsSelectors = {
mermaidTheme,
neutralColor,
primaryColor,
telemetry,
transitionMode,
};
@@ -3,6 +3,7 @@ import {
DEFAULT_AGENT_CONFIG,
DEFAULT_AGENT_META,
DEFAULT_HOTKEY_CONFIG,
DEFAULT_MEMORY_SETTINGS,
DEFAULT_SYSTEM_AGENT_CONFIG,
DEFAULT_TTS_CONFIG,
} from '@lobechat/const';
@@ -27,6 +28,9 @@ export const getProviderConfigById = (provider: string) => (s: UserStore) =>
const currentImageSettings = (s: UserStore) => currentSettings(s).image;
const currentMemorySettings = (s: UserStore) =>
merge(DEFAULT_MEMORY_SETTINGS, currentSettings(s).memory);
const currentTTS = (s: UserStore) => merge(DEFAULT_TTS_CONFIG, currentSettings(s).tts);
const defaultAgent = (s: UserStore) => merge(DEFAULT_AGENT, currentSettings(s).defaultAgent);
@@ -44,6 +48,7 @@ const getHotkeyById = (id: HotkeyId) => (s: UserStore) =>
export const settingsSelectors = {
currentImageSettings,
currentMemorySettings,
currentSettings,
currentSystemAgent,
currentTTS,
+4 -1
View File
@@ -7,6 +7,7 @@ import { createDevtools } from '../middleware/createDevtools';
import { type UserState, initialState } from './initialState';
import { type UserAuthAction, createAuthSlice } from './slices/auth/action';
import { type CommonAction, createCommonSlice } from './slices/common/action';
import { type OnboardingAction, createOnboardingSlice } from './slices/onboarding/action';
import { type PreferenceAction, createPreferenceSlice } from './slices/preference/action';
import { type UserSettingsAction, createSettingsSlice } from './slices/settings/action';
@@ -16,7 +17,8 @@ export type UserStore = UserState &
UserSettingsAction &
PreferenceAction &
UserAuthAction &
CommonAction;
CommonAction &
OnboardingAction;
const createStore: StateCreator<UserStore, [['zustand/devtools', never]]> = (...parameters) => ({
...initialState,
@@ -24,6 +26,7 @@ const createStore: StateCreator<UserStore, [['zustand/devtools', never]]> = (...
...createPreferenceSlice(...parameters),
...createAuthSlice(...parameters),
...createCommonSlice(...parameters),
...createOnboardingSlice(...parameters),
});
// =============== Implement useStore ============ //