@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.
- package/.cursor/rules/microcopy-cn.mdc +75 -63
- package/.cursor/rules/microcopy-en.mdc +4 -8
- package/CHANGELOG.md +50 -0
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/apps/desktop/src/main/locales/default/common.ts +2 -2
- package/changelog/v1.json +10 -0
- package/docs/development/database-schema.dbml +4 -0
- package/e2e/CLAUDE.md +43 -81
- package/e2e/cucumber.config.js +1 -0
- package/e2e/docs/local-setup.md +67 -219
- package/e2e/scripts/setup.ts +529 -0
- package/e2e/src/features/home/sidebarAgent.feature +62 -0
- package/e2e/src/features/home/sidebarGroup.feature +62 -0
- package/e2e/src/features/page/README.md +118 -0
- package/e2e/src/features/page/crud.feature +62 -0
- package/e2e/src/features/page/editor-content.feature +93 -0
- package/e2e/src/features/page/editor-meta.feature +60 -0
- package/e2e/src/steps/agent/conversation.steps.ts +4 -4
- package/e2e/src/steps/home/sidebarAgent.steps.ts +370 -0
- package/e2e/src/steps/home/sidebarGroup.steps.ts +168 -0
- package/e2e/src/steps/hooks.ts +4 -0
- package/e2e/src/steps/page/editor-content.steps.ts +344 -0
- package/e2e/src/steps/page/editor-meta.steps.ts +410 -0
- package/e2e/src/steps/page/page-crud.steps.ts +363 -0
- package/e2e/src/support/world.ts +12 -0
- package/locales/ar/file.json +2 -0
- package/locales/bg-BG/file.json +2 -0
- package/locales/de-DE/file.json +2 -0
- package/locales/en-US/auth.json +1 -1
- package/locales/en-US/file.json +2 -0
- package/locales/en-US/metadata.json +2 -2
- package/locales/es-ES/file.json +2 -0
- package/locales/fa-IR/file.json +2 -0
- package/locales/fr-FR/file.json +2 -0
- package/locales/it-IT/file.json +2 -0
- package/locales/ja-JP/file.json +2 -0
- package/locales/ko-KR/file.json +2 -0
- package/locales/nl-NL/file.json +2 -0
- package/locales/pl-PL/file.json +2 -0
- package/locales/pt-BR/file.json +2 -0
- package/locales/ru-RU/file.json +2 -0
- package/locales/tr-TR/file.json +2 -0
- package/locales/vi-VN/file.json +2 -0
- package/locales/zh-CN/file.json +2 -0
- package/locales/zh-TW/file.json +2 -0
- package/package.json +3 -3
- package/packages/builtin-agents/src/agents/agent-builder/index.ts +1 -1
- package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +1 -1
- package/packages/builtin-agents/src/agents/page-agent/index.ts +1 -1
- package/packages/const/src/settings/group.ts +0 -10
- package/packages/database/migrations/0068_update_group_data.sql +4 -0
- package/packages/database/migrations/meta/0068_snapshot.json +9588 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/models/__tests__/chatGroup.test.ts +5 -7
- package/packages/database/src/models/__tests__/knowledgeBase.test.ts +185 -0
- package/packages/database/src/models/knowledgeBase.ts +67 -3
- package/packages/database/src/repositories/agentGroup/index.test.ts +23 -29
- package/packages/database/src/repositories/agentGroup/index.ts +4 -9
- package/packages/database/src/repositories/knowledge/index.ts +3 -3
- package/packages/database/src/schemas/chatGroup.ts +4 -3
- package/packages/database/src/types/chatGroup.ts +0 -7
- package/packages/types/src/agentGroup/index.ts +30 -9
- package/packages/utils/src/multimodalContent.test.ts +302 -0
- package/packages/utils/src/server/__tests__/sse.test.ts +353 -0
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/Editing.tsx +4 -11
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +3 -3
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +9 -32
- package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +3 -37
- package/src/app/[variants]/(main)/home/_layout/hooks/useSessionGroupMenuItems.tsx +7 -53
- package/src/app/[variants]/(main)/home/features/RecentPage/List.tsx +2 -1
- package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +1 -1
- package/src/app/[variants]/(main)/resource/library/_layout/Sidebar.tsx +2 -2
- package/src/app/[variants]/(main)/resource/library/features/LibraryMenu.tsx +2 -2
- package/src/app/[variants]/(mobile)/chat/settings/features/SettingButton.tsx +2 -12
- package/src/components/ChatGroupWizard/ChatGroupWizard.tsx +5 -27
- package/src/components/DragUpload/index.tsx +24 -27
- package/src/components/MemberSelectionModal/MemberSelectionModal.tsx +2 -11
- package/src/features/ChatInput/ActionBar/Params/Controls.tsx +42 -7
- package/src/features/CommandMenu/useCommandMenu.ts +4 -14
- package/src/features/ResourceManager/components/Editor/index.tsx +2 -3
- package/src/features/ResourceManager/components/Explorer/Header/index.tsx +13 -17
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +1 -1
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/TruncatedFileName.tsx +130 -0
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +36 -4
- package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +4 -3
- package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +58 -2
- package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +58 -6
- package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +2 -5
- package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +9 -5
- package/src/features/ResourceManager/components/Explorer/index.tsx +11 -56
- package/src/features/ResourceManager/components/Header/AddButton.tsx +5 -6
- package/src/features/ResourceManager/components/LibraryHierarchy/HierarchyNode.tsx +382 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/index.tsx +396 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/styles.ts +19 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/treeState.ts +178 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/types.ts +10 -0
- package/src/features/ResourceManager/index.tsx +3 -0
- package/src/layout/GlobalProvider/GroupWizardProvider.tsx +6 -29
- package/src/locales/default/auth.ts +1 -1
- package/src/locales/default/file.ts +2 -0
- package/src/locales/default/metadata.ts +2 -2
- package/src/server/modules/AgentRuntime/AgentRuntimeCoordinator.ts +30 -30
- package/src/server/modules/AgentRuntime/AgentStateManager.ts +23 -23
- package/src/server/modules/AgentRuntime/InMemoryAgentStateManager.ts +16 -16
- package/src/server/modules/AgentRuntime/InMemoryStreamEventManager.ts +13 -13
- package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -2
- package/src/server/modules/AgentRuntime/StreamEventManager.ts +18 -18
- package/src/server/modules/AgentRuntime/types.ts +21 -21
- package/src/server/routers/lambda/__tests__/agentGroup.test.ts +8 -8
- package/src/server/routers/lambda/agentGroup.ts +10 -12
- package/src/server/services/document/index.ts +1 -0
- package/src/store/agentGroup/slices/curd.test.ts +4 -4
- package/src/store/file/slices/fileManager/action.ts +12 -4
- package/src/store/home/slices/homeInput/action.ts +0 -3
- package/src/store/home/slices/sidebarUI/action.ts +9 -0
- package/src/store/session/slices/session/action.ts +5 -9
- package/src/app/[variants]/(mobile)/chat/settings/features/AgentTeamSettings/index.tsx +0 -95
- package/src/features/GroupChatSettings/AgentCard.tsx +0 -154
- package/src/features/GroupChatSettings/AgentTeamChatSettings.tsx +0 -179
- package/src/features/GroupChatSettings/AgentTeamMembersSettings.tsx +0 -244
- package/src/features/GroupChatSettings/AgentTeamMetaSettings.tsx +0 -94
- package/src/features/GroupChatSettings/AgentTeamSettings.tsx +0 -54
- package/src/features/GroupChatSettings/GroupCategory/index.tsx +0 -30
- package/src/features/GroupChatSettings/GroupCategory/useGroupCategory.tsx +0 -42
- package/src/features/GroupChatSettings/GroupChatSettingsProvider.tsx +0 -19
- package/src/features/GroupChatSettings/HostMemberCard.tsx +0 -113
- package/src/features/GroupChatSettings/StoreUpdater.tsx +0 -34
- package/src/features/GroupChatSettings/hooks/useGroupChatSettings.ts +0 -25
- package/src/features/GroupChatSettings/index.ts +0 -16
- package/src/features/GroupChatSettings/store/action.ts +0 -105
- package/src/features/GroupChatSettings/store/index.ts +0 -18
- package/src/features/GroupChatSettings/store/initialState.ts +0 -23
- package/src/features/GroupChatSettings/store/selectors.ts +0 -13
- package/src/features/ResourceManager/components/Tree/index.tsx +0 -883
- /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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
244
|
-
|
|
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(
|
|
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,
|
|
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: {
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
|
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
|
-
|
|
426
|
-
|
|
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
|
|
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
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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:
|
|
116
|
-
provider:
|
|
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
|
|
173
|
-
provider: supervisorConfig?.provider
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
12
|
-
enableSupervisor: boolean;
|
|
13
|
-
maxResponseInRow: number;
|
|
13
|
+
allowDM?: boolean;
|
|
14
14
|
openingMessage?: string;
|
|
15
15
|
openingQuestions?: string[];
|
|
16
|
-
|
|
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;
|