@lobehub/chat 1.36.45 → 1.37.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 (88) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.ja-JP.md +8 -8
  3. package/README.md +8 -8
  4. package/README.zh-CN.md +8 -8
  5. package/changelog/v1.json +18 -0
  6. package/next.config.mjs +4 -1
  7. package/package.json +5 -3
  8. package/scripts/migrateClientDB/compile-migrations.ts +14 -0
  9. package/src/app/(main)/(mobile)/me/(home)/layout.tsx +2 -0
  10. package/src/app/(main)/chat/_layout/Desktop/index.tsx +3 -2
  11. package/src/app/(main)/chat/_layout/Mobile.tsx +5 -3
  12. package/src/app/(main)/chat/features/Migration/DBReader.ts +290 -0
  13. package/src/app/(main)/chat/features/Migration/UpgradeButton.tsx +4 -8
  14. package/src/app/(main)/chat/features/Migration/index.tsx +26 -15
  15. package/src/app/(main)/settings/_layout/Desktop/index.tsx +2 -0
  16. package/src/app/loading/Client/Content.tsx +11 -1
  17. package/src/app/loading/Client/Error.tsx +27 -0
  18. package/src/app/loading/stage.ts +8 -0
  19. package/src/components/FullscreenLoading/index.tsx +4 -3
  20. package/src/const/version.ts +1 -0
  21. package/src/database/_deprecated/models/file.ts +17 -3
  22. package/src/database/client/db.test.ts +172 -0
  23. package/src/database/client/db.ts +246 -0
  24. package/src/database/client/migrations.json +289 -0
  25. package/src/features/InitClientDB/EnableModal.tsx +111 -0
  26. package/src/features/InitClientDB/ErrorResult.tsx +125 -0
  27. package/src/features/InitClientDB/InitIndicator.tsx +124 -0
  28. package/src/features/InitClientDB/PGliteSVG.tsx +22 -0
  29. package/src/features/InitClientDB/index.tsx +37 -0
  30. package/src/hooks/useCheckPluginsIsInstalled.ts +2 -2
  31. package/src/hooks/useFetchInstalledPlugins.ts +2 -2
  32. package/src/hooks/useFetchMessages.ts +2 -2
  33. package/src/hooks/useFetchSessions.ts +2 -2
  34. package/src/hooks/useFetchThreads.ts +2 -2
  35. package/src/hooks/useFetchTopics.ts +2 -2
  36. package/src/layout/GlobalProvider/StoreInitialization.tsx +2 -2
  37. package/src/server/routers/lambda/file.ts +1 -3
  38. package/src/services/__tests__/upload.test.ts +175 -0
  39. package/src/services/baseClientService/index.ts +9 -0
  40. package/src/services/debug.ts +32 -34
  41. package/src/services/file/ClientS3/index.test.ts +115 -0
  42. package/src/services/file/ClientS3/index.ts +58 -0
  43. package/src/services/file/client.test.ts +9 -4
  44. package/src/services/file/client.ts +36 -8
  45. package/src/services/file/index.ts +6 -2
  46. package/src/services/file/pglite.test.ts +198 -0
  47. package/src/services/file/pglite.ts +84 -0
  48. package/src/services/file/type.ts +4 -3
  49. package/src/services/github.ts +17 -0
  50. package/src/services/import/index.ts +6 -2
  51. package/src/services/import/pglite.test.ts +997 -0
  52. package/src/services/import/pglite.ts +34 -0
  53. package/src/services/message/client.ts +2 -0
  54. package/src/services/message/index.ts +6 -2
  55. package/src/services/message/pglite.test.ts +430 -0
  56. package/src/services/message/pglite.ts +118 -0
  57. package/src/services/message/server.ts +9 -9
  58. package/src/services/message/type.ts +3 -4
  59. package/src/services/plugin/index.ts +6 -2
  60. package/src/services/plugin/pglite.test.ts +175 -0
  61. package/src/services/plugin/pglite.ts +51 -0
  62. package/src/services/session/client.ts +1 -1
  63. package/src/services/session/index.ts +6 -2
  64. package/src/services/session/pglite.test.ts +411 -0
  65. package/src/services/session/pglite.ts +184 -0
  66. package/src/services/session/type.ts +14 -1
  67. package/src/services/topic/index.ts +6 -3
  68. package/src/services/topic/pglite.test.ts +212 -0
  69. package/src/services/topic/pglite.ts +85 -0
  70. package/src/services/upload.ts +8 -16
  71. package/src/services/user/client.test.ts +0 -1
  72. package/src/services/user/index.ts +8 -2
  73. package/src/services/user/pglite.test.ts +98 -0
  74. package/src/services/user/pglite.ts +92 -0
  75. package/src/store/chat/slices/builtinTool/action.test.ts +12 -4
  76. package/src/store/file/slices/upload/action.ts +33 -67
  77. package/src/store/global/actions/clientDb.ts +51 -0
  78. package/src/store/global/initialState.ts +13 -0
  79. package/src/store/global/selectors.ts +24 -3
  80. package/src/store/global/store.ts +3 -1
  81. package/src/store/session/slices/sessionGroup/reducer.test.ts +6 -6
  82. package/src/store/user/slices/common/action.ts +2 -4
  83. package/src/types/clientDB.ts +29 -0
  84. package/src/types/files/upload.ts +8 -2
  85. package/src/types/importer.ts +17 -5
  86. package/src/types/meta.ts +0 -9
  87. package/src/types/session/sessionGroup.ts +3 -3
  88. package/src/services/message/index.test.ts +0 -48
