@lobehub/lobehub 2.0.0-next.266 → 2.0.0-next.268

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 (136) hide show
  1. package/.cursor/rules/microcopy-cn.mdc +75 -63
  2. package/.cursor/rules/microcopy-en.mdc +4 -8
  3. package/CHANGELOG.md +50 -0
  4. package/README.md +8 -8
  5. package/README.zh-CN.md +8 -8
  6. package/apps/desktop/src/main/locales/default/common.ts +2 -2
  7. package/changelog/v1.json +10 -0
  8. package/docs/development/database-schema.dbml +4 -0
  9. package/e2e/CLAUDE.md +43 -81
  10. package/e2e/cucumber.config.js +1 -0
  11. package/e2e/docs/local-setup.md +67 -219
  12. package/e2e/scripts/setup.ts +529 -0
  13. package/e2e/src/features/home/sidebarAgent.feature +62 -0
  14. package/e2e/src/features/home/sidebarGroup.feature +62 -0
  15. package/e2e/src/features/page/README.md +118 -0
  16. package/e2e/src/features/page/crud.feature +62 -0
  17. package/e2e/src/features/page/editor-content.feature +93 -0
  18. package/e2e/src/features/page/editor-meta.feature +60 -0
  19. package/e2e/src/steps/agent/conversation.steps.ts +4 -4
  20. package/e2e/src/steps/home/sidebarAgent.steps.ts +370 -0
  21. package/e2e/src/steps/home/sidebarGroup.steps.ts +168 -0
  22. package/e2e/src/steps/hooks.ts +4 -0
  23. package/e2e/src/steps/page/editor-content.steps.ts +344 -0
  24. package/e2e/src/steps/page/editor-meta.steps.ts +410 -0
  25. package/e2e/src/steps/page/page-crud.steps.ts +363 -0
  26. package/e2e/src/support/world.ts +12 -0
  27. package/locales/ar/file.json +2 -0
  28. package/locales/bg-BG/file.json +2 -0
  29. package/locales/de-DE/file.json +2 -0
  30. package/locales/en-US/auth.json +1 -1
  31. package/locales/en-US/file.json +2 -0
  32. package/locales/en-US/metadata.json +2 -2
  33. package/locales/es-ES/file.json +2 -0
  34. package/locales/fa-IR/file.json +2 -0
  35. package/locales/fr-FR/file.json +2 -0
  36. package/locales/it-IT/file.json +2 -0
  37. package/locales/ja-JP/file.json +2 -0
  38. package/locales/ko-KR/file.json +2 -0
  39. package/locales/nl-NL/file.json +2 -0
  40. package/locales/pl-PL/file.json +2 -0
  41. package/locales/pt-BR/file.json +2 -0
  42. package/locales/ru-RU/file.json +2 -0
  43. package/locales/tr-TR/file.json +2 -0
  44. package/locales/vi-VN/file.json +2 -0
  45. package/locales/zh-CN/file.json +2 -0
  46. package/locales/zh-TW/file.json +2 -0
  47. package/package.json +3 -3
  48. package/packages/builtin-agents/src/agents/agent-builder/index.ts +1 -1
  49. package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +1 -1
  50. package/packages/builtin-agents/src/agents/page-agent/index.ts +1 -1
  51. package/packages/const/src/settings/group.ts +0 -10
  52. package/packages/database/migrations/0068_update_group_data.sql +4 -0
  53. package/packages/database/migrations/meta/0068_snapshot.json +9588 -0
  54. package/packages/database/migrations/meta/_journal.json +7 -0
  55. package/packages/database/src/models/__tests__/chatGroup.test.ts +5 -7
  56. package/packages/database/src/models/__tests__/knowledgeBase.test.ts +185 -0
  57. package/packages/database/src/models/knowledgeBase.ts +67 -3
  58. package/packages/database/src/repositories/agentGroup/index.test.ts +23 -29
  59. package/packages/database/src/repositories/agentGroup/index.ts +4 -9
  60. package/packages/database/src/repositories/knowledge/index.ts +3 -3
  61. package/packages/database/src/schemas/chatGroup.ts +4 -3
  62. package/packages/database/src/types/chatGroup.ts +0 -7
  63. package/packages/types/src/agentGroup/index.ts +30 -9
  64. package/packages/utils/src/multimodalContent.test.ts +302 -0
  65. package/packages/utils/src/server/__tests__/sse.test.ts +353 -0
  66. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/Editing.tsx +4 -11
  67. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +3 -3
  68. package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +9 -32
  69. package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +3 -37
  70. package/src/app/[variants]/(main)/home/_layout/hooks/useSessionGroupMenuItems.tsx +7 -53
  71. package/src/app/[variants]/(main)/home/features/RecentPage/List.tsx +2 -1
  72. package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +1 -1
  73. package/src/app/[variants]/(main)/resource/library/_layout/Sidebar.tsx +2 -2
  74. package/src/app/[variants]/(main)/resource/library/features/LibraryMenu.tsx +2 -2
  75. package/src/app/[variants]/(mobile)/chat/settings/features/SettingButton.tsx +2 -12
  76. package/src/components/ChatGroupWizard/ChatGroupWizard.tsx +5 -27
  77. package/src/components/DragUpload/index.tsx +24 -27
  78. package/src/components/MemberSelectionModal/MemberSelectionModal.tsx +2 -11
  79. package/src/features/ChatInput/ActionBar/Params/Controls.tsx +42 -7
  80. package/src/features/CommandMenu/useCommandMenu.ts +4 -14
  81. package/src/features/ResourceManager/components/Editor/index.tsx +2 -3
  82. package/src/features/ResourceManager/components/Explorer/Header/index.tsx +13 -17
  83. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +1 -1
  84. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/TruncatedFileName.tsx +130 -0
  85. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +36 -4
  86. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +4 -3
  87. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +58 -2
  88. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +58 -6
  89. package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +2 -5
  90. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +9 -5
  91. package/src/features/ResourceManager/components/Explorer/index.tsx +11 -56
  92. package/src/features/ResourceManager/components/Header/AddButton.tsx +5 -6
  93. package/src/features/ResourceManager/components/LibraryHierarchy/HierarchyNode.tsx +382 -0
  94. package/src/features/ResourceManager/components/LibraryHierarchy/index.tsx +396 -0
  95. package/src/features/ResourceManager/components/LibraryHierarchy/styles.ts +19 -0
  96. package/src/features/ResourceManager/components/LibraryHierarchy/treeState.ts +178 -0
  97. package/src/features/ResourceManager/components/LibraryHierarchy/types.ts +10 -0
  98. package/src/features/ResourceManager/index.tsx +3 -0
  99. package/src/layout/GlobalProvider/GroupWizardProvider.tsx +6 -29
  100. package/src/locales/default/auth.ts +1 -1
  101. package/src/locales/default/file.ts +2 -0
  102. package/src/locales/default/metadata.ts +2 -2
  103. package/src/server/modules/AgentRuntime/AgentRuntimeCoordinator.ts +30 -30
  104. package/src/server/modules/AgentRuntime/AgentStateManager.ts +23 -23
  105. package/src/server/modules/AgentRuntime/InMemoryAgentStateManager.ts +16 -16
  106. package/src/server/modules/AgentRuntime/InMemoryStreamEventManager.ts +13 -13
  107. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -2
  108. package/src/server/modules/AgentRuntime/StreamEventManager.ts +18 -18
  109. package/src/server/modules/AgentRuntime/types.ts +21 -21
  110. package/src/server/routers/lambda/__tests__/agentGroup.test.ts +8 -8
  111. package/src/server/routers/lambda/agentGroup.ts +10 -12
  112. package/src/server/services/document/index.ts +1 -0
  113. package/src/store/agentGroup/slices/curd.test.ts +4 -4
  114. package/src/store/file/slices/fileManager/action.ts +12 -4
  115. package/src/store/home/slices/homeInput/action.ts +0 -3
  116. package/src/store/home/slices/sidebarUI/action.ts +9 -0
  117. package/src/store/session/slices/session/action.ts +5 -9
  118. package/src/app/[variants]/(mobile)/chat/settings/features/AgentTeamSettings/index.tsx +0 -95
  119. package/src/features/GroupChatSettings/AgentCard.tsx +0 -154
  120. package/src/features/GroupChatSettings/AgentTeamChatSettings.tsx +0 -179
  121. package/src/features/GroupChatSettings/AgentTeamMembersSettings.tsx +0 -244
  122. package/src/features/GroupChatSettings/AgentTeamMetaSettings.tsx +0 -94
  123. package/src/features/GroupChatSettings/AgentTeamSettings.tsx +0 -54
  124. package/src/features/GroupChatSettings/GroupCategory/index.tsx +0 -30
  125. package/src/features/GroupChatSettings/GroupCategory/useGroupCategory.tsx +0 -42
  126. package/src/features/GroupChatSettings/GroupChatSettingsProvider.tsx +0 -19
  127. package/src/features/GroupChatSettings/HostMemberCard.tsx +0 -113
  128. package/src/features/GroupChatSettings/StoreUpdater.tsx +0 -34
  129. package/src/features/GroupChatSettings/hooks/useGroupChatSettings.ts +0 -25
  130. package/src/features/GroupChatSettings/index.ts +0 -16
  131. package/src/features/GroupChatSettings/store/action.ts +0 -105
  132. package/src/features/GroupChatSettings/store/index.ts +0 -18
  133. package/src/features/GroupChatSettings/store/initialState.ts +0 -23
  134. package/src/features/GroupChatSettings/store/selectors.ts +0 -13
  135. package/src/features/ResourceManager/components/Tree/index.tsx +0 -883
  136. /package/src/features/ResourceManager/components/{Tree → LibraryHierarchy}/TreeSkeleton.tsx +0 -0
