@lobehub/chat 0.82.8 → 0.83.0

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 (125) hide show
  1. package/CHANGELOG.md +42 -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/{features/Header → (desktop)/features/ChatHeader}/Desktop.tsx +2 -2
  16. package/src/app/chat/{features/Header → (desktop)/features/ChatHeader}/index.tsx +1 -1
  17. package/src/app/chat/(desktop)/features/ChatInput/index.tsx +22 -0
  18. package/src/app/chat/{features/SessionList/Header → (desktop)/features/SessionHeader}/Desktop.tsx +1 -1
  19. package/src/app/chat/{features/SessionList/Header → (desktop)/features/SessionHeader}/index.tsx +1 -1
  20. package/src/app/chat/{features → (desktop)/features}/SessionList/Desktop.tsx +1 -1
  21. package/src/app/chat/(desktop)/features/SessionList/index.tsx +20 -0
  22. package/src/app/chat/{features/Sidebar → (desktop)/features/SideBar}/Desktop.tsx +4 -4
  23. package/src/app/chat/(desktop)/features/SideBar/index.tsx +20 -0
  24. package/src/app/chat/(desktop)/index.tsx +24 -0
  25. package/src/app/chat/{layout.desktop.tsx → (desktop)/layout/Desktop.tsx} +4 -4
  26. package/src/app/chat/(desktop)/layout/index.tsx +25 -0
  27. package/src/app/chat/(mobile)/features/ChatHeader.tsx +1 -0
  28. package/src/app/chat/(mobile)/features/ChatInput.tsx +14 -0
  29. package/src/app/chat/(mobile)/features/SessionList.tsx +12 -0
  30. package/src/app/chat/(mobile)/features/Topics.tsx +12 -0
  31. package/src/app/chat/(mobile)/index.tsx +20 -0
  32. package/src/app/chat/(mobile)/layout.tsx +15 -0
  33. package/src/app/chat/(mobile)/mobile/page.tsx +21 -0
  34. package/src/app/chat/{features/Header → components/ChatHeader}/Mobile.tsx +2 -1
  35. package/src/app/chat/{features/Header → components/ChatHeader}/ShareButton/Preview.tsx +3 -3
  36. package/src/app/chat/{features/Conversation/Input → components/ChatInput}/index.tsx +23 -21
  37. package/src/app/chat/components/Inspector/index.tsx +17 -0
  38. package/src/app/chat/{features → components}/SessionList/List/Item/index.tsx +2 -2
  39. package/src/app/chat/{features → components}/SessionList/Mobile.tsx +1 -1
  40. package/src/app/chat/{features → components}/SessionList/index.tsx +3 -10
  41. package/src/app/chat/features/Conversation/index.tsx +8 -4
  42. package/src/app/chat/features/PageTitle.tsx +19 -0
  43. package/src/app/chat/layout.tsx +14 -0
  44. package/src/app/chat/page.tsx +9 -4
  45. package/src/app/chat/settings/(desktop)/Header/index.tsx +13 -0
  46. package/src/app/chat/settings/{layout.desktop.tsx → (desktop)/layout.desktop.tsx} +5 -4
  47. package/src/app/chat/settings/(mobile)/Header.tsx +12 -0
  48. package/src/app/chat/settings/{layout.mobile.tsx → (mobile)/layout.mobile.tsx} +3 -1
  49. package/src/app/chat/settings/{features → components}/Header/index.tsx +4 -13
  50. package/src/app/chat/settings/layout.tsx +14 -0
  51. package/src/app/chat/settings/page.tsx +36 -5
  52. package/src/app/home/index.tsx +15 -11
  53. package/src/app/layout.tsx +4 -1
  54. package/src/app/metadata.ts +4 -1
  55. package/src/app/settings/agent/Agent.tsx +3 -3
  56. package/src/app/settings/agent/index.tsx +1 -0
  57. package/src/app/settings/common/Common.tsx +109 -117
  58. package/src/app/settings/features/SideBar/List.tsx +11 -5
  59. package/src/app/settings/llm/LLM/index.tsx +2 -2
  60. package/src/app/welcome/page.tsx +2 -6
  61. package/src/components/PageTitle.tsx +11 -0
  62. package/src/config/client.ts +13 -0
  63. package/src/const/locale.ts +9 -0
  64. package/src/const/settings.ts +1 -1
  65. package/src/const/url.ts +1 -6
  66. package/src/helpers/export.ts +2 -2
  67. package/src/layout/AppMobileLayout.tsx +15 -24
  68. package/src/layout/GlobalLayout/Locale.tsx +44 -0
  69. package/src/layout/GlobalLayout/LocaleLoading.tsx +21 -0
  70. package/src/layout/GlobalLayout/StoreHydration.tsx +28 -0
  71. package/src/layout/GlobalLayout/index.tsx +29 -41
  72. package/src/locales/create.ts +43 -26
  73. package/src/locales/default/setting.ts +2 -1
  74. package/src/locales/options.ts +6 -5
  75. package/src/services/agentMarket.ts +6 -4
  76. package/src/services/pluginMarket.ts +2 -2
  77. package/src/store/global/hooks/useSwitchSettingsOnInit.ts +5 -2
  78. package/src/store/global/{selectors.test.ts → selectors/settings.test.ts} +11 -11
  79. package/src/store/global/selectors/settings.ts +44 -0
  80. package/src/store/global/selectors.ts +1 -36
  81. package/src/store/global/slices/common.ts +3 -4
  82. package/src/types/locale.ts +4 -0
  83. package/src/types/settings.ts +2 -2
  84. package/src/utils/env.ts +2 -0
  85. package/src/utils/responsive.ts +1 -0
  86. package/src/utils/switchLang.ts +5 -1
  87. package/src/app/chat/features/Sidebar/index.tsx +0 -29
  88. package/src/app/chat/index.tsx +0 -54
  89. package/src/app/chat/layout.mobile.tsx +0 -20
  90. package/src/app/chat/mobile/index.tsx +0 -38
  91. package/src/app/chat/mobile/page.tsx +0 -7
  92. package/src/app/chat/settings/index.tsx +0 -51
  93. package/src/layout/GlobalLayout/style.ts +0 -18
  94. /package/src/app/chat/{features/Conversation/Input → (desktop)/features/ChatInput}/Desktop.tsx +0 -0
  95. /package/src/app/chat/{features/SessionList/Header/Mobile.tsx → (mobile)/features/SessionHeader.tsx} +0 -0
  96. /package/src/app/chat/{features/Header → components/ChatHeader}/PluginTag/PluginStatus.tsx +0 -0
  97. /package/src/app/chat/{features/Header → components/ChatHeader}/PluginTag/index.tsx +0 -0
  98. /package/src/app/chat/{features/Header → components/ChatHeader}/ShareButton/Inner.tsx +0 -0
  99. /package/src/app/chat/{features/Header → components/ChatHeader}/ShareButton/index.tsx +0 -0
  100. /package/src/app/chat/{features/Header → components/ChatHeader}/ShareButton/style.ts +0 -0
  101. /package/src/app/chat/{features/Conversation/Input → components/ChatInput}/ActionBar/ActionLeft.tsx +0 -0
  102. /package/src/app/chat/{features/Conversation/Input → components/ChatInput}/ActionBar/ActionRight.tsx +0 -0
  103. /package/src/app/chat/{features/Conversation/Input → components/ChatInput}/ActionBar/Token.tsx +0 -0
  104. /package/src/app/chat/{features/Conversation/Input → components/ChatInput}/Footer/SaveTopic.tsx +0 -0
  105. /package/src/app/chat/{features/Conversation/Input → components/ChatInput}/Footer/index.tsx +0 -0
  106. /package/src/app/chat/{features/Conversation/Input → components/ChatInput}/Mobile.tsx +0 -0
  107. /package/src/app/chat/{features/Sidebar → components/Inspector}/Mobile.tsx +0 -0
  108. /package/src/app/chat/{features/Sidebar → components/Inspector}/SystemRole/Header.tsx +0 -0
  109. /package/src/app/chat/{features/Sidebar → components/Inspector}/SystemRole/index.tsx +0 -0
  110. /package/src/app/chat/{features/Sidebar → components/Inspector}/SystemRole/style.ts +0 -0
  111. /package/src/app/chat/{features/Sidebar → components/Inspector}/Topic/DefaultContent.tsx +0 -0
  112. /package/src/app/chat/{features/Sidebar → components/Inspector}/Topic/SkeletonList.tsx +0 -0
  113. /package/src/app/chat/{features/Sidebar → components/Inspector}/Topic/TopicContent.tsx +0 -0
  114. /package/src/app/chat/{features/Sidebar → components/Inspector}/Topic/TopicItem.tsx +0 -0
  115. /package/src/app/chat/{features/Sidebar → components/Inspector}/Topic/index.tsx +0 -0
  116. /package/src/app/chat/{features/Sidebar → components/Inspector}/TopicSearchBar/index.tsx +0 -0
  117. /package/src/app/chat/{features → components}/SessionList/CollapseGroup/index.tsx +0 -0
  118. /package/src/app/chat/{features → components}/SessionList/Inbox/index.tsx +0 -0
  119. /package/src/app/chat/{features → components}/SessionList/List/AddButton.tsx +0 -0
  120. /package/src/app/chat/{features → components}/SessionList/List/Item/Actions.tsx +0 -0
  121. /package/src/app/chat/{features → components}/SessionList/List/SkeletonList.tsx +0 -0
  122. /package/src/app/chat/{features → components}/SessionList/List/index.tsx +0 -0
  123. /package/src/app/chat/settings/{features → (desktop)}/Header/Desktop.tsx +0 -0
  124. /package/src/app/chat/settings/{features → components}/Header/Mobile.tsx +0 -0
  125. /package/src/{components/AppTheme/index.tsx → layout/GlobalLayout/AppTheme.tsx} +0 -0
