@lobehub/chat 1.86.1 → 1.87.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.
- package/CHANGELOG.md +59 -0
- package/Dockerfile +1 -1
- package/Dockerfile.database +1 -1
- package/Dockerfile.pglite +1 -1
- package/changelog/v1.json +21 -0
- package/locales/ar/setting.json +43 -31
- package/locales/bg-BG/setting.json +43 -31
- package/locales/de-DE/setting.json +43 -31
- package/locales/en-US/setting.json +43 -31
- package/locales/es-ES/setting.json +43 -31
- package/locales/fa-IR/setting.json +43 -31
- package/locales/fr-FR/setting.json +43 -31
- package/locales/it-IT/setting.json +43 -31
- package/locales/ja-JP/setting.json +43 -31
- package/locales/ko-KR/setting.json +43 -31
- package/locales/nl-NL/setting.json +43 -31
- package/locales/pl-PL/setting.json +43 -31
- package/locales/pt-BR/setting.json +43 -31
- package/locales/ru-RU/setting.json +43 -31
- package/locales/tr-TR/setting.json +43 -31
- package/locales/vi-VN/setting.json +43 -31
- package/locales/zh-CN/setting.json +43 -31
- package/locales/zh-TW/setting.json +43 -31
- package/package.json +3 -3
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx +1 -1
- package/src/app/[variants]/(main)/settings/agent/index.tsx +8 -2
- package/src/app/[variants]/(main)/settings/common/features/Appearance/Preview.tsx +298 -0
- package/src/app/[variants]/(main)/settings/common/features/{Theme → Appearance}/ThemeSwatches/ThemeSwatchesNeutral.tsx +6 -11
- package/src/app/[variants]/(main)/settings/common/features/{Theme → Appearance}/ThemeSwatches/ThemeSwatchesPrimary.tsx +6 -10
- package/src/app/[variants]/(main)/settings/common/features/Appearance/index.tsx +67 -0
- package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/ChatPreview.tsx +35 -0
- package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/HighlighterPreview.tsx +55 -0
- package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/MermaidPreview.tsx +51 -0
- package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/index.tsx +128 -0
- package/src/app/[variants]/(main)/settings/common/features/Common.tsx +74 -42
- package/src/app/[variants]/(main)/settings/common/index.tsx +4 -2
- package/src/app/[variants]/(main)/settings/hotkey/features/{HotkeySetting.tsx → Conversation.tsx} +19 -18
- package/src/app/[variants]/(main)/settings/hotkey/features/Essential.tsx +88 -0
- package/src/app/[variants]/(main)/settings/hotkey/page.tsx +8 -2
- package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/ModelConfigModal/index.tsx +1 -1
- package/src/app/[variants]/(main)/settings/storage/Advanced.tsx +26 -0
- package/src/app/[variants]/(main)/settings/system-agent/features/createForm.tsx +37 -22
- package/src/app/[variants]/(main)/settings/tts/features/OpenAI.tsx +20 -10
- package/src/app/[variants]/(main)/settings/tts/features/STT.tsx +20 -11
- package/src/config/aiModels/internlm.ts +21 -5
- package/src/config/aiModels/spark.ts +16 -14
- package/src/config/modelProviders/spark.ts +4 -0
- package/src/const/settings/common.ts +2 -0
- package/src/features/AgentSetting/AgentTTS/index.tsx +1 -1
- package/src/features/ChatItem/index.tsx +35 -0
- package/src/features/Conversation/components/ChatItem/index.tsx +2 -6
- package/src/features/User/UserPanel/LangButton.tsx +1 -1
- package/src/features/User/UserPanel/ThemeButton.tsx +3 -3
- package/src/libs/model-runtime/{AgentRuntime.test.ts → ModelRuntime.test.ts} +1 -1
- package/src/libs/model-runtime/{AgentRuntime.ts → ModelRuntime.ts} +3 -3
- package/src/libs/model-runtime/index.ts +1 -1
- package/src/libs/model-runtime/internlm/index.ts +15 -3
- package/src/libs/model-runtime/spark/index.test.ts +3 -0
- package/src/libs/model-runtime/spark/index.ts +23 -1
- package/src/libs/model-runtime/utils/streams/spark.test.ts +66 -0
- package/src/libs/model-runtime/utils/streams/spark.ts +31 -2
- package/src/libs/oidc-provider/config.ts +1 -1
- package/src/locales/default/setting.ts +45 -31
- package/src/store/electron/initialState.ts +1 -1
- package/src/store/electron/selectors/__tests__/desktopState.test.ts +55 -0
- package/src/store/global/selectors/systemStatus.ts +2 -0
- package/src/store/user/slices/settings/selectors/general.test.ts +29 -1
- package/src/store/user/slices/settings/selectors/general.ts +4 -0
- package/src/types/user/settings/general.ts +3 -1
- package/src/app/[variants]/(main)/settings/common/features/Theme/index.tsx +0 -146
- /package/src/app/[variants]/(main)/settings/common/features/{Theme → Appearance}/ThemeSwatches/index.ts +0 -0
@@ -1,14 +1,18 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import {
|
4
|
-
import {
|
3
|
+
import { Form, type FormGroupItemType, Icon, ImageSelect, InputPassword } from '@lobehub/ui';
|
4
|
+
import { Select } from '@lobehub/ui';
|
5
|
+
import { Skeleton } from 'antd';
|
5
6
|
import isEqual from 'fast-deep-equal';
|
6
|
-
import {
|
7
|
+
import { Loader2Icon, Monitor, Moon, Sun } from 'lucide-react';
|
8
|
+
import { memo, useState } from 'react';
|
7
9
|
import { useTranslation } from 'react-i18next';
|
8
10
|
|
9
|
-
import { useSyncSettings } from '@/app/[variants]/(main)/settings/hooks/useSyncSettings';
|
10
11
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
11
|
-
import {
|
12
|
+
import { imageUrl } from '@/const/url';
|
13
|
+
import { localeOptions } from '@/locales/resources';
|
14
|
+
import { useGlobalStore } from '@/store/global';
|
15
|
+
import { systemStatusSelectors } from '@/store/global/selectors';
|
12
16
|
import { useServerConfigStore } from '@/store/serverConfig';
|
13
17
|
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
|
14
18
|
import { useUserStore } from '@/store/user';
|
@@ -16,30 +20,67 @@ import { settingsSelectors } from '@/store/user/selectors';
|
|
16
20
|
|
17
21
|
const Common = memo(() => {
|
18
22
|
const { t } = useTranslation('setting');
|
19
|
-
const [form] = Form.useForm();
|
20
23
|
|
21
24
|
const showAccessCodeConfig = useServerConfigStore(serverConfigSelectors.enabledAccessCode);
|
22
|
-
|
23
25
|
const settings = useUserStore(settingsSelectors.currentSettings, isEqual);
|
24
|
-
const
|
25
|
-
|
26
|
-
const
|
26
|
+
const themeMode = useGlobalStore(systemStatusSelectors.themeMode);
|
27
|
+
const language = useGlobalStore(systemStatusSelectors.language);
|
28
|
+
const [setSettings, isUserStateInit] = useUserStore((s) => [s.setSettings, s.isUserStateInit]);
|
29
|
+
const [setThemeMode, switchLocale, isStatusInit] = useGlobalStore((s) => [
|
30
|
+
s.switchThemeMode,
|
31
|
+
s.switchLocale,
|
32
|
+
s.isStatusInit,
|
33
|
+
]);
|
34
|
+
const [loading, setLoading] = useState(false);
|
27
35
|
|
28
|
-
|
29
|
-
|
30
|
-
centered: true,
|
31
|
-
okButtonProps: { danger: true },
|
32
|
-
onOk: () => {
|
33
|
-
resetSettings();
|
34
|
-
form.setFieldsValue(DEFAULT_SETTINGS);
|
35
|
-
message.success(t('danger.reset.success'));
|
36
|
-
},
|
37
|
-
title: t('danger.reset.confirm'),
|
38
|
-
});
|
39
|
-
}, []);
|
36
|
+
if (!(isStatusInit && isUserStateInit))
|
37
|
+
return <Skeleton active paragraph={{ rows: 5 }} title={false} />;
|
40
38
|
|
41
|
-
const
|
39
|
+
const theme: FormGroupItemType = {
|
42
40
|
children: [
|
41
|
+
{
|
42
|
+
children: (
|
43
|
+
<ImageSelect
|
44
|
+
height={60}
|
45
|
+
onChange={setThemeMode}
|
46
|
+
options={[
|
47
|
+
{
|
48
|
+
icon: Sun,
|
49
|
+
img: imageUrl('theme_light.webp'),
|
50
|
+
label: t('settingCommon.themeMode.light'),
|
51
|
+
value: 'light',
|
52
|
+
},
|
53
|
+
{
|
54
|
+
icon: Moon,
|
55
|
+
img: imageUrl('theme_dark.webp'),
|
56
|
+
label: t('settingCommon.themeMode.dark'),
|
57
|
+
value: 'dark',
|
58
|
+
},
|
59
|
+
{
|
60
|
+
icon: Monitor,
|
61
|
+
img: imageUrl('theme_auto.webp'),
|
62
|
+
label: t('settingCommon.themeMode.auto'),
|
63
|
+
value: 'auto',
|
64
|
+
},
|
65
|
+
]}
|
66
|
+
unoptimized={false}
|
67
|
+
value={themeMode}
|
68
|
+
width={100}
|
69
|
+
/>
|
70
|
+
),
|
71
|
+
label: t('settingCommon.themeMode.title'),
|
72
|
+
minWidth: undefined,
|
73
|
+
},
|
74
|
+
{
|
75
|
+
children: (
|
76
|
+
<Select
|
77
|
+
defaultValue={language}
|
78
|
+
onChange={switchLocale}
|
79
|
+
options={[{ label: t('settingCommon.lang.autoMode'), value: 'auto' }, ...localeOptions]}
|
80
|
+
/>
|
81
|
+
),
|
82
|
+
label: t('settingCommon.lang.title'),
|
83
|
+
},
|
43
84
|
{
|
44
85
|
children: (
|
45
86
|
<InputPassword
|
@@ -50,32 +91,23 @@ const Common = memo(() => {
|
|
50
91
|
desc: t('settingSystem.accessCode.desc'),
|
51
92
|
hidden: !showAccessCodeConfig,
|
52
93
|
label: t('settingSystem.accessCode.title'),
|
53
|
-
name:
|
54
|
-
},
|
55
|
-
{
|
56
|
-
children: (
|
57
|
-
<Button danger onClick={handleReset} type={'primary'}>
|
58
|
-
{t('danger.reset.action')}
|
59
|
-
</Button>
|
60
|
-
),
|
61
|
-
desc: t('danger.reset.desc'),
|
62
|
-
label: t('danger.reset.title'),
|
63
|
-
layout: 'horizontal',
|
64
|
-
minWidth: undefined,
|
94
|
+
name: 'password',
|
65
95
|
},
|
66
96
|
],
|
67
|
-
|
97
|
+
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
98
|
+
title: t('settingCommon.title'),
|
68
99
|
};
|
69
100
|
|
70
|
-
useSyncSettings(form);
|
71
|
-
|
72
101
|
return (
|
73
102
|
<Form
|
74
|
-
|
75
|
-
|
76
|
-
items={[system]}
|
103
|
+
initialValues={settings.keyVaults}
|
104
|
+
items={[theme]}
|
77
105
|
itemsType={'group'}
|
78
|
-
onValuesChange={
|
106
|
+
onValuesChange={async (v) => {
|
107
|
+
setLoading(true);
|
108
|
+
await setSettings({ keyVaults: v });
|
109
|
+
setLoading(false);
|
110
|
+
}}
|
79
111
|
variant={'borderless'}
|
80
112
|
{...FORM_STYLE}
|
81
113
|
/>
|
@@ -1,11 +1,13 @@
|
|
1
|
+
import Appearance from './features/Appearance';
|
2
|
+
import ChatAppearance from './features/ChatAppearance';
|
1
3
|
import Common from './features/Common';
|
2
|
-
import Theme from './features/Theme';
|
3
4
|
|
4
5
|
const Page = () => {
|
5
6
|
return (
|
6
7
|
<>
|
7
|
-
<Theme />
|
8
8
|
<Common />
|
9
|
+
<Appearance />
|
10
|
+
<ChatAppearance />
|
9
11
|
</>
|
10
12
|
);
|
11
13
|
};
|
package/src/app/[variants]/(main)/settings/hotkey/features/{HotkeySetting.tsx → Conversation.tsx}
RENAMED
@@ -1,8 +1,10 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import { Form, type FormGroupItemType, HotkeyInput } from '@lobehub/ui';
|
3
|
+
import { Form, type FormGroupItemType, HotkeyInput, Icon } from '@lobehub/ui';
|
4
|
+
import { Skeleton } from 'antd';
|
4
5
|
import isEqual from 'fast-deep-equal';
|
5
|
-
import {
|
6
|
+
import { Loader2Icon } from 'lucide-react';
|
7
|
+
import { memo, useState } from 'react';
|
6
8
|
import { useTranslation } from 'react-i18next';
|
7
9
|
|
8
10
|
import { HOTKEYS_REGISTRATION } from '@/const/hotkeys';
|
@@ -20,17 +22,18 @@ const filterByDesktop = (item: HotkeyItem) => {
|
|
20
22
|
if (!isDesktop) return !item.isDesktop;
|
21
23
|
};
|
22
24
|
|
23
|
-
const HOTKEY_SETTING_KEY = 'hotkey';
|
24
|
-
|
25
25
|
const HotkeySetting = memo(() => {
|
26
26
|
const { t } = useTranslation(['setting', 'hotkey']);
|
27
27
|
const [form] = Form.useForm();
|
28
28
|
|
29
|
-
const
|
30
|
-
const [setSettings] = useUserStore((s) => [s.setSettings]);
|
29
|
+
const { hotkey } = useUserStore(settingsSelectors.currentSettings, isEqual);
|
30
|
+
const [setSettings, isUserStateInit] = useUserStore((s) => [s.setSettings, s.isUserStateInit]);
|
31
|
+
const [loading, setLoading] = useState(false);
|
32
|
+
|
33
|
+
if (!isUserStateInit) return <Skeleton active paragraph={{ rows: 5 }} title={false} />;
|
31
34
|
|
32
35
|
const mapHotkeyItem = (item: HotkeyItem) => {
|
33
|
-
const hotkeyConflicts = Object.entries(
|
36
|
+
const hotkeyConflicts = Object.entries(hotkey)
|
34
37
|
.map(([key, value]) => {
|
35
38
|
if (key === item.id) return false;
|
36
39
|
return value;
|
@@ -53,31 +56,29 @@ const HotkeySetting = memo(() => {
|
|
53
56
|
),
|
54
57
|
desc: hotkeyMeta[item.id].desc ? t(`${item.id}.desc`, { ns: 'hotkey' }) : undefined,
|
55
58
|
label: t(`${item.id}.title`, { ns: 'hotkey' }),
|
56
|
-
name:
|
59
|
+
name: item.id,
|
57
60
|
};
|
58
61
|
};
|
59
62
|
|
60
|
-
const essential: FormGroupItemType = {
|
61
|
-
children: HOTKEYS_REGISTRATION.filter((item) => item.group === HotkeyGroupEnum.Essential)
|
62
|
-
.filter((item) => filterByDesktop(item))
|
63
|
-
.map((item) => mapHotkeyItem(item)),
|
64
|
-
title: t('hotkey.group.essential'),
|
65
|
-
};
|
66
|
-
|
67
63
|
const conversation: FormGroupItemType = {
|
68
64
|
children: HOTKEYS_REGISTRATION.filter((item) => item.group === HotkeyGroupEnum.Conversation)
|
69
65
|
.filter((item) => filterByDesktop(item))
|
70
66
|
.map((item) => mapHotkeyItem(item)),
|
67
|
+
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
71
68
|
title: t('hotkey.group.conversation'),
|
72
69
|
};
|
73
70
|
|
74
71
|
return (
|
75
72
|
<Form
|
76
73
|
form={form}
|
77
|
-
initialValues={
|
78
|
-
items={[
|
74
|
+
initialValues={hotkey}
|
75
|
+
items={[conversation]}
|
79
76
|
itemsType={'group'}
|
80
|
-
onValuesChange={
|
77
|
+
onValuesChange={async (values) => {
|
78
|
+
setLoading(true);
|
79
|
+
await setSettings({ hotkey: values });
|
80
|
+
setLoading(false);
|
81
|
+
}}
|
81
82
|
variant={'borderless'}
|
82
83
|
{...FORM_STYLE}
|
83
84
|
/>
|
@@ -0,0 +1,88 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { Form, type FormGroupItemType, HotkeyInput, Icon } from '@lobehub/ui';
|
4
|
+
import { Skeleton } from 'antd';
|
5
|
+
import isEqual from 'fast-deep-equal';
|
6
|
+
import { Loader2Icon } from 'lucide-react';
|
7
|
+
import { memo, useState } from 'react';
|
8
|
+
import { useTranslation } from 'react-i18next';
|
9
|
+
|
10
|
+
import { HOTKEYS_REGISTRATION } from '@/const/hotkeys';
|
11
|
+
import { FORM_STYLE } from '@/const/layoutTokens';
|
12
|
+
import { isDesktop } from '@/const/version';
|
13
|
+
import hotkeyMeta from '@/locales/default/hotkey';
|
14
|
+
import { useUserStore } from '@/store/user';
|
15
|
+
import { settingsSelectors } from '@/store/user/selectors';
|
16
|
+
import { HotkeyGroupEnum, HotkeyItem } from '@/types/hotkey';
|
17
|
+
|
18
|
+
const filterByDesktop = (item: HotkeyItem) => {
|
19
|
+
if (isDesktop) return true;
|
20
|
+
|
21
|
+
// is not desktop, filter out desktop only items
|
22
|
+
if (!isDesktop) return !item.isDesktop;
|
23
|
+
};
|
24
|
+
|
25
|
+
const HotkeySetting = memo(() => {
|
26
|
+
const { t } = useTranslation(['setting', 'hotkey']);
|
27
|
+
const [form] = Form.useForm();
|
28
|
+
|
29
|
+
const { hotkey } = useUserStore(settingsSelectors.currentSettings, isEqual);
|
30
|
+
const [setSettings, isUserStateInit] = useUserStore((s) => [s.setSettings, s.isUserStateInit]);
|
31
|
+
const [loading, setLoading] = useState(false);
|
32
|
+
|
33
|
+
if (!isUserStateInit) return <Skeleton active paragraph={{ rows: 5 }} title={false} />;
|
34
|
+
|
35
|
+
const mapHotkeyItem = (item: HotkeyItem) => {
|
36
|
+
const hotkeyConflicts = Object.entries(hotkey)
|
37
|
+
.map(([key, value]) => {
|
38
|
+
if (key === item.id) return false;
|
39
|
+
return value;
|
40
|
+
})
|
41
|
+
.filter(Boolean) as string[];
|
42
|
+
|
43
|
+
return {
|
44
|
+
children: (
|
45
|
+
<HotkeyInput
|
46
|
+
disabled={item.nonEditable}
|
47
|
+
hotkeyConflicts={hotkeyConflicts}
|
48
|
+
placeholder={t('hotkey.record')}
|
49
|
+
resetValue={item.keys}
|
50
|
+
texts={{
|
51
|
+
conflicts: t('hotkey.conflicts'),
|
52
|
+
invalidCombination: t('hotkey.invalidCombination'),
|
53
|
+
reset: t('hotkey.reset'),
|
54
|
+
}}
|
55
|
+
/>
|
56
|
+
),
|
57
|
+
desc: hotkeyMeta[item.id].desc ? t(`${item.id}.desc`, { ns: 'hotkey' }) : undefined,
|
58
|
+
label: t(`${item.id}.title`, { ns: 'hotkey' }),
|
59
|
+
name: item.id,
|
60
|
+
};
|
61
|
+
};
|
62
|
+
|
63
|
+
const essential: FormGroupItemType = {
|
64
|
+
children: HOTKEYS_REGISTRATION.filter((item) => item.group === HotkeyGroupEnum.Essential)
|
65
|
+
.filter((item) => filterByDesktop(item))
|
66
|
+
.map((item) => mapHotkeyItem(item)),
|
67
|
+
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
68
|
+
title: t('hotkey.group.essential'),
|
69
|
+
};
|
70
|
+
|
71
|
+
return (
|
72
|
+
<Form
|
73
|
+
form={form}
|
74
|
+
initialValues={hotkey}
|
75
|
+
items={[essential]}
|
76
|
+
itemsType={'group'}
|
77
|
+
onValuesChange={async (values) => {
|
78
|
+
setLoading(true);
|
79
|
+
await setSettings({ hotkey: values });
|
80
|
+
setLoading(false);
|
81
|
+
}}
|
82
|
+
variant={'borderless'}
|
83
|
+
{...FORM_STYLE}
|
84
|
+
/>
|
85
|
+
);
|
86
|
+
});
|
87
|
+
|
88
|
+
export default HotkeySetting;
|
@@ -3,7 +3,8 @@ import { translation } from '@/server/translation';
|
|
3
3
|
import { DynamicLayoutProps } from '@/types/next';
|
4
4
|
import { RouteVariants } from '@/utils/server/routeVariants';
|
5
5
|
|
6
|
-
import
|
6
|
+
import Conversation from './features/Conversation';
|
7
|
+
import Essential from './features/Essential';
|
7
8
|
|
8
9
|
export const generateMetadata = async (props: DynamicLayoutProps) => {
|
9
10
|
const locale = await RouteVariants.getLocale(props);
|
@@ -16,7 +17,12 @@ export const generateMetadata = async (props: DynamicLayoutProps) => {
|
|
16
17
|
};
|
17
18
|
|
18
19
|
const Page = () => {
|
19
|
-
return
|
20
|
+
return (
|
21
|
+
<>
|
22
|
+
<Essential />
|
23
|
+
<Conversation />
|
24
|
+
</>
|
25
|
+
);
|
20
26
|
};
|
21
27
|
|
22
28
|
Page.displayName = 'HotkeySetting';
|
@@ -5,7 +5,7 @@ import { memo, useState } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
6
6
|
|
7
7
|
import { useUserStore } from '@/store/user';
|
8
|
-
import { modelConfigSelectors } from '@/store/user/
|
8
|
+
import { modelConfigSelectors } from '@/store/user/selectors';
|
9
9
|
|
10
10
|
import ModelConfigForm from './Form';
|
11
11
|
|
@@ -8,6 +8,7 @@ import { useCallback } from 'react';
|
|
8
8
|
import { useTranslation } from 'react-i18next';
|
9
9
|
|
10
10
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
11
|
+
import { DEFAULT_SETTINGS } from '@/const/settings';
|
11
12
|
import DataImporter from '@/features/DataImporter';
|
12
13
|
import { configService } from '@/services/config';
|
13
14
|
import { useChatStore } from '@/store/chat';
|
@@ -32,6 +33,7 @@ const AdvancedActions = () => {
|
|
32
33
|
const [removeAllFiles] = useFileStore((s) => [s.removeAllFiles]);
|
33
34
|
const removeAllPlugins = useToolStore((s) => s.removeAllPlugins);
|
34
35
|
const settings = useUserStore(settingsSelectors.currentSettings, isEqual);
|
36
|
+
const [resetSettings] = useUserStore((s) => [s.resetSettings]);
|
35
37
|
|
36
38
|
const handleClear = useCallback(() => {
|
37
39
|
modal.confirm({
|
@@ -53,6 +55,19 @@ const AdvancedActions = () => {
|
|
53
55
|
});
|
54
56
|
}, []);
|
55
57
|
|
58
|
+
const handleReset = useCallback(() => {
|
59
|
+
modal.confirm({
|
60
|
+
centered: true,
|
61
|
+
okButtonProps: { danger: true },
|
62
|
+
onOk: () => {
|
63
|
+
resetSettings();
|
64
|
+
form.setFieldsValue(DEFAULT_SETTINGS);
|
65
|
+
message.success(t('danger.reset.success'));
|
66
|
+
},
|
67
|
+
title: t('danger.reset.confirm'),
|
68
|
+
});
|
69
|
+
}, []);
|
70
|
+
|
56
71
|
const system: FormGroupItemType = {
|
57
72
|
children: [
|
58
73
|
{
|
@@ -93,6 +108,17 @@ const AdvancedActions = () => {
|
|
93
108
|
layout: 'horizontal',
|
94
109
|
minWidth: undefined,
|
95
110
|
},
|
111
|
+
{
|
112
|
+
children: (
|
113
|
+
<Button danger onClick={handleReset} type={'primary'}>
|
114
|
+
{t('danger.reset.action')}
|
115
|
+
</Button>
|
116
|
+
),
|
117
|
+
desc: t('danger.reset.desc'),
|
118
|
+
label: t('danger.reset.title'),
|
119
|
+
layout: 'horizontal',
|
120
|
+
minWidth: undefined,
|
121
|
+
},
|
96
122
|
],
|
97
123
|
title: t('storage.actions.title'),
|
98
124
|
};
|
@@ -1,11 +1,12 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import { Button, Form, type FormGroupItemType, type FormItemProps } from '@lobehub/ui';
|
4
|
-
import { Form as AntForm, Switch } from 'antd';
|
3
|
+
import { Button, Form, type FormGroupItemType, type FormItemProps, Icon } from '@lobehub/ui';
|
4
|
+
import { Form as AntForm, Skeleton, Switch } from 'antd';
|
5
5
|
import isEqual from 'fast-deep-equal';
|
6
|
-
import { PencilIcon } from 'lucide-react';
|
7
|
-
import { memo } from 'react';
|
6
|
+
import { Loader2Icon, PencilIcon } from 'lucide-react';
|
7
|
+
import { memo, useState } from 'react';
|
8
8
|
import { useTranslation } from 'react-i18next';
|
9
|
+
import { Flexbox } from 'react-layout-kit';
|
9
10
|
|
10
11
|
import TextArea from '@/components/TextArea';
|
11
12
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
@@ -14,8 +15,6 @@ import { useUserStore } from '@/store/user';
|
|
14
15
|
import { settingsSelectors } from '@/store/user/selectors';
|
15
16
|
import { type UserSystemAgentConfigKey } from '@/types/user/settings';
|
16
17
|
|
17
|
-
import { useSyncSystemAgent } from './useSync';
|
18
|
-
|
19
18
|
interface SystemAgentFormProps {
|
20
19
|
allowCustomPrompt?: boolean;
|
21
20
|
allowDisable?: boolean;
|
@@ -26,11 +25,16 @@ interface SystemAgentFormProps {
|
|
26
25
|
const SystemAgentForm = memo(
|
27
26
|
({ systemAgentKey, allowDisable, allowCustomPrompt, defaultPrompt }: SystemAgentFormProps) => {
|
28
27
|
const { t } = useTranslation('setting');
|
29
|
-
|
28
|
+
const [form] = AntForm.useForm();
|
30
29
|
const settings = useUserStore(settingsSelectors.currentSystemAgent, isEqual);
|
31
|
-
const [updateSystemAgent] = useUserStore((s) => [
|
30
|
+
const [updateSystemAgent, isUserStateInit] = useUserStore((s) => [
|
31
|
+
s.updateSystemAgent,
|
32
|
+
s.isUserStateInit,
|
33
|
+
]);
|
34
|
+
const [loading, setLoading] = useState(false);
|
35
|
+
|
36
|
+
if (!isUserStateInit) return <Skeleton active paragraph={{ rows: 5 }} title={false} />;
|
32
37
|
|
33
|
-
const [form] = AntForm.useForm();
|
34
38
|
const value = settings[systemAgentKey];
|
35
39
|
|
36
40
|
const systemAgentSettings: FormGroupItemType = {
|
@@ -38,8 +42,10 @@ const SystemAgentForm = memo(
|
|
38
42
|
{
|
39
43
|
children: (
|
40
44
|
<ModelSelect
|
41
|
-
onChange={(props) => {
|
42
|
-
|
45
|
+
onChange={async (props) => {
|
46
|
+
setLoading(true);
|
47
|
+
await updateSystemAgent(systemAgentKey, props);
|
48
|
+
setLoading(false);
|
43
49
|
}}
|
44
50
|
showAbility={false}
|
45
51
|
// value={value}
|
@@ -52,8 +58,10 @@ const SystemAgentForm = memo(
|
|
52
58
|
(!!allowCustomPrompt && {
|
53
59
|
children: !!value.customPrompt ? (
|
54
60
|
<TextArea
|
55
|
-
|
56
|
-
|
61
|
+
onBlur={async (e) => {
|
62
|
+
setLoading(true);
|
63
|
+
await updateSystemAgent(systemAgentKey, { customPrompt: e.target.value });
|
64
|
+
setLoading(false);
|
57
65
|
}}
|
58
66
|
placeholder={t('systemAgent.customPrompt.placeholder')}
|
59
67
|
style={{ minHeight: 160 }}
|
@@ -64,7 +72,9 @@ const SystemAgentForm = memo(
|
|
64
72
|
block
|
65
73
|
icon={PencilIcon}
|
66
74
|
onClick={async () => {
|
75
|
+
setLoading(true);
|
67
76
|
await updateSystemAgent(systemAgentKey, { customPrompt: defaultPrompt });
|
77
|
+
setLoading(false);
|
68
78
|
}}
|
69
79
|
>
|
70
80
|
{t('systemAgent.customPrompt.addPrompt')}
|
@@ -75,13 +85,20 @@ const SystemAgentForm = memo(
|
|
75
85
|
name: [systemAgentKey, 'customPrompt'],
|
76
86
|
}) as FormItemProps,
|
77
87
|
].filter(Boolean),
|
78
|
-
extra:
|
79
|
-
<
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
88
|
+
extra: (
|
89
|
+
<Flexbox>
|
90
|
+
{loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />}
|
91
|
+
{allowDisable && (
|
92
|
+
<Switch
|
93
|
+
onChange={async (enabled) => {
|
94
|
+
setLoading(true);
|
95
|
+
await updateSystemAgent(systemAgentKey, { enabled });
|
96
|
+
setLoading(false);
|
97
|
+
}}
|
98
|
+
value={value.enabled}
|
99
|
+
/>
|
100
|
+
)}
|
101
|
+
</Flexbox>
|
85
102
|
),
|
86
103
|
title: (
|
87
104
|
<span
|
@@ -94,8 +111,6 @@ const SystemAgentForm = memo(
|
|
94
111
|
),
|
95
112
|
};
|
96
113
|
|
97
|
-
useSyncSystemAgent(form, settings);
|
98
|
-
|
99
114
|
return (
|
100
115
|
<Form form={form} initialValues={settings} items={[systemAgentSettings]} {...FORM_STYLE} />
|
101
116
|
);
|
@@ -1,9 +1,11 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import { Form, type FormGroupItemType } from '@lobehub/ui';
|
3
|
+
import { Form, type FormGroupItemType, Icon } from '@lobehub/ui';
|
4
4
|
import { Select } from '@lobehub/ui';
|
5
|
+
import { Skeleton } from 'antd';
|
5
6
|
import isEqual from 'fast-deep-equal';
|
6
|
-
import {
|
7
|
+
import { Loader2Icon } from 'lucide-react';
|
8
|
+
import { memo, useState } from 'react';
|
7
9
|
import { useTranslation } from 'react-i18next';
|
8
10
|
|
9
11
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
@@ -12,37 +14,45 @@ import { settingsSelectors } from '@/store/user/selectors';
|
|
12
14
|
|
13
15
|
import { opeanaiSTTOptions, opeanaiTTSOptions } from './const';
|
14
16
|
|
15
|
-
const TTS_SETTING_KEY = 'tts';
|
16
|
-
|
17
17
|
const OpenAI = memo(() => {
|
18
18
|
const { t } = useTranslation('setting');
|
19
19
|
const [form] = Form.useForm();
|
20
|
-
const
|
21
|
-
const [setSettings] = useUserStore((s) => [s.setSettings]);
|
20
|
+
const { tts } = useUserStore(settingsSelectors.currentSettings, isEqual);
|
21
|
+
const [setSettings, isUserStateInit] = useUserStore((s) => [s.setSettings, s.isUserStateInit]);
|
22
|
+
const [loading, setLoading] = useState(false);
|
23
|
+
|
24
|
+
if (!isUserStateInit) return <Skeleton active paragraph={{ rows: 5 }} title={false} />;
|
22
25
|
|
23
26
|
const openai: FormGroupItemType = {
|
24
27
|
children: [
|
25
28
|
{
|
26
29
|
children: <Select options={opeanaiTTSOptions} />,
|
27
30
|
label: t('settingTTS.openai.ttsModel'),
|
28
|
-
name: [
|
31
|
+
name: ['openAI', 'ttsModel'],
|
29
32
|
},
|
30
33
|
{
|
31
34
|
children: <Select options={opeanaiSTTOptions} />,
|
32
35
|
label: t('settingTTS.openai.sttModel'),
|
33
|
-
name: [
|
36
|
+
name: ['openAI', 'sttModel'],
|
34
37
|
},
|
35
38
|
],
|
39
|
+
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
36
40
|
title: t('settingTTS.openai.title'),
|
37
41
|
};
|
38
42
|
|
39
43
|
return (
|
40
44
|
<Form
|
41
45
|
form={form}
|
42
|
-
initialValues={
|
46
|
+
initialValues={tts}
|
43
47
|
items={[openai]}
|
44
48
|
itemsType={'group'}
|
45
|
-
onValuesChange={
|
49
|
+
onValuesChange={async (values) => {
|
50
|
+
setLoading(true);
|
51
|
+
await setSettings({
|
52
|
+
tts: values,
|
53
|
+
});
|
54
|
+
setLoading(false);
|
55
|
+
}}
|
46
56
|
variant={'borderless'}
|
47
57
|
{...FORM_STYLE}
|
48
58
|
/>
|
@@ -1,9 +1,10 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import { Form, type FormGroupItemType, Select } from '@lobehub/ui';
|
4
|
-
import { Switch } from 'antd';
|
3
|
+
import { Form, type FormGroupItemType, Icon, Select } from '@lobehub/ui';
|
4
|
+
import { Skeleton, Switch } from 'antd';
|
5
5
|
import isEqual from 'fast-deep-equal';
|
6
|
-
import {
|
6
|
+
import { Loader2Icon } from 'lucide-react';
|
7
|
+
import { memo, useState } from 'react';
|
7
8
|
import { useTranslation } from 'react-i18next';
|
8
9
|
|
9
10
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
@@ -12,13 +13,14 @@ import { settingsSelectors } from '@/store/user/selectors';
|
|
12
13
|
|
13
14
|
import { sttOptions } from './const';
|
14
15
|
|
15
|
-
const TTS_SETTING_KEY = 'tts';
|
16
|
-
|
17
16
|
const STT = memo(() => {
|
18
17
|
const { t } = useTranslation('setting');
|
19
18
|
const [form] = Form.useForm();
|
20
|
-
const
|
21
|
-
const [setSettings] = useUserStore((s) => [s.setSettings]);
|
19
|
+
const { tts } = useUserStore(settingsSelectors.currentSettings, isEqual);
|
20
|
+
const [setSettings, isUserStateInit] = useUserStore((s) => [s.setSettings, s.isUserStateInit]);
|
21
|
+
const [loading, setLoading] = useState(false);
|
22
|
+
|
23
|
+
if (!isUserStateInit) return <Skeleton active paragraph={{ rows: 5 }} title={false} />;
|
22
24
|
|
23
25
|
const stt: FormGroupItemType = {
|
24
26
|
children: [
|
@@ -26,7 +28,7 @@ const STT = memo(() => {
|
|
26
28
|
children: <Select options={sttOptions} />,
|
27
29
|
desc: t('settingTTS.sttService.desc'),
|
28
30
|
label: t('settingTTS.sttService.title'),
|
29
|
-
name:
|
31
|
+
name: 'sttServer',
|
30
32
|
},
|
31
33
|
{
|
32
34
|
children: <Switch />,
|
@@ -34,20 +36,27 @@ const STT = memo(() => {
|
|
34
36
|
label: t('settingTTS.sttAutoStop.title'),
|
35
37
|
layout: 'horizontal',
|
36
38
|
minWidth: undefined,
|
37
|
-
name:
|
39
|
+
name: 'sttAutoStop',
|
38
40
|
valuePropName: 'checked',
|
39
41
|
},
|
40
42
|
],
|
43
|
+
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
41
44
|
title: t('settingTTS.stt'),
|
42
45
|
};
|
43
46
|
|
44
47
|
return (
|
45
48
|
<Form
|
46
49
|
form={form}
|
47
|
-
initialValues={
|
50
|
+
initialValues={tts}
|
48
51
|
items={[stt]}
|
49
52
|
itemsType={'group'}
|
50
|
-
onValuesChange={
|
53
|
+
onValuesChange={async (values) => {
|
54
|
+
setLoading(true);
|
55
|
+
await setSettings({
|
56
|
+
tts: values,
|
57
|
+
});
|
58
|
+
setLoading(false);
|
59
|
+
}}
|
51
60
|
variant={'borderless'}
|
52
61
|
{...FORM_STYLE}
|
53
62
|
/>
|