@@ -476,6 +476,13 @@
476
476
  "when": 1767929492232,
477
477
  "tag": "0067_add_agent_cron_tables",
478
478
  "breakpoints": true
479
+ },
480
+ {
481
+ "idx": 68,
482
+ "version": "7",
483
+ "when": 1768189437504,
484
+ "tag": "0068_update_group_data",
485
+ "breakpoints": true
479
486
  }
480
487
  ],
481
488
  "version": "6"
@@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
 
5
5
  import { LobeChatDatabase } from '@/database/type';
6
6
 
7
+ import { getTestDB } from '../../core/getTestDB';
7
8
  import {
8
9
  NewChatGroup,
9
10
  agents as agentsTable,
@@ -12,7 +13,6 @@ import {
12
13
  users,
13
14
  } from '../../schemas';
14
15
  import { ChatGroupModel } from '../chatGroup';
15
- import { getTestDB } from '../../core/getTestDB';
16
16
 
17
17
  const userId = 'test-user';
18
18
  const otherUserId = 'other-user';
@@ -226,9 +226,8 @@ describe('ChatGroupModel', () => {
226
226
  description: 'A test chat group',
227
227
  pinned: true,
228
228
  config: {
229
- maxResponseInRow: 5,
230
- responseOrder: 'sequential',
231
- scene: 'casual',
229
+ allowDM: true,
230
+ revealDM: false,
232
231
  },
233
232
  };
234
233
 
@@ -240,9 +239,8 @@ describe('ChatGroupModel', () => {
240
239
  expect(result.description).toBe('A test chat group');
241
240
  expect(result.pinned).toBe(true);
242
241
  expect(result.config).toEqual({
243
- maxResponseInRow: 5,
244
- responseOrder: 'sequential',
245
- scene: 'casual',
242
+ allowDM: true,
243
+ revealDM: false,
246
244
  });
247
245
  expect(result.id.startsWith('cg_')).toBe(true);
248
246
  });
@@ -6,6 +6,7 @@ import { sleep } from '@/utils/sleep';
6
6
 
7
7
  import {
8
8
  NewKnowledgeBase,
9
+ documents,
9
10
  files,
10
11
  globalFiles,
11
12
  knowledgeBaseFiles,
@@ -193,6 +194,132 @@ describe('KnowledgeBaseModel', () => {
193
194
  });
194
195
  expect(addedFiles).toHaveLength(2);
195
196
  });
197
+
198
+ it('should add documents (with docs_ prefix) to a knowledge base by resolving to file IDs', async () => {
199
+ await serverDB.insert(globalFiles).values([
200
+ {
201
+ hashId: 'hash1',
202
+ url: 'https://example.com/document.pdf',
203
+ size: 1000,
204
+ fileType: 'application/pdf',
205
+ creator: userId,
206
+ },
207
+ ]);
208
+
209
+ // Create document first
210
+ await serverDB.insert(documents).values([
211
+ {
212
+ id: 'docs_test123',
213
+ title: 'Test Document',
214
+ content: 'Test content',
215
+ fileType: 'application/pdf',
216
+ totalCharCount: 100,
217
+ totalLineCount: 10,
218
+ sourceType: 'file',
219
+ source: 'test.pdf',
220
+ userId,
221
+ },
222
+ ]);
223
+
224
+ // Create mirror file with parentId pointing to the document
225
+ await serverDB.insert(files).values([
226
+ {
227
+ id: 'file1',
228
+ name: 'document.pdf',
229
+ url: 'https://example.com/document.pdf',
230
+ fileHash: 'hash1',
231
+ size: 1000,
232
+ fileType: 'application/pdf',
233
+ parentId: 'docs_test123', // Mirror file points to document
234
+ userId,
235
+ },
236
+ ]);
237
+
238
+ const { id: knowledgeBaseId } = await knowledgeBaseModel.create({ name: 'Test Group' });
239
+
240
+ // Pass document ID (with docs_ prefix)
241
+ const result = await knowledgeBaseModel.addFilesToKnowledgeBase(knowledgeBaseId, [
242
+ 'docs_test123',
243
+ ]);
244
+
245
+ // Should resolve to file1 and insert that
246
+ expect(result).toHaveLength(1);
247
+ expect(result[0].fileId).toBe('file1');
248
+ expect(result[0].knowledgeBaseId).toBe(knowledgeBaseId);
249
+
250
+ const addedFiles = await serverDB.query.knowledgeBaseFiles.findMany({
251
+ where: eq(knowledgeBaseFiles.knowledgeBaseId, knowledgeBaseId),
252
+ });
253
+ expect(addedFiles).toHaveLength(1);
254
+ expect(addedFiles[0].fileId).toBe('file1');
255
+
256
+ // Verify document.knowledgeBaseId was updated
257
+ const document = await serverDB.query.documents.findFirst({
258
+ where: eq(documents.id, 'docs_test123'),
259
+ });
260
+ expect(document?.knowledgeBaseId).toBe(knowledgeBaseId);
261
+ });
262
+
263
+ it('should handle mixed document IDs and file IDs', async () => {
264
+ await serverDB.insert(globalFiles).values([
265
+ {
266
+ hashId: 'hash1',
267
+ url: 'https://example.com/document.pdf',
268
+ size: 1000,
269
+ fileType: 'application/pdf',
270
+ creator: userId,
271
+ },
272
+ {
273
+ hashId: 'hash2',
274
+ url: 'https://example.com/image.jpg',
275
+ size: 500,
276
+ fileType: 'image/jpeg',
277
+ creator: userId,
278
+ },
279
+ ]);
280
+
281
+ // Create document first
282
+ await serverDB.insert(documents).values([
283
+ {
284
+ id: 'docs_test456',
285
+ title: 'Test Document',
286
+ content: 'Test content',
287
+ fileType: 'application/pdf',
288
+ totalCharCount: 100,
289
+ totalLineCount: 10,
290
+ sourceType: 'file',
291
+ source: 'test.pdf',
292
+ userId,
293
+ },
294
+ ]);
295
+
296
+ // Create files - file1 is mirror of the document, file2 is standalone
297
+ await serverDB.insert(files).values([
298
+ {
299
+ id: 'file1',
300
+ name: 'document.pdf',
301
+ url: 'https://example.com/document.pdf',
302
+ fileHash: 'hash1',
303
+ size: 1000,
304
+ fileType: 'application/pdf',
305
+ parentId: 'docs_test456', // Mirror file points to document
306
+ userId,
307
+ },
308
+ fileList[1], // file2 - standalone file
309
+ ]);
310
+
311
+ const { id: knowledgeBaseId } = await knowledgeBaseModel.create({ name: 'Test Group' });
312
+
313
+ // Mix of document ID and direct file ID
314
+ const result = await knowledgeBaseModel.addFilesToKnowledgeBase(knowledgeBaseId, [
315
+ 'docs_test456',
316
+ 'file2',
317
+ ]);
318
+
319
+ expect(result).toHaveLength(2);
320
+ const fileIds = result.map((r) => r.fileId).sort();
321
+ expect(fileIds).toEqual(['file1', 'file2']);
322
+ });
196
323
  });
