@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.
Files changed (142) hide show
  1. package/.changelogrc.js +1 -0
  2. package/.commitlintrc.js +1 -0
  3. package/.editorconfig +16 -0
  4. package/.eslintignore +32 -0
  5. package/.eslintrc.js +6 -0
  6. package/.github/ISSUE_TEMPLATE/1_bug_report.yml +45 -0
  7. package/.github/ISSUE_TEMPLATE/2_feature_request.yml +21 -0
  8. package/.github/ISSUE_TEMPLATE/3_question.yml +15 -0
  9. package/.github/ISSUE_TEMPLATE/4_other.md +7 -0
  10. package/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  11. package/.github/dependabot.yml +17 -0
  12. package/.github/workflows/auto-merge.yml +32 -0
  13. package/.github/workflows/contributor-help.yml +29 -0
  14. package/.github/workflows/issue-check-inactive.yml +22 -0
  15. package/.github/workflows/issue-close-require.yml +46 -0
  16. package/.github/workflows/issue-remove-inactive.yml +25 -0
  17. package/.github/workflows/release.yml +34 -0
  18. package/.github/workflows/test.yml +30 -0
  19. package/.gitpod.yml +3 -0
  20. package/.husky/commit-msg +4 -0
  21. package/.husky/pre-commit +5 -0
  22. package/.i18nrc.js +13 -0
  23. package/.prettierignore +63 -0
  24. package/.prettierrc.js +1 -0
  25. package/.releaserc.js +1 -0
  26. package/.remarkrc.js +1 -0
  27. package/.stylelintrc.js +8 -0
  28. package/CHANGELOG.md +80 -0
  29. package/README.md +147 -0
  30. package/locales/en_US/common.json +40 -0
  31. package/locales/en_US/setting.json +97 -0
  32. package/locales/zh_CN/common.json +40 -0
  33. package/locales/zh_CN/setting.json +98 -0
  34. package/next.config.mjs +32 -0
  35. package/package.json +138 -0
  36. package/public/next.svg +1 -0
  37. package/public/vercel.svg +1 -0
  38. package/scripts/genDefaultLocale.mjs +12 -0
  39. package/scripts/toc.mjs +40 -0
  40. package/src/const/fetch.ts +1 -0
  41. package/src/const/modelTokens.ts +8 -0
  42. package/src/features/FolderPanel/index.tsx +55 -0
  43. package/src/helpers/prompt.test.ts +36 -0
  44. package/src/helpers/prompt.ts +36 -0
  45. package/src/helpers/url.ts +17 -0
  46. package/src/layout/index.tsx +42 -0
  47. package/src/layout/style.ts +18 -0
  48. package/src/locales/create.ts +48 -0
  49. package/src/locales/default/common.ts +41 -0
  50. package/src/locales/default/setting.ts +97 -0
  51. package/src/locales/index.ts +5 -0
  52. package/src/locales/resources/en_US.ts +9 -0
  53. package/src/locales/resources/index.ts +7 -0
  54. package/src/locales/resources/zh_CN.ts +9 -0
  55. package/src/migrations/FromV0ToV1.ts +12 -0
  56. package/src/migrations/index.ts +13 -0
  57. package/src/pages/Sidebar.tsx +36 -0
  58. package/src/pages/_app.page.tsx +13 -0
  59. package/src/pages/_document.page.tsx +70 -0
  60. package/src/pages/api/LangChainStream.ts +95 -0
  61. package/src/pages/api/chain.api.ts +17 -0
  62. package/src/pages/api/openai.api.ts +31 -0
  63. package/src/pages/chat/SessionList/Header.tsx +56 -0
  64. package/src/pages/chat/SessionList/List/SessionItem.tsx +90 -0
  65. package/src/pages/chat/SessionList/List/index.tsx +31 -0
  66. package/src/pages/chat/SessionList/List/style.ts +77 -0
  67. package/src/pages/chat/SessionList/index.tsx +18 -0
  68. package/src/pages/chat/[id]/Config/ConfigCell.tsx +68 -0
  69. package/src/pages/chat/[id]/Config/ReadMode.tsx +63 -0
  70. package/src/pages/chat/[id]/Config/index.tsx +79 -0
  71. package/src/pages/chat/[id]/Conversation/ChatList.tsx +36 -0
  72. package/src/pages/chat/[id]/Conversation/Input.tsx +61 -0
  73. package/src/pages/chat/[id]/Conversation/index.tsx +32 -0
  74. package/src/pages/chat/[id]/Header.tsx +86 -0
  75. package/src/pages/chat/[id]/edit/AgentConfig.tsx +95 -0
  76. package/src/pages/chat/[id]/edit/AgentMeta.tsx +117 -0
  77. package/src/pages/chat/[id]/edit/FormItem.tsx +26 -0
  78. package/src/pages/chat/[id]/edit/Prompt.tsx +68 -0
  79. package/src/pages/chat/[id]/edit/index.page.tsx +62 -0
  80. package/src/pages/chat/[id]/edit/style.ts +42 -0
  81. package/src/pages/chat/[id]/index.page.tsx +40 -0
  82. package/src/pages/chat/index.page.tsx +1 -0
  83. package/src/pages/chat/layout.tsx +51 -0
  84. package/src/pages/index.page.tsx +1 -0
  85. package/src/pages/setting/Header.tsx +27 -0
  86. package/src/pages/setting/SettingForm.tsx +42 -0
  87. package/src/pages/setting/index.page.tsx +41 -0
  88. package/src/prompts/agent.ts +65 -0
  89. package/src/services/chatModel.ts +34 -0
  90. package/src/services/langChain.ts +18 -0
  91. package/src/services/url.ts +8 -0
  92. package/src/store/middleware/createHashStorage.ts +49 -0
  93. package/src/store/session/index.ts +33 -0
  94. package/src/store/session/initialState.ts +11 -0
  95. package/src/store/session/selectors.ts +3 -0
  96. package/src/store/session/slices/agentConfig/action.ts +226 -0
  97. package/src/store/session/slices/agentConfig/index.ts +3 -0
  98. package/src/store/session/slices/agentConfig/initialState.ts +34 -0
  99. package/src/store/session/slices/agentConfig/selectors.ts +54 -0
  100. package/src/store/session/slices/chat/action.ts +210 -0
  101. package/src/store/session/slices/chat/index.ts +3 -0
  102. package/src/store/session/slices/chat/initialState.ts +12 -0
  103. package/src/store/session/slices/chat/messageReducer.test.ts +70 -0
  104. package/src/store/session/slices/chat/messageReducer.ts +84 -0
  105. package/src/store/session/slices/chat/selectors.ts +83 -0
  106. package/src/store/session/slices/session/action.ts +118 -0
  107. package/src/store/session/slices/session/index.ts +3 -0
  108. package/src/store/session/slices/session/initialState.ts +31 -0
  109. package/src/store/session/slices/session/reducers/session.test.ts +456 -0
  110. package/src/store/session/slices/session/reducers/session.ts +113 -0
  111. package/src/store/session/slices/session/selectors/chat.ts +4 -0
  112. package/src/store/session/slices/session/selectors/index.ts +20 -0
  113. package/src/store/session/slices/session/selectors/list.ts +65 -0
  114. package/src/store/session/store.ts +17 -0
  115. package/src/store/settings/action.ts +31 -0
  116. package/src/store/settings/index.ts +23 -0
  117. package/src/store/settings/initialState.ts +25 -0
  118. package/src/store/settings/selectors.ts +9 -0
  119. package/src/store/settings/store.ts +13 -0
  120. package/src/styles/antdOverride.ts +29 -0
  121. package/src/styles/global.ts +23 -0
  122. package/src/styles/index.ts +6 -0
  123. package/src/types/chatMessage.ts +46 -0
  124. package/src/types/exportConfig.ts +23 -0
  125. package/src/types/global.d.ts +14 -0
  126. package/src/types/i18next.d.ts +8 -0
  127. package/src/types/langchain.ts +34 -0
  128. package/src/types/llm.ts +49 -0
  129. package/src/types/locale.ts +7 -0
  130. package/src/types/meta.ts +26 -0
  131. package/src/types/openai.ts +62 -0
  132. package/src/types/session.ts +59 -0
  133. package/src/utils/VersionController.test.ts +90 -0
  134. package/src/utils/VersionController.ts +64 -0
  135. package/src/utils/compass.ts +94 -0
  136. package/src/utils/fetch.ts +132 -0
  137. package/src/utils/filter.test.ts +120 -0
  138. package/src/utils/filter.ts +29 -0
  139. package/src/utils/uploadFIle.ts +8 -0
  140. package/src/utils/uuid.ts +9 -0
  141. package/tsconfig.json +26 -0
  142. 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,5 @@
