@lobehub/chat 0.82.9 → 0.83.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/locales/en_US/error.json +2 -1
  3. package/locales/en_US/plugin.json +1 -0
  4. package/locales/en_US/setting.json +2 -1
  5. package/locales/ru_RU/error.json +2 -1
  6. package/locales/ru_RU/plugin.json +1 -0
  7. package/locales/ru_RU/setting.json +2 -1
  8. package/locales/zh_CN/error.json +1 -0
  9. package/locales/zh_CN/plugin.json +2 -1
  10. package/locales/zh_CN/setting.json +2 -1
  11. package/locales/zh_TW/error.json +2 -1
  12. package/locales/zh_TW/plugin.json +1 -0
  13. package/locales/zh_TW/setting.json +2 -1
  14. package/package.json +6 -5
  15. package/src/app/chat/components/SessionList/List/Item/index.tsx +2 -2
  16. package/src/app/home/index.tsx +15 -11
  17. package/src/app/layout.tsx +4 -1
  18. package/src/app/settings/agent/Agent.tsx +3 -3
  19. package/src/app/settings/agent/page.tsx +19 -5
  20. package/src/app/settings/common/Common.tsx +109 -117
  21. package/src/app/settings/common/page.tsx +18 -5
  22. package/src/app/settings/features/SideBar/List.tsx +11 -5
  23. package/src/app/settings/llm/LLM/index.tsx +2 -2
  24. package/src/app/settings/llm/page.tsx +18 -5
  25. package/src/config/client.ts +13 -0
  26. package/src/const/locale.ts +9 -0
  27. package/src/const/settings.ts +1 -1
  28. package/src/const/url.ts +1 -6
  29. package/src/helpers/export.ts +2 -2
  30. package/src/layout/GlobalLayout/Locale.tsx +44 -0
  31. package/src/layout/GlobalLayout/LocaleLoading.tsx +21 -0
  32. package/src/layout/GlobalLayout/StoreHydration.tsx +28 -0
  33. package/src/layout/GlobalLayout/index.tsx +29 -41
  34. package/src/locales/create.ts +43 -26
  35. package/src/locales/default/setting.ts +2 -1
  36. package/src/locales/options.ts +6 -5
  37. package/src/services/agentMarket.ts +6 -4
  38. package/src/services/pluginMarket.ts +2 -2
  39. package/src/store/global/hooks/useSwitchSettingsOnInit.ts +5 -2
  40. package/src/store/global/{selectors.test.ts → selectors/settings.test.ts} +11 -11
  41. package/src/store/global/selectors/settings.ts +44 -0
  42. package/src/store/global/selectors.ts +1 -36
  43. package/src/store/global/slices/common.ts +3 -4
  44. package/src/types/locale.ts +4 -0
  45. package/src/types/settings.ts +2 -2
  46. package/src/utils/env.ts +2 -0
  47. package/src/utils/responsive.ts +1 -1
  48. package/src/utils/switchLang.ts +5 -1
  49. package/src/app/settings/agent/index.tsx +0 -20
  50. package/src/app/settings/common/index.tsx +0 -20
  51. package/src/app/settings/llm/index.tsx +0 -20
  52. package/src/layout/GlobalLayout/style.ts +0 -18
  53. /package/src/{components/AppTheme/index.tsx → layout/GlobalLayout/AppTheme.tsx} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { useResponsive } from 'antd-style';
2
2
  import { Bot, Settings2, Webhook } from 'lucide-react';
3
- import { useRouter } from 'next/navigation';
3
+ import Link from 'next/link';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
@@ -11,9 +11,9 @@ import Item from './Item';
11
11
 
