@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
|
@@ -39,6 +39,7 @@ export default {
|
|
|
39
39
|
'header.actions.notionGuide.title': 'Import from Notion',
|
|
40
40
|
'header.actions.uploadFile': 'Upload File',
|
|
41
41
|
'header.actions.uploadFolder': 'Upload Folder',
|
|
42
|
+
'header.actions.uploadFolder.creatingFolders': 'Creating folder structure...',
|
|
42
43
|
'header.newPageButton': 'New Page',
|
|
43
44
|
'header.uploadButton': 'Upload',
|
|
44
45
|
'home.getStarted': 'Get Started',
|
|
@@ -129,6 +130,8 @@ export default {
|
|
|
129
130
|
'title': 'Resources',
|
|
130
131
|
'toggleLeftPanel': 'Show/Hide Left Panel',
|
|
131
132
|
'uploadDock.body.collapse': 'Collapse',
|
|
133
|
+
'uploadDock.body.item.cancel': 'Cancel',
|
|
134
|
+
'uploadDock.body.item.cancelled': 'Cancelled',
|
|
132
135
|
'uploadDock.body.item.done': 'Uploaded',
|
|
133
136
|
'uploadDock.body.item.error': 'Upload failed, please try again',
|
|
134
137
|
'uploadDock.body.item.pending': 'Preparing to upload...',
|
|
@@ -137,6 +140,7 @@ export default {
|
|
|
137
140
|
'uploadDock.fileQueueInfo':
|
|
138
141
|
'Uploading the first {{count}} files, {{remaining}} remaining in queue',
|
|
139
142
|
'uploadDock.totalCount': 'Total {{count}} items',
|
|
143
|
+
'uploadDock.uploadStatus.cancelled': 'Upload cancelled',
|
|
140
144
|
'uploadDock.uploadStatus.error': 'Upload error',
|
|
141
145
|
'uploadDock.uploadStatus.pending': 'Waiting to upload',
|
|
142
146
|
'uploadDock.uploadStatus.processing': 'Uploading',
|
|
@@ -603,6 +603,9 @@ export default {
|
|
|
603
603
|
'tools.klavis.servers': 'servers',
|
|
604
604
|
'tools.klavis.tools': 'tools',
|
|
605
605
|
'tools.klavis.verifyAuth': 'I have completed authentication',
|
|
606
|
+
'tools.lobehubSkill.authorize': 'Authorize',
|
|
607
|
+
'tools.lobehubSkill.connect': 'Connect',
|
|
608
|
+
'tools.lobehubSkill.error': 'Error',
|
|
606
609
|
'tools.notInstalled': 'Not Installed',
|
|
607
610
|
'tools.notInstalledWarning':
|
|
608
611
|
'This skill is not currently installed, which may affect agent functionality.',
|
|
@@ -76,6 +76,7 @@ export const getServerGlobalConfig = async () => {
|
|
|
76
76
|
},
|
|
77
77
|
enableEmailVerification: authEnv.AUTH_EMAIL_VERIFICATION,
|
|
78
78
|
enableKlavis: !!klavisEnv.KLAVIS_API_KEY,
|
|
79
|
+
enableLobehubSkill: !!(appEnv.MARKET_TRUSTED_CLIENT_SECRET && appEnv.MARKET_TRUSTED_CLIENT_ID),
|
|
79
80
|
enableMagicLink: authEnv.ENABLE_MAGIC_LINK,
|
|
80
81
|
enableMarketTrustedClient: !!(
|
|
81
82
|
appEnv.MARKET_TRUSTED_CLIENT_SECRET && appEnv.MARKET_TRUSTED_CLIENT_ID
|
|
@@ -26,7 +26,7 @@ import { ClientSecretPayload } from '@lobechat/types';
|
|
|
26
26
|
import { ModelProvider } from 'model-bank';
|
|
27
27
|
import { describe, expect, it, vi } from 'vitest';
|
|
28
28
|
|
|
29
|
-
import { initModelRuntimeWithUserPayload } from './index';
|
|
29
|
+
import { buildPayloadFromKeyVaults, initModelRuntimeWithUserPayload } from './index';
|
|
30
30
|
|
|
31
31
|
// 模拟依赖项
|
|
32
32
|
vi.mock('@/envs/llm', () => ({
|
|
@@ -496,3 +496,216 @@ describe('initModelRuntimeWithUserPayload method', () => {
|
|
|
496
496
|
});
|
|
497
497
|
});
|
|
498
498
|
});
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Test cases for buildPayloadFromKeyVaults function
|
|
502
|
+
* This function builds ClientSecretPayload based on runtimeProvider (sdkType)
|
|
503
|
+
* to ensure provider-specific fields are correctly forwarded
|
|
504
|
+
*/
|
|
505
|
+
describe('buildPayloadFromKeyVaults', () => {
|
|
506
|
+
describe('should build payload with correct fields based on runtimeProvider', () => {
|
|
507
|
+
it('OpenAI compatible: returns apiKey, baseURL and runtimeProvider', () => {
|
|
508
|
+
const keyVaults = {
|
|
509
|
+
apiKey: 'test-api-key',
|
|
510
|
+
baseURL: 'https://custom-endpoint.com/v1',
|
|
511
|
+
};
|
|
512
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.OpenAI);
|
|
513
|
+
|
|
514
|
+
expect(payload).toEqual({
|
|
515
|
+
apiKey: 'test-api-key',
|
|
516
|
+
baseURL: 'https://custom-endpoint.com/v1',
|
|
517
|
+
runtimeProvider: ModelProvider.OpenAI,
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('Azure: returns apiKey, baseURL, azureApiVersion and runtimeProvider', () => {
|
|
522
|
+
const keyVaults = {
|
|
523
|
+
apiKey: 'azure-api-key',
|
|
524
|
+
baseURL: 'https://my-azure.openai.azure.com',
|
|
525
|
+
apiVersion: '2024-06-01',
|
|
526
|
+
endpoint: 'https://fallback-endpoint.com',
|
|
527
|
+
};
|
|
528
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Azure);
|
|
529
|
+
|
|
530
|
+
expect(payload).toEqual({
|
|
531
|
+
apiKey: 'azure-api-key',
|
|
532
|
+
azureApiVersion: '2024-06-01',
|
|
533
|
+
baseURL: 'https://my-azure.openai.azure.com',
|
|
534
|
+
runtimeProvider: ModelProvider.Azure,
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('Azure: uses endpoint as fallback when baseURL is not provided', () => {
|
|
539
|
+
const keyVaults = {
|
|
540
|
+
apiKey: 'azure-api-key',
|
|
541
|
+
endpoint: 'https://fallback-endpoint.com',
|
|
542
|
+
apiVersion: '2024-06-01',
|
|
543
|
+
};
|
|
544
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Azure);
|
|
545
|
+
|
|
546
|
+
expect(payload.baseURL).toBe('https://fallback-endpoint.com');
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('Cloudflare: returns apiKey, cloudflareBaseURLOrAccountID and runtimeProvider', () => {
|
|
550
|
+
const keyVaults = {
|
|
551
|
+
apiKey: 'cloudflare-api-key',
|
|
552
|
+
baseURLOrAccountID: 'my-account-id',
|
|
553
|
+
};
|
|
554
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Cloudflare);
|
|
555
|
+
|
|
556
|
+
expect(payload).toEqual({
|
|
557
|
+
apiKey: 'cloudflare-api-key',
|
|
558
|
+
cloudflareBaseURLOrAccountID: 'my-account-id',
|
|
559
|
+
runtimeProvider: ModelProvider.Cloudflare,
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('Bedrock: returns AWS credentials and runtimeProvider', () => {
|
|
564
|
+
const keyVaults = {
|
|
565
|
+
accessKeyId: 'aws-access-key',
|
|
566
|
+
secretAccessKey: 'aws-secret-key',
|
|
567
|
+
region: 'us-east-1',
|
|
568
|
+
sessionToken: 'session-token',
|
|
569
|
+
};
|
|
570
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Bedrock);
|
|
571
|
+
|
|
572
|
+
expect(payload).toEqual({
|
|
573
|
+
apiKey: 'aws-secret-keyaws-access-key',
|
|
574
|
+
awsAccessKeyId: 'aws-access-key',
|
|
575
|
+
awsRegion: 'us-east-1',
|
|
576
|
+
awsSecretAccessKey: 'aws-secret-key',
|
|
577
|
+
awsSessionToken: 'session-token',
|
|
578
|
+
runtimeProvider: ModelProvider.Bedrock,
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it('Ollama: returns baseURL and runtimeProvider', () => {
|
|
583
|
+
const keyVaults = {
|
|
584
|
+
baseURL: 'http://localhost:11434',
|
|
585
|
+
};
|
|
586
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Ollama);
|
|
587
|
+
|
|
588
|
+
expect(payload).toEqual({
|
|
589
|
+
baseURL: 'http://localhost:11434',
|
|
590
|
+
runtimeProvider: ModelProvider.Ollama,
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it('VertexAI: returns apiKey, baseURL, vertexAIRegion and runtimeProvider', () => {
|
|
595
|
+
const keyVaults = {
|
|
596
|
+
apiKey: 'vertex-credentials-json',
|
|
597
|
+
baseURL: 'https://vertex-endpoint.com',
|
|
598
|
+
region: 'us-central1',
|
|
599
|
+
};
|
|
600
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.VertexAI);
|
|
601
|
+
|
|
602
|
+
expect(payload).toEqual({
|
|
603
|
+
apiKey: 'vertex-credentials-json',
|
|
604
|
+
baseURL: 'https://vertex-endpoint.com',
|
|
605
|
+
runtimeProvider: ModelProvider.VertexAI,
|
|
606
|
+
vertexAIRegion: 'us-central1',
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('ComfyUI: returns all auth fields and runtimeProvider', () => {
|
|
611
|
+
const keyVaults = {
|
|
612
|
+
apiKey: 'comfyui-api-key',
|
|
613
|
+
authType: 'bearer',
|
|
614
|
+
baseURL: 'http://localhost:8188',
|
|
615
|
+
customHeaders: { 'X-Custom': 'header' },
|
|
616
|
+
password: 'pass',
|
|
617
|
+
username: 'user',
|
|
618
|
+
} as const;
|
|
619
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.ComfyUI);
|
|
620
|
+
|
|
621
|
+
expect(payload).toEqual({
|
|
622
|
+
apiKey: 'comfyui-api-key',
|
|
623
|
+
authType: 'bearer',
|
|
624
|
+
baseURL: 'http://localhost:8188',
|
|
625
|
+
customHeaders: { 'X-Custom': 'header' },
|
|
626
|
+
password: 'pass',
|
|
627
|
+
runtimeProvider: ModelProvider.ComfyUI,
|
|
628
|
+
username: 'user',
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it('Unknown provider: falls back to default with apiKey, baseURL and runtimeProvider', () => {
|
|
633
|
+
const keyVaults = {
|
|
634
|
+
apiKey: 'unknown-api-key',
|
|
635
|
+
baseURL: 'https://unknown-endpoint.com',
|
|
636
|
+
};
|
|
637
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, 'unknown-provider');
|
|
638
|
+
|
|
639
|
+
expect(payload).toEqual({
|
|
640
|
+
apiKey: 'unknown-api-key',
|
|
641
|
+
baseURL: 'https://unknown-endpoint.com',
|
|
642
|
+
runtimeProvider: 'unknown-provider',
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
describe('custom provider with sdkType should include provider-specific fields', () => {
|
|
648
|
+
it('custom provider with Azure sdkType includes azureApiVersion', () => {
|
|
649
|
+
const keyVaults = {
|
|
650
|
+
apiKey: 'custom-azure-key',
|
|
651
|
+
baseURL: 'https://custom-azure.openai.azure.com',
|
|
652
|
+
apiVersion: '2024-06-01',
|
|
653
|
+
};
|
|
654
|
+
// Simulates a custom provider where runtimeProvider is resolved to 'azure'
|
|
655
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Azure);
|
|
656
|
+
|
|
657
|
+
expect(payload.azureApiVersion).toBe('2024-06-01');
|
|
658
|
+
expect(payload.runtimeProvider).toBe(ModelProvider.Azure);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('custom provider with Cloudflare sdkType includes cloudflareBaseURLOrAccountID', () => {
|
|
662
|
+
const keyVaults = {
|
|
663
|
+
apiKey: 'custom-cloudflare-key',
|
|
664
|
+
baseURLOrAccountID: 'custom-account-id',
|
|
665
|
+
};
|
|
666
|
+
// Simulates a custom provider where runtimeProvider is resolved to 'cloudflare'
|
|
667
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Cloudflare);
|
|
668
|
+
|
|
669
|
+
expect(payload.cloudflareBaseURLOrAccountID).toBe('custom-account-id');
|
|
670
|
+
expect(payload.runtimeProvider).toBe(ModelProvider.Cloudflare);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('custom provider with Bedrock sdkType includes AWS credentials', () => {
|
|
674
|
+
const keyVaults = {
|
|
675
|
+
accessKeyId: 'custom-aws-id',
|
|
676
|
+
secretAccessKey: 'custom-aws-secret',
|
|
677
|
+
region: 'eu-west-1',
|
|
678
|
+
};
|
|
679
|
+
// Simulates a custom provider where runtimeProvider is resolved to 'bedrock'
|
|
680
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Bedrock);
|
|
681
|
+
|
|
682
|
+
expect(payload.awsAccessKeyId).toBe('custom-aws-id');
|
|
683
|
+
expect(payload.awsSecretAccessKey).toBe('custom-aws-secret');
|
|
684
|
+
expect(payload.awsRegion).toBe('eu-west-1');
|
|
685
|
+
expect(payload.runtimeProvider).toBe(ModelProvider.Bedrock);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it('custom provider with Ollama sdkType includes baseURL', () => {
|
|
689
|
+
const keyVaults = {
|
|
690
|
+
baseURL: 'http://custom-ollama:11434',
|
|
691
|
+
};
|
|
692
|
+
// Simulates a custom provider where runtimeProvider is resolved to 'ollama'
|
|
693
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Ollama);
|
|
694
|
+
|
|
695
|
+
expect(payload.baseURL).toBe('http://custom-ollama:11434');
|
|
696
|
+
expect(payload.runtimeProvider).toBe(ModelProvider.Ollama);
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
it('custom provider with VertexAI sdkType includes vertexAIRegion', () => {
|
|
700
|
+
const keyVaults = {
|
|
701
|
+
apiKey: 'custom-vertex-creds',
|
|
702
|
+
region: 'asia-northeast1',
|
|
703
|
+
};
|
|
704
|
+
// Simulates a custom provider where runtimeProvider is resolved to 'vertexai'
|
|
705
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.VertexAI);
|
|
706
|
+
|
|
707
|
+
expect(payload.vertexAIRegion).toBe('asia-northeast1');
|
|
708
|
+
expect(payload.runtimeProvider).toBe(ModelProvider.VertexAI);
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
});
|
|
@@ -32,6 +32,24 @@ type ProviderKeyVaults = OpenAICompatibleKeyVault &
|
|
|
32
32
|
ComfyUIKeyVault &
|
|
33
33
|
VertexAIKeyVault;
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the runtime provider for a given provider.
|
|
37
|
+
*
|
|
38
|
+
* This is the server-side equivalent of the frontend's resolveRuntimeProvider function.
|
|
39
|
+
* For builtin providers, returns the provider as-is.
|
|
40
|
+
* For custom providers, returns the sdkType from settings (defaults to 'openai').
|
|
41
|
+
*
|
|
42
|
+
* @param provider - The provider id
|
|
43
|
+
* @param sdkType - The sdkType from provider settings
|
|
44
|
+
* @returns The resolved runtime provider
|
|
45
|
+
*/
|
|
46
|
+
const resolveRuntimeProvider = (provider: string, sdkType?: string): string => {
|
|
47
|
+
const isBuiltin = Object.values(ModelProvider).includes(provider as ModelProvider);
|
|
48
|
+
if (isBuiltin) return provider;
|
|
49
|
+
|
|
50
|
+
return sdkType || 'openai';
|
|
51
|
+
};
|
|
52
|
+
|
|
35
53
|
/**
|
|
36
54
|
* Build ClientSecretPayload from keyVaults stored in database
|
|
37
55
|
*
|
|
@@ -39,15 +57,21 @@ type ProviderKeyVaults = OpenAICompatibleKeyVault &
|
|
|
39
57
|
* It converts the keyVaults object from database to the ClientSecretPayload format
|
|
40
58
|
* expected by initModelRuntimeWithUserPayload.
|
|
41
59
|
*
|
|
42
|
-
*
|
|
60
|
+
* For custom providers, we use runtimeProvider (sdkType) to determine which fields
|
|
61
|
+
* to include in the payload. This ensures that provider-specific fields like
|
|
62
|
+
* cloudflareBaseURLOrAccountID or azureApiVersion are correctly forwarded.
|
|
63
|
+
*
|
|
43
64
|
* @param keyVaults - The keyVaults object from database (already decrypted)
|
|
65
|
+
* @param runtimeProvider - The runtime provider (sdkType) to use for building payload
|
|
44
66
|
* @returns ClientSecretPayload for the provider
|
|
45
67
|
*/
|
|
46
68
|
export const buildPayloadFromKeyVaults = (
|
|
47
|
-
provider: string,
|
|
48
69
|
keyVaults: ProviderKeyVaults,
|
|
70
|
+
runtimeProvider: string,
|
|
49
71
|
): ClientSecretPayload => {
|
|
50
|
-
|
|
72
|
+
// Use runtimeProvider to determine which fields to include
|
|
73
|
+
// This handles both builtin providers and custom providers with sdkType
|
|
74
|
+
switch (runtimeProvider) {
|
|
51
75
|
case ModelProvider.Bedrock: {
|
|
52
76
|
const { accessKeyId, region, secretAccessKey, sessionToken } = keyVaults;
|
|
53
77
|
const apiKey = (secretAccessKey || '') + (accessKeyId || '');
|
|
@@ -58,6 +82,7 @@ export const buildPayloadFromKeyVaults = (
|
|
|
58
82
|
awsRegion: region,
|
|
59
83
|
awsSecretAccessKey: secretAccessKey,
|
|
60
84
|
awsSessionToken: sessionToken,
|
|
85
|
+
runtimeProvider,
|
|
61
86
|
};
|
|
62
87
|
}
|
|
63
88
|
|
|
@@ -66,17 +91,19 @@ export const buildPayloadFromKeyVaults = (
|
|
|
66
91
|
apiKey: keyVaults.apiKey,
|
|
67
92
|
azureApiVersion: keyVaults.apiVersion,
|
|
68
93
|
baseURL: keyVaults.baseURL || keyVaults.endpoint,
|
|
94
|
+
runtimeProvider,
|
|
69
95
|
};
|
|
70
96
|
}
|
|
71
97
|
|
|
72
98
|
case ModelProvider.Ollama: {
|
|
73
|
-
return { baseURL: keyVaults.baseURL };
|
|
99
|
+
return { baseURL: keyVaults.baseURL, runtimeProvider };
|
|
74
100
|
}
|
|
75
101
|
|
|
76
102
|
case ModelProvider.Cloudflare: {
|
|
77
103
|
return {
|
|
78
104
|
apiKey: keyVaults.apiKey,
|
|
79
105
|
cloudflareBaseURLOrAccountID: keyVaults.baseURLOrAccountID,
|
|
106
|
+
runtimeProvider,
|
|
80
107
|
};
|
|
81
108
|
}
|
|
82
109
|
|
|
@@ -87,6 +114,7 @@ export const buildPayloadFromKeyVaults = (
|
|
|
87
114
|
baseURL: keyVaults.baseURL,
|
|
88
115
|
customHeaders: keyVaults.customHeaders,
|
|
89
116
|
password: keyVaults.password,
|
|
117
|
+
runtimeProvider,
|
|
90
118
|
username: keyVaults.username,
|
|
91
119
|
};
|
|
92
120
|
}
|
|
@@ -95,6 +123,7 @@ export const buildPayloadFromKeyVaults = (
|
|
|
95
123
|
return {
|
|
96
124
|
apiKey: keyVaults.apiKey,
|
|
97
125
|
baseURL: keyVaults.baseURL,
|
|
126
|
+
runtimeProvider,
|
|
98
127
|
vertexAIRegion: keyVaults.region,
|
|
99
128
|
};
|
|
100
129
|
}
|
|
@@ -103,6 +132,7 @@ export const buildPayloadFromKeyVaults = (
|
|
|
103
132
|
return {
|
|
104
133
|
apiKey: keyVaults.apiKey,
|
|
105
134
|
baseURL: keyVaults.baseURL,
|
|
135
|
+
runtimeProvider,
|
|
106
136
|
};
|
|
107
137
|
}
|
|
108
138
|
}
|
|
@@ -350,10 +380,16 @@ export const initModelRuntimeFromDB = async (
|
|
|
350
380
|
KeyVaultsGateKeeper.getUserKeyVaults,
|
|
351
381
|
);
|
|
352
382
|
|
|
353
|
-
// 2.
|
|
383
|
+
// 2. Resolve the runtime provider for custom providers
|
|
384
|
+
// For custom providers, use sdkType from settings (defaults to 'openai')
|
|
385
|
+
const sdkType = providerConfig?.settings?.sdkType;
|
|
386
|
+
const runtimeProvider = resolveRuntimeProvider(provider, sdkType);
|
|
387
|
+
|
|
388
|
+
// 3. Build ClientSecretPayload from keyVaults based on runtimeProvider
|
|
389
|
+
// This ensures provider-specific fields (e.g., cloudflareBaseURLOrAccountID) are included
|
|
354
390
|
const keyVaults = (providerConfig?.keyVaults || {}) as ProviderKeyVaults;
|
|
355
|
-
const payload = buildPayloadFromKeyVaults(
|
|
391
|
+
const payload = buildPayloadFromKeyVaults(keyVaults, runtimeProvider);
|
|
356
392
|
|
|
357
|
-
//
|
|
393
|
+
// 4. Initialize ModelRuntime with the payload
|
|
358
394
|
return initModelRuntimeWithUserPayload(provider, payload);
|
|
359
395
|
};
|
|
@@ -14,15 +14,15 @@ export interface ResolvedContext {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* Resolve conversation context
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Resolves agentId to sessionId (if agentId is provided)
|
|
20
|
+
* Priority: agentId > sessionId
|
|
21
21
|
*
|
|
22
|
-
* @param input -
|
|
23
|
-
* @param db -
|
|
24
|
-
* @param userId -
|
|
25
|
-
* @returns
|
|
22
|
+
* @param input - Input context parameters
|
|
23
|
+
* @param db - Database instance
|
|
24
|
+
* @param userId - User ID
|
|
25
|
+
* @returns Resolved context with sessionId resolved from agentId
|
|
26
26
|
*/
|
|
27
27
|
export const resolveContext = async (
|
|
28
28
|
input: ConversationContextInput,
|
|
@@ -31,7 +31,7 @@ export const resolveContext = async (
|
|
|
31
31
|
): Promise<ResolvedContext> => {
|
|
32
32
|
let resolvedSessionId: string | null = input.sessionId ?? null;
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// If agentId is provided, prioritize looking up the corresponding sessionId from agentsToSessions table
|
|
35
35
|
if (input.agentId) {
|
|
36
36
|
const [relation] = await db
|
|
37
37
|
.select({ sessionId: agentsToSessions.sessionId })
|
|
@@ -213,7 +213,7 @@ export const agentRouter = router({
|
|
|
213
213
|
|
|
214
214
|
return [
|
|
215
215
|
...files
|
|
216
|
-
//
|
|
216
|
+
// Filter out all images
|
|
217
217
|
.filter((file) => !file.fileType.startsWith('image'))
|
|
218
218
|
.map((file) => ({
|
|
219
219
|
enabled: knowledge.files.some((item) => item.id === file.id),
|
|
@@ -13,7 +13,7 @@ import type { WorkflowContext } from '@/server/services/comfyui/types';
|
|
|
13
13
|
// Other RuntimeImageGenParams fields are passed through automatically
|
|
14
14
|
const ComfyUIParamsSchema = z
|
|
15
15
|
.object({
|
|
16
|
-
prompt: z.string(), //
|
|
16
|
+
prompt: z.string(), // Only validate required fields
|
|
17
17
|
})
|
|
18
18
|
.passthrough();
|
|
19
19
|
|
|
@@ -55,6 +55,50 @@ export const documentRouter = router({
|
|
|
55
55
|
});
|
|
56
56
|
}),
|
|
57
57
|
|
|
58
|
+
createDocuments: documentProcedure
|
|
59
|
+
.input(
|
|
60
|
+
z.object({
|
|
61
|
+
documents: z.array(
|
|
62
|
+
z.object({
|
|
63
|
+
content: z.string().optional(),
|
|
64
|
+
editorData: z.string(),
|
|
65
|
+
fileType: z.string().optional(),
|
|
66
|
+
knowledgeBaseId: z.string().optional(),
|
|
67
|
+
metadata: z.record(z.any()).optional(),
|
|
68
|
+
parentId: z.string().optional(),
|
|
69
|
+
slug: z.string().optional(),
|
|
70
|
+
title: z.string(),
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
73
|
+
}),
|
|
74
|
+
)
|
|
75
|
+
.mutation(async ({ ctx, input }) => {
|
|
76
|
+
// Process each document: resolve parentId and parse editorData
|
|
77
|
+
const processedDocuments = await Promise.all(
|
|
78
|
+
input.documents.map(async (doc) => {
|
|
79
|
+
// Resolve parentId if it's a slug
|
|
80
|
+
let resolvedParentId = doc.parentId;
|
|
81
|
+
if (doc.parentId) {
|
|
82
|
+
const docBySlug = await ctx.documentModel.findBySlug(doc.parentId);
|
|
83
|
+
if (docBySlug) {
|
|
84
|
+
resolvedParentId = docBySlug.id;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Parse editorData from JSON string to object
|
|
89
|
+
const editorData = JSON.parse(doc.editorData);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
...doc,
|
|
93
|
+
editorData,
|
|
94
|
+
parentId: resolvedParentId,
|
|
95
|
+
};
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return ctx.documentService.createDocuments(processedDocuments);
|
|
100
|
+
}),
|
|
101
|
+
|
|
58
102
|
deleteDocument: documentProcedure
|
|
59
103
|
.input(z.object({ id: z.string() }))
|
|
60
104
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -97,9 +97,9 @@ export const imageRouter = router({
|
|
|
97
97
|
|
|
98
98
|
log('Starting image creation process, input: %O', input);
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// Normalize reference image addresses, store S3 keys uniformly (avoid storing expiring presigned URLs in database)
|
|
101
101
|
let configForDatabase = { ...params };
|
|
102
|
-
// 1)
|
|
102
|
+
// 1) Process multiple images in imageUrls
|
|
103
103
|
if (Array.isArray(params.imageUrls) && params.imageUrls.length > 0) {
|
|
104
104
|
log('Converting imageUrls to S3 keys for database storage: %O', params.imageUrls);
|
|
105
105
|
try {
|
|
@@ -119,7 +119,7 @@ export const imageRouter = router({
|
|
|
119
119
|
log('Keeping original imageUrls due to conversion error');
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
// 2)
|
|
122
|
+
// 2) Process single image in imageUrl
|
|
123
123
|
if (typeof params.imageUrl === 'string' && params.imageUrl) {
|
|
124
124
|
try {
|
|
125
125
|
const key = fileService.getKeyFromFullUrl(params.imageUrl);
|
|
@@ -127,11 +127,11 @@ export const imageRouter = router({
|
|
|
127
127
|
configForDatabase = { ...configForDatabase, imageUrl: key };
|
|
128
128
|
} catch (error) {
|
|
129
129
|
log('Error converting imageUrl to key: %O', error);
|
|
130
|
-
//
|
|
130
|
+
// Keep original value if conversion fails
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
//
|
|
134
|
+
// Defensive check: ensure no full URLs enter the database
|
|
135
135
|
validateNoUrlsInConfig(configForDatabase, 'configForDatabase');
|
|
136
136
|
|
|
137
137
|
const chargeResult = await chargeBeforeGenerate({
|
|
@@ -148,11 +148,11 @@ export const imageRouter = router({
|
|
|
148
148
|
return chargeResult;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
//
|
|
151
|
+
// Step 1: Atomically create all database records in a transaction
|
|
152
152
|
const { batch: createdBatch, generationsWithTasks } = await serverDB.transaction(async (tx) => {
|
|
153
153
|
log('Starting database transaction for image generation');
|
|
154
154
|
|
|
155
|
-
// 1.
|
|
155
|
+
// 1. Create generationBatch
|
|
156
156
|
const newBatch: NewGenerationBatch = {
|
|
157
157
|
config: configForDatabase,
|
|
158
158
|
generationTopicId,
|
|
@@ -161,13 +161,13 @@ export const imageRouter = router({
|
|
|
161
161
|
prompt: params.prompt,
|
|
162
162
|
provider,
|
|
163
163
|
userId,
|
|
164
|
-
width: params.width, //
|
|
164
|
+
width: params.width, // Use converted config for database storage
|
|
165
165
|
};
|
|
166
166
|
log('Creating generation batch: %O', newBatch);
|
|
167
167
|
const [batch] = await tx.insert(generationBatches).values(newBatch).returning();
|
|
168
168
|
log('Generation batch created successfully: %s', batch.id);
|
|
169
169
|
|
|
170
|
-
// 2.
|
|
170
|
+
// 2. Create 4 generations (fixed at 4 images for phase one)
|
|
171
171
|
const seeds =
|
|
172
172
|
'seed' in params
|
|
173
173
|
? generateUniqueSeeds(imageNum)
|
|
@@ -187,11 +187,11 @@ export const imageRouter = router({
|
|
|
187
187
|
createdGenerations.map((g) => g.id),
|
|
188
188
|
);
|
|
189
189
|
|
|
190
|
-
// 3.
|
|
190
|
+
// 3. Concurrently create asyncTask for each generation (within transaction)
|
|
191
191
|
log('Creating async tasks for generations');
|
|
192
192
|
const generationsWithTasks = await Promise.all(
|
|
193
193
|
createdGenerations.map(async (generation) => {
|
|
194
|
-
//
|
|
194
|
+
// Create asyncTask directly in transaction
|
|
195
195
|
const [createdAsyncTask] = await tx
|
|
196
196
|
.insert(asyncTasks)
|
|
197
197
|
.values({
|
|
@@ -204,7 +204,7 @@ export const imageRouter = router({
|
|
|
204
204
|
const asyncTaskId = createdAsyncTask.id;
|
|
205
205
|
log('Created async task %s for generation %s', asyncTaskId, generation.id);
|
|
206
206
|
|
|
207
|
-
//
|
|
207
|
+
// Update generation's asyncTaskId
|
|
208
208
|
await tx
|
|
209
209
|
.update(generations)
|
|
210
210
|
.set({ asyncTaskId })
|
|
@@ -259,7 +259,7 @@ export const imageRouter = router({
|
|
|
259
259
|
console.error('[createImage] Failed to process async tasks:', e);
|
|
260
260
|
log('Failed to process async tasks: %O', e);
|
|
261
261
|
|
|
262
|
-
//
|
|
262
|
+
// If overall failure occurs, update all task statuses to failed
|
|
263
263
|
try {
|
|
264
264
|
await Promise.allSettled(
|
|
265
265
|
generationsWithTasks.map(({ asyncTaskId }) =>
|