@@ -0,0 +1,92 @@
1
+ import { DeepPartial } from 'utility-types';
2
+
3
+ import { clientDB } from '@/database/client/db';
4
+ import { users } from '@/database/schemas';
5
+ import { MessageModel } from '@/database/server/models/message';
6
+ import { SessionModel } from '@/database/server/models/session';
7
+ import { UserModel } from '@/database/server/models/user';
8
+ import { BaseClientService } from '@/services/baseClientService';
9
+ import { UserGuide, UserInitializationState, UserPreference } from '@/types/user';
10
+ import { UserSettings } from '@/types/user/settings';
11
+ import { AsyncLocalStorage } from '@/utils/localStorage';
12
+
13
+ import { IUserService } from './type';
14
+
15
+ export class ClientService extends BaseClientService implements IUserService {
16
+ private preferenceStorage: AsyncLocalStorage<UserPreference>;
17
+
18
+ private get userModel(): UserModel {
19
+ return new UserModel(clientDB as any, this.userId);
20
+ }
21
+ private get messageModel(): MessageModel {
22
+ return new MessageModel(clientDB as any, this.userId);
23
+ }
24
+ private get sessionModel(): SessionModel {
25
+ return new SessionModel(clientDB as any, this.userId);
26
+ }
27
+
28
+ constructor(userId?: string) {
29
+ super(userId);
30
+ this.preferenceStorage = new AsyncLocalStorage('LOBE_PREFERENCE');
31
+ }
32
+
33
+ async getUserState(): Promise<UserInitializationState> {
34
+ // if user not exist in the db, create one to make sure the user exist
35
+ await this.makeSureUserExist();
36
+
37
+ const state = await this.userModel.getUserState((encryptKeyVaultsStr) =>
38
+ encryptKeyVaultsStr ? JSON.parse(encryptKeyVaultsStr) : {},
39
+ );
40
+
41
+ const user = await UserModel.findById(clientDB as any, this.userId);
42
+ const messageCount = await this.messageModel.count();
43
+ const sessionCount = await this.sessionModel.count();
44
+
45
+ return {
46
+ ...state,
47
+ avatar: user?.avatar as string,
48
+ canEnablePWAGuide: messageCount >= 4,
49
+ canEnableTrace: messageCount >= 4,
50
+ hasConversation: messageCount > 0 || sessionCount > 0,
51
+ isOnboard: true,
52
+ preference: await this.preferenceStorage.getFromLocalStorage(),
53
+ };
54
+ }
55
+
56
+ updateUserSettings = async (value: DeepPartial<UserSettings>) => {
57
+ const { keyVaults, ...res } = value;
58
+
59
+ return this.userModel.updateSetting({ ...res, keyVaults: JSON.stringify(keyVaults) });
60
+ };
61
+
62
+ resetUserSettings = async () => {
63
+ return this.userModel.deleteSetting();
64
+ };
65
+
66
+ async updateAvatar(avatar: string) {
67
+ await this.userModel.updateUser({ avatar });
68
+ }
69
+
70
+ async updatePreference(preference: Partial<UserPreference>) {
71
+ await this.preferenceStorage.saveToLocalStorage(preference);
72
+ }
73
+
74
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars,unused-imports/no-unused-vars
75
+ async updateGuide(guide: Partial<UserGuide>) {
76
+ throw new Error('Method not implemented.');
77
+ }
78
+
79
+ async makeSureUserExist() {
80
+ const existUsers = await clientDB.query.users.findMany();
81
+
82
+ let user: { id: string };
83
+ if (existUsers.length === 0) {
84
+ const result = await clientDB.insert(users).values({ id: this.userId }).returning();
85
+ user = result[0];
86
+ } else {
87
+ user = existUsers[0];
88
+ }
89
+
90
+ return user;
91
+ }
92
+ }
@@ -2,6 +2,8 @@ import { act, renderHook } from '@testing-library/react';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { fileService } from '@/services/file';
5
+ import { ClientService } from '@/services/file/client';
6
+ import { messageService } from '@/services/message';
5
7
  import { imageGenerationService } from '@/services/textToImage';
