@lobehub/chat 0.106.0 → 0.107.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 (214) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/locales/en_US/common.json +19 -0
  3. package/locales/en_US/migration.json +45 -0
  4. package/locales/en_US/setting.json +7 -7
  5. package/locales/ja_JP/common.json +19 -0
  6. package/locales/ja_JP/migration.json +45 -0
  7. package/locales/ja_JP/setting.json +2 -2
  8. package/locales/ko_KR/common.json +19 -0
  9. package/locales/ko_KR/migration.json +45 -0
  10. package/locales/ko_KR/setting.json +2 -2
  11. package/locales/ru_RU/common.json +19 -0
  12. package/locales/ru_RU/migration.json +45 -0
  13. package/locales/ru_RU/setting.json +2 -2
  14. package/locales/zh_CN/common.json +19 -0
  15. package/locales/zh_CN/migration.json +45 -0
  16. package/locales/zh_CN/setting.json +1 -1
  17. package/locales/zh_TW/common.json +19 -0
  18. package/locales/zh_TW/migration.json +45 -0
  19. package/locales/zh_TW/setting.json +2 -2
  20. package/package.json +2 -1
  21. package/src/app/api/openai/chat/createChatCompletion.test.ts +4 -4
  22. package/src/app/api/openai/chat/createChatCompletion.ts +4 -10
  23. package/src/app/api/openai/createBizOpenAI/index.ts +1 -1
  24. package/src/app/api/openai/errorResponse.test.ts +1 -1
  25. package/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx +2 -1
  26. package/src/app/chat/(mobile)/features/ChatInput/SendButton.tsx +2 -2
  27. package/src/app/chat/features/ChatHeader/ShareButton/Inner.tsx +2 -5
  28. package/src/app/chat/features/ChatHeader/ShareButton/index.tsx +2 -2
  29. package/src/app/chat/features/ChatInput/ActionBar/Clear.tsx +2 -2
  30. package/src/app/chat/features/ChatInput/ActionBar/Token/TokenTag.tsx +8 -2
  31. package/src/app/chat/features/ChatInput/InputAreaInner/index.tsx +2 -2
  32. package/src/app/chat/features/ChatInput/STT/index.tsx +2 -2
  33. package/src/app/chat/features/ChatInput/Topic/index.tsx +2 -2
  34. package/src/app/chat/features/ChatInput/useSend.ts +3 -3
  35. package/src/app/chat/features/Conversation/ChatList/Actions/index.ts +2 -2
  36. package/src/app/chat/features/Conversation/ChatList/Error/ApiKeyForm.tsx +2 -2
  37. package/src/app/chat/features/Conversation/ChatList/Error/InvalidAccess.tsx +2 -2
  38. package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +2 -2
  39. package/src/app/chat/features/Conversation/ChatList/Extras/Assistant.test.tsx +4 -4
  40. package/src/app/chat/features/Conversation/ChatList/Extras/Assistant.tsx +30 -31
  41. package/src/app/chat/features/Conversation/ChatList/Extras/TTS.tsx +2 -2
  42. package/src/app/chat/features/Conversation/ChatList/Extras/Translate.tsx +2 -2
  43. package/src/app/chat/features/Conversation/ChatList/Extras/User.tsx +8 -5
  44. package/src/app/chat/features/Conversation/ChatList/Extras/index.ts +3 -3
  45. package/src/app/chat/features/Conversation/ChatList/Extras/type.ts +5 -0
  46. package/src/app/chat/features/Conversation/ChatList/Messages/Assistant.tsx +10 -6
  47. package/src/app/chat/features/Conversation/ChatList/Messages/Default.tsx +7 -3
  48. package/src/app/chat/features/Conversation/ChatList/Messages/Function.tsx +7 -6
  49. package/src/app/chat/features/Conversation/ChatList/Messages/index.ts +7 -7
  50. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +6 -6
  51. package/src/app/chat/features/Conversation/ChatList/index.tsx +16 -24
  52. package/src/app/chat/features/Conversation/ScrollAnchor/index.tsx +2 -2
  53. package/src/app/chat/features/Conversation/index.tsx +3 -3
  54. package/src/app/chat/features/Conversation/useInitConversation.ts +43 -0
  55. package/src/app/chat/features/Migration/ExportConfigButton.tsx +33 -0
  56. package/src/app/chat/features/Migration/Failed.tsx +115 -0
  57. package/src/app/chat/features/Migration/Modal.tsx +80 -0
  58. package/src/app/chat/features/Migration/Start.tsx +106 -0
  59. package/src/app/chat/features/Migration/UpgradeButton.tsx +74 -0
  60. package/src/app/chat/features/Migration/const.ts +15 -0
  61. package/src/app/chat/features/Migration/index.tsx +44 -0
  62. package/src/app/chat/features/SessionListContent/DefaultMode.tsx +54 -0
  63. package/src/app/chat/features/SessionListContent/List/Item/Actions.tsx +5 -4
  64. package/src/app/chat/features/SessionListContent/List/Item/index.tsx +22 -28
  65. package/src/app/chat/features/SessionListContent/SearchMode.tsx +15 -0
  66. package/src/app/chat/features/SessionListContent/index.tsx +4 -44
  67. package/src/app/chat/features/SessionSearchBar/index.tsx +11 -3
  68. package/src/app/chat/features/TopicListContent/Header.tsx +4 -4
  69. package/src/app/chat/features/TopicListContent/Topic/TopicContent.tsx +11 -9
  70. package/src/app/chat/features/TopicListContent/Topic/TopicItem.tsx +15 -13
  71. package/src/app/chat/features/TopicListContent/Topic/index.tsx +9 -16
  72. package/src/app/chat/features/TopicListContent/TopicSearchBar/index.tsx +13 -4
  73. package/src/app/chat/features/TopicListContent/index.tsx +1 -2
  74. package/src/app/chat/page.tsx +6 -1
  75. package/src/app/chat/settings/features/HeaderContent.tsx +3 -3
  76. package/src/app/home/Redirect.tsx +13 -7
  77. package/src/app/settings/(mobile)/features/ExtraList.tsx +12 -4
  78. package/src/app/settings/common/Common.tsx +16 -4
  79. package/src/app/welcome/features/Banner/index.tsx +9 -10
  80. package/src/chains/summaryTitle.ts +5 -2
  81. package/src/components/DataStyleModal/index.tsx +64 -0
  82. package/src/config/client.ts +5 -0
  83. package/src/const/session.ts +21 -0
  84. package/src/const/settings.ts +1 -1
  85. package/src/database/core/__tests__/db.test.ts +8 -5
  86. package/src/database/core/__tests__/model.test.ts +3 -3
  87. package/src/database/core/db.ts +38 -10
  88. package/src/database/core/index.ts +1 -0
  89. package/src/database/core/model.ts +121 -10
  90. package/src/database/core/schemas.ts +22 -0
  91. package/src/database/core/types/db.ts +13 -0
  92. package/src/database/models/__DEBUG.ts +119 -0
  93. package/src/database/models/{file.test.ts → __tests__/file.test.ts} +19 -6
  94. package/src/database/models/__tests__/message.test.ts +314 -0
  95. package/src/database/models/__tests__/session.test.ts +117 -0
  96. package/src/database/models/__tests__/topic.test.ts +250 -0
  97. package/src/database/models/file.ts +9 -5
  98. package/src/database/models/message.ts +186 -0
  99. package/src/database/models/session.ts +160 -0
  100. package/src/database/models/topic.ts +196 -0
  101. package/src/{types/database → database/schemas}/files.ts +2 -2
  102. package/src/database/schemas/message.ts +37 -0
  103. package/src/database/schemas/session.ts +56 -0
  104. package/src/database/schemas/topic.ts +12 -0
  105. package/src/features/AgentSetting/AgentTTS/SelectWithTTSPreview.tsx +1 -1
  106. package/src/features/AgentSetting/store/action.ts +1 -1
  107. package/src/features/AgentSetting/store/initialState.ts +1 -1
  108. package/src/features/AgentSetting/store/reducers/config.ts +1 -1
  109. package/src/features/DataImporter/index.tsx +240 -0
  110. package/src/features/DebugUI/index.tsx +34 -0
  111. package/src/features/SideBar/BottomActions.tsx +8 -14
  112. package/src/hooks/useImportConfig.ts +13 -30
  113. package/src/hooks/useTTS.ts +1 -1
  114. package/src/layout/GlobalLayout/index.tsx +16 -1
  115. package/src/locales/default/common.ts +19 -0
  116. package/src/locales/default/migration.ts +47 -0
  117. package/src/locales/default/setting.ts +1 -1
  118. package/src/locales/resources/en_US.ts +2 -0
  119. package/src/locales/resources/ja_JP.ts +2 -0
  120. package/src/locales/resources/ko_KR.ts +2 -0
  121. package/src/locales/resources/ru_RU.ts +2 -0
  122. package/src/locales/resources/zh_CN.ts +2 -0
  123. package/src/locales/resources/zh_TW.ts +2 -0
  124. package/src/migrations/FromV0ToV1.ts +2 -4
  125. package/src/migrations/FromV1ToV2/fixtures/input-v1-session.json +191 -0
  126. package/src/migrations/FromV1ToV2/fixtures/output-v2.json +202 -0
  127. package/src/migrations/FromV1ToV2/index.ts +82 -0
  128. package/src/migrations/FromV1ToV2/migrations.test.ts +224 -0
  129. package/src/migrations/FromV1ToV2/types/v1.ts +78 -0
  130. package/src/migrations/FromV1ToV2/types/v2.ts +52 -0
  131. package/src/{utils → migrations}/VersionController.test.ts +1 -3
  132. package/src/{utils → migrations}/VersionController.ts +5 -2
  133. package/src/migrations/index.ts +17 -3
  134. package/src/services/__tests__/file.test.ts +3 -3
  135. package/src/services/chat.ts +66 -5
  136. package/src/services/config.ts +184 -0
  137. package/src/services/debug.ts +13 -0
  138. package/src/services/file.ts +6 -2
  139. package/src/services/github.ts +25 -0
  140. package/src/services/message.ts +79 -0
  141. package/src/services/session.ts +63 -0
  142. package/src/services/topic.ts +56 -0
  143. package/src/store/chat/actions/enhance.ts +95 -0
  144. package/src/store/{session/slices/chat → chat}/actions/message.ts +225 -247
  145. package/src/store/{session/slices/chat → chat}/actions/plugin.ts +29 -34
  146. package/src/store/{session/slices/chat → chat}/actions/share.ts +9 -13
  147. package/src/store/chat/actions/topic.ts +197 -0
  148. package/src/store/chat/helpers.ts +23 -0
  149. package/src/store/chat/index.ts +3 -0
  150. package/src/store/chat/initialState.ts +48 -0
  151. package/src/store/chat/reducers/message.test.ts +83 -0
  152. package/src/store/chat/reducers/message.ts +83 -0
  153. package/src/store/chat/selectors/__tests__/topic.test.ts +43 -0
  154. package/src/store/chat/selectors/chat.ts +119 -0
  155. package/src/store/chat/selectors/index.ts +2 -0
  156. package/src/store/chat/selectors/topic.ts +28 -0
  157. package/src/store/chat/store.ts +63 -0
  158. package/src/store/files/slices/images/action.ts +6 -4
  159. package/src/store/global/store.ts +1 -1
  160. package/src/store/middleware/createHyperStorage/index.ts +40 -24
  161. package/src/store/middleware/createHyperStorage/keyMapper.ts +8 -2
  162. package/src/store/middleware/createHyperStorage/type.ts +10 -8
  163. package/src/store/plugin/slices/plugin/action.ts +1 -1
  164. package/src/store/plugin/store.ts +2 -4
  165. package/src/store/session/helpers.ts +1 -1
  166. package/src/store/session/hooks/useSessionHydrated.ts +6 -10
  167. package/src/store/session/initialState.ts +26 -7
  168. package/src/store/session/selectors.ts +1 -2
  169. package/src/store/session/slices/agent/action.ts +26 -35
  170. package/src/store/session/slices/agent/selectors.ts +32 -37
  171. package/src/store/session/slices/session/action.ts +81 -88
  172. package/src/store/session/slices/session/helpers.ts +19 -0
  173. package/src/store/session/slices/session/initialState.ts +7 -10
  174. package/src/store/session/slices/session/selectors/export.ts +12 -9
  175. package/src/store/session/slices/session/selectors/index.ts +2 -35
  176. package/src/store/session/slices/session/selectors/list.test.ts +25 -203
  177. package/src/store/session/slices/session/selectors/list.ts +40 -78
  178. package/src/store/session/store.ts +11 -17
  179. package/src/types/agent/index.ts +61 -0
  180. package/src/types/chatMessage.ts +11 -20
  181. package/src/types/exportConfig.ts +7 -2
  182. package/src/types/files.ts +2 -2
  183. package/src/types/llm.ts +1 -1
  184. package/src/types/meta.ts +24 -12
  185. package/src/types/openai/chat.ts +9 -1
  186. package/src/types/openai/functionCall.ts +4 -0
  187. package/src/types/session.ts +10 -94
  188. package/src/types/translate.ts +2 -1
  189. package/src/utils/config.ts +5 -11
  190. package/src/utils/fetch.ts +8 -2
  191. package/src/app/chat/features/Conversation/usePluginsInit.ts +0 -14
  192. package/src/database/core/schema.ts +0 -9
  193. package/src/helpers/export.ts +0 -69
  194. package/src/hooks/useExportConfig.ts +0 -6
  195. package/src/store/session/slices/chat/actions/index.ts +0 -32
  196. package/src/store/session/slices/chat/actions/topic.ts +0 -171
  197. package/src/store/session/slices/chat/actions/translate.ts +0 -113
  198. package/src/store/session/slices/chat/helpers.ts +0 -9
  199. package/src/store/session/slices/chat/initialState.ts +0 -13
  200. package/src/store/session/slices/chat/reducers/message.test.ts +0 -319
  201. package/src/store/session/slices/chat/reducers/message.ts +0 -156
  202. package/src/store/session/slices/chat/reducers/topic.test.ts +0 -179
  203. package/src/store/session/slices/chat/reducers/topic.ts +0 -81
  204. package/src/store/session/slices/chat/selectors/chat.ts +0 -111
  205. package/src/store/session/slices/chat/selectors/index.ts +0 -26
  206. package/src/store/session/slices/chat/selectors/topic.ts +0 -30
  207. package/src/store/session/slices/chat/selectors/utils.test.ts +0 -385
  208. package/src/store/session/slices/chat/selectors/utils.ts +0 -91
  209. package/src/store/session/slices/chat/utils.ts +0 -13
  210. package/src/store/session/slices/session/reducers/session.test.ts +0 -577
  211. package/src/store/session/slices/session/reducers/session.ts +0 -182
  212. package/src/types/database/db.ts +0 -4
  213. /package/src/store/{session/slices/chat → chat}/reducers/files.test.ts +0 -0
  214. /package/src/store/{session/slices/chat → chat}/reducers/files.ts +0 -0
