@lobehub/chat 1.76.0 → 1.77.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 (135) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/Dockerfile +3 -2
  3. package/Dockerfile.database +3 -1
  4. package/Dockerfile.pglite +3 -1
  5. package/changelog/v1.json +18 -0
  6. package/locales/ar/common.json +12 -1
  7. package/locales/ar/error.json +10 -0
  8. package/locales/ar/models.json +12 -6
  9. package/locales/ar/setting.json +28 -0
  10. package/locales/bg-BG/common.json +12 -1
  11. package/locales/bg-BG/error.json +10 -0
  12. package/locales/bg-BG/models.json +12 -6
  13. package/locales/bg-BG/setting.json +28 -0
  14. package/locales/de-DE/common.json +12 -1
  15. package/locales/de-DE/error.json +10 -0
  16. package/locales/de-DE/models.json +12 -6
  17. package/locales/de-DE/setting.json +28 -0
  18. package/locales/en-US/common.json +12 -1
  19. package/locales/en-US/error.json +10 -0
  20. package/locales/en-US/models.json +12 -6
  21. package/locales/en-US/setting.json +28 -0
  22. package/locales/es-ES/common.json +12 -1
  23. package/locales/es-ES/error.json +10 -0
  24. package/locales/es-ES/models.json +12 -6
  25. package/locales/es-ES/setting.json +28 -0
  26. package/locales/fa-IR/common.json +12 -1
  27. package/locales/fa-IR/error.json +10 -0
  28. package/locales/fa-IR/models.json +12 -6
  29. package/locales/fa-IR/setting.json +28 -0
  30. package/locales/fr-FR/common.json +12 -1
  31. package/locales/fr-FR/error.json +10 -0
  32. package/locales/fr-FR/models.json +12 -6
  33. package/locales/fr-FR/setting.json +28 -0
  34. package/locales/it-IT/common.json +12 -1
  35. package/locales/it-IT/error.json +10 -0
  36. package/locales/it-IT/models.json +12 -6
  37. package/locales/it-IT/setting.json +28 -0
  38. package/locales/ja-JP/common.json +12 -1
  39. package/locales/ja-JP/error.json +10 -0
  40. package/locales/ja-JP/models.json +12 -6
  41. package/locales/ja-JP/setting.json +28 -0
  42. package/locales/ko-KR/common.json +12 -1
  43. package/locales/ko-KR/error.json +10 -0
  44. package/locales/ko-KR/models.json +12 -6
  45. package/locales/ko-KR/setting.json +28 -0
  46. package/locales/nl-NL/common.json +12 -1
  47. package/locales/nl-NL/error.json +10 -0
  48. package/locales/nl-NL/models.json +12 -6
  49. package/locales/nl-NL/setting.json +28 -0
  50. package/locales/pl-PL/common.json +12 -1
  51. package/locales/pl-PL/error.json +10 -0
  52. package/locales/pl-PL/models.json +12 -6
  53. package/locales/pl-PL/setting.json +28 -0
  54. package/locales/pt-BR/common.json +12 -1
  55. package/locales/pt-BR/error.json +10 -0
  56. package/locales/pt-BR/models.json +12 -6
  57. package/locales/pt-BR/setting.json +28 -0
  58. package/locales/ru-RU/common.json +12 -1
  59. package/locales/ru-RU/error.json +10 -0
  60. package/locales/ru-RU/models.json +12 -6
  61. package/locales/ru-RU/setting.json +28 -0
  62. package/locales/tr-TR/common.json +12 -1
  63. package/locales/tr-TR/error.json +10 -0
  64. package/locales/tr-TR/models.json +12 -6
  65. package/locales/tr-TR/setting.json +28 -0
  66. package/locales/vi-VN/common.json +12 -1
  67. package/locales/vi-VN/error.json +10 -0
  68. package/locales/vi-VN/models.json +12 -6
  69. package/locales/vi-VN/setting.json +28 -0
  70. package/locales/zh-CN/common.json +12 -1
  71. package/locales/zh-CN/error.json +10 -0
  72. package/locales/zh-CN/models.json +12 -6
  73. package/locales/zh-CN/setting.json +28 -0
  74. package/locales/zh-TW/common.json +12 -1
  75. package/locales/zh-TW/error.json +10 -0
  76. package/locales/zh-TW/models.json +12 -6
  77. package/locales/zh-TW/setting.json +28 -0
  78. package/package.json +1 -1
  79. package/src/app/[variants]/(main)/(mobile)/me/data/features/Category.tsx +1 -1
  80. package/src/app/[variants]/(main)/chat/features/Migration/UpgradeButton.tsx +2 -1
  81. package/src/app/[variants]/(main)/settings/common/features/Common.tsx +0 -44
  82. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +40 -14
  83. package/src/app/[variants]/(main)/settings/storage/Advanced.tsx +133 -0
  84. package/src/app/[variants]/(main)/settings/storage/IndexedDBStorage.tsx +55 -0
  85. package/src/app/[variants]/(main)/settings/storage/page.tsx +17 -0
  86. package/src/app/[variants]/(main)/settings/tts/features/const.tsx +4 -0
  87. package/src/components/GroupIcon/index.tsx +25 -0
  88. package/src/components/IndexCard/index.tsx +143 -0
  89. package/src/components/ProgressItem/index.tsx +75 -0
  90. package/src/config/aiModels/openai.ts +10 -0
  91. package/src/database/repositories/dataExporter/index.test.ts +330 -0
  92. package/src/database/repositories/dataExporter/index.ts +216 -0
  93. package/src/database/repositories/dataImporter/__tests__/fixtures/agents.json +65 -0
  94. package/src/database/repositories/dataImporter/__tests__/fixtures/agentsToSessions.json +541 -0
  95. package/src/database/repositories/dataImporter/__tests__/fixtures/topic.json +269 -0
  96. package/src/database/repositories/dataImporter/__tests__/fixtures/userSettings.json +18 -0
  97. package/src/database/repositories/dataImporter/__tests__/fixtures/with-client-id.json +778 -0
  98. package/src/database/repositories/dataImporter/__tests__/index.test.ts +120 -880
  99. package/src/database/repositories/dataImporter/deprecated/__tests__/index.test.ts +940 -0
  100. package/src/database/repositories/dataImporter/deprecated/index.ts +326 -0
  101. package/src/database/repositories/dataImporter/index.ts +684 -289
  102. package/src/features/DataImporter/ImportDetail.tsx +203 -0
  103. package/src/features/DataImporter/SuccessResult.tsx +22 -6
  104. package/src/features/DataImporter/_deprecated.ts +43 -0
  105. package/src/features/DataImporter/config.ts +21 -0
  106. package/src/features/DataImporter/index.tsx +112 -31
  107. package/src/features/DevPanel/PostgresViewer/DataTable/index.tsx +6 -0
  108. package/src/features/User/UserPanel/useMenu.tsx +0 -35
  109. package/src/features/User/__tests__/useMenu.test.tsx +0 -2
  110. package/src/locales/default/common.ts +11 -0
  111. package/src/locales/default/error.ts +10 -0
  112. package/src/locales/default/setting.ts +28 -0
  113. package/src/server/routers/lambda/exporter.ts +25 -0
  114. package/src/server/routers/lambda/importer.ts +19 -3
  115. package/src/server/routers/lambda/index.ts +2 -0
  116. package/src/services/config.ts +80 -135
  117. package/src/services/export/_deprecated.ts +155 -0
  118. package/src/services/export/client.ts +15 -0
  119. package/src/services/export/index.ts +6 -0
  120. package/src/services/export/server.ts +9 -0
  121. package/src/services/export/type.ts +5 -0
  122. package/src/services/import/_deprecated.ts +42 -1
  123. package/src/services/import/client.test.ts +1 -1
  124. package/src/services/import/client.ts +30 -1
  125. package/src/services/import/server.ts +70 -2
  126. package/src/services/import/type.ts +10 -0
  127. package/src/store/global/initialState.ts +1 -0
  128. package/src/types/export.ts +11 -0
  129. package/src/types/exportConfig.ts +2 -0
  130. package/src/types/importer.ts +15 -0
  131. package/src/types/user/settings/tts.ts +1 -1
  132. package/src/utils/client/exportFile.ts +21 -0
  133. package/vitest.config.ts +1 -1
  134. package/src/utils/config.ts +0 -109
  135. /package/src/database/repositories/dataImporter/{__tests__ → deprecated/__tests__}/fixtures/messages.json +0 -0