6
8
  import { uploadService } from '@/services/upload';
7
9
  import { chatSelectors } from '@/store/chat/selectors';
@@ -39,17 +41,22 @@ describe('chatToolSlice', () => {
39
41
  vi.spyOn(uploadService, 'getImageFileByUrlWithCORS').mockResolvedValue(
40
42
  new File(['1'], 'file.png', { type: 'image/png' }),
41
43
  );
42
- vi.spyOn(uploadService, 'uploadToClientDB').mockResolvedValue({} as any);
43
- vi.spyOn(fileService, 'createFile').mockResolvedValue({ id: mockId, url: '' });
44
+ vi.spyOn(uploadService, 'uploadToClientS3').mockResolvedValue({} as any);
45
+ vi.spyOn(ClientService.prototype, 'createFile').mockResolvedValue({
46
+ id: mockId,
47
+ url: '',
48
+ });
44
49
  vi.spyOn(result.current, 'toggleDallEImageLoading');
50
+ vi.spyOn(ClientService.prototype, 'checkFileHash').mockImplementation(
51
+ async () => ({ isExist: false }) as any,
52
+ );
45
53
 
46
54
  await act(async () => {
47
55
  await result.current.generateImageFromPrompts(prompts, messageId);
48
56
  });
49
57
  // For each prompt, loading is toggled on and then off
50
58
  expect(imageGenerationService.generateImage).toHaveBeenCalledTimes(prompts.length);
51
- expect(uploadService.uploadToClientDB).toHaveBeenCalledTimes(prompts.length);
52
-
59
+ expect(uploadService.uploadToClientS3).toHaveBeenCalledTimes(prompts.length);
53
60
  expect(result.current.toggleDallEImageLoading).toHaveBeenCalledTimes(prompts.length * 2);
54
61
  });
55
62
  });
@@ -75,6 +82,7 @@ describe('chatToolSlice', () => {
75
82
  content: initialMessageContent,
76
83
  }) as ChatMessage,
77
84
  );
85
+ vi.spyOn(messageService, 'updateMessage').mockResolvedValueOnce(undefined);
78
86
 
