@lobehub/chat 1.0.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.
- 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 +80 -0
- package/README.md +147 -0
- package/locales/en_US/common.json +40 -0
- package/locales/en_US/setting.json +97 -0
- package/locales/zh_CN/common.json +40 -0
- package/locales/zh_CN/setting.json +98 -0
- package/next.config.mjs +32 -0
- package/package.json +138 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/scripts/genDefaultLocale.mjs +12 -0
- package/scripts/toc.mjs +40 -0
- package/src/const/fetch.ts +1 -0
- package/src/const/modelTokens.ts +8 -0
- package/src/features/FolderPanel/index.tsx +55 -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 +42 -0
- package/src/layout/style.ts +18 -0
- package/src/locales/create.ts +48 -0
- package/src/locales/default/common.ts +41 -0
- package/src/locales/default/setting.ts +97 -0
- package/src/locales/index.ts +5 -0
- package/src/locales/resources/en_US.ts +9 -0
- package/src/locales/resources/index.ts +7 -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/Sidebar.tsx +36 -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 +42 -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 +31 -0
- package/src/store/settings/index.ts +23 -0
- package/src/store/settings/initialState.ts +25 -0
- package/src/store/settings/selectors.ts +9 -0
- package/src/store/settings/store.ts +13 -0
- package/src/styles/antdOverride.ts +29 -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 +23 -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 +7 -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 +26 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { DraggablePanel } from '@lobehub/ui';
|
|
2
|
+
import { createStyles } from 'antd-style';
|
|
3
|
+
import isEqual from 'fast-deep-equal';
|
|
4
|
+
import { PropsWithChildren, useState } from 'react';
|
|
5
|
+
import { shallow } from 'zustand/shallow';
|
|
6
|
+
|
|
7
|
+
import { useSettings } from '@/store/settings';
|
|
8
|
+
|
|
9
|
+
export const useStyles = createStyles(({ css, token }) => ({
|
|
10
|
+
panel: css`
|
|
11
|
+
height: 100vh;
|
|
12
|
+
color: ${token.colorTextSecondary};
|
|
13
|
+
background: ${token.colorBgContainer};
|
|
14
|
+
`,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
export default ({ children }: PropsWithChildren) => {
|
|
18
|
+
const { styles } = useStyles();
|
|
19
|
+
const [sessionsWidth, sessionExpandable] = useSettings(
|
|
20
|
+
(s) => [s.sessionsWidth, s.sessionExpandable],
|
|
21
|
+
shallow,
|
|
22
|
+
);
|
|
23
|
+
const [tmpWidth, setWidth] = useState(sessionsWidth);
|
|
24
|
+
if (tmpWidth !== sessionsWidth) setWidth(sessionsWidth);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<DraggablePanel
|
|
28
|
+
className={styles.panel}
|
|
29
|
+
defaultSize={{ width: tmpWidth }}
|
|
30
|
+
expand={sessionExpandable}
|
|
31
|
+
maxWidth={400}
|
|
32
|
+
minWidth={256}
|
|
33
|
+
onExpandChange={(expand) => {
|
|
34
|
+
useSettings.setState({
|
|
35
|
+
sessionExpandable: expand,
|
|
36
|
+
sessionsWidth: expand ? 320 : 0,
|
|
37
|
+
});
|
|
38
|
+
}}
|
|
39
|
+
onSizeChange={(_, size) => {
|
|
40
|
+
if (!size) return;
|
|
41
|
+
|
|
42
|
+
const nextWidth = typeof size.width === 'string' ? Number.parseInt(size.width) : size.width;
|
|
43
|
+
|
|
44
|
+
if (isEqual(nextWidth, sessionsWidth)) return;
|
|
45
|
+
|
|
46
|
+
setWidth(nextWidth);
|
|
47
|
+
useSettings.setState({ sessionsWidth: nextWidth });
|
|
48
|
+
}}
|
|
49
|
+
placement="left"
|
|
50
|
+
size={{ height: '100vh', width: sessionsWidth }}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</DraggablePanel>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ChatMessage } from '@lobehub/ui';
|
|
2
|
+
|
|
3
|
+
import { getInputVariablesFromMessages } from '@/helpers/prompt';
|
|
4
|
+
|
|
5
|
+
describe('getInputVariablesFromMessages 方法', () => {
|
|
6
|
+
it('应当在输入为空数组时返回空数组', () => {
|
|
7
|
+
const result = getInputVariablesFromMessages([]);
|
|
8
|
+
expect(result).toEqual([]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('应当在输入消息不包含变量时返回空数组', () => {
|
|
12
|
+
const input: ChatMessage[] = [{ role: 'user', content: 'Hello, how are you?' }];
|
|
13
|
+
const result = getInputVariablesFromMessages(input);
|
|
14
|
+
expect(result).toEqual([]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('应当在输入消息包含变量时返回包含变量、模板和消息索引的对象数组', () => {
|
|
18
|
+
const input: ChatMessage[] = [
|
|
19
|
+
{ role: 'user', content: 'Hello {name}, how are you?' },
|
|
20
|
+
{ role: 'user', content: 'My name is {name}' },
|
|
21
|
+
];
|
|
22
|
+
const expectedOutput = ['name'];
|
|
23
|
+
const result = getInputVariablesFromMessages(input);
|
|
24
|
+
expect(result).toEqual(expectedOutput);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('多个变量的情况', () => {
|
|
28
|
+
const input: ChatMessage[] = [
|
|
29
|
+
{ role: 'user', content: 'Hello {name}, how are {you}?' },
|
|
30
|
+
{ role: 'user', content: 'My name is {name}' },
|
|
31
|
+
];
|
|
32
|
+
const expectedOutput = ['name', 'you'];
|
|
33
|
+
const result = getInputVariablesFromMessages(input);
|
|
34
|
+
expect(result).toEqual(expectedOutput);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ChatMessage } from '@lobehub/ui';
|
|
2
|
+
import {
|
|
3
|
+
AIMessagePromptTemplate,
|
|
4
|
+
ChatPromptTemplate,
|
|
5
|
+
HumanMessagePromptTemplate,
|
|
6
|
+
SystemMessagePromptTemplate,
|
|
7
|
+
} from 'langchain/prompts';
|
|
8
|
+
|
|
9
|
+
export const getChatPromptTemplate = (chatMessages: ChatMessage[]) =>
|
|
10
|
+
ChatPromptTemplate.fromPromptMessages(
|
|
11
|
+
chatMessages.map((m) => {
|
|
12
|
+
switch (m.role) {
|
|
13
|
+
default:
|
|
14
|
+
case 'user': {
|
|
15
|
+
return HumanMessagePromptTemplate.fromTemplate(m.content);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
case 'system': {
|
|
19
|
+
return SystemMessagePromptTemplate.fromTemplate(m.content);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
case 'assistant': {
|
|
23
|
+
return AIMessagePromptTemplate.fromTemplate(m.content);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
export const getInputVariablesFromMessages = (chatMessages: ChatMessage[]) => {
|
|
29
|
+
let inputVariables: string[] = [];
|
|
30
|
+
try {
|
|
31
|
+
const chatPrompt = getChatPromptTemplate(chatMessages);
|
|
32
|
+
inputVariables = chatPrompt.inputVariables;
|
|
33
|
+
} catch {}
|
|
34
|
+
|
|
35
|
+
return inputVariables;
|
|
36
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ChatMessage } from '@lobehub/ui';
|
|
2
|
+
|
|
3
|
+
import { Compressor } from '@/utils/compass';
|
|
4
|
+
|
|
5
|
+
export const genShareMessagesUrl = (messages: ChatMessage[], systemRole?: string) => {
|
|
6
|
+
const compassedMsg = systemRole
|
|
7
|
+
? [{ content: systemRole, role: 'system' }, ...messages]
|
|
8
|
+
: messages;
|
|
9
|
+
|
|
10
|
+
return `/share?messages=${Compressor.compress(JSON.stringify(compassedMsg))}`;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const genSystemRoleQuery = async (content: string) => {
|
|
14
|
+
const x = { state: { systemRole: content } };
|
|
15
|
+
const systemRole = await Compressor.compressAsync(JSON.stringify(x));
|
|
16
|
+
return `#systemRole=${systemRole}`;
|
|
17
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ThemeProvider } from '@lobehub/ui';
|
|
2
|
+
import { App, ConfigProvider } from 'antd';
|
|
3
|
+
import 'antd/dist/reset.css';
|
|
4
|
+
import Zh_CN from 'antd/locale/zh_CN';
|
|
5
|
+
import { PropsWithChildren, useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useSessionStore } from '@/store/session';
|
|
8
|
+
import { useSettings } from '@/store/settings';
|
|
9
|
+
import { GlobalStyle } from '@/styles';
|
|
10
|
+
|
|
11
|
+
import i18n from '../locales';
|
|
12
|
+
import { useStyles } from './style';
|
|
13
|
+
|
|
14
|
+
const Layout = ({ children }: PropsWithChildren) => {
|
|
15
|
+
const { styles } = useStyles();
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
// 用一种比较奇怪的方式import 了 18n
|
|
19
|
+
i18n.finally(() => {});
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<ConfigProvider locale={Zh_CN}>
|
|
24
|
+
<App className={styles.bg}>{children}</App>
|
|
25
|
+
</ConfigProvider>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default ({ children }: PropsWithChildren) => {
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
// refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
|
|
32
|
+
useSessionStore.persist.rehydrate();
|
|
33
|
+
useSettings.persist.rehydrate();
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<ThemeProvider themeMode={'auto'}>
|
|
38
|
+
<GlobalStyle />
|
|
39
|
+
<Layout>{children}</Layout>
|
|
40
|
+
</ThemeProvider>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createStyles } from 'antd-style';
|
|
2
|
+
|
|
3
|
+
export const useStyles = createStyles(({ css, token }) => ({
|
|
4
|
+
bg: css`
|
|
5
|
+
overflow-y: scroll;
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
align-items: center;
|
|
9
|
+
|
|
10
|
+
height: 100%;
|
|
11
|
+
|
|
12
|
+
background: ${token.colorBgLayout};
|
|
13
|
+
|
|
14
|
+
:has(#ChatLayout, #FlowLayout) {
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
}
|
|
17
|
+
`,
|
|
18
|
+
}));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import i18n from 'i18next';
|
|
2
|
+
import LanguageDetector from 'i18next-browser-languagedetector';
|
|
3
|
+
import { isArray } from 'lodash-es';
|
|
4
|
+
import { initReactI18next } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import type { Namespaces, Resources } from '@/types/locale';
|
|
7
|
+
|
|
8
|
+
import resources from './resources';
|
|
9
|
+
|
|
10
|
+
const getRes = (res: Resources, namespace: Namespaces[]) => {
|
|
11
|
+
const newRes: any = {};
|
|
12
|
+
for (const [locale, value] of Object.entries(res)) {
|
|
13
|
+
newRes[locale] = {};
|
|
14
|
+
for (const ns of namespace) {
|
|
15
|
+
newRes[locale][ns] = value[ns];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return newRes;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const createI18nNext = (namespace?: Namespaces[] | Namespaces) => {
|
|
22
|
+
const ns: Namespaces[] = namespace
|
|
23
|
+
? isArray(namespace)
|
|
24
|
+
? ['common', ...namespace]
|
|
25
|
+
: ['common', namespace]
|
|
26
|
+
: ['common'];
|
|
27
|
+
return (
|
|
28
|
+
i18n
|
|
29
|
+
// detect user language
|
|
30
|
+
// learn more: https://github.com/i18next/i18next-browser-languageDetector
|
|
31
|
+
.use(LanguageDetector)
|
|
32
|
+
// pass the i18n instance to react-i18next.
|
|
33
|
+
.use(initReactI18next)
|
|
34
|
+
// init i18next
|
|
35
|
+
// for all options read: https://www.i18next.com/overview/configuration-options
|
|
36
|
+
.init({
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
debug: process.env.NODE_ENV === 'development',
|
|
39
|
+
defaultNS: ns,
|
|
40
|
+
fallbackLng: 'zh-CN',
|
|
41
|
+
interpolation: {
|
|
42
|
+
escapeValue: false, // not needed for react as it escapes by default
|
|
43
|
+
},
|
|
44
|
+
ns,
|
|
45
|
+
resources: getRes(resources, ns),
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
'advanceSettings': '高级设置',
|
|
3
|
+
'agentAvatar': '头像',
|
|
4
|
+
'agentDescription': '描述',
|
|
5
|
+
'agentDescriptionPlaceholder': '请输入描述',
|
|
6
|
+
'agentModel': '模型',
|
|
7
|
+
'agentName': '名称',
|
|
8
|
+
'agentNamePlaceholder': '请输入名称',
|
|
9
|
+
'agentProfile': '助手信息',
|
|
10
|
+
'agentPrompt': '提示词',
|
|
11
|
+
'agentPromptPlaceholder': '请输入 AI 提示词',
|
|
12
|
+
'agentTag': '标签',
|
|
13
|
+
'agentTagPlaceholder': '请输入标签',
|
|
14
|
+
'archive': '归档',
|
|
15
|
+
'autoGenerate': '自动补全',
|
|
16
|
+
'autoGenerateTooltip': '基于提示词自动补全助手描述',
|
|
17
|
+
'cancel': '取消',
|
|
18
|
+
'close': '关闭',
|
|
19
|
+
'confirmRemoveSessionItemAlert': '即将删除该助手,删除后该将无法找回,请确认你的操作',
|
|
20
|
+
'defaultAgent': '默认助手',
|
|
21
|
+
'edit': '编辑',
|
|
22
|
+
'editAgentProfile': '编辑助手信息',
|
|
23
|
+
'export': '导出',
|
|
24
|
+
'gpt-3.5-turbo': 'GPT 3.5',
|
|
25
|
+
'gpt-3.5-turbo-16k': 'GPT 3.5 (16K)',
|
|
26
|
+
'gpt-4': 'GPT 4',
|
|
27
|
+
'gpt-4-32k': 'GPT 4 (32K)',
|
|
28
|
+
'modelConfig': '模型配置',
|
|
29
|
+
'modelTemperature': '发散度',
|
|
30
|
+
'newAgent': '新建助手',
|
|
31
|
+
'noDescription': '暂无描述',
|
|
32
|
+
'ok': '确定',
|
|
33
|
+
'profile': '助手身份',
|
|
34
|
+
'reset': '重置',
|
|
35
|
+
'searchAgentPlaceholder': '搜索助手和对话...',
|
|
36
|
+
'sessionSetting': '会话设置',
|
|
37
|
+
'setting': '设置',
|
|
38
|
+
'share': '分享',
|
|
39
|
+
'updateAgent': '更新助理信息',
|
|
40
|
+
'updatePrompt': '更新提示词',
|
|
41
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
danger: {
|
|
3
|
+
clear: {
|
|
4
|
+
action: '立即清除',
|
|
5
|
+
confirm: '确认清除所有聊天、设置数据?',
|
|
6
|
+
desc: '清除所有聊天、设置数据',
|
|
7
|
+
title: '清除所有数据',
|
|
8
|
+
},
|
|
9
|
+
reset: {
|
|
10
|
+
action: '立即重置',
|
|
11
|
+
confirm: '确认重置所有设置?',
|
|
12
|
+
currentVersion: '当前版本',
|
|
13
|
+
desc: '重置所有设置项回默认值',
|
|
14
|
+
title: '重置所有设置',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
header: '设置',
|
|
18
|
+
settingChat: {
|
|
19
|
+
compressThreshold: {
|
|
20
|
+
desc: '当未压缩的历史消息超过该值时,将进行压缩',
|
|
21
|
+
title: '历史消息长度压缩阈值',
|
|
22
|
+
},
|
|
23
|
+
historyCount: {
|
|
24
|
+
desc: '每次请求携带的历史消息数',
|
|
25
|
+
title: '附带历史消息数',
|
|
26
|
+
},
|
|
27
|
+
inputTemplate: {
|
|
28
|
+
desc: '用户最新的一条消息会填充到此模板',
|
|
29
|
+
title: '用户输入预处理',
|
|
30
|
+
},
|
|
31
|
+
maxTokens: {
|
|
32
|
+
desc: '单次交互所用的最大 Token 数',
|
|
33
|
+
title: '单次回复限制 (max_tokens)',
|
|
34
|
+
},
|
|
35
|
+
sendKey: {
|
|
36
|
+
title: '发送键',
|
|
37
|
+
},
|
|
38
|
+
title: '聊天设置',
|
|
39
|
+
},
|
|
40
|
+
settingModel: {
|
|
41
|
+
frequencyPenalty: {
|
|
42
|
+
desc: '值越大,越有可能降低重复字词',
|
|
43
|
+
title: '频率惩罚度 (frequency_penalty)',
|
|
44
|
+
},
|
|
45
|
+
model: {
|
|
46
|
+
title: '模型',
|
|
47
|
+
},
|
|
48
|
+
presencePenalty: {
|
|
49
|
+
desc: '值越大,越有可能扩展到新话题',
|
|
50
|
+
title: '话题新鲜度 (presence_penalty)',
|
|
51
|
+
},
|
|
52
|
+
temperature: {
|
|
53
|
+
desc: '值越大,回复越随机',
|
|
54
|
+
title: '随机性 (temperature)',
|
|
55
|
+
},
|
|
56
|
+
title: '模型设置',
|
|
57
|
+
topP: {
|
|
58
|
+
desc: '与随机性类似,但不要和随机性一起更改',
|
|
59
|
+
title: '核采样 (top_p)',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
settingOpenAI: {
|
|
63
|
+
endpoint: {
|
|
64
|
+
desc: '除默认地址外,必须包含 http(s)://',
|
|
65
|
+
title: '接口地址',
|
|
66
|
+
},
|
|
67
|
+
title: 'OpenAI 设置',
|
|
68
|
+
token: {
|
|
69
|
+
desc: '使用自己的 Key 可绕过密码访问限制',
|
|
70
|
+
placeholder: 'OpenAI API Key',
|
|
71
|
+
title: 'API Key',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
settingSystem: {
|
|
75
|
+
accessCode: {
|
|
76
|
+
desc: '管理员已开启加密访问',
|
|
77
|
+
placeholder: '请输入访问密码',
|
|
78
|
+
title: '访问密码',
|
|
79
|
+
},
|
|
80
|
+
title: '系统设置',
|
|
81
|
+
},
|
|
82
|
+
settingTheme: {
|
|
83
|
+
avatar: {
|
|
84
|
+
desc: '支持 URL / Base64 / Emoji 表情符号',
|
|
85
|
+
title: '头像',
|
|
86
|
+
},
|
|
87
|
+
fontSize: {
|
|
88
|
+
desc: '聊天内容的字体大小',
|
|
89
|
+
title: '字体大小',
|
|
90
|
+
},
|
|
91
|
+
lang: {
|
|
92
|
+
all: '所有语言',
|
|
93
|
+
name: '语言设置',
|
|
94
|
+
},
|
|
95
|
+
title: '主题设置',
|
|
96
|
+
},
|
|
97
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ConfigState } from '@/types/exportConfig';
|
|
2
|
+
import { VersionController } from '@/utils/VersionController';
|
|
3
|
+
|
|
4
|
+
// 当前最新的版本号
|
|
5
|
+
export const CURRENT_CONFIG_VERSION = 1;
|
|
6
|
+
|
|
7
|
+
// 历史记录版本升级模块
|
|
8
|
+
export const ConfigMigrations = [];
|
|
9
|
+
|
|
10
|
+
export const Migration = new VersionController<ConfigState>(
|
|
11
|
+
ConfigMigrations,
|
|
12
|
+
CURRENT_CONFIG_VERSION,
|
|
13
|
+
);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ActionIcon, Logo, SideNav } from '@lobehub/ui';
|
|
2
|
+
import { MessageSquare, Settings2, Sticker } from 'lucide-react';
|
|
3
|
+
import Router from 'next/router';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { shallow } from 'zustand/shallow';
|
|
6
|
+
|
|
7
|
+
import { useSettings } from '@/store/settings';
|
|
8
|
+
|
|
9
|
+
const Sidebar = memo(() => {
|
|
10
|
+
const [tab, setTab] = useSettings((s) => [s.sidebarKey, s.switchSideBar], shallow);
|
|
11
|
+
return (
|
|
12
|
+
<SideNav
|
|
13
|
+
avatar={<Logo size={40} />}
|
|
14
|
+
bottomActions={<ActionIcon icon={Settings2} onClick={() => Router.push('/setting')} />}
|
|
15
|
+
style={{ height: '100vh' }}
|
|
16
|
+
topActions={
|
|
17
|
+
<>
|
|
18
|
+
<ActionIcon
|
|
19
|
+
active={tab === 'chat'}
|
|
20
|
+
icon={MessageSquare}
|
|
21
|
+
onClick={() => setTab('chat')}
|
|
22
|
+
size="large"
|
|
23
|
+
/>
|
|
24
|
+
<ActionIcon
|
|
25
|
+
active={tab === 'market'}
|
|
26
|
+
icon={Sticker}
|
|
27
|
+
onClick={() => setTab('market')}
|
|
28
|
+
size="large"
|
|
29
|
+
/>
|
|
30
|
+
</>
|
|
31
|
+
}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export default Sidebar;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Analytics } from '@vercel/analytics/react';
|
|
2
|
+
import type { AppProps } from 'next/app';
|
|
3
|
+
|
|
4
|
+
import Layout from '@/layout';
|
|
5
|
+
|
|
6
|
+
export default ({ Component, pageProps }: AppProps) => {
|
|
7
|
+
return (
|
|
8
|
+
<Layout>
|
|
9
|
+
<Component {...pageProps} />
|
|
10
|
+
<Analytics />
|
|
11
|
+
</Layout>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { StyleProvider, extractStaticStyle } from 'antd-style';
|
|
2
|
+
import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document';
|
|
3
|
+
|
|
4
|
+
class MyDocument extends Document {
|
|
5
|
+
static async getStaticProps(ctx: DocumentContext) {
|
|
6
|
+
const page = await ctx.renderPage({
|
|
7
|
+
enhanceApp: (App) => (props) =>
|
|
8
|
+
(
|
|
9
|
+
<StyleProvider cache={extractStaticStyle.cache}>
|
|
10
|
+
<App {...props} />
|
|
11
|
+
</StyleProvider>
|
|
12
|
+
),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const styles = extractStaticStyle(page.html).map((item) => item.style);
|
|
16
|
+
|
|
17
|
+
const initialProps = await Document.getInitialProps(ctx);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
...initialProps,
|
|
21
|
+
styles: (
|
|
22
|
+
<>
|
|
23
|
+
{initialProps.styles}
|
|
24
|
+
{styles}
|
|
25
|
+
</>
|
|
26
|
+
),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render() {
|
|
31
|
+
return (
|
|
32
|
+
<Html>
|
|
33
|
+
<Head>
|
|
34
|
+
<link
|
|
35
|
+
href="https://npm.elemecdn.com/@lobehub/assets-favicons/assets/apple-touch-icon.png"
|
|
36
|
+
rel="apple-touch-icon"
|
|
37
|
+
sizes="180x180"
|
|
38
|
+
/>
|
|
39
|
+
<link
|
|
40
|
+
href="https://npm.elemecdn.com/@lobehub/assets-favicons/assets/favicon-32x32.png"
|
|
41
|
+
rel="icon"
|
|
42
|
+
sizes="32x32"
|
|
43
|
+
type="image/png"
|
|
44
|
+
/>
|
|
45
|
+
<link
|
|
46
|
+
href="https://npm.elemecdn.com/@lobehub/assets-favicons/assets/favicon-16x16.png"
|
|
47
|
+
rel="icon"
|
|
48
|
+
sizes="16x16"
|
|
49
|
+
type="image/png"
|
|
50
|
+
/>
|
|
51
|
+
<link
|
|
52
|
+
color="#000000"
|
|
53
|
+
href="https://npm.elemecdn.com/@lobehub/assets-favicons/assets/safari-pinned-tab.svg"
|
|
54
|
+
rel="mask-icon"
|
|
55
|
+
/>
|
|
56
|
+
<meta content="LobeHub" name="apple-mobile-web-app-title" />
|
|
57
|
+
<meta content="LobeHub" name="application-name" />
|
|
58
|
+
<meta content="#000000" name="msapplication-TileColor" />
|
|
59
|
+
<meta content="#000000" name="theme-color" />
|
|
60
|
+
</Head>
|
|
61
|
+
<body>
|
|
62
|
+
<Main />
|
|
63
|
+
<NextScript />
|
|
64
|
+
</body>
|
|
65
|
+
</Html>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default MyDocument;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { LLMChain } from 'langchain/chains';
|
|
2
|
+
import { ChatOpenAI } from 'langchain/chat_models/openai';
|
|
3
|
+
import {
|
|
4
|
+
AIMessagePromptTemplate,
|
|
5
|
+
ChatPromptTemplate,
|
|
6
|
+
HumanMessagePromptTemplate,
|
|
7
|
+
SystemMessagePromptTemplate,
|
|
8
|
+
} from 'langchain/prompts';
|
|
9
|
+
|
|
10
|
+
import { LangChainParams } from '@/types/langchain';
|
|
11
|
+
|
|
12
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
13
|
+
const OPENAI_PROXY_URL = process.env.OPENAI_PROXY_URL;
|
|
14
|
+
|
|
15
|
+
export function LangChainStream(payload: LangChainParams) {
|
|
16
|
+
const { prompts, vars, llm } = payload;
|
|
17
|
+
|
|
18
|
+
// 将 payload 中的消息转换为 ChatOpenAI 所需的 HumanChatMessage、SystemChatMessage 和 AIChatMessage 类型
|
|
19
|
+
const chatPrompt = ChatPromptTemplate.fromPromptMessages(
|
|
20
|
+
prompts.map((m) => {
|
|
21
|
+
switch (m.role) {
|
|
22
|
+
default:
|
|
23
|
+
case 'user': {
|
|
24
|
+
return HumanMessagePromptTemplate.fromTemplate(m.content);
|
|
25
|
+
}
|
|
26
|
+
case 'system': {
|
|
27
|
+
return SystemMessagePromptTemplate.fromTemplate(m.content);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
case 'assistant': {
|
|
31
|
+
return AIMessagePromptTemplate.fromTemplate(m.content);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// 使用 TextEncoder 将字符串转换为字节数组,以便在 ReadableStream 中发送
|
|
38
|
+
const encoder = new TextEncoder();
|
|
39
|
+
|
|
40
|
+
// 初始化换行符计数器
|
|
41
|
+
|
|
42
|
+
return new ReadableStream({
|
|
43
|
+
async start(controller) {
|
|
44
|
+
let newlineCounter = 0;
|
|
45
|
+
|
|
46
|
+
const chat = new ChatOpenAI(
|
|
47
|
+
{
|
|
48
|
+
streaming: true,
|
|
49
|
+
...llm,
|
|
50
|
+
|
|
51
|
+
callbacks: [
|
|
52
|
+
{
|
|
53
|
+
handleLLMNewToken(token) {
|
|
54
|
+
// 如果 message 是换行符,且 newlineCounter 小于 2,那么跳过该换行符
|
|
55
|
+
if (newlineCounter < 2 && token === '\n') {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 将 message 编码为字节并添加到流中
|
|
60
|
+
const queue = encoder.encode(token);
|
|
61
|
+
controller.enqueue(queue);
|
|
62
|
+
newlineCounter++;
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
// 暂时设定不重试 ,后续看是否需要支持重试
|
|
67
|
+
maxRetries: 0,
|
|
68
|
+
},
|
|
69
|
+
isDev && OPENAI_PROXY_URL ? { basePath: OPENAI_PROXY_URL } : undefined,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const chain = new LLMChain({
|
|
73
|
+
callbacks: [
|
|
74
|
+
{
|
|
75
|
+
handleChainError(err: Error): Promise<void> | void {
|
|
76
|
+
console.log(err.message);
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
llm: chat,
|
|
81
|
+
prompt: chatPrompt,
|
|
82
|
+
verbose: true,
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
85
|
+
// 使用转换后的聊天消息作为输入开始聊天
|
|
86
|
+
await chain.call(vars);
|
|
87
|
+
// 完成后,关闭流
|
|
88
|
+
controller.close();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
// 如果在执行过程中发生错误,向流发送错误
|
|
91
|
+
controller.error(error);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|