@@ -0,0 +1,330 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { clientDB, initializeDB } from '@/database/client/db';
4
+ import {
5
+ agents,
6
+ agentsKnowledgeBases,
7
+ agentsToSessions,
8
+ files,
9
+ filesToSessions,
10
+ globalFiles,
11
+ knowledgeBaseFiles,
12
+ knowledgeBases,
13
+ messages,
14
+ sessionGroups,
15
+ sessions,
16
+ topics,
17
+ userSettings,
18
+ users,
19
+ } from '@/database/schemas';
20
+ import { LobeChatDatabase } from '@/database/type';
21
+
22
+ import { DATA_EXPORT_CONFIG, DataExporterRepos } from './index';
23
+
24
+ let db = clientDB as LobeChatDatabase;
25
+
26
+ // 设置测试数据
27
+ describe('DataExporterRepos', () => {
28
+ // 测试数据 ID
29
+ const testIds = {
30
+ userId: 'test-user-id',
31
+ fileId: 'test-file-id',
32
+ fileHash: 'test-file-hash',
33
+ sessionId: 'test-session-id',
34
+ agentId: 'test-agent-id',
35
+ topicId: 'test-topic-id',
36
+ messageId: 'test-message-id',
37
+ knowledgeBaseId: 'test-kb-id',
38
+ };
39
+
40
+ // 设置测试环境
41
+ let userId: string = testIds.userId;
42
+
43
+ const setupTestData = async () => {
44
+ await db.transaction(async (trx) => {
45
+ // 用户数据
46
+ await trx.insert(users).values({
47
+ id: testIds.userId,
48
+ username: 'testuser',
49
+ email: 'test@example.com',
50
+ });
51
+
52
+ // 用户设置
53
+ await trx.insert(userSettings).values({
54
+ id: testIds.userId,
55
+ general: { theme: 'light' },
56
+ });
57
+
58
+ // 全局文件
59
+ await trx.insert(globalFiles).values({
60
+ hashId: testIds.fileHash,
61
+ fileType: 'text/plain',
62
+ size: 1024,
63
+ url: 'https://example.com/test-file.txt',
64
+ creator: testIds.userId,
65
+ });
66
+
67
+ // 文件数据
68
+ await trx.insert(files).values({
69
+ id: testIds.fileId,
70
+ userId: testIds.userId,
71
+ fileType: 'text/plain',
72
+ fileHash: testIds.fileHash,
73
+ name: 'test-file.txt',
74
+ size: 1024,
75
+ url: 'https://example.com/test-file.txt',
76
+ });
77
+
78
+ // 会话组
79
+ await trx.insert(sessionGroups).values({
80
+ name: 'Test Group',
81
+ userId: testIds.userId,
82
+ });
83
+
84
+ // 会话
85
+ await trx.insert(sessions).values({
86
+ id: testIds.sessionId,
87
+ slug: 'test-session',
88
+ title: 'Test Session',
89
+ userId: testIds.userId,
90
+ });
91
+
92
+ // 主题
93
+ await trx.insert(topics).values({
94
+ id: testIds.topicId,
95
+ title: 'Test Topic',
96
+ sessionId: testIds.sessionId,
97
+ userId: testIds.userId,
98
+ });
99
+
100
+ // 消息
101
+ await trx.insert(messages).values({
102
+ id: testIds.messageId,
103
+ role: 'user',
104
+ content: 'Hello, world!',
105
+ userId: testIds.userId,
106
+ sessionId: testIds.sessionId,
107
+ topicId: testIds.topicId,
108
+ });
109
+
110
+ // 代理
111
+ await trx.insert(agents).values({
112
+ id: testIds.agentId,
113
+ title: 'Test Agent',
114
+ userId: testIds.userId,
115
+ });
116
+
117
+ // 代理到会话的关联
118
+ await trx.insert(agentsToSessions).values({
119
+ agentId: testIds.agentId,
120
+ sessionId: testIds.sessionId,
121
+ userId: testIds.userId,
122
+ });
123
+
124
+ // 文件到会话的关联
125
+ await trx.insert(filesToSessions).values({
126
+ fileId: testIds.fileId,
127
+ sessionId: testIds.sessionId,
128
+ userId: testIds.userId,
129
+ });
130
+
131
+ // 知识库
132
+ await trx.insert(knowledgeBases).values({
133
+ id: testIds.knowledgeBaseId,
134
+ name: 'Test Knowledge Base',
135
+ userId: testIds.userId,
136
+ });
137
+
138
+ // 知识库文件
139
+ await trx.insert(knowledgeBaseFiles).values({
140
+ knowledgeBaseId: testIds.knowledgeBaseId,
141
+ fileId: testIds.fileId,
142
+ userId: testIds.userId,
143
+ });
144
+
145
+ // 代理知识库
146
+ await trx.insert(agentsKnowledgeBases).values({
147
+ agentId: testIds.agentId,
148
+ knowledgeBaseId: testIds.knowledgeBaseId,
149
+ userId: testIds.userId,
150
+ });
151
+ });
152
+ };
153
+
154
+ beforeEach(async () => {
155
+ // 创建内存数据库
156
+ await initializeDB();
157
+
158
+ // 插入测试数据
159
+ await setupTestData();
160
+ });
161
+
162
+ afterEach(async () => {
163
+ await db.delete(users);
164
+ await db.delete(globalFiles);
165
+
166
+ vi.restoreAllMocks();
167
+ });
168
+
169
+ describe('export', () => {
170
+ it('should export all user data correctly', async () => {
171
+ // 创建导出器实例
172
+ const dataExporter = new DataExporterRepos(db, userId);
173
+
174
+ // 执行导出
175
+ const result = await dataExporter.export();
176
+
177
+ // 验证基础表导出结果
178
+ // expect(result).toHaveProperty('users');
179
+ // expect(result.users).toHaveLength(1);
180
+ // expect(result.users[0]).toHaveProperty('id', testIds.userId);
181
+ // expect(result.users[0]).not.toHaveProperty('userId'); // userId 字段应该被移除
182
+
183
+ expect(result).toHaveProperty('userSettings');
184
+ expect(result.userSettings).toHaveLength(1);
185
+ expect(result.userSettings[0]).toHaveProperty('id', testIds.userId);
186
+
187
+ // expect(result).toHaveProperty('files');
188
+ // expect(result.files).toHaveLength(1);
189
+ // expect(result.files[0]).toHaveProperty('id', testIds.fileId);
190
+ // expect(result.files[0]).toHaveProperty('fileHash', testIds.fileHash);
191
+ // expect(result.files[0]).not.toHaveProperty('userId');
192
+
193
+ expect(result).toHaveProperty('sessions');
194
+ expect(result.sessions).toHaveLength(1);
195
+ expect(result.sessions[0]).toHaveProperty('id', testIds.sessionId);
196
+
197
+ expect(result).toHaveProperty('topics');
198
+ expect(result.topics).toHaveLength(1);
199
+ expect(result.topics[0]).toHaveProperty('id', testIds.topicId);
200
+
201
+ expect(result).toHaveProperty('messages');
202
+ expect(result.messages).toHaveLength(1);
203
+ expect(result.messages[0]).toHaveProperty('id', testIds.messageId);
204
+
205
+ expect(result).toHaveProperty('agents');
206
+ expect(result.agents).toHaveLength(1);
207
+ expect(result.agents[0]).toHaveProperty('id', testIds.agentId);
208
+
209
+ // expect(result).toHaveProperty('knowledgeBases');
210
+ // expect(result.knowledgeBases).toHaveLength(1);
211
+ // expect(result.knowledgeBases[0]).toHaveProperty('id', testIds.knowledgeBaseId);
212
+
213
+ // 验证关联表导出结果
214
+ // expect(result).toHaveProperty('globalFiles');
215
+ // expect(result.globalFiles).toHaveLength(1);
216
+ // expect(result.globalFiles[0]).toHaveProperty('hashId', testIds.fileHash);
217
+
218
+ expect(result).toHaveProperty('agentsToSessions');
219
+ expect(result.agentsToSessions).toHaveLength(1);
220
+ expect(result.agentsToSessions[0]).toHaveProperty('agentId', testIds.agentId);
221
+ expect(result.agentsToSessions[0]).toHaveProperty('sessionId', testIds.sessionId);
222
+
223
+ // expect(result).toHaveProperty('filesToSessions');
224
+ // expect(result.filesToSessions).toHaveLength(1);
225
+ // expect(result.filesToSessions[0]).toHaveProperty('fileId', testIds.fileId);
226
+ // expect(result.filesToSessions[0]).toHaveProperty('sessionId', testIds.sessionId);
227
+
228
+ // expect(result).toHaveProperty('knowledgeBaseFiles');
229
+ // expect(result.knowledgeBaseFiles).toHaveLength(1);
230
+ // expect(result.knowledgeBaseFiles[0]).toHaveProperty(
231
+ // 'knowledgeBaseId',
232
+ // testIds.knowledgeBaseId,
233
+ // );
234
+ // expect(result.knowledgeBaseFiles[0]).toHaveProperty('fileId', testIds.fileId);
235
+ });
236
+
237
+ it('should handle empty database gracefully', async () => {
238
+ // 清空数据库
239
+
240
+ await db.delete(users);
241
+ await db.delete(globalFiles);
242
+
243
+ // 创建导出器实例
244
+ const dataExporter = new DataExporterRepos(db, userId);
245
+
246
+ // 执行导出
247
+ const result = await dataExporter.export();
248
+
249
+ // 验证所有表都返回空数组
250
+ DATA_EXPORT_CONFIG.baseTables.forEach(({ table }) => {
251
+ expect(result).toHaveProperty(table);
252
+ expect(result[table]).toEqual([]);
253
+ });
254
+
255
+ DATA_EXPORT_CONFIG.relationTables.forEach(({ table }) => {
256
+ expect(result).toHaveProperty(table);
257
+ expect(result[table]).toEqual([]);
258
+ });
259
+ });
260
+
261
+ it('should handle database query errors', async () => {
262
+ // 模拟查询错误
263
+ // @ts-ignore
264
+ vi.spyOn(db.query.users, 'findMany').mockRejectedValueOnce(new Error('Database error'));
265
+
266
+ // 创建导出器实例
267
+ const dataExporter = new DataExporterRepos(db, userId);
268
+
269
+ // 执行导出
270
+ const result = await dataExporter.export();
271
+
272
+ // 验证其他表仍然被导出
273
+ expect(result).toHaveProperty('sessions');
274
+ expect(result.sessions).toHaveLength(1);
275
+ });
276
+
277
+ it.skip('should skip relation tables when source tables have no data', async () => {
278
+ // 删除文件数据,这将导致 globalFiles 表被跳过
279
+ await db.delete(files);
280
+
281
+ // 创建导出器实例
282
+ const dataExporter = new DataExporterRepos(db, userId);
283
+
284
+ // 执行导出
285
+ const result = await dataExporter.export();
286
+
287
+ // 验证文件表为空
288
+ // expect(result).toHaveProperty('files');
289
+ // expect(result.files).toEqual([]);
290
+
291
+ // 验证关联表也为空
292
+ // expect(result).toHaveProperty('globalFiles');
293
+ // expect(result.globalFiles).toEqual([]);
294
+ });
295
+
296
+ it('should export data for a different user', async () => {
297
+ // 创建另一个用户
298
+ const anotherUserId = 'another-user-id';
299
+ await db.transaction(async (trx) => {
300
+ await trx.insert(users).values({
301
+ id: anotherUserId,
302
+ username: 'anotheruser',
303
+ email: 'another@example.com',
304
+ });
305
+ await trx.insert(sessions).values({
306
+ id: 'another-session-id',
307
+ slug: 'another-session',
308
+ title: 'Another Session',
309
+ userId: anotherUserId,
310
+ });
311
+ });
312
+
313
+ // 创建导出器实例,使用另一个用户 ID
314
+ const dataExporter = new DataExporterRepos(db, anotherUserId);
315
+
316
+ // 执行导出
317
+ const result = await dataExporter.export();
318
+
319
+ // 验证只导出了另一个用户的数据
320
+ // expect(result).toHaveProperty('users');
321
+ // expect(result.users).toHaveLength(1);
322
+ // expect(result.users[0]).toHaveProperty('id', anotherUserId);
323
+
324
+ expect(result).toHaveProperty('sessions');
325
+ expect(result.sessions).toHaveLength(1);
326
+ expect(result.sessions[0]).not.toHaveProperty('userId', anotherUserId);
327
+ expect(result.sessions[0]).toHaveProperty('id', 'another-session-id');
328
+ });
329
+ });
330
+ });
@@ -0,0 +1,216 @@
1
+ import { and, eq, inArray } from 'drizzle-orm/expressions';
2
+ import pMap from 'p-map';
3
+
4
+ import * as EXPORT_TABLES from '@/database/schemas';
5
+ import { LobeChatDatabase } from '@/database/type';
6
+
7
+ interface BaseTableConfig {
8
+ table: keyof typeof EXPORT_TABLES;
9
+ type: 'base';
10
+ userField?: string;
11
+ }
12
+
13
+ export interface RelationTableConfig {
14
+ relations: {
15
+ field: string;
16
+ sourceField?: string;
17
+ sourceTable: keyof typeof EXPORT_TABLES;
18
+ }[];
19
+ table: keyof typeof EXPORT_TABLES;
20
+ type: 'relation';
21
+ }
22
+
23
+ export const DATA_EXPORT_CONFIG = {
24
+ baseTables: [
25
+ // { table: 'users', userField: 'id' },
26
+ { table: 'userSettings', userField: 'id' },
27
+ { table: 'userInstalledPlugins' },
28
+ { table: 'agents' },
29
+ // { table: 'agentsFiles' },
30
+ // { table: 'agentsKnowledgeBases' },
31
+ // { table: 'agentsToSessions' },
32
+ { table: 'aiModels' },
33
+ { table: 'aiProviders' },
34
+ // async tasks should not be included
35
+ // { table: 'asyncTasks' },
36
+ // { table: 'chunks' },
37
+ // { table: 'unstructuredChunks' },
38
+ // { table: 'embeddings' },
39
+ // { table: 'files' },
40
+ // { table: 'fileChunks' },
41
+ // { table: 'filesToSessions' },
42
+ // { table: 'knowledgeBases' },
43
+ // { table: 'knowledgeBaseFiles' },
44
+ { table: 'messageChunks' },
45
+ { table: 'messagePlugins' },
46
+ // { table: 'messageQueryChunks' },
47
+ // { table: 'messageQueries' },
48
+ { table: 'messageTranslates' },
49
+ // { table: 'messageTTS' },
50
+ { table: 'messages' },
51
+ // { table: 'messagesFiles' },
52
+
53
+ // next auth tables won't be included
54
+ // { table: 'nextauthAccounts' },
55
+ // { table: 'nextauthSessions' },
56
+ // { table: 'nextauthAuthenticators' },
57
+ // { table: 'nextauthVerificationTokens' },
58
+ { table: 'sessionGroups' },
59
+ { table: 'sessions' },
60
+ { table: 'threads' },
61
+ { table: 'topics' },
62
+ ] as BaseTableConfig[],
63
+ relationTables: [
64
+ // {
65
+ // relations: [{ field: 'hashId', sourceField: 'fileHash', sourceTable: 'files' }],
66
+ // table: 'globalFiles',
67
+ // },
68
+ {
69
+ relations: [
70
+ { field: 'agentId', sourceField: 'id', sourceTable: 'agents' },
71
+ { field: 'sessionId', sourceField: 'id', sourceTable: 'sessions' },
72
+ ],
73
+ table: 'agentsToSessions',
74
+ },
75
+
76
+ // {
77
+ // relations: [{ field: 'id', sourceField: 'id', sourceTable: 'messages' }],
78
+ // table: 'messagePlugins',
79
+ // },
80
+ ] as RelationTableConfig[],
81
+ };
82
+
83
+ export class DataExporterRepos {
84
+ private userId: string;
85
+ private db: LobeChatDatabase;
86
+
87
+ constructor(db: LobeChatDatabase, userId: string) {
88
+ this.db = db;
89
+ this.userId = userId;
90
+ }
91
+
92
+ private removeUserId(data: any[]) {
93
+ return data.map((item) => {
94
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
95
+ const { userId: _, ...rest } = item;
96
+ return rest;
97
+ });
98
+ }
99
+
100
+ private async queryTable(config: RelationTableConfig, existingData: Record<string, any[]>) {
101
+ const { table } = config;
102
+ const tableObj = EXPORT_TABLES[table];
103
+ if (!tableObj) throw new Error(`Table ${table} not found`);
104
+
105
+ try {
106
+ const conditions = [];
107
+
108
+ // 处理每个关联条件
109
+ for (const relation of config.relations) {
110
+ const sourceData = existingData[relation.sourceTable] || [];
111
+
112
+ // 如果源数据为空,这个表可能无法查询到任何数据
113
+ if (sourceData.length === 0) {
114
+ console.log(
115
+ `Source table ${relation.sourceTable} has no data, skipping query for ${table}`,
116
+ );
117
+ return [];
118
+ }
119
+
120
+ const sourceIds = sourceData.map((item) => item[relation.sourceField || 'id']);
121
+ conditions.push(inArray(tableObj[relation.field], sourceIds));
122
+ }
123
+
124
+ // 如果表有userId字段并且不是users表,添加用户过滤
125
+ if ('userId' in tableObj && table !== 'users' && !config.relations) {
126
+ conditions.push(eq(tableObj.userId, this.userId));
127
+ }
128
+
129
+ // 组合所有条件
130
+ const where = conditions.length === 1 ? conditions[0] : and(...conditions);
131
+
132
+ // @ts-expect-error query
133
+ const result = await this.db.query[table].findMany({ where });
134
+
135
+ // 只对使用 userId 查询的表移除 userId 字段
136
+ console.log(`Successfully exported table: ${table}, count: ${result.length}`);
137
+ return config.relations ? result : this.removeUserId(result);
138
+ } catch (error) {
139
+ console.error(`Error querying table ${table}:`, error);
140
+ return [];
141
+ }
142
+ }
143
+
144
+ private async queryBaseTables(config: BaseTableConfig) {
145
+ const { table } = config;
146
+ const tableObj = EXPORT_TABLES[table];
147
+ if (!tableObj) throw new Error(`Table ${table} not found`);
148
+
149
+ try {
150
+ // 如果有关联配置,使用关联查询
151
+
152
+ // 默认使用 userId 查询,特殊情况使用 userField
153
+ const userField = config.userField || 'userId';
154
+ const where = eq(tableObj[userField], this.userId);
155
+
156
+ // @ts-expect-error query
157
+ const result = await this.db.query[table].findMany({ where });
158
+
159
+ // 只对使用 userId 查询的表移除 userId 字段
160
+ console.log(`Successfully exported table: ${table}, count: ${result.length}`);
161
+ return this.removeUserId(result);
162
+ } catch (error) {
163
+ console.error(`Error querying table ${table}:`, error);
164
+ return [];
165
+ }
166
+ }
167
+
168
+ async export(concurrency = 10) {
169
+ const result: Record<string, any[]> = {};
170
+
171
+ // 1. 首先并发查询所有基础表
172
+ console.log('Querying base tables...');
173
+ const baseResults = await pMap(
174
+ DATA_EXPORT_CONFIG.baseTables,
175
+ async (config) => ({ data: await this.queryBaseTables(config), table: config.table }),
176
+ { concurrency },
177
+ );
178
+
179
+ // 更新结果集
180
+ baseResults.forEach(({ table, data }) => {
181
+ result[table] = data;
182
+ });
183
+
184
+ // 2. 然后并发查询所有关联表
185
+
186
+ const relationResults = await pMap(
187
+ DATA_EXPORT_CONFIG.relationTables,
188
+ async (config) => {
189
+ // 检查所有依赖的源表是否有数据
190
+ const allSourcesHaveData = config.relations.every(
191
+ (relation) => (result[relation.sourceTable] || []).length > 0,
192
+ );
193
+
194
+ if (!allSourcesHaveData) {
195
+ console.log(`Skipping table ${config.table} as some source tables have no data`);
196
+ return { data: [], table: config.table };
197
+ }
198
+
199
+ return {
200
+ data: await this.queryTable(config, result),
201
+ table: config.table,
202
+ };
203
+ },
204
+ { concurrency },
205
+ );
206
+
207
+ // 更新结果集
208
+ relationResults.forEach(({ table, data }) => {
209
+ result[table] = data;
210
+ });
211
+
212
+ console.log('finalResults:', result);
213
+
214
+ return result;
215
+ }
216
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "data": {
3
+ "agents": [
4
+ {
5
+ "id": "agt_CSLkGPIXIQms",
6
+ "slug": "room-possible-somewhere-numeral",
7
+ "title": null,
8
+ "description": null,
9
+ "tags": [],
10
+ "avatar": null,
11
+ "backgroundColor": null,
12
+ "plugins": [],
13
+ "chatConfig": {
14
+ "searchMode": "off"
15
+ },
16
+ "fewShots": null,
17
+ "model": "gemini-2.0-flash-exp",
18
+ "params": {
19
+ "top_p": 1,
20
+ "temperature": 1,
21
+ "presence_penalty": 0,
22
+ "frequency_penalty": 0
23
+ },
24
+ "provider": "google",
25
+ "systemRole": "",
26
+ "tts": {
27
+ "voice": {
28
+ "openai": "alloy"
29
+ },
30
+ "sttLocale": "auto",
31
+ "ttsService": "openai",
32
+ "showAllLocaleVoice": false
33
+ },
34
+ "accessedAt": "2025-03-05T05:25:44.521Z",
35
+ "createdAt": "2025-03-05T05:25:44.522Z",
36
+ "updatedAt": "2025-03-05T05:25:44.522Z"
37
+ }
38
+ ],
39
+ "agentsToSessions": [
40
+ {
41
+ "agentId": "agt_CSLkGPIXIQms",
42
+ "sessionId": "ssn_WdL6m1mReqyb"
43
+ }
44
+ ],
45
+ "sessions": [
46
+ {
47
+ "id": "ssn_WdL6m1mReqyb",
48
+ "slug": "inbox-t",
49
+ "title": null,
50
+ "description": null,
51
+ "avatar": null,
52
+ "backgroundColor": null,
53
+ "type": "agent",
54
+ "groupId": null,
55
+ "clientId": null,
56
+ "pinned": false,
57
+ "accessedAt": "2025-03-05T05:25:44.521Z",
58
+ "createdAt": "2025-03-05T05:25:44.528Z",
59
+ "updatedAt": "2025-03-05T05:25:44.528Z"
60
+ }
61
+ ]
62
+ },
63
+ "mode": "pglite",
64
+ "schemaHash": "89e91285be422d5f44511c7405f57b57f8dfda4f0304126ae9b0f266e5fd60f1"
65
+ }