79
87
  await act(async () => {
80
88
  await result.current.updateImageItem(messageId, updateFunction);
@@ -6,14 +6,11 @@ import { message } from '@/components/AntdStaticMethods';
6
6
  import { LOBE_CHAT_CLOUD } from '@/const/branding';
7
7
  import { isServerMode } from '@/const/version';
8
8
  import { fileService } from '@/services/file';
9
- import { ServerService } from '@/services/file/server';
10
9
  import { uploadService } from '@/services/upload';
11
10
  import { FileMetadata, UploadFileItem } from '@/types/files';
12
11
 
13
12
  import { FileStore } from '../../store';
14
13
 
15
- const serverFileService = new ServerService();
16
-
17
14
  interface UploadWithProgressParams {
18
15
  file: File;
19
16
  knowledgeBaseId?: string;
@@ -43,10 +40,6 @@ interface UploadWithProgressResult {
43
40
  }
44
41
 
45
42
  export interface FileUploadAction {
46
- internal_uploadToClientDB: (
47
- params: Omit<UploadWithProgressParams, 'knowledgeBaseId'>,
48
- ) => Promise<UploadWithProgressResult | undefined>;
49
- internal_uploadToServer: (params: UploadWithProgressParams) => Promise<UploadWithProgressResult>;
50
43
  uploadWithProgress: (
51
44
  params: UploadWithProgressParams,
52
45
  ) => Promise<UploadWithProgressResult | undefined>;
@@ -57,51 +50,14 @@ export const createFileUploadSlice: StateCreator<
57
50
  [['zustand/devtools', never]],
58
51
  [],
59
52
  FileUploadAction
60
- > = (set, get) => ({
61
- internal_uploadToClientDB: async ({ file, onStatusUpdate, skipCheckFileType }) => {
62
- if (!skipCheckFileType && !file.type.startsWith('image')) {
63
- onStatusUpdate?.({ id: file.name, type: 'removeFile' });
64
- message.info({
65
- content: t('upload.fileOnlySupportInServerMode', {
66
- cloud: LOBE_CHAT_CLOUD,
67
- ext: file.name.split('.').pop(),
68
- ns: 'error',
69
- }),
70
- duration: 5,
71
- });
72
- return;
73
- }
74
-
75
- const fileArrayBuffer = await file.arrayBuffer();
76
-
77
- const hash = sha256(fileArrayBuffer);
78
-
79
- const data = await uploadService.uploadToClientDB(
80
- { fileType: file.type, hash, name: file.name, saveMode: 'local', size: file.size },
81
- file,
82
- );
83
-
84
- onStatusUpdate?.({
85
- id: file.name,
86
- type: 'updateFile',
87
- value: {
88
- fileUrl: data.url,
89
- id: data.id,
90
- status: 'success',
91
- uploadState: { progress: 100, restTime: 0, speed: 0 },
92
- },
93
- });
94
-
95
- return data;
96
- },
97
-
98
- internal_uploadToServer: async ({ file, onStatusUpdate, knowledgeBaseId }) => {
53
+ > = () => ({
54
+ uploadWithProgress: async ({ file, onStatusUpdate, knowledgeBaseId, skipCheckFileType }) => {
99
55
  const fileArrayBuffer = await file.arrayBuffer();
100
56
 
101
57
  // 1. check file hash
102
58
  const hash = sha256(fileArrayBuffer);
103
59
 
104
- const checkStatus = await serverFileService.checkFileHash(hash);
60
+ const checkStatus = await fileService.checkFileHash(hash);
105
61
  let metadata: FileMetadata;
106
62
 
107
63
  // 2. if file exist, just skip upload
@@ -112,17 +68,37 @@ export const createFileUploadSlice: StateCreator<
112
68
  type: 'updateFile',
113
69
  value: { status: 'processing', uploadState: { progress: 100, restTime: 0, speed: 0 } },
114
70
  });
115
- } else {
116
- // 2. if file don't exist, need upload files
117
- metadata = await uploadService.uploadWithProgress(file, {
118
- onProgress: (status, upload) => {
119
- onStatusUpdate?.({
120
- id: file.name,
121
- type: 'updateFile',
122
- value: { status: status === 'success' ? 'processing' : status, uploadState: upload },
71
+ }
72
+ // 2. if file don't exist, need upload files
73
+ else {
74
+ // if is server mode, upload to server s3, or upload to client s3
75
+ if (isServerMode) {
76
+ metadata = await uploadService.uploadWithProgress(file, {
77
+ onProgress: (status, upload) => {
78
+ onStatusUpdate?.({
79
+ id: file.name,
80
+ type: 'updateFile',
81
+ value: { status: status === 'success' ? 'processing' : status, uploadState: upload },
82
+ });
83
+ },
84
+ });
85
+ } else {
86
+ if (!skipCheckFileType && !file.type.startsWith('image')) {
87
+ onStatusUpdate?.({ id: file.name, type: 'removeFile' });
88
+ message.info({
89
+ content: t('upload.fileOnlySupportInServerMode', {
90
+ cloud: LOBE_CHAT_CLOUD,
91
+ ext: file.name.split('.').pop(),
92
+ ns: 'error',
93
+ }),
94
+ duration: 5,
123
95
  });
124
- },
125
- });
96
+ return;
97
+ }
98
+
99
+ // Upload to the indexeddb in the browser
100
+ metadata = await uploadService.uploadToClientS3(hash, file);
101
+ }
126
102
  }
127
103
 
128
104
  // 3. use more powerful file type detector to get file type
@@ -138,12 +114,10 @@ export const createFileUploadSlice: StateCreator<
138
114
  // 4. create file to db
139
115
  const data = await fileService.createFile(
140
116
  {
141
- createdAt: Date.now(),
142
117
  fileType,
143
118
  hash,
144
119
  metadata,
145
120
  name: file.name,
146
- saveMode: 'url',
147
121
  size: file.size,
148
122
  url: metadata.path,
149
123
  },
@@ -163,12 +137,4 @@ export const createFileUploadSlice: StateCreator<
163
137
 
164
138
  return data;
165
139
  },
166
-
167
- uploadWithProgress: async (payload) => {
168
- const { internal_uploadToServer, internal_uploadToClientDB } = get();
169
-
170
- if (isServerMode) return internal_uploadToServer(payload);
171
-
172
- return internal_uploadToClientDB(payload);
173
- },
174
140
  });
@@ -0,0 +1,51 @@
1
+ import { SWRResponse } from 'swr';
2
+ import type { StateCreator } from 'zustand/vanilla';
3
+
4
+ import { useOnlyFetchOnceSWR } from '@/libs/swr';
5
+ import type { GlobalStore } from '@/store/global';
6
+ import { DatabaseLoadingState, OnStageChange } from '@/types/clientDB';
7
+
8
+ type InitClientDBParams = { onStateChange: OnStageChange };
9
+ /**
10
+ * 设置操作
11
+ */
12
+ export interface GlobalClientDBAction {
13
+ initializeClientDB: (params?: InitClientDBParams) => Promise<void>;
14
+ markPgliteEnabled: () => void;
15
+ useInitClientDB: (params?: InitClientDBParams) => SWRResponse;
16
+ }
17
+
18
+ export const clientDBSlice: StateCreator<
19
+ GlobalStore,
20
+ [['zustand/devtools', never]],
21
+ [],
22
+ GlobalClientDBAction
23
+ > = (set, get) => ({
24
+ initializeClientDB: async (params) => {
25
+ // if the db has started initialized or not error, just skip.
26
+ if (
27
+ get().initClientDBStage !== DatabaseLoadingState.Idle &&
28
+ get().initClientDBStage !== DatabaseLoadingState.Error
29
+ )
30
+ return;
31
+
32
+ const { initializeDB } = await import('@/database/client/db');
33
+ await initializeDB({
34
+ onError: (error) => {
35
+ set({ initClientDBError: error });
36
+ },
37
+ onProgress: (data) => {
38
+ set({ initClientDBProcess: data });
39
+ },
40
+ onStateChange: (state) => {
41
+ set({ initClientDBStage: state });
42
+ params?.onStateChange?.(state);
43
+ },
44
+ });
45
+ },
46
+ markPgliteEnabled: () => {
47
+ get().updateSystemStatus({ isEnablePglite: true });
48
+ },
49
+ useInitClientDB: (params) =>
50
+ useOnlyFetchOnceSWR('initClientDB', () => get().initializeClientDB(params)),
51
+ });
@@ -1,5 +1,6 @@
1
1
  import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
2
2
 
3
+ import { DatabaseLoadingState } from '@/types/clientDB';
3
4
  import { SessionDefaultGroup } from '@/types/session';
4
5
  import { AsyncLocalStorage } from '@/utils/localStorage';
5
6
 
@@ -37,6 +38,10 @@ export interface SystemStatus {
37
38
  hidePWAInstaller?: boolean;
38
39
  hideThreadLimitAlert?: boolean;
39
40
  inputHeight: number;
41
+ /**
42
+ * 应用初始化时不启用 PGLite,只有当用户手动开启时才启用
43
+ */
44
+ isEnablePglite?: boolean;
40
45
  mobileShowPortal?: boolean;
41
46
  mobileShowTopic?: boolean;
42
47
  sessionsWidth: number;
@@ -50,6 +55,13 @@ export interface SystemStatus {
50
55
 
51
56
  export interface GlobalState {
52
57
  hasNewVersion?: boolean;
58
+ initClientDBError?: Error;
59
+ initClientDBProcess?: { costTime?: number; phase: 'wasm' | 'dependencies'; progress: number };
60
+ /**
61
+ * 客户端数据库初始化状态
62
+ * 启动时为 Idle,完成为 Ready,报错为 Error
63
+ */
64
+ initClientDBStage: DatabaseLoadingState;
53
65
  isMobile?: boolean;
54
66
  isStatusInit?: boolean;
55
67
  latestVersion?: string;
@@ -76,6 +88,7 @@ export const INITIAL_STATUS = {
76
88
  } satisfies SystemStatus;
77
89
 
78
90
  export const initialState: GlobalState = {
91
+ initClientDBStage: DatabaseLoadingState.Idle,
79
92
  isMobile: false,
80
93
  isStatusInit: false,
81
94
  sidebarKey: SidebarTabKey.Chat,
@@ -1,4 +1,6 @@
1
+ import { isServerMode, isUsePgliteDB } from '@/const/version';
1
2
  import { GlobalStore } from '@/store/global';
3
+ import { DatabaseLoadingState } from '@/types/clientDB';
2
4
 
3
5
  import { INITIAL_STATUS } from './initialState';
4
6
 
@@ -22,17 +24,36 @@ const filePanelWidth = (s: GlobalStore) => s.status.filePanelWidth;
22
24
  const inputHeight = (s: GlobalStore) => s.status.inputHeight;
23
25
  const threadInputHeight = (s: GlobalStore) => s.status.threadInputHeight;
24
26
 
25
- const isPgliteNotEnabled = () => false;
27
+ const isPgliteNotEnabled = (s: GlobalStore) =>
28
+ isUsePgliteDB && !isServerMode && s.isStatusInit && !s.status.isEnablePglite;
26
29
 
27
- const isPgliteNotInited = () => false;
30
+ /**
31
+ * 当且仅当 client db 模式,且 pglite 未初始化完成时返回 true
32
+ */
33
+ const isPgliteNotInited = (s: GlobalStore) =>
34
+ isUsePgliteDB &&
35
+ s.isStatusInit &&
36
+ s.status.isEnablePglite &&
37
+ s.initClientDBStage !== DatabaseLoadingState.Ready;
28
38
 
29
- const isPgliteInited = (): boolean => true;
39
+ /**
40
+ * 当且仅当 client db 模式,且 pglite 初始化完成时返回 true
41
+ */
42
+ const isPgliteInited = (s: GlobalStore): boolean =>
43
+ (s.isStatusInit &&
44
+ s.status.isEnablePglite &&
45
+ s.initClientDBStage === DatabaseLoadingState.Ready) ||
46
+ false;
47
+
48
+ // 这个变量控制 clientdb 是否完成初始化,正常来说,只有 pgliteDB 模式下,才会存在变化,其他时候都是 true
49
+ const isDBInited = (s: GlobalStore): boolean => (isUsePgliteDB ? isPgliteInited(s) : true);
30
50
 
31
51
  export const systemStatusSelectors = {
32
52
  filePanelWidth,
33
53
  hidePWAInstaller,
34
54
  inZenMode,
35
55
  inputHeight,
56
+ isDBInited,
36
57
  isPgliteInited,
37
58
  isPgliteNotEnabled,
38
59
  isPgliteNotInited,
@@ -5,15 +5,17 @@ import { StateCreator } from 'zustand/vanilla';
5
5
 
6
6
  import { createDevtools } from '../middleware/createDevtools';
7
7
  import { type GlobalStoreAction, globalActionSlice } from './action';
8
+ import { type GlobalClientDBAction, clientDBSlice } from './actions/clientDb';
8
9
  import { type GlobalState, initialState } from './initialState';
9
10
 
10
11
  // =============== 聚合 createStoreFn ============ //
11
12
 
12
- export type GlobalStore = GlobalState & GlobalStoreAction;
13
+ export type GlobalStore = GlobalState & GlobalStoreAction & GlobalClientDBAction;
13
14
 
14
15
  const createStore: StateCreator<GlobalStore, [['zustand/devtools', never]]> = (...parameters) => ({
15
16
  ...initialState,
16
17
  ...globalActionSlice(...parameters),
18
+ ...clientDBSlice(...parameters),
17
19
  });
18
20
 
19
21
  // =============== 实装 useStore ============ //
@@ -10,14 +10,14 @@ describe('sessionGroupsReducer', () => {
10
10
  {
11
11
  id: nanoid(),
12
12
  name: 'Group 1',
13
- createdAt: Date.now(),
14
- updatedAt: Date.now(),
13
+ createdAt: new Date(),
14
+ updatedAt: new Date(),
15
15
  },
16
16
  {
17
17
  id: nanoid(),
18
18
  name: 'Group 2',
19
- createdAt: Date.now(),
20
- updatedAt: Date.now(),
19
+ createdAt: new Date(),
20
+ updatedAt: new Date(),
21
21
  sort: 1,
22
22
  },
23
23
  ];
@@ -26,8 +26,8 @@ describe('sessionGroupsReducer', () => {
26
26
  const newItem: SessionGroupItem = {
27
27
  id: nanoid(),
28
28
  name: 'New Group',
29
- createdAt: Date.now(),
30
- updatedAt: Date.now(),
29
+ createdAt: new Date(),
30
+ updatedAt: new Date(),
31
31
  };
32
32
 
33
33
  const result = sessionGroupsReducer(initialState, {
@@ -46,11 +46,9 @@ export const createCommonSlice: StateCreator<
46
46
  await mutate(GET_USER_STATE_KEY);
47
47
  },
48
48
  updateAvatar: async (avatar) => {
49
- const { ClientService } = await import('@/services/user/client');
49
+ const { userClientService } = await import('@/services/user');
50
50
 
51
- const clientService = new ClientService();
52
-
53
- await clientService.updateAvatar(avatar);
51
+ await userClientService.updateAvatar(avatar);
54
52
  await get().refreshUserState();
55
53
  },
56
54
 
@@ -0,0 +1,29 @@
1
+ // 定义加载状态类型
2
+ export enum DatabaseLoadingState {
3
+ Error = 'error',
4
+ Finished = 'finished',
5
+ Idle = 'idle',
6
+ Initializing = 'initializing',
7
+ LoadingDependencies = 'loadingDependencies',
8
+ LoadingWasm = 'loadingWasm',
9
+ Migrating = 'migrating',
10
+ Ready = 'ready',
11
+ }
12
+
13
+ export const ClientDatabaseInitStages = [
14
+ DatabaseLoadingState.Idle,
15
+ DatabaseLoadingState.Initializing,
16
+ DatabaseLoadingState.LoadingDependencies,
17
+ DatabaseLoadingState.LoadingWasm,
18
+ DatabaseLoadingState.Migrating,
19
+ DatabaseLoadingState.Finished,
20
+ ];
21
+
22
+ // 定义进度回调接口
23
+ export interface ClientDBLoadingProgress {
24
+ costTime?: number;
25
+ phase: 'wasm' | 'dependencies';
26
+ progress: number;
27
+ }
28
+
29
+ export type OnStageChange = (state: DatabaseLoadingState) => void;
@@ -53,7 +53,6 @@ export const FileMetadataSchema = z.object({
53
53
  export type FileMetadata = z.infer<typeof FileMetadataSchema>;
54
54
 
55
55
  export const UploadFileSchema = z.object({
56
- data: z.instanceof(ArrayBuffer).optional(),
57
56
  /**
58
57
  * file type
59
58
  * @example 'image/png'
@@ -77,7 +76,6 @@ export const UploadFileSchema = z.object({
77
76
  * local mean save the raw file into data
78
77
  * url mean upload the file to a cdn and then save the url
79
78
  */
80
- saveMode: z.enum(['local', 'url']),
81
79
  /**
82
80
  * file size
83
81
  */
@@ -89,3 +87,11 @@ export const UploadFileSchema = z.object({
89
87
  });
90
88
 
91
89
  export type UploadFileParams = z.infer<typeof UploadFileSchema>;
90
+
91
+ export interface CheckFileHashResult {
92
+ fileType?: string;
93
+ isExist: boolean;
94
+ metadata?: unknown;
95
+ size?: number;
96
+ url?: string;
97
+ }
@@ -9,9 +9,8 @@ import {
9
9
  } from '@/types/message';
10
10
  import { MetaData } from '@/types/meta';
11
11
  import { SessionGroupId } from '@/types/session';
12
- import { ChatTopic } from '@/types/topic';
13
12
 
14
- interface ImportSession {
13
+ export interface ImportSession {
15
14
  config: LobeAgentConfig;
16
15
  createdAt: string;
17
16
  group?: SessionGroupId;
@@ -22,7 +21,7 @@ interface ImportSession {
22
21
  updatedAt: string;
23
22
  }
24
23
 
25
- interface ImportMessage {
24
+ export interface ImportMessage {
26
25
  content: string;
27
26
  createdAt: number;
28
27
  error?: ChatMessageError;
@@ -64,19 +63,32 @@ interface ImportMessage {
64
63
  updatedAt: number;
65
64
  }
66
65
 
67
- interface ImportSessionGroup {
66
+ export interface ImportSessionGroup {
68
67
  createdAt: number;
69
68
  id: string;
70
69
  name: string;
71
70
  sort?: number | null;
72
71
  updatedAt: number;
73
72
  }
73
+ export interface ImportTopic {
74
+ createdAt: number;
75
+ favorite?: boolean;
76
+ historySummary?: string;
77
+ id: string;
78
+ metadata?: {
79
+ model?: string;
80
+ provider?: string;
81
+ };
82
+ sessionId?: string;
83
+ title: string;
84
+ updatedAt: number;
85
+ }
74
86
 
75
87
  export interface ImporterEntryData {
76
88
  messages?: ImportMessage[];
77
89
  sessionGroups?: ImportSessionGroup[];
78
90
  sessions?: ImportSession[];
79
- topics?: ChatTopic[];
91
+ topics?: ImportTopic[];
80
92
  version: number;
81
93
  }
82
94
 
package/src/types/meta.ts CHANGED
@@ -21,19 +21,10 @@ export const LobeMetaDataSchema = z.object({
21
21
  export type MetaData = z.infer<typeof LobeMetaDataSchema>;
22
22
 
23
23
  export interface BaseDataModel {
24
- /**
25
- * @deprecated
26
- */
27
- createAt?: number;
28
-
29
24
  createdAt: number;
30
25
 
31
26
  id: string;
32
27
  meta: MetaData;
33
28
 
34
- /**
35
- * @deprecated
36
- */
37
- updateAt?: number;
38
29
  updatedAt: number;
39
30
  }
@@ -8,11 +8,11 @@ export enum SessionDefaultGroup {
8
8
  export type SessionGroupId = SessionDefaultGroup | string;
9
9
 
10
10
  export interface SessionGroupItem {
11
- createdAt: number;
11
+ createdAt: Date;
12
12
  id: string;
13
13
  name: string;
14
- sort?: number;
15
- updatedAt: number;
14
+ sort?: number | null;
15
+ updatedAt: Date;
16
16
  }
17
17
 
18
18
  export type SessionGroups = SessionGroupItem[];