@@ -3,32 +3,27 @@ import { Form as AntForm, App, Button, Input, Select } from 'antd';
3
3
  import isEqual from 'fast-deep-equal';
4
4
  import { debounce } from 'lodash-es';
5
5
  import { AppWindow, Monitor, Moon, Palette, Sun } from 'lucide-react';
6
- import { memo, useCallback, useMemo } from 'react';
6
+ import { memo, useCallback } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
9
  import { FORM_STYLE } from '@/const/layoutTokens';
10
10
  import { DEFAULT_SETTINGS } from '@/const/settings';
11
11
  import AvatarWithUpload from '@/features/AvatarWithUpload';
12
12
  import { localeOptions } from '@/locales/options';
13
- import { globalSelectors, useGlobalStore } from '@/store/global';
13
+ import { settingsSelectors, useGlobalStore } from '@/store/global';
14
14
  import { useSessionStore } from '@/store/session';
15
- import { ConfigKeys } from '@/types/settings';
16
15
  import { switchLang } from '@/utils/switchLang';
17
16
 
18
17
  import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from '../features/ThemeSwatches';
19
18
 
20
- type SettingItemGroup = ItemGroup & {
21
- children: {
22
- name?: ConfigKeys | string;
23
- }[];
24
- };
19
+ type SettingItemGroup = ItemGroup;
25
20
 