1
+ import { createI18nNext } from './create';
2
+
3
+ const initI18n = createI18nNext();
4
+
5
+ export default initI18n;
@@ -0,0 +1,9 @@
1
+ import common from '../../../locales/en_US/common.json';
2
+ import setting from '../../../locales/en_US/setting.json';
3
+
4
+ const resources = {
5
+ common,
6
+ setting,
7
+ } as const;
8
+
9
+ export default resources;
@@ -0,0 +1,7 @@
1
+ import en_US from './en_US';
2
+ import zh_CN from './zh_CN';
3
+
4
+ export default {
5
+ 'en-US': en_US,
6
+ 'zh-CN': zh_CN,
7
+ };
@@ -0,0 +1,9 @@
1
+ import common from '../default/common';
2
+ import setting from '../default/setting';
3
+
4
+ const resources = {
5
+ common,
6
+ setting,
7
+ } as const;
8
+
9
+ export default resources;
@@ -0,0 +1,12 @@
1
+ import type { Migration, MigrationData } from '../utils/VersionController';
2
+
3
+ export class MigrationV0ToV1 implements Migration {
4
+ /***
5
+ * 配置项里的当前版本号
6
+ */
7
+ version = 0;
8
+
9
+ migrate(data: MigrationData): MigrationData {
10
+ return data;
11
+ }
12
+ }
@@ -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
+ }