12
12
  const List = memo(() => {
13
13
  const { t } = useTranslation('setting');
14
- const tab = useGlobalStore((s) => s.settingsTab);
15
- const router = useRouter();
14
+ const [tab, switchSettingTabs] = useGlobalStore((s) => [s.settingsTab, s.switchSettingTabs]);
16
15
  const { mobile } = useResponsive();
16
+
17
17
  const items = [
18
18
  { icon: Settings2, label: t('tab.common'), value: SettingsTabs.Common },
19
19
  { icon: Webhook, label: t('tab.llm'), value: SettingsTabs.LLM },
@@ -21,9 +21,15 @@ const List = memo(() => {
21
21
  ];
22
22
 
23
23
  return items.map(({ value, icon, label }) => (
24
- <div key={value} onClick={() => router.push(`/settings/${value}`)}>
24
+ <Link
25
+ href={`/settings/${value}`}
26
+ key={value}
27
+ onClick={() => {
28
+ switchSettingTabs(value);
29
+ }}
30
+ >
25
31
  <Item active={mobile ? false : tab === value} icon={icon} label={label} />
26
- </div>
32
+ </Link>
27
33
  ));
28
34
  });
29
35
 
@@ -9,7 +9,7 @@ import { Trans, useTranslation } from 'react-i18next';
9
9
  import { Flexbox } from 'react-layout-kit';
10
10
 
11
11
  import { FORM_STYLE } from '@/const/layoutTokens';
12
- import { globalSelectors, useEffectAfterGlobalHydrated, useGlobalStore } from '@/store/global';
12
+ import { settingsSelectors, useEffectAfterGlobalHydrated, useGlobalStore } from '@/store/global';
13
13
 
14
14
  import Checker from './Checker';
15
15
 
@@ -42,7 +42,7 @@ const LLM = memo(() => {
42
42
  const [setSettings] = useGlobalStore((s) => [s.setSettings]);
43
43
 
44
44
  useEffectAfterGlobalHydrated((store) => {
45
- const settings = globalSelectors.currentSettings(store.getState());
45
+ const settings = settingsSelectors.currentSettings(store.getState());
46
46
 
47
47
  form.setFieldsValue(settings);
48
48
  }, []);
@@ -1,7 +1,20 @@
1
- import Page from './index';
1
+ 'use client';
2
2
 
3
- const Index = () => {
4
- return <Page />;
5
- };
3
+ import { memo } from 'react';
6
4
 
7
- export default Index;
5
+ import { useSwitchSideBarOnInit } from '@/store/global/hooks/useSwitchSettingsOnInit';
6
+ import { SettingsTabs } from '@/store/global/initialState';
7
+
8
+ import Layout from '../index';
9
+ import LLM from './LLM';
10
+
11
+ const LLMSetting = memo(() => {
12
+ useSwitchSideBarOnInit(SettingsTabs.LLM);
13
+ return (
14
+ <Layout>
15
+ <LLM />
16
+ </Layout>
17
+ );
18
+ });
19
+
20
+ export default LLMSetting;
@@ -21,6 +21,10 @@ declare global {
21
21
  NEXT_PUBLIC_POSTHOG_KEY: string;
22
22
  NEXT_PUBLIC_POSTHOG_HOST: string;
23
23
  NEXT_PUBLIC_POSTHOG_DEBUG: string;
24
+
25
+ NEXT_PUBLIC_I18N_DEBUG: string;
26
+ NEXT_PUBLIC_I18N_DEBUG_BROWSER: string;
27
+ NEXT_PUBLIC_I18N_DEBUG_SERVER: string;
24
28
  }
25
29
  }
26
30
  }
@@ -29,18 +33,27 @@ export const getClientConfig = () => ({
29
33
  AGENTS_INDEX_URL: process.env.AGENTS_INDEX_URL,
30
34
  PLUGINS_INDEX_URL: process.env.PLUGINS_INDEX_URL,
31
35
 
36
+ // Vercel Analytics
32
37
  ANALYTICS_VERCEL: process.env.NEXT_PUBLIC_ANALYTICS_VERCEL !== '0',
33
38
  VERCEL_DEBUG: process.env.NEXT_PUBLIC_VERCEL_DEBUG === '1',
34
39
 
40
+ // Plausible Analytics
35
41
  ANALYTICS_PLAUSIBLE: process.env.NEXT_PUBLIC_ANALYTICS_PLAUSIBLE === '1',
36
42
  PLAUSIBLE_DOMAIN: process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN,
37
43
 
44
+ // Mixpanel Analytics
38
45
  ANALYTICS_MIXPANEL: process.env.NEXT_PUBLIC_ANALYTICS_MIXPANEL === '1',
39
46
  MIXPANEL_PROJECT_TOKEN: process.env.NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN,
40
47
  MIXPANEL_DEBUG: process.env.NEXT_PUBLIC_MIXPANEL_DEBUG === '1',
41
48
 
49
+ // Posthog Analytics
42
50
  ANALYTICS_POSTHOG: process.env.NEXT_PUBLIC_ANALYTICS_POSTHOG === '1',
43
51
  POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
44
52
  POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
45
53
  POSTHOG_DEBUG: process.env.NEXT_PUBLIC_POSTHOG_DEBUG === '1',
54
+
55
+ // i18n debug mode
56
+ I18N_DEBUG: process.env.NEXT_PUBLIC_I18N_DEBUG === '1',
57
+ I18N_DEBUG_BROWSER: process.env.NEXT_PUBLIC_I18N_DEBUG_BROWSER === '1',
58
+ I18N_DEBUG_SERVER: process.env.NEXT_PUBLIC_I18N_DEBUG_SERVER === '1',
46
59
  });
@@ -0,0 +1,9 @@
1
+ import { localeOptions } from '@/locales/options';
2
+ import { Locales } from '@/locales/resources';
3
+
4
+ export const DEFAULT_LANG = 'en-US';
5
+ export const LOBE_LOCALE_COOKIE = 'LOBE_LOCALE';
6
+
7
+ export const checkLang = (lang: Locales) => {
8
+ return lang === DEFAULT_LANG || !localeOptions.map((o) => o.value).includes(lang);
9
+ };
@@ -12,7 +12,7 @@ import {
12
12
  export const DEFAULT_BASE_SETTINGS: GlobalBaseSettings = {
13
13
  avatar: '',
14
14
  fontSize: 14,
15
- language: 'en-US',
15
+ language: 'auto',
16
16
  password: '',
17
17
  themeMode: 'auto',
18
18
  };
package/src/const/url.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import urlJoin from 'url-join';
2
2
 
3
3
  import { getClientConfig } from '@/config/client';
4
- import { localeOptions } from '@/locales/options';
5
4
  import { Locales } from '@/locales/resources';
6
5
 
7
6
  import pkg from '../../package.json';
7
+ import { DEFAULT_LANG, checkLang } from './locale';
8
8
  import { INBOX_SESSION_ID } from './session';
9
9
 
10
10
  export const GITHUB = pkg.homepage;
@@ -16,11 +16,6 @@ export const DISCORD = 'https://discord.gg/AYFPHvv2jT';
16
16
  export const PLUGINS_INDEX_URL =
17
17
  getClientConfig().PLUGINS_INDEX_URL ?? 'https://chat-plugins.lobehub.com';
18
18
 
19
- export const DEFAULT_LANG = 'en-US';
20
-
21
- export const checkLang = (lang: Locales) => {
22
- return lang === DEFAULT_LANG || !localeOptions.map((o) => o.value).includes(lang);
23
- };
24
19
  export const getPluginIndexJSON = (lang: Locales = DEFAULT_LANG, baseUrl = PLUGINS_INDEX_URL) => {
25
20
  if (checkLang(lang)) return baseUrl;
26
21
 
@@ -1,4 +1,4 @@
1
- import { globalSelectors, useGlobalStore } from '@/store/global';
1
+ import { settingsSelectors, useGlobalStore } from '@/store/global';
2
2
  import { useSessionStore } from '@/store/session';
3
3
  import { sessionSelectors } from '@/store/session/selectors';
4
4
  import { createConfigFile, exportConfigFile } from '@/utils/config';
@@ -6,7 +6,7 @@ import { createConfigFile, exportConfigFile } from '@/utils/config';
6
6
  const getSessions = () => sessionSelectors.exportSessions(useSessionStore.getState());
7
7
  const getSession = (id: string) => sessionSelectors.getSessionById(id)(useSessionStore.getState());
8
8
 
9
- const getSettings = () => globalSelectors.exportSettings(useGlobalStore.getState());
9
+ const getSettings = () => settingsSelectors.exportSettings(useGlobalStore.getState());
10
10
 
11
11
  // ============= 导出所有角色 ============= //
12
12
 
@@ -0,0 +1,44 @@
1
+ import { ConfigProvider } from 'antd';
2
+ import Zh_CN from 'antd/locale/zh_CN';
3
+ import { PropsWithChildren, memo, useState } from 'react';
4
+
5
+ import { createI18nNext } from '@/locales/create';
6
+ import { useOnFinishHydrationGlobal } from '@/store/global';
7
+ import { isOnServerSide } from '@/utils/env';
8
+ import { switchLang } from '@/utils/switchLang';
9
+
10
+ interface LocaleLayoutProps extends PropsWithChildren {
11
+ lang?: string;
12
+ }
13
+
14
+ const InnerLocale = memo<LocaleLayoutProps>(({ children, lang }) => {
15
+ const [i18n] = useState(createI18nNext(lang));
16
+
17
+ // if run on server side, init i18n instance everytime
18
+ if (isOnServerSide) {
19
+ i18n.init();
20
+ } else {
21
+ // if on browser side, init i18n instance only once
22
+ if (!i18n.instance.isInitialized)
23
+ // console.log('locale', lang);
24
+ i18n.init().then(() => {
25
+ // console.log('inited.');
26
+ });
27
+ }
28
+
29
+ useOnFinishHydrationGlobal((s) => {
30
+ if (s.settings.language === 'auto') {
31
+ switchLang('auto');
32
+ }
33
+ }, []);
34
+
35
+ return <ConfigProvider locale={Zh_CN}>{children}</ConfigProvider>;
36
+ });
37
+
38
+ // const Locale = memo<LocaleLayoutProps>((props) => (
39
+ // <Suspense fallback={<Loading />}>
40
+ // <InnerLocale {...props} />
41
+ // </Suspense>
42
+ // ));
43
+
44
+ export default InnerLocale;
@@ -0,0 +1,21 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { Loader2 } from 'lucide-react';
3
+ import { memo } from 'react';
4
+ import { Center } from 'react-layout-kit';
5
+
6
+ const Loading = memo(() => (
7
+ <div
8
+ style={{
9
+ backdropFilter: 'blur(3px)',
10
+ height: '100vh',
11
+ width: '100vw',
12
+ zIndex: 100,
13
+ }}
14
+ >
15
+ <Center flex={1} gap={12} width={'100%'}>
16
+ <Icon icon={Loader2} spin />
17
+ </Center>
18
+ </div>
19
+ ));
20
+
21
+ export default Loading;
@@ -0,0 +1,28 @@
1
+ import { useRouter } from 'next/navigation';
2
+ import { memo, useEffect } from 'react';
3
+
4
+ import { useGlobalStore } from '@/store/global';
5
+ import { usePluginStore } from '@/store/plugin';
6
+ import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
7
+
8
+ const StoreHydration = memo(() => {
9
+ const router = useRouter();
10
+
11
+ useEffect(() => {
12
+ // refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
13
+ useSessionStore.persist.rehydrate();
14
+ useGlobalStore.persist.rehydrate();
15
+ usePluginStore.persist.rehydrate();
16
+ }, []);
17
+
18
+ useOnFinishHydrationSession((s, store) => {
19
+ usePluginStore.getState().checkLocalEnabledPlugins(s.sessions);
20
+
21
+ // add router instance to store
22
+ store.setState({ router });
23
+ });
24
+
25
+ return null;
26
+ });
27
+
28
+ export default StoreHydration;
@@ -1,56 +1,44 @@
1
1
  'use client';
2
2
 
3
- import { App, ConfigProvider } from 'antd';
3
+ import { App } from 'antd';
4
+ import { createStyles } from 'antd-style';
4
5
  import 'antd/dist/reset.css';
5
- import Zh_CN from 'antd/locale/zh_CN';
6
- import { useRouter } from 'next/navigation';
7
- import { PropsWithChildren, memo, useEffect } from 'react';
6
+ import { PropsWithChildren, memo } from 'react';
8
7
 
9
- import AppTheme, { AppThemeProps } from '@/components/AppTheme';
10
- import { createI18nNext } from '@/locales/create';
11
- import { useGlobalStore, useOnFinishHydrationGlobal } from '@/store/global';
12
- import { usePluginStore } from '@/store/plugin';
13
- import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
14
- import { switchLang } from '@/utils/switchLang';
8
+ import AppTheme, { AppThemeProps } from './AppTheme';
9
+ import Locale from './Locale';
10
+ import StoreHydration from './StoreHydration';
15
11
 
16
- import { useStyles } from './style';
12
+ const useStyles = createStyles(({ css, token }) => ({
13
+ bg: css`
14
+ overflow-y: scroll;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
17
18
 
18
- const i18n = createI18nNext();
19
+ height: 100%;
19
20
 
20
- const Layout = memo<PropsWithChildren>(({ children }) => {
21
- const { styles } = useStyles();
22
-
23
- const router = useRouter();
24
-
25
- useEffect(() => {
26
- // refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
27
- useSessionStore.persist.rehydrate();
28
- useGlobalStore.persist.rehydrate();
29
- usePluginStore.persist.rehydrate();
30
- }, []);
31
-
32
- useOnFinishHydrationGlobal((state) => {
33
- i18n.then(() => switchLang(state.settings.language));
34
- });
21
+ background: ${token.colorBgLayout};
22
+ `,
23
+ }));
35
24
 
36
- useOnFinishHydrationSession((s, store) => {
37
- usePluginStore.getState().checkLocalEnabledPlugins(s.sessions);
38
-
39
- // add router instance to store
40
- store.setState({ router });
41
- });
25
+ const Container = memo<PropsWithChildren>(({ children }) => {
26
+ const { styles } = useStyles();
42
27
 
43
- return (
44
- <ConfigProvider locale={Zh_CN}>
45
- <App className={styles.bg}>{children}</App>
46
- </ConfigProvider>
47
- );
28
+ return <App className={styles.bg}>{children}</App>;
48
29
  });
49
30
 
50
- const ThemeWrapper = ({ children, ...theme }: AppThemeProps) => (
31
+ interface GlobalLayoutProps extends AppThemeProps {
32
+ defaultLang?: string;
33
+ }
34
+
35
+ const GlobalLayout = ({ children, defaultLang, ...theme }: GlobalLayoutProps) => (
51
36
  <AppTheme {...theme}>
52
- <Layout>{children}</Layout>
37
+ <Locale lang={defaultLang}>
38
+ <StoreHydration />
39
+ <Container>{children}</Container>
40
+ </Locale>
53
41
  </AppTheme>
54
42
  );
55
43
 
56
- export default ThemeWrapper;
44
+ export default GlobalLayout;
@@ -1,36 +1,53 @@
1
1
  import i18n from 'i18next';
2
2
  import LanguageDetector from 'i18next-browser-languagedetector';
3
- import { isArray } from 'lodash-es';
4
3
  import { initReactI18next } from 'react-i18next';
5
4
 
5
+ import { getClientConfig } from '@/config/client';
6
+ import { DEFAULT_LANG, LOBE_LOCALE_COOKIE } from '@/const/locale';
7
+ import resources from '@/locales/resources';
6
8
  import type { Namespaces } from '@/types/locale';
9
+ import { isOnServerSide } from '@/utils/env';
7
10
 
8
- import resources from './resources';
11
+ const { I18N_DEBUG, I18N_DEBUG_BROWSER, I18N_DEBUG_SERVER } = getClientConfig();
12
+ const debugMode = I18N_DEBUG ?? isOnServerSide ? I18N_DEBUG_SERVER : I18N_DEBUG_BROWSER;
9
13
 
10
- const isDev = process.env.NODE_ENV === 'development';
14
+ export const createI18nNext = (lang?: string) => {
15
+ const ns: Namespaces[] = [
16
+ 'error',
17
+ 'common',
18
+ 'empty',
19
+ 'error',
20
+ 'market',
21
+ 'plugin',
22
+ 'setting',
23
+ 'welcome',
24
+ ];
11
25
 
12
- export const createI18nNext = (params: { namespace?: Namespaces[] | Namespaces } = {}) => {
13
- const { namespace } = params;
26
+ const instance = i18n.use(initReactI18next).use(LanguageDetector);
27
+ // not find a good way to handle suspense loading
28
+ // .use(
29
+ // resourcesToBackend(
30
+ // isDev ? resources : (lng: string, ns: string) => import(`../../locales/${lng}/${ns}.json`),
31
+ // ),
32
+ // )
14
33
 
15
- const ns: Namespaces[] = isArray(namespace)
16
- ? ['error', 'common', ...namespace]
17
- : (['error', 'common', namespace].filter(Boolean) as Namespaces[]);
18
-
19
- return i18n
20
- .use(LanguageDetector)
21
- .use(initReactI18next)
22
- .init({
23
- debug: isDev,
24
- defaultNS: ns,
25
- detection: {
26
- caches: [],
27
- },
28
- fallbackLng: isDev ? 'zh-CN' : 'en-US',
29
- interpolation: {
30
- escapeValue: false,
31
- },
32
- lng: isDev ? 'zh-CN' : 'en-US',
33
- ns,
34
- resources,
35
- });
34
+ return {
35
+ init: () =>
36
+ instance.init({
37
+ debug: debugMode,
38
+ defaultNS: ['error', 'common'],
39
+ detection: {
40
+ caches: ['cookie'],
41
+ lookupCookie: LOBE_LOCALE_COOKIE,
42
+ },
43
+ fallbackLng: DEFAULT_LANG,
44
+ interpolation: {
45
+ escapeValue: false,
46
+ },
47
+ lng: lang,
48
+ ns,
49
+ resources,
50
+ }),
51
+ instance,
52
+ };
36
53
  };
@@ -198,7 +198,8 @@ export default {
198
198
  title: '字体大小',
199
199
  },
200
200
  lang: {
201
- title: '语言设置',
201
+ autoMode: '跟随系统',
202
+ title: '语言',
202
203
  },
203
204
  neutralColor: {
204
205
  desc: '不同色彩倾向的灰阶自定义',
@@ -1,11 +1,10 @@
1
- import type { SelectProps } from 'antd';
2
1
 
3
2
  import type { Locales } from '@/types/locale';
4
3
 
5
- type LocaleOptions = SelectProps['options'] &
6
- {
7
- value: Locales;
8
- }[];
4
+ type LocaleOptions = {
5
+ label: string;
6
+ value: Locales;
7
+ }[];
9
8
 
10
9
  export const localeOptions: LocaleOptions = [
11
10
  {
@@ -25,3 +24,5 @@ export const localeOptions: LocaleOptions = [
25
24
  value: 'ru-RU',
26
25
  },
27
26
  ] as LocaleOptions;
27
+
28
+ export const supportLangs: string[] = localeOptions.map((i) => i.value);
@@ -1,11 +1,11 @@
1
- import { getAgentIndexJSON, getAgentJSON } from '@/const/url';
2
- import { useGlobalStore } from '@/store/global';
1
+ import { getAgentJSON } from '@/const/url';
2
+ import { settingsSelectors, useGlobalStore } from '@/store/global';
3
3
 
4
4
  /**
5
5
  * 请求助手列表
6
6
  */
7
7
  export const getAgentList = async () => {
8
- const res = await fetch(getAgentIndexJSON(useGlobalStore.getState().settings.language));
8
+ const res = await fetch(settingsSelectors.currentLanguage(useGlobalStore.getState()));
9
9
 
10
10
  return res.json();
11
11
  };
@@ -15,7 +15,9 @@ export const getAgentList = async () => {
15
15
  */
16
16
  export const getAgentManifest = async (identifier?: string) => {
17
17
  if (!identifier) return;
18
- const res = await fetch(getAgentJSON(identifier, useGlobalStore.getState().settings.language));
18
+ const res = await fetch(
19
+ getAgentJSON(identifier, settingsSelectors.currentLanguage(useGlobalStore.getState())),
20
+ );
19
21
 
20
22
  return res.json();
21
23
  };
@@ -1,11 +1,11 @@
1
1
  import { getPluginIndexJSON } from '@/const/url';
2
- import { useGlobalStore } from '@/store/global';
2
+ import { settingsSelectors, useGlobalStore } from '@/store/global';
3
3
 
4
4
  /**
5
5
  * fetch Plugin Market List
6
6
  */
7
7
  export const getPluginList = async () => {
8
- const url = getPluginIndexJSON(useGlobalStore.getState().settings.language);
8
+ const url = getPluginIndexJSON(settingsSelectors.currentLanguage(useGlobalStore.getState()));
9
9
 
10
10
  const res = await fetch(url);
11
11
 
@@ -1,9 +1,12 @@
1
+ import { useOnFinishHydrationGlobal } from '@/store/global';
2
+
1
3
  import { SettingsTabs } from '../initialState';
2
- import { useGlobalStore } from '../store';
3
4
 
4
5
  /**
5
6
  * 切换设置侧边栏选项
6
7
  */
7
8
  export const useSwitchSideBarOnInit = (key: SettingsTabs) => {
8
- useGlobalStore.getState().switchSettingTabs(key);
9
+ useOnFinishHydrationGlobal((store) => {
10
+ store.switchSettingTabs(key);
11
+ });
9
12
  };
@@ -1,9 +1,9 @@
1
1
  import { LanguageModel } from '@/types/llm';
2
2
 
3
- import { globalSelectors } from './selectors';
4
- import { GlobalStore } from './store';
3
+ import { GlobalStore } from '../store';
4
+ import { settingsSelectors } from './settings';
5
5
 
6
- describe('globalSelectors', () => {
6
+ describe('settingsSelectors', () => {
7
7
  describe('currentSettings', () => {
8
8
  it('should merge DEFAULT_SETTINGS and s.settings correctly', () => {
9
9
  const s = {
@@ -36,7 +36,7 @@ describe('globalSelectors', () => {
36
36
  },
37
37
  } as unknown as GlobalStore;
38
38
 
39
- const result = globalSelectors.currentSettings(s);
39
+ const result = settingsSelectors.currentSettings(s);
40
40
 
41
41
  expect(result).toEqual({
42
42
  avatar: 'avatar.jpg',
@@ -93,7 +93,7 @@ describe('globalSelectors', () => {
93
93
  // },
94
94
  // };
95
95
  //
96
- // const result = globalSelectors.defaultAgent(s);
96
+ // const result = settingsSelectors.defaultAgent(s);
97
97
  //
98
98
  //
99
99
  //
@@ -114,9 +114,9 @@ describe('globalSelectors', () => {
114
114
  // },
115
115
  // };
116
116
  //
117
- // const result = globalSelectors.defaultAgentConfig(s);
117
+ // const result = settingsSelectors.defaultAgentConfig(s);
118
118
  //
119
- // const defaultAgent = globalSelectors.defaultAgent(s);
119
+ // const defaultAgent = settingsSelectors.defaultAgent(s);
120
120
  // const expected = merge({}, DEFAULT_AGENT_CONFIG, defaultAgent.config);
121
121
  //
122
122
  // expect(result).toEqual(expected);
@@ -136,9 +136,9 @@ describe('globalSelectors', () => {
136
136
  // },
137
137
  // };
138
138
  //
139
- // const result = globalSelectors.defaultAgentMeta(s);
139
+ // const result = settingsSelectors.defaultAgentMeta(s);
140
140
  //
141
- // const defaultAgent = globalSelectors.defaultAgent(s);
141
+ // const defaultAgent = settingsSelectors.defaultAgent(s);
142
142
  // const expected = merge({}, DEFAULT_AGENT_META, defaultAgent.meta);
143
143
  //
144
144
  // expect(result).toEqual(expected);
@@ -183,7 +183,7 @@ describe('globalSelectors', () => {
183
183
  // },
184
184
  // };
185
185
  //
186
- // const result = globalSelectors.exportSettings(s);
186
+ // const result = settingsSelectors.exportSettings(s);
187
187
  //
188
188
  // const expected = {
189
189
  // avatar: 'avatar.jpg',
@@ -255,7 +255,7 @@ describe('globalSelectors', () => {
255
255
  // },
256
256
  // };
257
257
  //
258
- // const result = globalSelectors.exportSettings(s);
258
+ // const result = settingsSelectors.exportSettings(s);
259
259
  //
260
260
  // expect(result).toBeInstanceOf(GlobalSettings);
261
261
  // });
@@ -0,0 +1,44 @@
1
+ import { DEFAULT_AGENT_META } from '@/const/meta';
2
+ import { DEFAULT_AGENT, DEFAULT_AGENT_CONFIG, DEFAULT_SETTINGS } from '@/const/settings';
3
+ import { Locales } from '@/locales/resources';
4
+ import { GlobalSettings } from '@/types/settings';
5
+ import { merge } from '@/utils/merge';
6
+
7
+ import { GlobalStore } from '../store';
8
+
9
+ const currentSettings = (s: GlobalStore) => merge(DEFAULT_SETTINGS, s.settings);
10
+
11
+ const defaultAgent = (s: GlobalStore) => merge(DEFAULT_AGENT, s.settings.defaultAgent);
12
+
13
+ const defaultAgentConfig = (s: GlobalStore) => merge(DEFAULT_AGENT_CONFIG, defaultAgent(s).config);
14
+
15
+ const defaultAgentMeta = (s: GlobalStore) => merge(DEFAULT_AGENT_META, defaultAgent(s).meta);
16
+
17
+ const openAIAPIKeySelectors = (s: GlobalStore) =>
18
+ s.settings.languageModel.openAI.OPENAI_API_KEY || s.settings.OPENAI_API_KEY;
19
+
20
+ export const exportSettings = (s: GlobalStore) => {
21
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
22
+ const { OPENAI_API_KEY: _, password: __, ...settings } = s.settings;
23
+
24
+ return settings as GlobalSettings;
25
+ };
26
+
27
+ const currentLanguage = (s: GlobalStore) => {
28
+ const locale = s.settings.language;
29
+ if (locale === 'auto') {
30
+ return navigator.language as Locales;
31
+ }
32
+
33
+ return locale;
34
+ };
35
+
36
+ export const settingsSelectors = {
37
+ currentLanguage,
38
+ currentSettings,
39
+ defaultAgent,
40
+ defaultAgentConfig,
41
+ defaultAgentMeta,
42
+ exportSettings,
43
+ openAIAPI: openAIAPIKeySelectors,
44
+ };