@lobehub/lobehub 2.0.0-next.233 → 2.0.0-next.235
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/.github/workflows/e2e.yml +6 -12
- package/.github/workflows/test.yml +3 -3
- package/CHANGELOG.md +59 -0
- package/CLAUDE.md +1 -1
- package/changelog/v1.json +18 -0
- package/docs/development/basic/feature-development.mdx +4 -5
- package/docs/development/basic/feature-development.zh-CN.mdx +4 -5
- package/e2e/README.md +6 -6
- package/e2e/src/features/community/detail-pages.feature +9 -9
- package/e2e/src/features/community/interactions.feature +13 -13
- package/e2e/src/features/community/smoke.feature +6 -6
- package/e2e/src/steps/agent/conversation-mgmt.steps.ts +196 -25
- package/e2e/src/steps/agent/conversation.steps.ts +58 -0
- package/e2e/src/steps/agent/message-ops.steps.ts +20 -15
- package/e2e/src/steps/community/detail-pages.steps.ts +60 -19
- package/e2e/src/steps/community/interactions.steps.ts +145 -32
- package/e2e/src/steps/hooks.ts +12 -2
- package/locales/ar/components.json +1 -0
- package/locales/ar/file.json +4 -0
- package/locales/ar/models.json +29 -0
- package/locales/ar/setting.json +7 -0
- package/locales/bg-BG/components.json +1 -0
- package/locales/bg-BG/file.json +4 -0
- package/locales/bg-BG/models.json +1 -0
- package/locales/bg-BG/setting.json +7 -0
- package/locales/de-DE/components.json +1 -0
- package/locales/de-DE/file.json +4 -0
- package/locales/de-DE/models.json +29 -0
- package/locales/de-DE/setting.json +7 -0
- package/locales/en-US/common.json +0 -1
- package/locales/en-US/components.json +1 -0
- package/locales/en-US/file.json +4 -0
- package/locales/en-US/models.json +1 -0
- package/locales/en-US/setting.json +3 -0
- package/locales/es-ES/components.json +1 -0
- package/locales/es-ES/file.json +4 -0
- package/locales/es-ES/models.json +43 -0
- package/locales/es-ES/setting.json +7 -0
- package/locales/fa-IR/components.json +1 -0
- package/locales/fa-IR/file.json +4 -0
- package/locales/fa-IR/models.json +54 -0
- package/locales/fa-IR/setting.json +7 -0
- package/locales/fr-FR/components.json +1 -0
- package/locales/fr-FR/file.json +4 -0
- package/locales/fr-FR/models.json +31 -0
- package/locales/fr-FR/setting.json +7 -0
- package/locales/it-IT/components.json +1 -0
- package/locales/it-IT/file.json +4 -0
- package/locales/it-IT/models.json +43 -0
- package/locales/it-IT/setting.json +7 -0
- package/locales/ja-JP/components.json +1 -0
- package/locales/ja-JP/file.json +4 -0
- package/locales/ja-JP/models.json +28 -0
- package/locales/ja-JP/setting.json +7 -0
- package/locales/ko-KR/components.json +1 -0
- package/locales/ko-KR/file.json +4 -0
- package/locales/ko-KR/models.json +37 -0
- package/locales/ko-KR/setting.json +7 -0
- package/locales/nl-NL/components.json +1 -0
- package/locales/nl-NL/file.json +4 -0
- package/locales/nl-NL/models.json +13 -0
- package/locales/nl-NL/setting.json +7 -0
- package/locales/pl-PL/components.json +1 -0
- package/locales/pl-PL/file.json +4 -0
- package/locales/pl-PL/models.json +13 -0
- package/locales/pl-PL/setting.json +7 -0
- package/locales/pt-BR/components.json +1 -0
- package/locales/pt-BR/file.json +4 -0
- package/locales/pt-BR/models.json +29 -0
- package/locales/pt-BR/setting.json +7 -0
- package/locales/ru-RU/components.json +1 -0
- package/locales/ru-RU/file.json +4 -0
- package/locales/ru-RU/models.json +1 -0
- package/locales/ru-RU/setting.json +7 -0
- package/locales/tr-TR/components.json +1 -0
- package/locales/tr-TR/file.json +4 -0
- package/locales/tr-TR/models.json +29 -0
- package/locales/tr-TR/setting.json +7 -0
- package/locales/vi-VN/components.json +1 -0
- package/locales/vi-VN/file.json +4 -0
- package/locales/vi-VN/models.json +1 -0
- package/locales/vi-VN/setting.json +7 -0
- package/locales/zh-CN/file.json +4 -0
- package/locales/zh-CN/models.json +46 -0
- package/locales/zh-CN/setting.json +3 -0
- package/locales/zh-TW/components.json +1 -0
- package/locales/zh-TW/file.json +4 -0
- package/locales/zh-TW/models.json +35 -0
- package/locales/zh-TW/setting.json +7 -0
- package/package.json +5 -5
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/lobehubSkill.ts +55 -0
- package/packages/types/package.json +1 -1
- package/packages/types/src/files/upload.ts +11 -1
- package/packages/types/src/message/common/tools.ts +1 -1
- package/packages/types/src/serverConfig.ts +1 -0
- package/public/not-compatible.html +1296 -0
- package/src/app/[variants]/(main)/resource/features/FileDetail.tsx +20 -12
- package/src/app/[variants]/(main)/resource/features/modal/FullscreenModal.tsx +2 -4
- package/src/app/[variants]/layout.tsx +50 -1
- package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +304 -0
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +74 -10
- package/src/features/Conversation/Messages/AssistantGroup/Tool/Inspector/ToolTitle.tsx +9 -0
- package/src/features/FileViewer/Renderer/Code/index.tsx +224 -0
- package/src/features/FileViewer/Renderer/Image/index.tsx +8 -1
- package/src/features/FileViewer/Renderer/PDF/index.tsx +3 -1
- package/src/features/FileViewer/Renderer/PDF/style.ts +2 -1
- package/src/features/FileViewer/index.tsx +135 -24
- package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +7 -4
- package/src/features/PageEditor/store/initialState.ts +2 -1
- package/src/features/ResourceManager/components/Editor/FileContent.tsx +1 -4
- package/src/features/ResourceManager/components/Editor/FileCopilot.tsx +64 -0
- package/src/features/ResourceManager/components/Editor/index.tsx +98 -31
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +3 -2
- package/src/features/ResourceManager/components/Explorer/ListView/ColumnResizeHandle.tsx +119 -0
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +67 -22
- package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +46 -11
- package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +140 -81
- package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +20 -12
- package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +18 -10
- package/src/features/ResourceManager/components/UploadDock/Item.tsx +38 -6
- package/src/features/ResourceManager/components/UploadDock/index.tsx +62 -41
- package/src/features/ResourceManager/index.tsx +1 -0
- package/src/helpers/toolEngineering/index.test.ts +3 -0
- package/src/helpers/toolEngineering/index.ts +12 -1
- package/src/locales/default/file.ts +4 -0
- package/src/locales/default/setting.ts +3 -0
- package/src/server/globalConfig/index.ts +1 -0
- package/src/server/modules/ModelRuntime/index.test.ts +214 -1
- package/src/server/modules/ModelRuntime/index.ts +43 -7
- package/src/server/routers/lambda/_helpers/resolveContext.ts +8 -8
- package/src/server/routers/lambda/agent.ts +1 -1
- package/src/server/routers/lambda/aiModel.ts +1 -1
- package/src/server/routers/lambda/comfyui.ts +1 -1
- package/src/server/routers/lambda/document.ts +44 -0
- package/src/server/routers/lambda/exporter.ts +1 -1
- package/src/server/routers/lambda/image.ts +13 -13
- package/src/server/routers/lambda/klavis.ts +10 -10
- package/src/server/routers/lambda/market/index.ts +6 -6
- package/src/server/routers/lambda/message.ts +2 -2
- package/src/server/routers/lambda/plugin.ts +1 -1
- package/src/server/routers/lambda/ragEval.ts +2 -2
- package/src/server/routers/lambda/topic.ts +3 -3
- package/src/server/routers/lambda/user.ts +10 -10
- package/src/server/routers/lambda/userMemories.ts +6 -6
- package/src/server/routers/tools/market.ts +261 -0
- package/src/server/services/document/index.ts +22 -0
- package/src/services/document/index.ts +4 -0
- package/src/services/upload.ts +22 -2
- package/src/store/chat/slices/plugin/actions/internals.ts +15 -2
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +104 -0
- package/src/store/file/slices/fileManager/action.test.ts +9 -3
- package/src/store/file/slices/fileManager/action.ts +165 -70
- package/src/store/file/slices/upload/action.ts +3 -0
- package/src/store/global/actions/general.ts +15 -0
- package/src/store/global/initialState.ts +13 -0
- package/src/store/serverConfig/selectors.ts +1 -0
- package/src/store/tool/initialState.ts +11 -2
- package/src/store/tool/selectors/index.ts +1 -0
- package/src/store/tool/selectors/tool.ts +3 -1
- package/src/store/tool/slices/lobehubSkillStore/action.ts +361 -0
- package/src/store/tool/slices/lobehubSkillStore/index.ts +4 -0
- package/src/store/tool/slices/lobehubSkillStore/initialState.ts +24 -0
- package/src/store/tool/slices/lobehubSkillStore/selectors.ts +145 -0
- package/src/store/tool/slices/lobehubSkillStore/types.ts +100 -0
- package/src/store/tool/store.ts +8 -2
- package/vitest.config.mts +1 -0
- package/src/features/FileViewer/Renderer/JavaScript/index.tsx +0 -66
- package/src/features/FileViewer/Renderer/TXT/index.tsx +0 -50
|
@@ -6,7 +6,11 @@ import { type StateCreator } from 'zustand/vanilla';
|
|
|
6
6
|
|
|
7
7
|
import { type ChatStore } from '@/store/chat/store';
|
|
8
8
|
import { useToolStore } from '@/store/tool';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
klavisStoreSelectors,
|
|
11
|
+
lobehubSkillStoreSelectors,
|
|
12
|
+
pluginSelectors,
|
|
13
|
+
} from '@/store/tool/selectors';
|
|
10
14
|
import { builtinTools } from '@/tools';
|
|
11
15
|
|
|
12
16
|
/**
|
|
@@ -34,7 +38,7 @@ export const pluginInternals: StateCreator<
|
|
|
34
38
|
const manifests: Record<string, LobeChatPluginManifest> = {};
|
|
35
39
|
|
|
36
40
|
// Track source for each identifier
|
|
37
|
-
const sourceMap: Record<string, 'builtin' | 'plugin' | 'mcp' | 'klavis'> = {};
|
|
41
|
+
const sourceMap: Record<string, 'builtin' | 'plugin' | 'mcp' | 'klavis' | 'lobehubSkill'> = {};
|
|
38
42
|
|
|
39
43
|
// Get all installed plugins
|
|
40
44
|
const installedPlugins = pluginSelectors.installedPlugins(toolStoreState);
|
|
@@ -63,6 +67,15 @@ export const pluginInternals: StateCreator<
|
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
// Get all LobeHub Skill tools
|
|
71
|
+
const lobehubSkillTools = lobehubSkillStoreSelectors.lobehubSkillAsLobeTools(toolStoreState);
|
|
72
|
+
for (const tool of lobehubSkillTools) {
|
|
73
|
+
if (tool.manifest) {
|
|
74
|
+
manifests[tool.identifier] = tool.manifest as LobeChatPluginManifest;
|
|
75
|
+
sourceMap[tool.identifier] = 'lobehubSkill';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
// Resolve tool calls and add source field
|
|
67
80
|
const resolved = toolNameResolver.resolve(toolCalls, manifests);
|
|
68
81
|
return resolved.map((payload) => ({
|
|
@@ -60,6 +60,14 @@ export interface PluginTypesAction {
|
|
|
60
60
|
*/
|
|
61
61
|
invokeKlavisTypePlugin: (id: string, payload: ChatToolPayload) => Promise<string | undefined>;
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Invoke LobeHub Skill type plugin
|
|
65
|
+
*/
|
|
66
|
+
invokeLobehubSkillTypePlugin: (
|
|
67
|
+
id: string,
|
|
68
|
+
payload: ChatToolPayload,
|
|
69
|
+
) => Promise<string | undefined>;
|
|
70
|
+
|
|
63
71
|
/**
|
|
64
72
|
* Invoke markdown type plugin
|
|
65
73
|
*/
|
|
@@ -93,6 +101,11 @@ export const pluginTypes: StateCreator<
|
|
|
93
101
|
return await get().invokeKlavisTypePlugin(id, payload);
|
|
94
102
|
}
|
|
95
103
|
|
|
104
|
+
// Check if this is a LobeHub Skill tool by source field
|
|
105
|
+
if (payload.source === 'lobehubSkill') {
|
|
106
|
+
return await get().invokeLobehubSkillTypePlugin(id, payload);
|
|
107
|
+
}
|
|
108
|
+
|
|
96
109
|
// Check if this is Cloud Code Interpreter - route to specific handler
|
|
97
110
|
if (payload.identifier === CloudSandboxIdentifier) {
|
|
98
111
|
return await get().invokeCloudCodeInterpreterTool(id, payload);
|
|
@@ -439,6 +452,97 @@ export const pluginTypes: StateCreator<
|
|
|
439
452
|
return data.content;
|
|
440
453
|
},
|
|
441
454
|
|
|
455
|
+
invokeLobehubSkillTypePlugin: async (id, payload) => {
|
|
456
|
+
let data: MCPToolCallResult | undefined;
|
|
457
|
+
|
|
458
|
+
// Get message to extract sessionId/topicId
|
|
459
|
+
const message = dbMessageSelectors.getDbMessageById(id)(get());
|
|
460
|
+
|
|
461
|
+
// Get abort controller from operation
|
|
462
|
+
const operationId = get().messageOperationMap[id];
|
|
463
|
+
const operation = operationId ? get().operations[operationId] : undefined;
|
|
464
|
+
const abortController = operation?.abortController;
|
|
465
|
+
|
|
466
|
+
log(
|
|
467
|
+
'[invokeLobehubSkillTypePlugin] messageId=%s, tool=%s, operationId=%s, aborted=%s',
|
|
468
|
+
id,
|
|
469
|
+
payload.apiName,
|
|
470
|
+
operationId,
|
|
471
|
+
abortController?.signal.aborted,
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
try {
|
|
475
|
+
// payload.identifier is the provider id (e.g., 'linear', 'microsoft')
|
|
476
|
+
const provider = payload.identifier;
|
|
477
|
+
|
|
478
|
+
// Parse arguments
|
|
479
|
+
const args = safeParseJSON(payload.arguments) || {};
|
|
480
|
+
|
|
481
|
+
// Call LobeHub Skill tool via store action
|
|
482
|
+
const result = await useToolStore.getState().callLobehubSkillTool({
|
|
483
|
+
args,
|
|
484
|
+
provider,
|
|
485
|
+
toolName: payload.apiName,
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
if (!result.success) {
|
|
489
|
+
throw new Error(result.error || 'LobeHub Skill tool execution failed');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Convert to MCPToolCallResult format
|
|
493
|
+
const content = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
|
|
494
|
+
data = {
|
|
495
|
+
content,
|
|
496
|
+
error: undefined,
|
|
497
|
+
state: { content: [{ text: content, type: 'text' }] },
|
|
498
|
+
success: true,
|
|
499
|
+
};
|
|
500
|
+
} catch (error) {
|
|
501
|
+
console.error('[invokeLobehubSkillTypePlugin] Error:', error);
|
|
502
|
+
|
|
503
|
+
// ignore the aborted request error
|
|
504
|
+
const err = error as Error;
|
|
505
|
+
if (err.message.includes('aborted')) {
|
|
506
|
+
log(
|
|
507
|
+
'[invokeLobehubSkillTypePlugin] Request aborted: messageId=%s, tool=%s',
|
|
508
|
+
id,
|
|
509
|
+
payload.apiName,
|
|
510
|
+
);
|
|
511
|
+
} else {
|
|
512
|
+
const result = await messageService.updateMessageError(id, error as any, {
|
|
513
|
+
agentId: message?.agentId,
|
|
514
|
+
topicId: message?.topicId,
|
|
515
|
+
});
|
|
516
|
+
if (result?.success && result.messages) {
|
|
517
|
+
get().replaceMessages(result.messages, {
|
|
518
|
+
context: {
|
|
519
|
+
agentId: message?.agentId,
|
|
520
|
+
topicId: message?.topicId,
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// If error occurred, exit
|
|
528
|
+
if (!data) return;
|
|
529
|
+
|
|
530
|
+
const context = operationId ? { operationId } : undefined;
|
|
531
|
+
|
|
532
|
+
// Use optimisticUpdateToolMessage to update content and state/error in a single call
|
|
533
|
+
await get().optimisticUpdateToolMessage(
|
|
534
|
+
id,
|
|
535
|
+
{
|
|
536
|
+
content: data.content,
|
|
537
|
+
pluginError: data.success ? undefined : data.error,
|
|
538
|
+
pluginState: data.success ? data.state : undefined,
|
|
539
|
+
},
|
|
540
|
+
context,
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
return data.content;
|
|
544
|
+
},
|
|
545
|
+
|
|
442
546
|
invokeMarkdownTypePlugin: async (id, payload) => {
|
|
443
547
|
const { internal_callPluginApi } = get();
|
|
444
548
|
|
|
@@ -278,11 +278,14 @@ describe('FileManagerActions', () => {
|
|
|
278
278
|
// Should only dispatch for the valid file
|
|
279
279
|
expect(dispatchSpy).toHaveBeenCalledWith({
|
|
280
280
|
atStart: true,
|
|
281
|
-
files: [
|
|
281
|
+
files: [
|
|
282
|
+
expect.objectContaining({ file: validFile, id: validFile.name, status: 'pending' }),
|
|
283
|
+
],
|
|
282
284
|
type: 'addFiles',
|
|
283
285
|
});
|
|
284
286
|
expect(uploadSpy).toHaveBeenCalledTimes(1);
|
|
285
287
|
expect(uploadSpy).toHaveBeenCalledWith({
|
|
288
|
+
abortController: expect.any(AbortController),
|
|
286
289
|
file: validFile,
|
|
287
290
|
knowledgeBaseId: undefined,
|
|
288
291
|
onStatusUpdate: expect.any(Function),
|
|
@@ -308,6 +311,7 @@ describe('FileManagerActions', () => {
|
|
|
308
311
|
});
|
|
309
312
|
|
|
310
313
|
expect(uploadSpy).toHaveBeenCalledWith({
|
|
314
|
+
abortController: expect.any(AbortController),
|
|
311
315
|
file,
|
|
312
316
|
knowledgeBaseId: 'kb-123',
|
|
313
317
|
onStatusUpdate: expect.any(Function),
|
|
@@ -502,7 +506,9 @@ describe('FileManagerActions', () => {
|
|
|
502
506
|
// Should upload extracted files
|
|
503
507
|
expect(dispatchSpy).toHaveBeenCalledWith({
|
|
504
508
|
atStart: true,
|
|
505
|
-
files: extractedFiles.map((file) =>
|
|
509
|
+
files: extractedFiles.map((file) =>
|
|
510
|
+
expect.objectContaining({ file, id: file.name, status: 'pending' }),
|
|
511
|
+
),
|
|
506
512
|
type: 'addFiles',
|
|
507
513
|
});
|
|
508
514
|
});
|
|
@@ -532,7 +538,7 @@ describe('FileManagerActions', () => {
|
|
|
532
538
|
// Should fallback to uploading the ZIP file itself
|
|
533
539
|
expect(dispatchSpy).toHaveBeenCalledWith({
|
|
534
540
|
atStart: true,
|
|
535
|
-
files: [{ file: zipFile, id: zipFile.name, status: 'pending' }],
|
|
541
|
+
files: [expect.objectContaining({ file: zipFile, id: zipFile.name, status: 'pending' })],
|
|
536
542
|
type: 'addFiles',
|
|
537
543
|
});
|
|
538
544
|
});
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildFolderTree,
|
|
3
|
+
createNanoId,
|
|
4
|
+
sanitizeFolderName,
|
|
5
|
+
topologicalSortFolders,
|
|
6
|
+
} from '@lobechat/utils';
|
|
7
|
+
import { t } from 'i18next';
|
|
2
8
|
import pMap from 'p-map';
|
|
3
9
|
import type { SWRResponse } from 'swr';
|
|
4
10
|
import { type StateCreator } from 'zustand/vanilla';
|
|
5
11
|
|
|
12
|
+
import { message } from '@/components/AntdStaticMethods';
|
|
6
13
|
import { FILE_UPLOAD_BLACKLIST, MAX_UPLOAD_FILE_COUNT } from '@/const/file';
|
|
7
14
|
import { mutate, useClientDataSWR } from '@/libs/swr';
|
|
15
|
+
import { documentService } from '@/services/document';
|
|
8
16
|
import { FileService, fileService } from '@/services/file';
|
|
9
17
|
import { ragService } from '@/services/rag';
|
|
10
18
|
import {
|
|
@@ -27,6 +35,7 @@ export interface FolderCrumb {
|
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
export interface FileManageAction {
|
|
38
|
+
cancelUpload: (id: string) => void;
|
|
30
39
|
dispatchDockFileList: (payload: UploadFileListDispatch) => void;
|
|
31
40
|
embeddingChunks: (fileIds: string[]) => Promise<void>;
|
|
32
41
|
loadMoreKnowledgeItems: () => Promise<void>;
|
|
@@ -67,6 +76,21 @@ export const createFileManageSlice: StateCreator<
|
|
|
67
76
|
[],
|
|
68
77
|
FileManageAction
|
|
69
78
|
> = (set, get) => ({
|
|
79
|
+
cancelUpload: (id) => {
|
|
80
|
+
const { dockUploadFileList, dispatchDockFileList } = get();
|
|
81
|
+
const uploadItem = dockUploadFileList.find((item) => item.id === id);
|
|
82
|
+
|
|
83
|
+
if (uploadItem?.abortController) {
|
|
84
|
+
uploadItem.abortController.abort();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Update status to cancelled
|
|
88
|
+
dispatchDockFileList({
|
|
89
|
+
id,
|
|
90
|
+
status: 'cancelled',
|
|
91
|
+
type: 'updateFileStatus',
|
|
92
|
+
});
|
|
93
|
+
},
|
|
70
94
|
dispatchDockFileList: (payload: UploadFileListDispatch) => {
|
|
71
95
|
const nextValue = uploadFileListReducer(get().dockUploadFileList, payload);
|
|
72
96
|
if (nextValue === get().dockUploadFileList) return;
|
|
@@ -186,19 +210,31 @@ export const createFileManageSlice: StateCreator<
|
|
|
186
210
|
// 1. skip file in blacklist
|
|
187
211
|
const files = filesToUpload.filter((file) => !FILE_UPLOAD_BLACKLIST.includes(file.name));
|
|
188
212
|
|
|
189
|
-
// 2.
|
|
213
|
+
// 2. Create upload items with abort controllers
|
|
214
|
+
const uploadFiles = files.map((file) => {
|
|
215
|
+
const abortController = new AbortController();
|
|
216
|
+
return {
|
|
217
|
+
abortController,
|
|
218
|
+
file,
|
|
219
|
+
id: file.name,
|
|
220
|
+
status: 'pending' as const,
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// 3. Add all files to dock
|
|
190
225
|
dispatchDockFileList({
|
|
191
226
|
atStart: true,
|
|
192
|
-
files:
|
|
227
|
+
files: uploadFiles,
|
|
193
228
|
type: 'addFiles',
|
|
194
229
|
});
|
|
195
230
|
|
|
196
|
-
//
|
|
231
|
+
// 4. Upload files with concurrency limit using p-map
|
|
197
232
|
const uploadResults = await pMap(
|
|
198
|
-
|
|
199
|
-
async (
|
|
233
|
+
uploadFiles,
|
|
234
|
+
async (uploadFileItem) => {
|
|
200
235
|
const result = await get().uploadWithProgress({
|
|
201
|
-
|
|
236
|
+
abortController: uploadFileItem.abortController,
|
|
237
|
+
file: uploadFileItem.file,
|
|
202
238
|
knowledgeBaseId,
|
|
203
239
|
onStatusUpdate: dispatchDockFileList,
|
|
204
240
|
parentId,
|
|
@@ -207,7 +243,11 @@ export const createFileManageSlice: StateCreator<
|
|
|
207
243
|
// Note: Don't refresh after each file to avoid flickering
|
|
208
244
|
// We'll refresh once at the end
|
|
209
245
|
|
|
210
|
-
return {
|
|
246
|
+
return {
|
|
247
|
+
file: uploadFileItem.file,
|
|
248
|
+
fileId: result?.id,
|
|
249
|
+
fileType: uploadFileItem.file.type,
|
|
250
|
+
};
|
|
211
251
|
},
|
|
212
252
|
{ concurrency: MAX_UPLOAD_FILE_COUNT },
|
|
213
253
|
);
|
|
@@ -215,7 +255,7 @@ export const createFileManageSlice: StateCreator<
|
|
|
215
255
|
// Refresh the file list once after all uploads are complete
|
|
216
256
|
await get().refreshFileList();
|
|
217
257
|
|
|
218
|
-
//
|
|
258
|
+
// 5. auto-embed files that support chunking
|
|
219
259
|
const fileIdsToEmbed = uploadResults
|
|
220
260
|
.filter(({ fileType, fileId }) => fileId && !isChunkingUnsupported(fileType))
|
|
221
261
|
.map(({ fileId }) => fileId!);
|
|
@@ -353,82 +393,137 @@ export const createFileManageSlice: StateCreator<
|
|
|
353
393
|
// 2. Sort folders by depth to ensure parents are created before children
|
|
354
394
|
const sortedFolderPaths = topologicalSortFolders(folders);
|
|
355
395
|
|
|
356
|
-
//
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
396
|
+
// Show toast notification if there are folders to create
|
|
397
|
+
const messageKey = 'uploadFolder.creatingFolders';
|
|
398
|
+
if (sortedFolderPaths.length > 0) {
|
|
399
|
+
message.loading({
|
|
400
|
+
content: t('header.actions.uploadFolder.creatingFolders', { ns: 'file' }),
|
|
401
|
+
duration: 0, // Don't auto-dismiss
|
|
402
|
+
key: messageKey,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
362
405
|
|
|
363
|
-
|
|
364
|
-
|
|
406
|
+
try {
|
|
407
|
+
// Map to store created folder IDs: relative path -> folder ID
|
|
408
|
+
const folderIdMap = new Map<string, string>();
|
|
409
|
+
|
|
410
|
+
// 3. Group folders by depth level for batch creation
|
|
411
|
+
const foldersByLevel = new Map<number, string[]>();
|
|
412
|
+
for (const folderPath of sortedFolderPaths) {
|
|
413
|
+
const depth = (folderPath.match(/\//g) || []).length;
|
|
414
|
+
if (!foldersByLevel.has(depth)) {
|
|
415
|
+
foldersByLevel.set(depth, []);
|
|
416
|
+
}
|
|
417
|
+
foldersByLevel.get(depth)!.push(folderPath);
|
|
418
|
+
}
|
|
365
419
|
|
|
366
|
-
//
|
|
367
|
-
const
|
|
420
|
+
// 4. Create folders level by level using batch API
|
|
421
|
+
const generateSlug = createNanoId(8);
|
|
422
|
+
const levels = Array.from(foldersByLevel.keys()).sort((a, b) => a - b);
|
|
423
|
+
for (const level of levels) {
|
|
424
|
+
const foldersAtThisLevel = foldersByLevel.get(level)!;
|
|
425
|
+
|
|
426
|
+
// Prepare batch creation data for this level
|
|
427
|
+
const batchCreateData = foldersAtThisLevel.map((folderPath) => {
|
|
428
|
+
const folder = folders[folderPath];
|
|
429
|
+
const parentId = folder.parent ? folderIdMap.get(folder.parent) : currentFolderId;
|
|
430
|
+
const sanitizedName = sanitizeFolderName(folder.name);
|
|
431
|
+
|
|
432
|
+
// Generate unique slug for the folder
|
|
433
|
+
const slug = generateSlug();
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
content: '',
|
|
437
|
+
editorData: '{}',
|
|
438
|
+
fileType: 'custom/folder',
|
|
439
|
+
knowledgeBaseId,
|
|
440
|
+
metadata: { createdAt: Date.now() },
|
|
441
|
+
parentId,
|
|
442
|
+
slug,
|
|
443
|
+
title: sanitizedName,
|
|
444
|
+
};
|
|
445
|
+
});
|
|
368
446
|
|
|
369
|
-
|
|
370
|
-
|
|
447
|
+
// Create all folders at this level in a single batch request
|
|
448
|
+
const createdFolders = await documentService.createDocuments(batchCreateData);
|
|
371
449
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
450
|
+
// Store folder ID mappings for the next level
|
|
451
|
+
for (const [i, element] of foldersAtThisLevel.entries()) {
|
|
452
|
+
folderIdMap.set(element, createdFolders[i].id);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
375
455
|
|
|
376
|
-
|
|
377
|
-
|
|
456
|
+
// Dismiss the toast after folders are created
|
|
457
|
+
if (sortedFolderPaths.length > 0) {
|
|
458
|
+
message.destroy(messageKey);
|
|
459
|
+
}
|
|
378
460
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const targetFolderId = folderPath ? folderIdMap.get(folderPath) : currentFolderId;
|
|
461
|
+
// Refresh file list to show the new folders
|
|
462
|
+
await get().refreshFileList();
|
|
382
463
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
file,
|
|
386
|
-
parentId: targetFolderId,
|
|
387
|
-
})),
|
|
388
|
-
);
|
|
389
|
-
}
|
|
464
|
+
// 5. Prepare all file uploads with their target folder IDs
|
|
465
|
+
const allUploads: Array<{ file: File; parentId: string | undefined }> = [];
|
|
390
466
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
);
|
|
467
|
+
for (const [folderPath, folderFiles] of Object.entries(filesByFolder)) {
|
|
468
|
+
// Root-level files (no folder path) go to currentFolderId
|
|
469
|
+
const targetFolderId = folderPath ? folderIdMap.get(folderPath) : currentFolderId;
|
|
395
470
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
471
|
+
allUploads.push(
|
|
472
|
+
...folderFiles.map((file) => ({
|
|
473
|
+
file,
|
|
474
|
+
parentId: targetFolderId,
|
|
475
|
+
})),
|
|
476
|
+
);
|
|
477
|
+
}
|
|
402
478
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const result = await get().uploadWithProgress({
|
|
408
|
-
file,
|
|
409
|
-
knowledgeBaseId,
|
|
410
|
-
onStatusUpdate: dispatchDockFileList,
|
|
411
|
-
parentId,
|
|
412
|
-
});
|
|
479
|
+
// 6. Filter out blacklisted files
|
|
480
|
+
const validUploads = allUploads.filter(
|
|
481
|
+
({ file }) => !FILE_UPLOAD_BLACKLIST.includes(file.name),
|
|
482
|
+
);
|
|
413
483
|
|
|
414
|
-
|
|
415
|
-
|
|
484
|
+
// 7. Add all files to dock
|
|
485
|
+
dispatchDockFileList({
|
|
486
|
+
atStart: true,
|
|
487
|
+
files: validUploads.map(({ file }) => ({ file, id: file.name, status: 'pending' })),
|
|
488
|
+
type: 'addFiles',
|
|
489
|
+
});
|
|
416
490
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
491
|
+
// 8. Upload files with concurrency limit
|
|
492
|
+
const uploadResults = await pMap(
|
|
493
|
+
validUploads,
|
|
494
|
+
async ({ file, parentId }) => {
|
|
495
|
+
const result = await get().uploadWithProgress({
|
|
496
|
+
file,
|
|
497
|
+
knowledgeBaseId,
|
|
498
|
+
onStatusUpdate: dispatchDockFileList,
|
|
499
|
+
parentId,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Note: Don't refresh after each file to avoid flickering
|
|
503
|
+
// We'll refresh once at the end
|
|
504
|
+
|
|
505
|
+
return { file, fileId: result?.id, fileType: file.type };
|
|
506
|
+
},
|
|
507
|
+
{ concurrency: MAX_UPLOAD_FILE_COUNT },
|
|
508
|
+
);
|
|
421
509
|
|
|
422
|
-
|
|
423
|
-
|
|
510
|
+
// Refresh the file list once after all uploads are complete
|
|
511
|
+
await get().refreshFileList();
|
|
424
512
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
513
|
+
// 9. Auto-embed files that support chunking
|
|
514
|
+
const fileIdsToEmbed = uploadResults
|
|
515
|
+
.filter(({ fileType, fileId }) => fileId && !isChunkingUnsupported(fileType))
|
|
516
|
+
.map(({ fileId }) => fileId!);
|
|
429
517
|
|
|
430
|
-
|
|
431
|
-
|
|
518
|
+
if (fileIdsToEmbed.length > 0) {
|
|
519
|
+
await get().parseFilesToChunks(fileIdsToEmbed, { skipExist: false });
|
|
520
|
+
}
|
|
521
|
+
} catch (error) {
|
|
522
|
+
// Dismiss toast on error
|
|
523
|
+
if (sortedFolderPaths.length > 0) {
|
|
524
|
+
message.destroy(messageKey);
|
|
525
|
+
}
|
|
526
|
+
throw error;
|
|
432
527
|
}
|
|
433
528
|
},
|
|
434
529
|
|
|
@@ -25,6 +25,7 @@ type OnStatusUpdate = (
|
|
|
25
25
|
) => void;
|
|
26
26
|
|
|
27
27
|
interface UploadWithProgressParams {
|
|
28
|
+
abortController?: AbortController;
|
|
28
29
|
file: File;
|
|
29
30
|
knowledgeBaseId?: string;
|
|
30
31
|
onStatusUpdate?: OnStatusUpdate;
|
|
@@ -93,6 +94,7 @@ export const createFileUploadSlice: StateCreator<
|
|
|
93
94
|
skipCheckFileType,
|
|
94
95
|
parentId,
|
|
95
96
|
source,
|
|
97
|
+
abortController,
|
|
96
98
|
}) => {
|
|
97
99
|
const fileArrayBuffer = await file.arrayBuffer();
|
|
98
100
|
|
|
@@ -117,6 +119,7 @@ export const createFileUploadSlice: StateCreator<
|
|
|
117
119
|
// 3. if file don't exist, need upload files
|
|
118
120
|
else {
|
|
119
121
|
const { data, success } = await uploadService.uploadFileToS3(file, {
|
|
122
|
+
abortController,
|
|
120
123
|
onNotSupported: () => {
|
|
121
124
|
onStatusUpdate?.({ id: file.name, type: 'removeFile' });
|
|
122
125
|
message.info({
|
|
@@ -20,6 +20,7 @@ export interface GlobalGeneralAction {
|
|
|
20
20
|
openAgentInNewWindow: (agentId: string) => Promise<void>;
|
|
21
21
|
openTopicInNewWindow: (agentId: string, topicId: string) => Promise<void>;
|
|
22
22
|
switchLocale: (locale: LocaleMode, params?: { skipBroadcast?: boolean }) => void;
|
|
23
|
+
updateResourceManagerColumnWidth: (column: 'name' | 'date' | 'size', width: number) => void;
|
|
23
24
|
updateSystemStatus: (status: Partial<SystemStatus>, action?: any) => void;
|
|
24
25
|
useCheckLatestVersion: (enabledCheck?: boolean) => SWRResponse<string>;
|
|
25
26
|
useInitSystemStatus: () => SWRResponse;
|
|
@@ -110,6 +111,20 @@ export const generalActionSlice: StateCreator<
|
|
|
110
111
|
})();
|
|
111
112
|
}
|
|
112
113
|
},
|
|
114
|
+
updateResourceManagerColumnWidth: (column, width) => {
|
|
115
|
+
const currentWidths = get().status.resourceManagerColumnWidths || {
|
|
116
|
+
date: 160,
|
|
117
|
+
name: 574,
|
|
118
|
+
size: 140,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
get().updateSystemStatus({
|
|
122
|
+
resourceManagerColumnWidths: {
|
|
123
|
+
...currentWidths,
|
|
124
|
+
[column]: width,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
},
|
|
113
128
|
updateSystemStatus: (status, action) => {
|
|
114
129
|
if (!get().isStatusInit) return;
|
|
115
130
|
|
|
@@ -127,6 +127,14 @@ export interface SystemStatus {
|
|
|
127
127
|
*/
|
|
128
128
|
pagePageSize?: number;
|
|
129
129
|
portalWidth: number;
|
|
130
|
+
/**
|
|
131
|
+
* Resource Manager column widths
|
|
132
|
+
*/
|
|
133
|
+
resourceManagerColumnWidths?: {
|
|
134
|
+
date: number;
|
|
135
|
+
name: number;
|
|
136
|
+
size: number;
|
|
137
|
+
};
|
|
130
138
|
showCommandMenu?: boolean;
|
|
131
139
|
showFilePanel?: boolean;
|
|
132
140
|
showHotkeyHelper?: boolean;
|
|
@@ -192,6 +200,11 @@ export const INITIAL_STATUS = {
|
|
|
192
200
|
noWideScreen: true,
|
|
193
201
|
pagePageSize: 20,
|
|
194
202
|
portalWidth: 400,
|
|
203
|
+
resourceManagerColumnWidths: {
|
|
204
|
+
date: 160,
|
|
205
|
+
name: 574,
|
|
206
|
+
size: 140,
|
|
207
|
+
},
|
|
195
208
|
showCommandMenu: false,
|
|
196
209
|
showFilePanel: true,
|
|
197
210
|
showHotkeyHelper: false,
|
|
@@ -6,6 +6,7 @@ export const serverConfigSelectors = {
|
|
|
6
6
|
enableEmailVerification: (s: ServerConfigStore) =>
|
|
7
7
|
s.serverConfig.enableEmailVerification || false,
|
|
8
8
|
enableKlavis: (s: ServerConfigStore) => s.serverConfig.enableKlavis || false,
|
|
9
|
+
enableLobehubSkill: (s: ServerConfigStore) => s.serverConfig.enableLobehubSkill || false,
|
|
9
10
|
enableMagicLink: (s: ServerConfigStore) => s.serverConfig.enableMagicLink || false,
|
|
10
11
|
enableMarketTrustedClient: (s: ServerConfigStore) =>
|
|
11
12
|
s.serverConfig.enableMarketTrustedClient || false,
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { type BuiltinToolState, initialBuiltinToolState } from './slices/builtin/initialState';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type CustomPluginState,
|
|
4
|
+
initialCustomPluginState,
|
|
5
|
+
} from './slices/customPlugin/initialState';
|
|
3
6
|
import { type KlavisStoreState, initialKlavisStoreState } from './slices/klavisStore/initialState';
|
|
7
|
+
import {
|
|
8
|
+
type LobehubSkillStoreState,
|
|
9
|
+
initialLobehubSkillStoreState,
|
|
10
|
+
} from './slices/lobehubSkillStore/initialState';
|
|
4
11
|
import { type MCPStoreState, initialMCPStoreState } from './slices/mcpStore/initialState';
|
|
5
12
|
import { type PluginStoreState, initialPluginStoreState } from './slices/oldStore/initialState';
|
|
6
13
|
import { type PluginState, initialPluginState } from './slices/plugin/initialState';
|
|
@@ -10,7 +17,8 @@ export type ToolStoreState = PluginState &
|
|
|
10
17
|
PluginStoreState &
|
|
11
18
|
BuiltinToolState &
|
|
12
19
|
MCPStoreState &
|
|
13
|
-
KlavisStoreState
|
|
20
|
+
KlavisStoreState &
|
|
21
|
+
LobehubSkillStoreState;
|
|
14
22
|
|
|
15
23
|
export const initialState: ToolStoreState = {
|
|
16
24
|
...initialPluginState,
|
|
@@ -19,4 +27,5 @@ export const initialState: ToolStoreState = {
|
|
|
19
27
|
...initialBuiltinToolState,
|
|
20
28
|
...initialMCPStoreState,
|
|
21
29
|
...initialKlavisStoreState,
|
|
30
|
+
...initialLobehubSkillStoreState,
|
|
22
31
|
};
|
|
@@ -4,6 +4,7 @@ export {
|
|
|
4
4
|
} from '../slices/builtin/selectors';
|
|
5
5
|
export { customPluginSelectors } from '../slices/customPlugin/selectors';
|
|
6
6
|
export { klavisStoreSelectors } from '../slices/klavisStore/selectors';
|
|
7
|
+
export { lobehubSkillStoreSelectors } from '../slices/lobehubSkillStore/selectors';
|
|
7
8
|
export { mcpStoreSelectors } from '../slices/mcpStore/selectors';
|
|
8
9
|
export { pluginStoreSelectors } from '../slices/oldStore/selectors';
|
|
9
10
|
export { pluginSelectors } from '../slices/plugin/selectors';
|
|
@@ -6,12 +6,14 @@ import { type LobeToolMeta } from '@/types/tool/tool';
|
|
|
6
6
|
|
|
7
7
|
import { type ToolStoreState } from '../initialState';
|
|
8
8
|
import { builtinToolSelectors } from '../slices/builtin/selectors';
|
|
9
|
+
import { lobehubSkillStoreSelectors } from '../slices/lobehubSkillStore/selectors';
|
|
9
10
|
import { pluginSelectors } from '../slices/plugin/selectors';
|
|
10
11
|
|
|
11
12
|
const metaList = (s: ToolStoreState): LobeToolMeta[] => {
|
|
12
13
|
const pluginList = pluginSelectors.installedPluginMetaList(s) as LobeToolMeta[];
|
|
14
|
+
const lobehubSkillList = lobehubSkillStoreSelectors.metaList(s) as LobeToolMeta[];
|
|
13
15
|
|
|
14
|
-
return builtinToolSelectors.metaList(s).concat(pluginList);
|
|
16
|
+
return builtinToolSelectors.metaList(s).concat(pluginList).concat(lobehubSkillList);
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
const getMetaById =
|