@@ -10,6 +10,8 @@ import { FORM_STYLE } from '@/const/layoutTokens';
10
10
  import { DEFAULT_SETTINGS } from '@/const/settings';
11
11
  import AvatarWithUpload from '@/features/AvatarWithUpload';
12
12
  import { localeOptions } from '@/locales/options';
13
+ import { useChatStore } from '@/store/chat';
14
+ import { useFileStore } from '@/store/files';
13
15
  import { settingsSelectors, useGlobalStore } from '@/store/global';
14
16
  import { usePluginStore } from '@/store/plugin';
15
17
  import { useSessionStore } from '@/store/session';
@@ -22,7 +24,13 @@ type SettingItemGroup = ItemGroup;
22
24
  const Common = memo(() => {
23
25
  const { t } = useTranslation('setting');
24
26
  const [form] = AntForm.useForm();
27
+
25
28
  const clearSessions = useSessionStore((s) => s.clearSessions);
29
+ const [clearTopics, clearAllMessages] = useChatStore((s) => [
30
+ s.removeAllTopics,
31
+ s.clearAllMessages,
32
+ ]);
33
+ const [removeAllFiles] = useFileStore((s) => [s.removeAllFiles]);
26
34
  const resetPluginSettings = usePluginStore((s) => s.resetPluginSettings);
27
35
 
28
36
  const settings = useGlobalStore(settingsSelectors.currentSettings, isEqual);
@@ -56,9 +64,13 @@ const Common = memo(() => {
56
64
  danger: true,
57
65
  },
58
66
  okText: t('ok', { ns: 'common' }),
59
- onOk: () => {
60
- clearSessions();
67
+ onOk: async () => {
68
+ await clearSessions();
61
69
  resetPluginSettings();
70
+ await clearTopics();
71
+ await removeAllFiles();
72
+ await clearAllMessages();
73
+
62
74
  message.success(t('danger.clear.success'));
63
75
  },
64
76
  title: t('danger.clear.confirm'),
@@ -161,8 +173,8 @@ const Common = memo(() => {
161
173
  {t('danger.clear.action')}
162
174
  </Button>
163
175
  ),
164
- desc: t('danger.clear.title'),
165
- label: t('danger.clear.desc'),
176
+ desc: t('danger.clear.desc'),
177
+ label: t('danger.clear.title'),
166
178
  minWidth: undefined,
167
179
  },
168
180
  ],
@@ -1,20 +1,19 @@
1
1
  'use client';
2
2
 
3
3
  import { Icon } from '@lobehub/ui';
4
- import { Button, Upload } from 'antd';
4
+ import { Button } from 'antd';
5
5
  import { SendHorizonal } from 'lucide-react';
6
- import { memo, useCallback } from 'react';
6
+ import { memo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
- import { useImportConfig } from '@/hooks/useImportConfig';
10
+ import DataImporter from '@/features/DataImporter';
11
11
  import { useSessionStore } from '@/store/session';
12
12
 
13
13
  import Hero from './Hero';
14
14
  import { useStyles } from './style';
15
15
 
16
16
  const Banner = memo<{ mobile?: boolean }>(({ mobile }) => {
17
- const { importConfig } = useImportConfig();
18
17
  const { t } = useTranslation('welcome');
19
18
  const { styles } = useStyles();
20
19
  const [switchSession, switchBackToChat, router, isMobile] = useSessionStore((s) => [
@@ -23,10 +22,6 @@ const Banner = memo<{ mobile?: boolean }>(({ mobile }) => {
23
22
  s.router,
24
23
  s.isMobile,
25
24
  ]);
26
- const handleImport = useCallback((e: any) => {
27
- importConfig(e);
28
- switchSession();
29
- }, []);
30
25
 
31
26
  return (
32
27
  <>
@@ -40,11 +35,15 @@ const Banner = memo<{ mobile?: boolean }>(({ mobile }) => {
40
35
  justify={'center'}
41
36
  width={'100%'}
42
37
  >
43
- <Upload maxCount={1} onChange={handleImport} showUploadList={false}>
38
+ <DataImporter
39
+ onFinishImport={() => {
40
+ switchSession();
41
+ }}
42
+ >
44
43
  <Button block={mobile} size={'large'}>
45
44
  {t('button.import')}
46
45
  </Button>
47
- </Upload>
46
+ </DataImporter>
48
47
  <Button
49
48
  block={mobile}
50
49
  onClick={() => (isMobile ? router?.push('/chat') : switchBackToChat())}
@@ -1,7 +1,10 @@
1
+ import { chatHelpers } from '@/store/chat/helpers';
1
2
  import { getCurrentLanguage } from '@/store/global/helpers';
2
- import { chatHelpers } from '@/store/session/slices/chat/helpers';
3
3
  import { LanguageModel } from '@/types/llm';
4
- import { OpenAIChatMessage, OpenAIChatStreamPayload } from '@/types/openai/chat';
4
+ import {
5
+ OpenAIChatMessage,
6
+ OpenAIChatStreamPayload,
7
+ } from '@/types/openai/chat';
5
8
 
6
9
  export const chainSummaryTitle = async (
7
10
  messages: OpenAIChatMessage[],
@@ -0,0 +1,64 @@
1
+ import { Icon, Modal } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { LucideIcon } from 'lucide-react';
4
+ import { rgba } from 'polished';
5
+ import { ReactNode, memo } from 'react';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ const useStyles = createStyles(({ css, token, prefixCls, isDarkMode }) => ({
9
+ modalTitle: css`
10
+ &.${prefixCls}-modal-header {
11
+ height: 80px;
12
+ background:
13
+ linear-gradient(
14
+ 180deg,
15
+ ${rgba(token.colorBgElevated, 0)},
16
+ ${token.colorBgContainer} ${isDarkMode ? '80' : '140'}px
17
+ ),
18
+ fixed 0 0 /10px 10px radial-gradient(${token.colorFill} 1px, transparent 0);
19
+ }
20
+
21
+ & .${prefixCls}-modal-title {
22
+ font-size: 24px;
23
+ }
24
+ `,
25
+ }));
26
+
27
+ interface DataStyleModalProps {
28
+ children: ReactNode;
29
+ icon: LucideIcon;
30
+ onOpenChange?: (open: boolean) => void;
31
+ open: boolean;
32
+ title: string;
33
+ width?: number;
34
+ }
35
+
36
+ const DataStyleModal = memo<DataStyleModalProps>(
37
+ ({ icon, onOpenChange, title, open, children, width = 550 }) => {
38
+ const { styles } = useStyles();
39
+
40
+ return (
41
+ <Modal
42
+ afterOpenChange={onOpenChange}
43
+ centered
44
+ classNames={{
45
+ header: styles.modalTitle,
46
+ }}
47
+ closable={false}
48
+ footer={null}
49
+ open={open}
50
+ title={
51
+ <Flexbox gap={8} horizontal>
52
+ <Icon icon={icon} />
53
+ {title}
54
+ </Flexbox>
55
+ }
56
+ width={width}
57
+ >
58
+ {children}
59
+ </Modal>
60
+ );
61
+ },
62
+ );
63
+
64
+ export default DataStyleModal;
@@ -28,6 +28,8 @@ declare global {
28
28
  NEXT_PUBLIC_I18N_DEBUG: string;
29
29
  NEXT_PUBLIC_I18N_DEBUG_BROWSER: string;
30
30
  NEXT_PUBLIC_I18N_DEBUG_SERVER: string;
31
+
32
+ NEXT_PUBLIC_DEVELOPER_DEBUG: string;
31
33
  }
32
34
  }
33
35
  }
@@ -63,4 +65,7 @@ export const getClientConfig = () => ({
63
65
  I18N_DEBUG: process.env.NEXT_PUBLIC_I18N_DEBUG === '1',
64
66
  I18N_DEBUG_BROWSER: process.env.NEXT_PUBLIC_I18N_DEBUG_BROWSER === '1',
65
67
  I18N_DEBUG_SERVER: process.env.NEXT_PUBLIC_I18N_DEBUG_SERVER === '1',
68
+
69
+ // developer debug mode
70
+ DEBUG_MODE: process.env.NEXT_PUBLIC_DEVELOPER_DEBUG === '1',
66
71
  });
@@ -1 +1,22 @@
1
+ import { DEFAULT_AGENT_META, DEFAULT_INBOX_AVATAR } from '@/const/meta';
2
+ import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
3
+ import { LobeAgentSession, LobeSessionType } from '@/types/session';
4
+ import { merge } from '@/utils/merge';
5
+
1
6
  export const INBOX_SESSION_ID = 'inbox';
7
+
8
+ export const DEFAULT_AGENT_LOBE_SESSION: LobeAgentSession = {
9
+ config: DEFAULT_AGENT_CONFIG,
10
+ createdAt: Date.now(),
11
+ id: '',
12
+ meta: DEFAULT_AGENT_META,
13
+ type: LobeSessionType.Agent,
14
+ updatedAt: Date.now(),
15
+ };
16
+
17
+ export const DEFAULT_INBOX_SESSION: LobeAgentSession = merge(DEFAULT_AGENT_LOBE_SESSION, {
18
+ id: 'inbox',
19
+ meta: {
20
+ avatar: DEFAULT_INBOX_AVATAR,
21
+ },
22
+ });
@@ -1,8 +1,8 @@
1
1
  import { getClientConfig } from '@/config/client';
2
2
  import { DEFAULT_OPENAI_MODEL_LIST } from '@/const/llm';
3
3
  import { DEFAULT_AGENT_META } from '@/const/meta';
4
+ import { LobeAgentConfig, LobeAgentTTSConfig } from '@/types/agent';
4
5
  import { LanguageModel } from '@/types/llm';
5
- import { LobeAgentConfig, LobeAgentTTSConfig } from '@/types/session';
6
6
  import {
7
7
  GlobalBaseSettings,
8
8
  GlobalDefaultAgent,
@@ -1,7 +1,7 @@
1
1
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
2
 
3
- import { DBModel } from '@/types/database/db';
4
- import { LocalFile } from '@/types/database/files';
3
+ import { DBModel } from '@/database/core/types/db';
4
+ import { DB_File } from '@/database/schemas/files';
5
5
 
6
6
  import { LocalDB } from '../db';
7
7
 
@@ -24,7 +24,7 @@ describe('LocalDB', () => {
24
24
  });
25
25
 
26
26
  it('should allow adding a file', async () => {
27
- const file: DBModel<LocalFile> = {
27
+ const file: DBModel<DB_File> = {
28
28
  id: 'file1',
29
29
  name: 'testfile.txt',
30
30
  data: new ArrayBuffer(3),
@@ -32,6 +32,7 @@ describe('LocalDB', () => {
32
32
  fileType: 'plain/text',
33
33
  size: 3,
34
34
  createdAt: Date.now(),
35
+ updatedAt: Date.now(),
35
36
  };
36
37
 
37
38
  await db.files.add(file);
@@ -40,7 +41,7 @@ describe('LocalDB', () => {
40
41
  });
41
42
 
42
43
  it('should allow updating a file', async () => {
43
- const file: DBModel<LocalFile> = {
44
+ const file: DBModel<DB_File> = {
44
45
  id: 'file1',
45
46
  name: 'testfile.txt',
46
47
  data: new ArrayBuffer(3),
@@ -48,6 +49,7 @@ describe('LocalDB', () => {
48
49
  fileType: 'plain/text',
49
50
  size: 3,
50
51
  createdAt: Date.now(),
52
+ updatedAt: Date.now(),
51
53
  };
52
54
  await db.files.add(file);
53
55
 
@@ -59,13 +61,14 @@ describe('LocalDB', () => {
59
61
  });
60
62
 
61
63
  it('should allow deleting a file', async () => {
62
- const file: DBModel<LocalFile> = {
64
+ const file: DBModel<DB_File> = {
63
65
  id: 'file1',
64
66
  name: 'testfile.txt',
65
67
  data: new ArrayBuffer(3),
66
68
  saveMode: 'local',
67
69
  fileType: 'plain/text',
68
70
  size: 3,
71
+ updatedAt: Date.now(),
69
72
  createdAt: Date.now(),
70
73
  };
71
74
  await db.files.add(file);
@@ -1,5 +1,5 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { ZodSchema, z } from 'zod';
2
+ import { z } from 'zod';
3
3
 
4
4
  import { BaseModel } from '../model';
5
5
 
@@ -37,7 +37,7 @@ describe('BaseModel', () => {
37
37
  content: 'Hello, World!',
38
38
  };
39
39
 
40
- const result = await baseModel['add'](validData);
40
+ const result = await baseModel['_add'](validData);
41
41
 
42
42
  expect(result).toHaveProperty('id');
43
43
  expect(console.error).not.toHaveBeenCalled();
@@ -49,7 +49,7 @@ describe('BaseModel', () => {
49
49
  content: 'Hello, World!',
50
50
  };
51
51
 
52
- await expect(baseModel['add'](invalidData)).rejects.toThrow(TypeError);
52
+ await expect(baseModel['_add'](invalidData)).rejects.toThrow(TypeError);
53
53
  });
54
54
  });
55
55
  });
@@ -1,25 +1,53 @@
1
1
  import Dexie from 'dexie';
2
2
 
3
- import { LobeDBSchemaMap as SchemaMap, lobeDBSchema } from '@/database/core/schema';
4
- import { DBModel } from '@/types/database/db';
3
+ import { DB_Message } from '@/database/schemas/message';
4
+ import { DB_Session } from '@/database/schemas/session';
5
+ import { DB_Topic } from '@/database/schemas/topic';
6
+ import { DBModel } from '@/database/core/types/db';
7
+ import { DB_File } from '@/database/schemas/files';
5
8
 
6
- export type LocalDBSchema = {
7
- [t in keyof SchemaMap]: {
8
- model: SchemaMap[t];
9
- table: Dexie.Table<DBModel<SchemaMap[t]>, string>;
10
- };
11
- };
9
+ import { dbSchemaV1, dbSchemaV2 } from './schemas';
10
+
11
+ interface LobeDBSchemaMap {
12
+ files: DB_File;
13
+ messages: DB_Message;
14
+ sessions: DB_Session;
15
+ topics: DB_Topic;
16
+ }
12
17
 
13
18
  // Define a local DB
14
19
  export class LocalDB extends Dexie {
15
- public files: LocalDBSchema['files']['table'];
20
+ public files: LobeDBTable<'files'>;
21
+ public sessions: LobeDBTable<'sessions'>;
22
+ public messages: LobeDBTable<'messages'>;
23
+ public topics: LobeDBTable<'topics'>;
16
24
 
17
25
  constructor() {
18
26
  super('LOBE_CHAT_DB');
19
- this.version(1).stores(lobeDBSchema);
27
+ this.version(1).stores(dbSchemaV1);
28
+
29
+ this.version(2).stores(dbSchemaV2);
20
30
 
21
31
  this.files = this.table('files');
32
+ this.sessions = this.table('sessions');
33
+ this.messages = this.table('messages');
34
+ this.topics = this.table('topics');
22
35
  }
23
36
  }
24
37
 
25
38
  export const LocalDBInstance = new LocalDB();
39
+
40
+ // ================================================ //
41
+ // ================================================ //
42
+ // ================================================ //
43
+ // ================================================ //
44
+ // ================================================ //
45
+
46
+ // types helper
47
+ export type LocalDBSchema = {
48
+ [t in keyof LobeDBSchemaMap]: {
49
+ model: LobeDBSchemaMap[t];
50
+ table: Dexie.Table<DBModel<LobeDBSchemaMap[t]>, string>;
51
+ };
52
+ };
53
+ type LobeDBTable<T extends keyof LobeDBSchemaMap> = LocalDBSchema[T]['table'];
@@ -1 +1,2 @@
1
+ export { LocalDBInstance } from './db';
1
2
  export * from './model';
@@ -1,24 +1,24 @@
1
- import { ZodSchema } from 'zod';
1
+ import Dexie, { BulkError } from 'dexie';
2
+ import { ZodObject } from 'zod';
2
3
 
3
- import { DBModel } from '@/types/database/db';
4
- import { LocalFile } from '@/types/database/files';
4
+ import { DBBaseFieldsSchema } from '@/database/core/types/db';
5
5
  import { nanoid } from '@/utils/uuid';
6
6
 
7
7
  import { LocalDB, LocalDBInstance, LocalDBSchema } from './db';
8
8
 
9
- export class BaseModel<N extends keyof LocalDBSchema = any> {
10
- private readonly db: LocalDB;
11
- private readonly schema: ZodSchema;
9
+ export class BaseModel<N extends keyof LocalDBSchema = any, T = LocalDBSchema[N]['table']> {
10
+ protected readonly db: LocalDB;
11
+ private readonly schema: ZodObject<any>;
12
12
  private readonly _tableName: keyof LocalDBSchema;
13
13
 
14
- constructor(table: N, schema: ZodSchema, db = LocalDBInstance) {
14
+ constructor(table: N, schema: ZodObject<any>, db = LocalDBInstance) {
15
15
  this.db = db;
16
16
  this.schema = schema;
17
17
  this._tableName = table;
18
18
  }
19
19
 
20
20
  get table() {
21
- return this.db[this._tableName];
21
+ return this.db[this._tableName] as Dexie.Table;
22
22
  }
23
23
 
24
24
  /**
@@ -26,7 +26,7 @@ export class BaseModel<N extends keyof LocalDBSchema = any> {
26
26
  * @param data
27
27
  * @param id
28
28
  */
29
- protected async add<T = LocalDBSchema[N]['model']>(data: T, id: string | number = nanoid()) {
29
+ protected async _add<T = LocalDBSchema[N]['model']>(data: T, id: string | number = nanoid()) {
30
30
  const result = this.schema.safeParse(data);
31
31
 
32
32
  if (!result.success) {
@@ -40,10 +40,121 @@ export class BaseModel<N extends keyof LocalDBSchema = any> {
40
40
 
41
41
  const tableName = this._tableName;
42
42
 
43
- const record: DBModel<LocalFile> = { ...result.data, createdAt: Date.now(), id };
43
+ const record: any = { ...result.data, createdAt: Date.now(), id, updatedAt: Date.now() };
44
44
 
45
45
  const newId = await this.db[tableName].add(record);
46
46
 
47
47
  return { id: newId };
48
48
  }
49
+
50
+ /**
51
+ * Batch create new records
52
+ * @param dataArray An array of data to be added
53
+ * @param options
54
+ * @param options.generateId
55
+ * @param options.createWithNewId
56
+ */
57
+ protected async _batchAdd<T = LocalDBSchema[N]['model']>(
58
+ dataArray: T[],
59
+ options: {
60
+ /**
61
+ * always create with a new id
62
+ */
63
+ createWithNewId?: boolean;
64
+ idGenerator?: () => string;
65
+ } = {},
66
+ ): Promise<{
67
+ added: number;
68
+ errors?: Error[];
69
+ ids: string[];
70
+ skips: string[];
71
+ success: boolean;
72
+ }> {
73
+ const { idGenerator = nanoid, createWithNewId = false } = options;
74
+ const validatedData = [];
75
+ const errors = [];
76
+ const skips: string[] = [];
77
+
78
+ for (const data of dataArray) {
79
+ const schemaWithId = this.schema.merge(DBBaseFieldsSchema.partial());
80
+
81
+ const result = schemaWithId.safeParse(data);
82
+
83
+ if (result.success) {
84
+ const item = result.data;
85
+ const autoId = idGenerator();
86
+
87
+ const id = createWithNewId ? autoId : item.id ?? autoId;
88
+
89
+ // skip if the id already exists
90
+ if (await this.table.get(id)) {
91
+ skips.push(id);
92
+ continue;
93
+ }
94
+
95
+ validatedData.push({
96
+ ...item,
97
+ createdAt: item.createdAt ?? Date.now(),
98
+ id,
99
+ updatedAt: item.updatedAt ?? Date.now(),
100
+ });
101
+ } else {
102
+ errors.push(result.error);
103
+
104
+ const errorMsg = `[${this.db.name}][${
105
+ this._tableName
106
+ }] Failed to create the record. Data: ${JSON.stringify(data)}. Errors: ${result.error}`;
107
+ console.error(new TypeError(errorMsg));
108
+ }
109
+ }
110
+ if (validatedData.length === 0) {
111
+ // No valid data to add
112
+ return { added: 0, errors, ids: [], skips, success: false };
113
+ }
114
+
115
+ // Using bulkAdd to insert validated data
116
+ try {
117
+ await this.table.bulkAdd(validatedData);
118
+
119
+ return {
120
+ added: validatedData.length,
121
+ ids: validatedData.map((item) => item.id),
122
+ skips,
123
+ success: true,
124
+ };
125
+ } catch (error) {
126
+ const bulkError = error as BulkError;
127
+ // Handle bulkAdd errors here
128
+ console.error(`[${this.db.name}][${this._tableName}] Bulk add error:`, bulkError);
129
+ // Return the number of successfully added records and errors
130
+ return {
131
+ added: validatedData.length - skips.length - bulkError.failures.length,
132
+ errors: bulkError.failures,
133
+ ids: validatedData.map((item) => item.id),
134
+ skips,
135
+ success: false,
136
+ };
137
+ }
138
+ }
139
+
140
+ protected async _update(id: string, data: Partial<T>) {
141
+ // we need to check whether the data is valid
142
+ // pick data related schema from the full schema
143
+ const keys = Object.keys(data);
144
+ const partialSchema = this.schema.pick(Object.fromEntries(keys.map((key) => [key, true])));
145
+
146
+ const result = partialSchema.safeParse(data);
147
+ if (!result.success) {
148
+ const errorMsg = `[${this.db.name}][${this._tableName}] Failed to update the record:${id}. Error: ${result.error}`;
149
+
150
+ const newError = new TypeError(errorMsg);
151
+ // make this error show on console to help debug
152
+ console.error(newError);
153
+ throw newError;
154
+ }
155
+
156
+ const success = await this.table.update(id, { ...data, updatedAt: Date.now() });
157
+
158
+ return { success };
159
+ }
49
160
  }
@@ -0,0 +1,22 @@
1
+ // ************************************** //
2
+ // ******* Version 1 - 2023-11-14 ******* //
3
+ // ************************************** //
4
+ // - Initial database schema with `files` table
5
+
6
+ export const dbSchemaV1 = {
7
+ files: '&id, name, fileType, saveMode',
8
+ };
9
+
10
+ // ************************************** //
11
+ // ******* Version 2 - 2023-11-27 ******* //
12
+ // ************************************** //
13
+ // - Added `sessions` 、`messages` 、`topics` tables
14
+ // - Added `createdAt` and `updatedAt` fields to all
15
+ export const dbSchemaV2 = {
16
+ files: '&id, name, fileType, saveMode, createdAt, updatedAt, messageId, sessionId',
17
+
18
+ messages:
19
+ '&id, role, content, fromModel, favorite, plugin.identifier, plugin.apiName, translate.content, createdAt, updatedAt, sessionId, topicId, quotaId, parentId, [sessionId+topicId]',
20
+ sessions: '&id, type, group, meta.title, meta.description, meta.tags, createdAt, updatedAt',
21
+ topics: '&id, title, favorite, createdAt, updatedAt, sessionId',
22
+ };
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+
3
+ export type DBModel<T> = T & {
4
+ createdAt: number;
5
+ id: string;
6
+ updatedAt: number;
7
+ };
8
+
9
+ export const DBBaseFieldsSchema = z.object({
10
+ createdAt: z.number(),
11
+ id: z.string(),
12
+ updatedAt: z.number(),
13
+ });