@lobehub/chat 0.1.6
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/.changelogrc.js +1 -0
- package/.commitlintrc.js +1 -0
- package/.editorconfig +16 -0
- package/.eslintignore +32 -0
- package/.eslintrc.js +6 -0
- package/.github/ISSUE_TEMPLATE/1_bug_report.yml +45 -0
- package/.github/ISSUE_TEMPLATE/2_feature_request.yml +21 -0
- package/.github/ISSUE_TEMPLATE/3_question.yml +15 -0
- package/.github/ISSUE_TEMPLATE/4_other.md +7 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +17 -0
- package/.github/dependabot.yml +17 -0
- package/.github/workflows/auto-merge.yml +32 -0
- package/.github/workflows/contributor-help.yml +29 -0
- package/.github/workflows/issue-check-inactive.yml +22 -0
- package/.github/workflows/issue-close-require.yml +46 -0
- package/.github/workflows/issue-remove-inactive.yml +25 -0
- package/.github/workflows/release.yml +34 -0
- package/.github/workflows/test.yml +30 -0
- package/.gitpod.yml +3 -0
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +5 -0
- package/.i18nrc.js +13 -0
- package/.prettierignore +63 -0
- package/.prettierrc.js +1 -0
- package/.releaserc.js +1 -0
- package/.remarkrc.js +1 -0
- package/.stylelintrc.js +8 -0
- package/CHANGELOG.md +20 -0
- package/README.md +147 -0
- package/locales/en_US/common.json +41 -0
- package/locales/en_US/setting.json +117 -0
- package/locales/zh_CN/common.json +41 -0
- package/locales/zh_CN/setting.json +117 -0
- package/next.config.mjs +32 -0
- package/package.json +140 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/scripts/genDefaultLocale.ts +18 -0
- package/scripts/genResources.ts +51 -0
- package/scripts/toc.ts +7 -0
- package/src/const/fetch.ts +1 -0
- package/src/const/modelTokens.ts +8 -0
- package/src/features/AvatarWithUpload/index.tsx +27 -0
- package/src/features/FolderPanel/index.tsx +55 -0
- package/src/features/SideBar/index.tsx +35 -0
- package/src/helpers/prompt.test.ts +36 -0
- package/src/helpers/prompt.ts +36 -0
- package/src/helpers/url.ts +17 -0
- package/src/layout/index.tsx +57 -0
- package/src/layout/style.ts +18 -0
- package/src/locales/create.ts +49 -0
- package/src/locales/default/common.ts +41 -0
- package/src/locales/default/setting.ts +117 -0
- package/src/locales/index.ts +5 -0
- package/src/locales/options.ts +19 -0
- package/src/locales/resources/en_US.ts +9 -0
- package/src/locales/resources/index.ts +13 -0
- package/src/locales/resources/zh_CN.ts +9 -0
- package/src/migrations/FromV0ToV1.ts +12 -0
- package/src/migrations/index.ts +13 -0
- package/src/pages/_app.page.tsx +13 -0
- package/src/pages/_document.page.tsx +70 -0
- package/src/pages/api/LangChainStream.ts +95 -0
- package/src/pages/api/chain.api.ts +17 -0
- package/src/pages/api/openai.api.ts +31 -0
- package/src/pages/chat/SessionList/Header.tsx +56 -0
- package/src/pages/chat/SessionList/List/SessionItem.tsx +90 -0
- package/src/pages/chat/SessionList/List/index.tsx +31 -0
- package/src/pages/chat/SessionList/List/style.ts +77 -0
- package/src/pages/chat/SessionList/index.tsx +18 -0
- package/src/pages/chat/[id]/Config/ConfigCell.tsx +68 -0
- package/src/pages/chat/[id]/Config/ReadMode.tsx +63 -0
- package/src/pages/chat/[id]/Config/index.tsx +79 -0
- package/src/pages/chat/[id]/Conversation/ChatList.tsx +36 -0
- package/src/pages/chat/[id]/Conversation/Input.tsx +61 -0
- package/src/pages/chat/[id]/Conversation/index.tsx +32 -0
- package/src/pages/chat/[id]/Header.tsx +86 -0
- package/src/pages/chat/[id]/edit/AgentConfig.tsx +95 -0
- package/src/pages/chat/[id]/edit/AgentMeta.tsx +117 -0
- package/src/pages/chat/[id]/edit/FormItem.tsx +26 -0
- package/src/pages/chat/[id]/edit/Prompt.tsx +68 -0
- package/src/pages/chat/[id]/edit/index.page.tsx +62 -0
- package/src/pages/chat/[id]/edit/style.ts +42 -0
- package/src/pages/chat/[id]/index.page.tsx +40 -0
- package/src/pages/chat/index.page.tsx +1 -0
- package/src/pages/chat/layout.tsx +51 -0
- package/src/pages/index.page.tsx +1 -0
- package/src/pages/setting/Header.tsx +27 -0
- package/src/pages/setting/SettingForm.tsx +299 -0
- package/src/pages/setting/SliderWithInput/index.tsx +40 -0
- package/src/pages/setting/ThemeSwatches/ThemeSwatchesNeutral.tsx +33 -0
- package/src/pages/setting/ThemeSwatches/ThemeSwatchesPrimary.tsx +33 -0
- package/src/pages/setting/ThemeSwatches/index.ts +2 -0
- package/src/pages/setting/index.page.tsx +41 -0
- package/src/prompts/agent.ts +65 -0
- package/src/services/chatModel.ts +34 -0
- package/src/services/langChain.ts +18 -0
- package/src/services/url.ts +8 -0
- package/src/store/middleware/createHashStorage.ts +49 -0
- package/src/store/session/index.ts +33 -0
- package/src/store/session/initialState.ts +11 -0
- package/src/store/session/selectors.ts +3 -0
- package/src/store/session/slices/agentConfig/action.ts +226 -0
- package/src/store/session/slices/agentConfig/index.ts +3 -0
- package/src/store/session/slices/agentConfig/initialState.ts +34 -0
- package/src/store/session/slices/agentConfig/selectors.ts +54 -0
- package/src/store/session/slices/chat/action.ts +210 -0
- package/src/store/session/slices/chat/index.ts +3 -0
- package/src/store/session/slices/chat/initialState.ts +12 -0
- package/src/store/session/slices/chat/messageReducer.test.ts +70 -0
- package/src/store/session/slices/chat/messageReducer.ts +84 -0
- package/src/store/session/slices/chat/selectors.ts +83 -0
- package/src/store/session/slices/session/action.ts +118 -0
- package/src/store/session/slices/session/index.ts +3 -0
- package/src/store/session/slices/session/initialState.ts +31 -0
- package/src/store/session/slices/session/reducers/session.test.ts +456 -0
- package/src/store/session/slices/session/reducers/session.ts +113 -0
- package/src/store/session/slices/session/selectors/chat.ts +4 -0
- package/src/store/session/slices/session/selectors/index.ts +20 -0
- package/src/store/session/slices/session/selectors/list.ts +65 -0
- package/src/store/session/store.ts +17 -0
- package/src/store/settings/action.ts +40 -0
- package/src/store/settings/index.ts +23 -0
- package/src/store/settings/initialState.ts +41 -0
- package/src/store/settings/selectors.ts +17 -0
- package/src/store/settings/store.ts +13 -0
- package/src/styles/antdOverride.ts +33 -0
- package/src/styles/global.ts +23 -0
- package/src/styles/index.ts +6 -0
- package/src/types/chatMessage.ts +46 -0
- package/src/types/exportConfig.ts +44 -0
- package/src/types/global.d.ts +14 -0
- package/src/types/i18next.d.ts +8 -0
- package/src/types/langchain.ts +34 -0
- package/src/types/llm.ts +49 -0
- package/src/types/locale.ts +1 -0
- package/src/types/meta.ts +26 -0
- package/src/types/openai.ts +62 -0
- package/src/types/session.ts +59 -0
- package/src/utils/VersionController.test.ts +90 -0
- package/src/utils/VersionController.ts +64 -0
- package/src/utils/compass.ts +94 -0
- package/src/utils/fetch.ts +132 -0
- package/src/utils/filter.test.ts +120 -0
- package/src/utils/filter.ts +29 -0
- package/src/utils/uploadFIle.ts +8 -0
- package/src/utils/uuid.ts +9 -0
- package/tsconfig.json +31 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createStyles } from 'antd-style';
|
|
2
|
+
|
|
3
|
+
export const useStyles = createStyles(({ css, token }) => ({
|
|
4
|
+
collapseHeader: css`
|
|
5
|
+
.ant-collapse-header {
|
|
6
|
+
align-items: center !important;
|
|
7
|
+
}
|
|
8
|
+
`,
|
|
9
|
+
footer: css`
|
|
10
|
+
position: sticky;
|
|
11
|
+
bottom: 0;
|
|
12
|
+
border-top: 1px solid ${token.colorBorder};
|
|
13
|
+
`,
|
|
14
|
+
form: css`
|
|
15
|
+
overflow-y: auto;
|
|
16
|
+
`,
|
|
17
|
+
header: css`
|
|
18
|
+
background: ${token.colorBgContainer};
|
|
19
|
+
border-bottom: 1px solid ${token.colorSplit};
|
|
20
|
+
`,
|
|
21
|
+
profile: css`
|
|
22
|
+
font-size: 20px;
|
|
23
|
+
font-weight: bold;
|
|
24
|
+
color: ${token.colorText};
|
|
25
|
+
`,
|
|
26
|
+
prompt: css`
|
|
27
|
+
padding: 12px;
|
|
28
|
+
background: ${token.colorFillTertiary};
|
|
29
|
+
`,
|
|
30
|
+
title: css`
|
|
31
|
+
font-size: 16px;
|
|
32
|
+
font-weight: 500;
|
|
33
|
+
|
|
34
|
+
.ant-collapse-header {
|
|
35
|
+
padding: 0 !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.ant-collapse-content-box {
|
|
39
|
+
padding: 0 !important;
|
|
40
|
+
}
|
|
41
|
+
`,
|
|
42
|
+
}));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import isEqual from 'fast-deep-equal';
|
|
2
|
+
import Head from 'next/head';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { Flexbox } from 'react-layout-kit';
|
|
5
|
+
|
|
6
|
+
import { sessionSelectors, useSessionStore } from '@/store/session';
|
|
7
|
+
|
|
8
|
+
import Layout from '../layout';
|
|
9
|
+
import Config from './Config';
|
|
10
|
+
import Conversation from './Conversation';
|
|
11
|
+
import Header from './Header';
|
|
12
|
+
|
|
13
|
+
const Chat = memo(() => {
|
|
14
|
+
const [title] = useSessionStore((s) => {
|
|
15
|
+
const context = sessionSelectors.currentSession(s);
|
|
16
|
+
return [context?.meta.title];
|
|
17
|
+
}, isEqual);
|
|
18
|
+
|
|
19
|
+
const pageTitle = title ? `${title} - LobeChat` : 'LobeChat';
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Layout>
|
|
23
|
+
<Head>
|
|
24
|
+
<title>{pageTitle}</title>
|
|
25
|
+
</Head>
|
|
26
|
+
|
|
27
|
+
<Flexbox flex={1}>
|
|
28
|
+
<Header />
|
|
29
|
+
<Flexbox
|
|
30
|
+
id={'lobe-conversion-container'}
|
|
31
|
+
style={{ height: 'calc(100vh - 64px)', position: 'relative' }}
|
|
32
|
+
>
|
|
33
|
+
<Conversation />
|
|
34
|
+
<Config />
|
|
35
|
+
</Flexbox>
|
|
36
|
+
</Flexbox>
|
|
37
|
+
</Layout>
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
export default Chat;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './[id]/index.page';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useRouter } from 'next/router';
|
|
2
|
+
import { PropsWithChildren, memo, useEffect } from 'react';
|
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
|
4
|
+
import { shallow } from 'zustand/shallow';
|
|
5
|
+
|
|
6
|
+
import SideBar from '@/features/SideBar';
|
|
7
|
+
import { createI18nNext } from '@/locales/create';
|
|
8
|
+
import { useSessionStore } from '@/store/session';
|
|
9
|
+
import { useSettings } from '@/store/settings';
|
|
10
|
+
|
|
11
|
+
import { Sessions } from './SessionList';
|
|
12
|
+
|
|
13
|
+
const initI18n = createI18nNext();
|
|
14
|
+
|
|
15
|
+
const ChatLayout = memo<PropsWithChildren>(({ children }) => {
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
initI18n.finally();
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const [activeSession] = useSessionStore((s) => {
|
|
21
|
+
return [s.activeSession];
|
|
22
|
+
}, shallow);
|
|
23
|
+
|
|
24
|
+
const router = useRouter();
|
|
25
|
+
const { id } = router.query;
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const hasRehydrated = useSessionStore.persist.hasHydrated();
|
|
29
|
+
// 只有当水合完毕后,才能正常去激活会话
|
|
30
|
+
if (typeof id === 'string' && hasRehydrated) {
|
|
31
|
+
activeSession(id);
|
|
32
|
+
}
|
|
33
|
+
}, [id]);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const hasRehydrated = useSettings.persist.hasHydrated();
|
|
37
|
+
if (hasRehydrated) {
|
|
38
|
+
useSettings.setState({ sidebarKey: 'chat' });
|
|
39
|
+
}
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Flexbox horizontal width={'100%'}>
|
|
44
|
+
<SideBar />
|
|
45
|
+
<Sessions />
|
|
46
|
+
{children}
|
|
47
|
+
</Flexbox>
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export default ChatLayout;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './chat/index.page';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ChatHeader } from '@lobehub/ui';
|
|
2
|
+
import { createStyles } from 'antd-style';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import Router from 'next/router';
|
|
5
|
+
import { memo } from 'react';
|
|
6
|
+
|
|
7
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
8
|
+
title: css`
|
|
9
|
+
font-size: 16px;
|
|
10
|
+
font-weight: bold;
|
|
11
|
+
color: ${token.colorText};
|
|
12
|
+
`,
|
|
13
|
+
}));
|
|
14
|
+
const Header = memo(() => {
|
|
15
|
+
const { t } = useTranslation('setting');
|
|
16
|
+
|
|
17
|
+
const { styles } = useStyles();
|
|
18
|
+
return (
|
|
19
|
+
<ChatHeader
|
|
20
|
+
left={<div className={styles.title}>{t('header')}</div>}
|
|
21
|
+
onBackClick={() => Router.back()}
|
|
22
|
+
showBackButton
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default Header;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { Form, type ItemGroup, ThemeSwitch } from '@lobehub/ui';
|
|
2
|
+
import { Form as AntForm, Button, Input, Popconfirm, Select, Switch } from 'antd';
|
|
3
|
+
import isEqual from 'fast-deep-equal';
|
|
4
|
+
import { debounce } from 'lodash-es';
|
|
5
|
+
import { AppWindow, Bot, MessagesSquare, Palette, Webhook } from 'lucide-react';
|
|
6
|
+
import { memo, useCallback, useMemo } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
import { shallow } from 'zustand/shallow';
|
|
9
|
+
|
|
10
|
+
import AvatarWithUpload from '@/features/AvatarWithUpload';
|
|
11
|
+
import { options } from '@/locales/options';
|
|
12
|
+
import SliderWithInput from '@/pages/setting/SliderWithInput';
|
|
13
|
+
import { settingsSelectors, useSettings } from '@/store/settings';
|
|
14
|
+
import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
|
|
15
|
+
import { ConfigKeys } from '@/types/exportConfig';
|
|
16
|
+
import { LanguageModel } from '@/types/llm';
|
|
17
|
+
|
|
18
|
+
import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from './ThemeSwatches';
|
|
19
|
+
|
|
20
|
+
type SettingItemGroup = ItemGroup & {
|
|
21
|
+
children: {
|
|
22
|
+
name?: ConfigKeys;
|
|
23
|
+
}[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const SettingForm = memo(() => {
|
|
27
|
+
const { t } = useTranslation('setting');
|
|
28
|
+
const [form] = AntForm.useForm();
|
|
29
|
+
const settings = useSettings(settingsSelectors.currentSettings, isEqual);
|
|
30
|
+
const { setThemeMode, setSettings, resetSettings } = useSettings(
|
|
31
|
+
(s) => ({
|
|
32
|
+
resetSettings: s.resetSettings,
|
|
33
|
+
setSettings: s.setSettings,
|
|
34
|
+
setThemeMode: s.setThemeMode,
|
|
35
|
+
}),
|
|
36
|
+
shallow,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const handleReset = useCallback(() => {
|
|
40
|
+
resetSettings();
|
|
41
|
+
form.setFieldsValue(DEFAULT_SETTINGS);
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const handleClear = useCallback(() => {
|
|
45
|
+
handleReset();
|
|
46
|
+
// TODO: 删除聊天记录
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const theme: SettingItemGroup = useMemo(
|
|
50
|
+
() => ({
|
|
51
|
+
children: [
|
|
52
|
+
{
|
|
53
|
+
children: <AvatarWithUpload />,
|
|
54
|
+
label: t('settingTheme.avatar.title'),
|
|
55
|
+
minWidth: undefined,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
children: (
|
|
59
|
+
<ThemeSwitch
|
|
60
|
+
labels={{
|
|
61
|
+
auto: t('settingTheme.themeMode.auto'),
|
|
62
|
+
dark: t('settingTheme.themeMode.dark'),
|
|
63
|
+
light: t('settingTheme.themeMode.light'),
|
|
64
|
+
}}
|
|
65
|
+
onThemeSwitch={setThemeMode}
|
|
66
|
+
themeMode={settings.themeMode}
|
|
67
|
+
type={'select'}
|
|
68
|
+
/>
|
|
69
|
+
),
|
|
70
|
+
label: t('settingTheme.themeMode.title'),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
children: <Select options={options} />,
|
|
74
|
+
label: t('settingTheme.lang.title'),
|
|
75
|
+
name: 'language',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
children: <SliderWithInput max={18} min={12} />,
|
|
79
|
+
desc: t('settingTheme.fontSize.desc'),
|
|
80
|
+
label: t('settingTheme.fontSize.title'),
|
|
81
|
+
name: 'fontSize',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
children: <ThemeSwatchesPrimary />,
|
|
85
|
+
desc: t('settingTheme.primaryColor.desc'),
|
|
86
|
+
label: t('settingTheme.primaryColor.title'),
|
|
87
|
+
minWidth: undefined,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
children: <ThemeSwatchesNeutral />,
|
|
91
|
+
desc: t('settingTheme.neutralColor.desc'),
|
|
92
|
+
label: t('settingTheme.neutralColor.title'),
|
|
93
|
+
minWidth: undefined,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
icon: Palette,
|
|
97
|
+
title: t('settingTheme.title'),
|
|
98
|
+
}),
|
|
99
|
+
[settings],
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const OpenAI: SettingItemGroup = useMemo(
|
|
103
|
+
() => ({
|
|
104
|
+
children: [
|
|
105
|
+
{
|
|
106
|
+
children: <Input.Password placeholder={t('settingOpenAI.token.placeholder')} />,
|
|
107
|
+
desc: t('settingOpenAI.token.desc'),
|
|
108
|
+
label: t('settingOpenAI.token.title'),
|
|
109
|
+
name: 'token',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
children: <Input placeholder={t('settingOpenAI.endpoint.placeholder')} />,
|
|
113
|
+
desc: t('settingOpenAI.endpoint.desc'),
|
|
114
|
+
|
|
115
|
+
label: t('settingOpenAI.endpoint.title'),
|
|
116
|
+
|
|
117
|
+
name: 'endpoint',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
icon: Webhook,
|
|
121
|
+
title: t('settingOpenAI.title'),
|
|
122
|
+
}),
|
|
123
|
+
[settings],
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const Chat: SettingItemGroup = useMemo(
|
|
127
|
+
() => ({
|
|
128
|
+
children: [
|
|
129
|
+
{
|
|
130
|
+
children: <Switch />,
|
|
131
|
+
label: t('settingChat.enableHistoryCount.title'),
|
|
132
|
+
minWidth: undefined,
|
|
133
|
+
name: 'enableHistoryCount',
|
|
134
|
+
valuePropName: 'checked',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
children: <SliderWithInput max={32} min={0} />,
|
|
138
|
+
desc: t('settingChat.historyCount.desc'),
|
|
139
|
+
hidden: !settings.enableHistoryCount,
|
|
140
|
+
label: t('settingChat.historyCount.title'),
|
|
141
|
+
name: 'historyCount',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
children: <Switch />,
|
|
145
|
+
label: t('settingChat.enableCompressThreshold.title'),
|
|
146
|
+
minWidth: undefined,
|
|
147
|
+
name: 'enableCompressThreshold',
|
|
148
|
+
valuePropName: 'checked',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
children: <SliderWithInput max={32} min={0} />,
|
|
152
|
+
desc: t('settingChat.compressThreshold.desc'),
|
|
153
|
+
hidden: !settings.enableCompressThreshold,
|
|
154
|
+
label: t('settingChat.compressThreshold.title'),
|
|
155
|
+
name: 'compressThreshold',
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
icon: MessagesSquare,
|
|
159
|
+
title: t('settingChat.title'),
|
|
160
|
+
}),
|
|
161
|
+
[settings],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const Model: SettingItemGroup = useMemo(
|
|
165
|
+
() => ({
|
|
166
|
+
children: [
|
|
167
|
+
{
|
|
168
|
+
children: (
|
|
169
|
+
<Select
|
|
170
|
+
options={Object.values(LanguageModel).map((value) => ({
|
|
171
|
+
label: value,
|
|
172
|
+
value,
|
|
173
|
+
}))}
|
|
174
|
+
/>
|
|
175
|
+
),
|
|
176
|
+
desc: t('settingModel.model.desc'),
|
|
177
|
+
label: t('settingModel.model.title'),
|
|
178
|
+
name: 'model',
|
|
179
|
+
tag: 'model',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
children: <SliderWithInput max={1} min={0} step={0.1} />,
|
|
183
|
+
desc: t('settingModel.temperature.desc'),
|
|
184
|
+
label: t('settingModel.temperature.title'),
|
|
185
|
+
name: 'temperature',
|
|
186
|
+
tag: 'temperature',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
children: <SliderWithInput max={1} min={0} step={0.1} />,
|
|
190
|
+
desc: t('settingModel.topP.desc'),
|
|
191
|
+
label: t('settingModel.topP.title'),
|
|
192
|
+
name: 'topP',
|
|
193
|
+
tag: 'top_p',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
children: <SliderWithInput max={2} min={-2} step={0.1} />,
|
|
197
|
+
desc: t('settingModel.presencePenalty.desc'),
|
|
198
|
+
label: t('settingModel.presencePenalty.title'),
|
|
199
|
+
name: 'presencePenalty',
|
|
200
|
+
tag: 'presence_penalty',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
children: <SliderWithInput max={2} min={-2} step={0.1} />,
|
|
204
|
+
desc: t('settingModel.frequencyPenalty.desc'),
|
|
205
|
+
label: t('settingModel.frequencyPenalty.title'),
|
|
206
|
+
name: 'frequencyPenalty',
|
|
207
|
+
tag: 'frequency_penalty',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
children: <Switch />,
|
|
211
|
+
label: t('settingModel.enableMaxTokens.title'),
|
|
212
|
+
minWidth: undefined,
|
|
213
|
+
name: 'enableMaxTokens',
|
|
214
|
+
valuePropName: 'checked',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
children: <SliderWithInput max={32_000} min={0} step={100} />,
|
|
218
|
+
desc: t('settingModel.maxTokens.desc'),
|
|
219
|
+
hidden: !settings.enableMaxTokens,
|
|
220
|
+
label: t('settingModel.maxTokens.title'),
|
|
221
|
+
name: 'maxTokens',
|
|
222
|
+
tag: 'max_tokens',
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
icon: Bot,
|
|
226
|
+
title: t('settingModel.title'),
|
|
227
|
+
}),
|
|
228
|
+
[settings],
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const System: SettingItemGroup = useMemo(
|
|
232
|
+
() => ({
|
|
233
|
+
children: [
|
|
234
|
+
{
|
|
235
|
+
children: <Input.Password placeholder={t('settingSystem.accessCode.placeholder')} />,
|
|
236
|
+
desc: t('settingSystem.accessCode.desc'),
|
|
237
|
+
label: t('settingSystem.accessCode.title'),
|
|
238
|
+
name: 'accessCode',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
children: (
|
|
242
|
+
<Popconfirm
|
|
243
|
+
arrow={false}
|
|
244
|
+
cancelText={t('cancel', { ns: 'common' })}
|
|
245
|
+
okButtonProps={{ danger: true }}
|
|
246
|
+
okText={t('ok', { ns: 'common' })}
|
|
247
|
+
onConfirm={handleReset}
|
|
248
|
+
title={t('danger.reset.confirm')}
|
|
249
|
+
>
|
|
250
|
+
<Button danger type="primary">
|
|
251
|
+
{t('danger.reset.action')}
|
|
252
|
+
</Button>
|
|
253
|
+
</Popconfirm>
|
|
254
|
+
),
|
|
255
|
+
desc: t('danger.reset.title'),
|
|
256
|
+
label: t('danger.reset.desc'),
|
|
257
|
+
minWidth: undefined,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
children: (
|
|
261
|
+
<Popconfirm
|
|
262
|
+
arrow={false}
|
|
263
|
+
cancelText={t('cancel', { ns: 'common' })}
|
|
264
|
+
okButtonProps={{ danger: true }}
|
|
265
|
+
okText={t('ok', { ns: 'common' })}
|
|
266
|
+
onConfirm={handleClear}
|
|
267
|
+
title={t('danger.clear.confirm')}
|
|
268
|
+
>
|
|
269
|
+
<Button danger type="primary">
|
|
270
|
+
{t('danger.clear.action')}
|
|
271
|
+
</Button>
|
|
272
|
+
</Popconfirm>
|
|
273
|
+
),
|
|
274
|
+
desc: t('danger.clear.title'),
|
|
275
|
+
label: t('danger.clear.desc'),
|
|
276
|
+
minWidth: undefined,
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
icon: AppWindow,
|
|
280
|
+
title: t('settingSystem.title'),
|
|
281
|
+
}),
|
|
282
|
+
[settings],
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const items = useMemo(() => [theme, OpenAI, Chat, Model, System], [settings]);
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<Form
|
|
289
|
+
form={form}
|
|
290
|
+
initialValues={settings}
|
|
291
|
+
itemMinWidth="max(30%,240px)"
|
|
292
|
+
items={items}
|
|
293
|
+
onValuesChange={debounce(setSettings, 100)}
|
|
294
|
+
style={{ maxWidth: 1024, width: '100%' }}
|
|
295
|
+
/>
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
export default SettingForm;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { InputNumber, Slider } from 'antd';
|
|
2
|
+
import { SliderSingleProps } from 'antd/es/slider';
|
|
3
|
+
import { isNull } from 'lodash-es';
|
|
4
|
+
import { memo, useCallback } from 'react';
|
|
5
|
+
import { Flexbox } from 'react-layout-kit';
|
|
6
|
+
|
|
7
|
+
const SliderWithInput = memo<SliderSingleProps>(
|
|
8
|
+
({ step, value, onChange, max, min, defaultValue, ...props }) => {
|
|
9
|
+
const handleOnchange = useCallback((value: number | null) => {
|
|
10
|
+
if (Number.isNaN(value) || isNull(value)) return;
|
|
11
|
+
onChange?.(value);
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Flexbox direction={'horizontal'} gap={8}>
|
|
16
|
+
<Slider
|
|
17
|
+
defaultValue={defaultValue}
|
|
18
|
+
max={max}
|
|
19
|
+
min={min}
|
|
20
|
+
onChange={handleOnchange}
|
|
21
|
+
step={step}
|
|
22
|
+
style={{ flex: 1 }}
|
|
23
|
+
value={typeof value === 'number' ? value : 0}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
<InputNumber
|
|
27
|
+
defaultValue={defaultValue}
|
|
28
|
+
max={max}
|
|
29
|
+
min={min}
|
|
30
|
+
onChange={handleOnchange}
|
|
31
|
+
step={Number(step)}
|
|
32
|
+
style={{ flex: 1, maxWidth: 64 }}
|
|
33
|
+
value={typeof value === 'number' ? value : 0}
|
|
34
|
+
/>
|
|
35
|
+
</Flexbox>
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export default SliderWithInput;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NeutralColors,
|
|
3
|
+
Swatches,
|
|
4
|
+
findCustomThemeName,
|
|
5
|
+
neutralColors,
|
|
6
|
+
neutralColorsSwatches,
|
|
7
|
+
} from '@lobehub/ui';
|
|
8
|
+
import { memo } from 'react';
|
|
9
|
+
import { shallow } from 'zustand/shallow';
|
|
10
|
+
|
|
11
|
+
import { useSettings } from '@/store/settings';
|
|
12
|
+
|
|
13
|
+
const ThemeSwatchesNeutral = memo(() => {
|
|
14
|
+
const { neutralColor, setSettings } = useSettings(
|
|
15
|
+
(s) => ({ neutralColor: s.settings.neutralColor, setSettings: s.setSettings }),
|
|
16
|
+
shallow,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const handleSelect = (v: any) => {
|
|
20
|
+
const name = findCustomThemeName('neutral', v) as NeutralColors;
|
|
21
|
+
setSettings({ neutralColor: name || '' });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Swatches
|
|
26
|
+
activeColor={neutralColor ? neutralColors[neutralColor] : undefined}
|
|
27
|
+
colors={neutralColorsSwatches}
|
|
28
|
+
onSelect={handleSelect}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export default ThemeSwatchesNeutral;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PrimaryColors,
|
|
3
|
+
Swatches,
|
|
4
|
+
findCustomThemeName,
|
|
5
|
+
primaryColors,
|
|
6
|
+
primaryColorsSwatches,
|
|
7
|
+
} from '@lobehub/ui';
|
|
8
|
+
import { memo } from 'react';
|
|
9
|
+
import { shallow } from 'zustand/shallow';
|
|
10
|
+
|
|
11
|
+
import { useSettings } from '@/store/settings';
|
|
12
|
+
|
|
13
|
+
const ThemeSwatchesPrimary = memo(() => {
|
|
14
|
+
const { primaryColor, setSettings } = useSettings(
|
|
15
|
+
(s) => ({ primaryColor: s.settings.primaryColor, setSettings: s.setSettings }),
|
|
16
|
+
shallow,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const handleSelect = (v: any) => {
|
|
20
|
+
const name = findCustomThemeName('primary', v) as PrimaryColors;
|
|
21
|
+
setSettings({ primaryColor: name || '' });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Swatches
|
|
26
|
+
activeColor={primaryColor ? primaryColors[primaryColor] : undefined}
|
|
27
|
+
colors={primaryColorsSwatches}
|
|
28
|
+
onSelect={handleSelect}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export default ThemeSwatchesPrimary;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Head from 'next/head';
|
|
2
|
+
import { memo, useEffect } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { Flexbox } from 'react-layout-kit';
|
|
5
|
+
|
|
6
|
+
import SideBar from '@/features/SideBar';
|
|
7
|
+
import { createI18nNext } from '@/locales/create';
|
|
8
|
+
import { Sessions } from '@/pages/chat/SessionList';
|
|
9
|
+
|
|
10
|
+
import Header from './Header';
|
|
11
|
+
import SettingForm from './SettingForm';
|
|
12
|
+
|
|
13
|
+
const initI18n = createI18nNext('setting');
|
|
14
|
+
|
|
15
|
+
const SettingLayout = memo(() => {
|
|
16
|
+
const { t } = useTranslation('setting');
|
|
17
|
+
const pageTitle = `${t('header')} - LobeChat`;
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
initI18n.finally();
|
|
21
|
+
}, []);
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<Head>
|
|
25
|
+
<title>{pageTitle}</title>
|
|
26
|
+
</Head>
|
|
27
|
+
<Flexbox height={'100vh'} horizontal width={'100%'}>
|
|
28
|
+
<SideBar />
|
|
29
|
+
<Sessions />
|
|
30
|
+
<Flexbox flex={1}>
|
|
31
|
+
<Header />
|
|
32
|
+
<Flexbox align={'center'} flex={1} padding={24} style={{ overflow: 'auto' }}>
|
|
33
|
+
<SettingForm />
|
|
34
|
+
</Flexbox>
|
|
35
|
+
</Flexbox>
|
|
36
|
+
</Flexbox>
|
|
37
|
+
</>
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export default SettingLayout;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { OpenAIStreamPayload } from '@/types/openai';
|
|
2
|
+
|
|
3
|
+
// 自动起名
|
|
4
|
+
export const promptSummaryAgentName = (content: string): Partial<OpenAIStreamPayload> => ({
|
|
5
|
+
messages: [
|
|
6
|
+
{
|
|
7
|
+
content: `你是一名擅长起名的起名大师,你需要将用户的描述总结为 20 个字以内的角色,格式要求如下:
|
|
8
|
+
输入: {文本作为JSON引用字符串}
|
|
9
|
+
输出: {角色名}
|
|
10
|
+
`,
|
|
11
|
+
role: 'system',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
content: `输入: {你是一名专业的前端开发者,擅长结合 vitest 和\`testing-library/react\` 书写单元测试。接下来用户会输入一串 ts 代码,你需要给出完善的单元测试。\n你需要注意,单元测试代码中,不应该使用 jest 。如果需要使用 \`jest.fn\`,请使用 \`vi.fn\` 替换}`,
|
|
15
|
+
role: 'user',
|
|
16
|
+
},
|
|
17
|
+
{ content: '前端 vitest 测试专家', role: 'assistant' },
|
|
18
|
+
{
|
|
19
|
+
content: `输入: {你是一名前端专家,请将下面的代码转成 ts,不要修改实现。如果原本 js 中没有定义的全局变量,需要补充 declare 的类型声明。}`,
|
|
20
|
+
role: 'user',
|
|
21
|
+
},
|
|
22
|
+
{ content: 'js 转 ts 专家', role: 'assistant' },
|
|
23
|
+
{
|
|
24
|
+
content: `输入:{你是一名擅长比喻和隐喻的UX Writter。用户会输入文案,你需要给出3个优化后的结果,使用 markdown格式的文本。下面是一个例子:
|
|
25
|
+
输入:页面加载中
|
|
26
|
+
输出:页面似乎在思考,一会儿才能准备好}`,
|
|
27
|
+
role: 'user',
|
|
28
|
+
},
|
|
29
|
+
{ content: '文案比喻优化专家', role: 'assistant' },
|
|
30
|
+
{ content: `输入: {${content}}`, role: 'user' },
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 自动挑选 emoji 和背景色
|
|
35
|
+
export const promptPickEmoji = (content: string): Partial<OpenAIStreamPayload> => ({
|
|
36
|
+
messages: [
|
|
37
|
+
{
|
|
38
|
+
content: `你是一名非常懂设计与时尚的设计师,你需要从用户的描述中匹配一个合适的 emoji。
|
|
39
|
+
输入:你是一名精通体验设计的设计系统设计师,设计系统存在诸多类别的 token,比如品牌色、成功色等,你需要为各个类别的 token 提供说明文案。
|
|
40
|
+
输出: 💅
|
|
41
|
+
|
|
42
|
+
输入:用户会输入一串 ts 代码,为了确保所有功能和分支的 100% 的覆盖率,你需要给出需要考虑哪些数据场景。
|
|
43
|
+
输出: 🧪
|
|
44
|
+
`,
|
|
45
|
+
role: 'system',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
content: `输入:${content}`,
|
|
49
|
+
role: 'user',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const promptSummaryDescription = (content: string): Partial<OpenAIStreamPayload> => ({
|
|
55
|
+
messages: [
|
|
56
|
+
{
|
|
57
|
+
content: '你是一名擅长会话的助理,你需要将用户的提示词做一个3句话以内的总结。',
|
|
58
|
+
role: 'system',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
content: `输入:${content}`,
|
|
62
|
+
role: 'user',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
});
|