26
21
  const Common = memo(() => {
27
22
  const { t } = useTranslation('setting');
28
23
  const [form] = AntForm.useForm();
29
24
  const clearSessions = useSessionStore((s) => s.clearSessions);
30
25
 
31
- const settings = useGlobalStore(globalSelectors.currentSettings, isEqual);
26
+ const settings = useGlobalStore(settingsSelectors.currentSettings, isEqual);
32
27
  const [setThemeMode, setSettings, resetSettings] = useGlobalStore((s) => [
33
28
  s.switchThemeMode,
34
29
  s.setSettings,
@@ -36,6 +31,7 @@ const Common = memo(() => {
36
31
  ]);
37
32
 
38
33
  const { message, modal } = App.useApp();
34
+
39
35
  const handleReset = useCallback(() => {
40
36
  modal.confirm({
41
37
  cancelText: t('cancel', { ns: 'common' }),
@@ -49,7 +45,6 @@ const Common = memo(() => {
49
45
  title: t('danger.reset.confirm'),
50
46
  });
51
47
  }, []);
52
-
53
48
  const handleClear = useCallback(() => {
54
49
  modal.confirm({
55
50
  cancelText: t('cancel', { ns: 'common' }),
@@ -66,119 +61,116 @@ const Common = memo(() => {
66
61
  });
67
62
  }, []);
68
63
 
69
- const theme: SettingItemGroup = useMemo(
70
- () => ({
71
- children: [
72
- {
73
- children: <AvatarWithUpload />,
74
- label: t('settingTheme.avatar.title'),
75
- minWidth: undefined,
76
- },
77
- {
78
- children: (
79
- <SelectWithImg
80
- defaultValue={settings.themeMode}
81
- height={60}
82
- onChange={setThemeMode}
83
- options={[
84
- {
85
- icon: Sun,
86
- img: '/images/theme_light.webp',
87
- label: t('settingTheme.themeMode.light'),
88
- value: 'light',
89
- },
90
- {
91
- icon: Moon,
92
- img: '/images/theme_dark.webp',
93
- label: t('settingTheme.themeMode.dark'),
94
- value: 'dark',
95
- },
96
- {
97
- icon: Monitor,
98
- img: '/images/theme_auto.webp',
99
- label: t('settingTheme.themeMode.auto'),
100
- value: 'auto',
101
- },
102
- ]}
103
- width={100}
104
- />
105
- ),
106
- label: t('settingTheme.themeMode.title'),
107
- minWidth: undefined,
108
- },
109
- {
110
- children: <Select onChange={switchLang} options={localeOptions} />,
111
- label: t('settingTheme.lang.title'),
112
- name: 'language',
113
- },
114
- {
115
- children: <SliderWithInput max={18} min={12} />,
116
- desc: t('settingTheme.fontSize.desc'),
117
- label: t('settingTheme.fontSize.title'),
118
- name: 'fontSize',
119
- },
120
- {
121
- children: <ThemeSwatchesPrimary />,
122
- desc: t('settingTheme.primaryColor.desc'),
123
- label: t('settingTheme.primaryColor.title'),
124
- minWidth: undefined,
125
- },
126
- {
127
- children: <ThemeSwatchesNeutral />,
128
- desc: t('settingTheme.neutralColor.desc'),
129
- label: t('settingTheme.neutralColor.title'),
130
- minWidth: undefined,
131
- },
132
- ],
133
- icon: Palette,
134
- title: t('settingTheme.title'),
135
- }),
136
- [settings],
137
- );
138
-
139
- const system: SettingItemGroup = useMemo(
140
- () => ({
141
- children: [
142
- {
143
- children: <Input.Password placeholder={t('settingSystem.accessCode.placeholder')} />,
144
- desc: t('settingSystem.accessCode.desc'),
145
- label: t('settingSystem.accessCode.title'),
146
- name: 'password',
147
- },
148
- {
149
- children: (
150
- <Button danger onClick={handleReset} type="primary">
151
- {t('danger.reset.action')}
152
- </Button>
153
- ),
154
- desc: t('danger.reset.title'),
155
- label: t('danger.reset.desc'),
156
- minWidth: undefined,
157
- },
158
- {
159
- children: (
160
- <Button danger onClick={handleClear} type="primary">
161
- {t('danger.clear.action')}
162
- </Button>
163
- ),
164
- desc: t('danger.clear.title'),
165
- label: t('danger.clear.desc'),
166
- minWidth: undefined,
167
- },
168
- ],
169
- icon: AppWindow,
170
- title: t('settingSystem.title'),
171
- }),
172
- [settings],
173
- );
64
+ const theme: SettingItemGroup = {
65
+ children: [
66
+ {
67
+ children: <AvatarWithUpload />,
68
+ label: t('settingTheme.avatar.title'),
69
+ minWidth: undefined,
70
+ },
71
+ {
72
+ children: (
73
+ <SelectWithImg
74
+ defaultValue={settings.themeMode}
75
+ height={60}
76
+ onChange={setThemeMode}
77
+ options={[
78
+ {
79
+ icon: Sun,
80
+ img: '/images/theme_light.webp',
81
+ label: t('settingTheme.themeMode.light'),
82
+ value: 'light',
83
+ },
84
+ {
85
+ icon: Moon,
86
+ img: '/images/theme_dark.webp',
87
+ label: t('settingTheme.themeMode.dark'),
88
+ value: 'dark',
89
+ },
90
+ {
91
+ icon: Monitor,
92
+ img: '/images/theme_auto.webp',
93
+ label: t('settingTheme.themeMode.auto'),
94
+ value: 'auto',
95
+ },
96
+ ]}
97
+ width={100}
98
+ />
99
+ ),
100
+ label: t('settingTheme.themeMode.title'),
101
+ minWidth: undefined,
102
+ },
103
+ {
104
+ children: (
105
+ <Select
106
+ onChange={switchLang}
107
+ options={[{ label: t('settingTheme.lang.autoMode'), value: 'auto' }, ...localeOptions]}
108
+ />
109
+ ),
110
+ label: t('settingTheme.lang.title'),
111
+ name: 'language',
112
+ },
113
+ {
114
+ children: <SliderWithInput max={18} min={12} />,
115
+ desc: t('settingTheme.fontSize.desc'),
116
+ label: t('settingTheme.fontSize.title'),
117
+ name: 'fontSize',
118
+ },
119
+ {
120
+ children: <ThemeSwatchesPrimary />,
121
+ desc: t('settingTheme.primaryColor.desc'),
122
+ label: t('settingTheme.primaryColor.title'),
123
+ minWidth: undefined,
124
+ },
125
+ {
126
+ children: <ThemeSwatchesNeutral />,
127
+ desc: t('settingTheme.neutralColor.desc'),
128
+ label: t('settingTheme.neutralColor.title'),
129
+ minWidth: undefined,
130
+ },
131
+ ],
132
+ icon: Palette,
133
+ title: t('settingTheme.title'),
134
+ };
174
135
 
175
- const items = useMemo(() => [theme, system], [settings]);
136
+ const system: SettingItemGroup = {
137
+ children: [
138
+ {
139
+ children: <Input.Password placeholder={t('settingSystem.accessCode.placeholder')} />,
140
+ desc: t('settingSystem.accessCode.desc'),
141
+ label: t('settingSystem.accessCode.title'),
142
+ name: 'password',
143
+ },
144
+ {
145
+ children: (
146
+ <Button danger onClick={handleReset} type="primary">
147
+ {t('danger.reset.action')}
148
+ </Button>
149
+ ),
150
+ desc: t('danger.reset.title'),
151
+ label: t('danger.reset.desc'),
152
+ minWidth: undefined,
153
+ },
154
+ {
155
+ children: (
156
+ <Button danger onClick={handleClear} type="primary">
157
+ {t('danger.clear.action')}
158
+ </Button>
159
+ ),
160
+ desc: t('danger.clear.title'),
161
+ label: t('danger.clear.desc'),
162
+ minWidth: undefined,
163
+ },
164
+ ],
165
+ icon: AppWindow,
166
+ title: t('settingSystem.title'),
167
+ };
176
168
 
177
169
  return (
178
170
  <Form
179
171
  form={form}
180
172
  initialValues={settings}
181
- items={items}
173
+ items={[theme, system]}
182
174
  onValuesChange={debounce(setSettings, 100)}
183
175
  {...FORM_STYLE}
184
176
  />
@@ -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,16 +1,12 @@
1
1
  import { t } from 'i18next';
2
2
  import { Metadata } from 'next';
3
3
 
4
- import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
5
-
6
4
  import Banner from './features/Banner';
7
5
 
8
6
  export const generateMetadata = (): Metadata => ({
9
- title: genSiteHeadTitle(t('header', { ns: 'welcome' })),
7
+ title: t('header', { ns: 'welcome' }),
10
8
  });
11
9
 
12
- const Page = () => {
13
- return <Banner />;
14
- };
10
+ const Page = () => <Banner />;
15
11
 
16
12
  export default Page;
@@ -0,0 +1,11 @@
1
+ import { memo, useEffect } from 'react';
2
+
3
+ const PageTitle = memo<{ title: string }>(({ title }) => {
4
+ useEffect(() => {
5
+ document.title = title;
6
+ }, [title]);
7
+
8
+ return null;
9
+ });
10
+
11
+ export default PageTitle;
@@ -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
 
@@ -1,6 +1,5 @@
1
1
  import { type MobileNavBarTitleProps } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
- import Head from 'next/head';
4
3
  import { CSSProperties, PropsWithChildren, ReactNode, memo } from 'react';
5
4
  import { Flexbox } from 'react-layout-kit';
6
5
 
@@ -49,29 +48,21 @@ const AppMobileLayout = memo<AppMobileLayoutProps>(
49
48
  const { styles, cx } = useStyles();
50
49
 
51
50
  return (
52
- <>
53
- <Head>
54
- <meta
55
- content="width=device-width, initial-scale=1.0, viewport-fit=cover"
56
- name="viewport"
57
- />
58
- </Head>
59
- <Flexbox className={cx(styles.container, className)} style={style}>
60
- {navBar && (
61
- <>
62
- <div className={styles.mobileNavBar}>{navBar}</div>
63
- <SafeSpacing mobile position={'top'} />
64
- </>
65
- )}
66
- {children}
67
- {showTabBar && (
68
- <>
69
- <SafeSpacing mobile position={'bottom'} />
70
- <MobileTabBar className={styles.mobileTabBar} />
71
- </>
72
- )}
73
- </Flexbox>
74
- </>
51
+ <Flexbox className={cx(styles.container, className)} style={style}>
52
+ {navBar && (
53
+ <>
54
+ <div className={styles.mobileNavBar}>{navBar}</div>
55
+ <SafeSpacing mobile position={'top'} />
56
+ </>
57
+ )}
58
+ {children}
59
+ {showTabBar && (
60
+ <>
61
+ <SafeSpacing mobile position={'bottom'} />
62
+ <MobileTabBar className={styles.mobileTabBar} />
63
+ </>
64
+ )}
65
+ </Flexbox>
75
66
  );
76
67
  },
77
68
  );
@@ -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;