197
324
 
198
325
  describe('removeFilesFromKnowledgeBase', () => {
@@ -230,6 +357,64 @@ describe('KnowledgeBaseModel', () => {
230
357
  expect(remainingFiles[0].fileId).toBe('file2');
231
358
  });
232
359
 
360
+ it('should remove documents (with docs_ prefix) from a knowledge base by resolving to file IDs', async () => {
361
+ await serverDB.insert(globalFiles).values([
362
+ {
363
+ hashId: 'hash1',
364
+ url: 'https://example.com/document.pdf',
365
+ size: 1000,
366
+ fileType: 'application/pdf',
367
+ creator: userId,
368
+ },
369
+ ]);
370
+
371
+ // Create document first
372
+ await serverDB.insert(documents).values([
373
+ {
374
+ id: 'docs_test789',
375
+ title: 'Test Document',
376
+ content: 'Test content',
377
+ fileType: 'application/pdf',
378
+ totalCharCount: 100,
379
+ totalLineCount: 10,
380
+ sourceType: 'file',
381
+ source: 'test.pdf',
382
+ userId,
383
+ },
384
+ ]);
385
+
386
+ // Create mirror file with parentId pointing to the document
387
+ await serverDB.insert(files).values([
388
+ {
389
+ id: 'file1',
390
+ name: 'document.pdf',
391
+ url: 'https://example.com/document.pdf',
392
+ fileHash: 'hash1',
393
+ size: 1000,
394
+ fileType: 'application/pdf',
395
+ parentId: 'docs_test789', // Mirror file points to document
396
+ userId,
397
+ },
398
+ ]);
399
+
400
+ const { id: knowledgeBaseId } = await knowledgeBaseModel.create({ name: 'Test Group' });
401
+ await knowledgeBaseModel.addFilesToKnowledgeBase(knowledgeBaseId, ['docs_test789']);
402
+
403
+ // Remove using document ID
404
+ await knowledgeBaseModel.removeFilesFromKnowledgeBase(knowledgeBaseId, ['docs_test789']);
405
+
406
+ const remainingFiles = await serverDB.query.knowledgeBaseFiles.findMany({
407
+ where: eq(knowledgeBaseFiles.knowledgeBaseId, knowledgeBaseId),
408
+ });
409
+ expect(remainingFiles).toHaveLength(0);
410
+
411
+ // Verify document.knowledgeBaseId was cleared
412
+ const document = await serverDB.query.documents.findFirst({
413
+ where: eq(documents.id, 'docs_test789'),
414
+ });
415
+ expect(document?.knowledgeBaseId).toBeNull();
416
+ });
417
+
233
418
  it('should not allow removing files from another user knowledge base', async () => {
234
419
  await serverDB.insert(globalFiles).values([
235
420
  {
@@ -1,7 +1,7 @@
1
1
  import { KnowledgeBaseItem } from '@lobechat/types';
2
2
  import { and, desc, eq, inArray } from 'drizzle-orm';
3
3
 
4
- import { NewKnowledgeBase, knowledgeBaseFiles, knowledgeBases } from '../schemas';
4
+ import { NewKnowledgeBase, documents, files, knowledgeBaseFiles, knowledgeBases } from '../schemas';
5
5
  import { LobeChatDatabase } from '../type';
6
6
 
7
7
  export class KnowledgeBaseModel {
@@ -25,9 +25,39 @@ export class KnowledgeBaseModel {
25
25
  };
26
26
 
27
27
  addFilesToKnowledgeBase = async (id: string, fileIds: string[]) => {
28
+ // Separate document IDs from file IDs
29
+ const documentIds = fileIds.filter((id) => id.startsWith('docs_'));
30
+ const directFileIds = fileIds.filter((id) => !id.startsWith('docs_'));
31
+
32
+ // Resolve document IDs to their mirror file IDs
33
+ // For Pages, files.parentId points to the document ID
34
+ let resolvedFileIds = [...directFileIds];
35
+ if (documentIds.length > 0) {
36
+ const mirrorFiles = await this.db
37
+ .select({ id: files.id })
38
+ .from(files)
39
+ .where(and(inArray(files.parentId, documentIds), eq(files.userId, this.userId)));
40
+
41
+ const mirrorFileIds = mirrorFiles.map((file) => file.id);
42
+ resolvedFileIds = [...resolvedFileIds, ...mirrorFileIds];
43
+
44
+ // Update documents.knowledgeBaseId for pages
45
+ await this.db
46
+ .update(documents)
47
+ .set({ knowledgeBaseId: id })
48
+ .where(and(inArray(documents.id, documentIds), eq(documents.userId, this.userId)));
49
+ }
50
+
51
+ // Insert using resolved file IDs
52
+ if (resolvedFileIds.length === 0) {
53
+ return [];
54
+ }
55
+
28
56
  return this.db
29
57
  .insert(knowledgeBaseFiles)
30
- .values(fileIds.map((fileId) => ({ fileId, knowledgeBaseId: id, userId: this.userId })))
58
+ .values(
59
+ resolvedFileIds.map((fileId) => ({ fileId, knowledgeBaseId: id, userId: this.userId })),
60
+ )
31
61
  .returning();
32
62
  };
33
63
 
@@ -43,13 +73,47 @@ export class KnowledgeBaseModel {
43
73
  };
44
74
 
45
75
  removeFilesFromKnowledgeBase = async (knowledgeBaseId: string, ids: string[]) => {
76
+ // Separate document IDs from file IDs
77
+ const documentIds = ids.filter((id) => id.startsWith('docs_'));
78
+ const directFileIds = ids.filter((id) => !id.startsWith('docs_'));
79
+
80
+ // Resolve document IDs to their mirror file IDs
81
+ // For Pages, files.parentId points to the document ID
82
+ let resolvedFileIds = [...directFileIds];
83
+ if (documentIds.length > 0) {
84
+ const mirrorFiles = await this.db
85
+ .select({ id: files.id })
86
+ .from(files)
87
+ .where(and(inArray(files.parentId, documentIds), eq(files.userId, this.userId)));
88
+
89
+ const mirrorFileIds = mirrorFiles.map((file) => file.id);
90
+ resolvedFileIds = [...resolvedFileIds, ...mirrorFileIds];
91
+
92
+ // Clear documents.knowledgeBaseId for pages
93
+ await this.db
94
+ .update(documents)
95
+ .set({ knowledgeBaseId: null })
96
+ .where(
97
+ and(
98
+ inArray(documents.id, documentIds),
99
+ eq(documents.userId, this.userId),
100
+ eq(documents.knowledgeBaseId, knowledgeBaseId),
101
+ ),
102
+ );
103
+ }
104
+
105
+ // Delete using resolved file IDs
106
+ if (resolvedFileIds.length === 0) {
107
+ return;
108
+ }
109
+
46
110
  return this.db
47
111
  .delete(knowledgeBaseFiles)
48
112
  .where(
49
113
  and(
50
114
  eq(knowledgeBaseFiles.userId, this.userId),
51
115
  eq(knowledgeBaseFiles.knowledgeBaseId, knowledgeBaseId),
52
- inArray(knowledgeBaseFiles.fileId, ids),
116
+ inArray(knowledgeBaseFiles.fileId, resolvedFileIds),
53
117
  ),
54
118
  );
55
119
  };
@@ -132,7 +132,7 @@ describe('AgentGroupRepository', () => {
132
132
 
133
133
  // Create group
134
134
  await serverDB.insert(chatGroups).values({
135
- config: { scene: 'casual' },
135
+ config: { allowDM: true },
136
136
  id: 'detail-group',
137
137
  title: 'Detail Group',
138
138
  userId,
@@ -201,9 +201,9 @@ describe('AgentGroupRepository', () => {
201
201
  it('should return group with config', async () => {
202
202
  await serverDB.insert(chatGroups).values({
203
203
  config: {
204
- maxResponseInRow: 3,
205
- responseOrder: 'sequential',
206
- scene: 'productive',
204
+ allowDM: true,
205
+ openingMessage: 'Welcome!',
206
+ revealDM: false,
207
207
  },
208
208
  description: 'Group with config',
209
209
  id: 'config-group',
@@ -216,9 +216,9 @@ describe('AgentGroupRepository', () => {
216
216
 
217
217
  expect(result).not.toBeNull();
218
218
  expect(result!.config).toEqual({
219
- maxResponseInRow: 3,
220
- responseOrder: 'sequential',
221
- scene: 'productive',
219
+ allowDM: true,
220
+ openingMessage: 'Welcome!',
221
+ revealDM: false,
222
222
  });
223
223
  expect(result!.pinned).toBe(true);
224
224
  });
@@ -274,9 +274,8 @@ describe('AgentGroupRepository', () => {
274
274
  // Create group without supervisor
275
275
  await serverDB.insert(chatGroups).values({
276
276
  config: {
277
- orchestratorModel: 'gpt-4o',
278
- orchestratorProvider: 'openai',
279
- scene: 'casual',
277
+ allowDM: true,
278
+ revealDM: true,
280
279
  },
281
280
  id: 'no-supervisor-group',
282
281
  title: 'Group without Supervisor',
@@ -307,13 +306,11 @@ describe('AgentGroupRepository', () => {
307
306
  // Should have 2 agents: auto-created supervisor + regular agent
308
307
  expect(result!.agents).toHaveLength(2);
309
308
 
310
- // Verify agents include auto-created supervisor with config from group
309
+ // Verify agents include auto-created supervisor
311
310
  expect(result!.agents).toEqual(
312
311
  expect.arrayContaining([
313
312
  expect.objectContaining({
314
313
  isSupervisor: true,
315
- model: 'gpt-4o',
316
- provider: 'openai',
317
314
  title: 'Supervisor',
318
315
  virtual: true,
319
316
  }),
@@ -422,9 +419,8 @@ describe('AgentGroupRepository', () => {
422
419
  it('should create group with supervisor agent', async () => {
423
420
  const result = await agentGroupRepo.createGroupWithSupervisor({
424
421
  config: {
425
- orchestratorModel: 'gpt-4',
426
- orchestratorProvider: 'openai',
427
- scene: 'productive',
422
+ allowDM: true,
423
+ openingMessage: 'Hello team!',
428
424
  },
429
425
  title: 'New Group with Supervisor',
430
426
  });
@@ -435,7 +431,7 @@ describe('AgentGroupRepository', () => {
435
431
  expect(result.supervisorAgentId).toBeDefined();
436
432
  expect(result.agents).toEqual([expect.objectContaining({ role: 'supervisor' })]);
437
433
 
438
- // Verify supervisor agent was created with correct config
434
+ // Verify supervisor agent was created
439
435
  const groupDetail = await agentGroupRepo.findByIdWithAgents(result.group.id);
440
436
  expect(groupDetail).toMatchObject({
441
437
  supervisorAgentId: result.supervisorAgentId,
@@ -444,8 +440,6 @@ describe('AgentGroupRepository', () => {
444
440
  expect.arrayContaining([
445
441
  expect.objectContaining({
446
442
  id: result.supervisorAgentId,
447
- model: 'gpt-4',
448
- provider: 'openai',
449
443
  title: 'Supervisor',
450
444
  virtual: true,
451
445
  }),
@@ -772,11 +766,11 @@ describe('AgentGroupRepository', () => {
772
766
  // Create source group with full config
773
767
  await serverDB.insert(chatGroups).values({
774
768
  config: {
775
- maxResponseInRow: 5,
776
- orchestratorModel: 'gpt-4o',
777
- orchestratorProvider: 'openai',
778
- responseOrder: 'sequential',
779
- scene: 'productive',
769
+ allowDM: true,
770
+ openingMessage: 'Welcome!',
771
+ openingQuestions: ['How can I help?'],
772
+ revealDM: false,
773
+ systemPrompt: 'You are a helpful assistant.',
780
774
  },
781
775
  id: 'source-group',
782
776
  pinned: true,
@@ -819,11 +813,11 @@ describe('AgentGroupRepository', () => {
819
813
  expect(duplicatedGroup).toEqual(
820
814
  expect.objectContaining({
821
815
  config: {
822
- maxResponseInRow: 5,
823
- orchestratorModel: 'gpt-4o',
824
- orchestratorProvider: 'openai',
825
- responseOrder: 'sequential',
826
- scene: 'productive',
816
+ allowDM: true,
817
+ openingMessage: 'Welcome!',
818
+ openingQuestions: ['How can I help?'],
819
+ revealDM: false,
820
+ systemPrompt: 'You are a helpful assistant.',
827
821
  },
828
822
  pinned: true,
829
823
  title: 'Source Group (Copy)',
@@ -13,7 +13,6 @@ import {
13
13
  chatGroupsAgents,
14
14
  } from '../../schemas';
15
15
  import { LobeChatDatabase } from '../../type';
16
- import { ChatGroupConfig } from '../../types/chatGroup';
17
16
 
18
17
  export interface SupervisorAgentConfig {
19
18
  model?: string;
@@ -106,14 +105,12 @@ export class AgentGroupRepository {
106
105
 
107
106
  // 4. If no supervisor exists, create a virtual supervisor agent
108
107
  if (!supervisorAgentId) {
109
- const groupConfig = group.config as ChatGroupConfig | undefined;
110
-
111
108
  // Create supervisor agent (virtual agent)
112
109
  const [supervisorAgent] = await this.db
113
110
  .insert(agents)
114
111
  .values({
115
- model: groupConfig?.orchestratorModel,
116
- provider: groupConfig?.orchestratorProvider,
112
+ model: undefined,
113
+ provider: undefined,
117
114
  title: 'Supervisor',
118
115
  userId: this.userId,
119
116
  virtual: true,
@@ -163,14 +160,12 @@ export class AgentGroupRepository {
163
160
  agentMembers: string[] = [],
164
161
  supervisorConfig?: SupervisorAgentConfig,
165
162
  ): Promise<CreateGroupWithSupervisorResult> {
166
- const groupConfig = groupParams.config as ChatGroupConfig | undefined;
167
-
168
163
  // 1. Create supervisor agent (virtual agent)
169
164
  const [supervisorAgent] = await this.db
170
165
  .insert(agents)
171
166
  .values({
172
- model: supervisorConfig?.model ?? groupConfig?.orchestratorModel,
173
- provider: supervisorConfig?.provider ?? groupConfig?.orchestratorProvider,
167
+ model: supervisorConfig?.model,
168
+ provider: supervisorConfig?.provider,
174
169
  title: supervisorConfig?.title ?? 'Supervisor',
175
170
  userId: this.userId,
176
171
  virtual: true,
@@ -202,7 +202,7 @@ export class KnowledgeRepo {
202
202
  FROM ${documents}
203
203
  WHERE user_id = ${this.userId}
204
204
  AND source_type != ${'file'}
205
- AND (metadata->>'knowledgeBaseId') IS NULL
205
+ AND knowledge_base_id IS NULL
206
206
  `;
207
207
 
208
208
  const combinedQuery = sql`
@@ -554,11 +554,11 @@ export class KnowledgeRepo {
554
554
  }
555
555
 
556
556
  // When in a knowledge base, return standalone documents (folders and notes without fileId)
557
- // that have the knowledgeBaseId set in their metadata. Documents with fileId are already
557
+ // that have the knowledgeBaseId column set. Documents with fileId are already
558
558
  // returned by the file query via their linked file records.
559
559
  kbWhereConditions.push(
560
560
  sql`d.file_id IS NULL`,
561
- sql`d.metadata->>'knowledgeBaseId' = ${knowledgeBaseId}`,
561
+ sql`d.knowledge_base_id = ${knowledgeBaseId}`,
562
562
  );
563
563
 
564
564
  return sql`
@@ -9,7 +9,6 @@ import {
9
9
  text,
10
10
  uniqueIndex,
11
11
  } from 'drizzle-orm/pg-core';
12
- import { createInsertSchema } from 'drizzle-zod';
13
12
 
14
13
  import type { ChatGroupConfig } from '../types/chatGroup';
15
14
  import { idGenerator } from '../utils/idGenerator';
@@ -31,6 +30,10 @@ export const chatGroups = pgTable(
31
30
  .notNull(),
32
31
  title: text('title'),
33
32
  description: text('description'),
33
+ avatar: text('avatar'),
34
+ backgroundColor: text('background_color'),
35
+ content: text('content'),
36
+ editorData: jsonb('editor_data').$type<Record<string, any>>(),
34
37
 
35
38
  config: jsonb('config').$type<ChatGroupConfig>(),
36
39
 
@@ -52,8 +55,6 @@ export const chatGroups = pgTable(
52
55
  ],
53
56
  );
54
57
 
55
- export const insertChatGroupSchema = createInsertSchema(chatGroups);
56
-
57
58
  export type NewChatGroup = typeof chatGroups.$inferInsert;
58
59
  export type ChatGroupItem = typeof chatGroups.$inferSelect;
59
60
 
@@ -1,14 +1,7 @@
1
1
  export interface ChatGroupConfig {
2
2
  allowDM?: boolean;
3
- enableSupervisor?: boolean;
4
- maxResponseInRow?: number;
5
3
  openingMessage?: string;
6
4
  openingQuestions?: string[];
7
- orchestratorModel?: string;
8
- orchestratorProvider?: string;
9
- responseOrder?: 'sequential' | 'natural';
10
- responseSpeed?: 'slow' | 'medium' | 'fast';
11
5
  revealDM?: boolean;
12
- scene: 'casual' | 'productive';
13
6
  systemPrompt?: string;
14
7
  }
@@ -1,3 +1,5 @@
1
+ import { z } from 'zod';
2
+
1
3
  import { AgentItem } from '../agent';
2
4
  import { TaskDetail, UIChatMessage } from '../message';
3
5
  import { ChatTopic } from '../topic';
@@ -8,23 +10,42 @@ export interface LobeChatGroupMetaConfig {
8
10
  }
9
11
 
10
12
  export interface LobeChatGroupChatConfig {
11
- allowDM: boolean;
12
- enableSupervisor: boolean;
13
- maxResponseInRow: number;
13
+ allowDM?: boolean;
14
14
  openingMessage?: string;
15
15
  openingQuestions?: string[];
16
- orchestratorModel: string;
17
- orchestratorProvider: string;
18
- responseOrder: 'sequential' | 'natural';
19
- responseSpeed: 'slow' | 'medium' | 'fast';
20
- revealDM: boolean;
21
- scene: 'casual' | 'productive';
16
+ revealDM?: boolean;
22
17
  systemPrompt?: string;
23
18
  }
24
19
 
25
20
  // Database config type (flat structure)
26
21
  export type LobeChatGroupConfig = LobeChatGroupChatConfig;
27
22
 
23
+ // Zod schema for ChatGroupConfig (database insert)
24
+ export const ChatGroupConfigSchema = z.object({
25
+ allowDM: z.boolean().optional(),
26
+ openingMessage: z.string().optional(),
27
+ openingQuestions: z.array(z.string()).optional(),
28
+ revealDM: z.boolean().optional(),
29
+ systemPrompt: z.string().optional(),
30
+ });
31
+
32
+ // Zod schema for inserting ChatGroup
33
+ export const InsertChatGroupSchema = z.object({
34
+ avatar: z.string().optional().nullable(),
35
+ backgroundColor: z.string().optional().nullable(),
36
+ clientId: z.string().optional().nullable(),
37
+ config: ChatGroupConfigSchema.optional().nullable(),
38
+ content: z.string().optional().nullable(),
39
+ description: z.string().optional().nullable(),
40
+ editorData: z.record(z.string(), z.any()).optional().nullable(),
41
+ groupId: z.string().optional().nullable(),
42
+ id: z.string().optional(),
43
+ pinned: z.boolean().optional().nullable(),
44
+ title: z.string().optional().nullable(),
45
+ });
46
+
47
+ export type InsertChatGroup = z.infer<typeof InsertChatGroupSchema>;
48
+
28
49
  // Full group type with nested structure for UI components
29
50
  export interface LobeChatGroupFullConfig {
30
51
  chat: LobeChatGroupChatConfig;