@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.
Files changed (169) hide show
  1. package/.github/workflows/e2e.yml +6 -12
  2. package/.github/workflows/test.yml +3 -3
  3. package/CHANGELOG.md +59 -0
  4. package/CLAUDE.md +1 -1
  5. package/changelog/v1.json +18 -0
  6. package/docs/development/basic/feature-development.mdx +4 -5
  7. package/docs/development/basic/feature-development.zh-CN.mdx +4 -5
  8. package/e2e/README.md +6 -6
  9. package/e2e/src/features/community/detail-pages.feature +9 -9
  10. package/e2e/src/features/community/interactions.feature +13 -13
  11. package/e2e/src/features/community/smoke.feature +6 -6
  12. package/e2e/src/steps/agent/conversation-mgmt.steps.ts +196 -25
  13. package/e2e/src/steps/agent/conversation.steps.ts +58 -0
  14. package/e2e/src/steps/agent/message-ops.steps.ts +20 -15
  15. package/e2e/src/steps/community/detail-pages.steps.ts +60 -19
  16. package/e2e/src/steps/community/interactions.steps.ts +145 -32
  17. package/e2e/src/steps/hooks.ts +12 -2
  18. package/locales/ar/components.json +1 -0
  19. package/locales/ar/file.json +4 -0
  20. package/locales/ar/models.json +29 -0
  21. package/locales/ar/setting.json +7 -0
  22. package/locales/bg-BG/components.json +1 -0
  23. package/locales/bg-BG/file.json +4 -0
  24. package/locales/bg-BG/models.json +1 -0
  25. package/locales/bg-BG/setting.json +7 -0
  26. package/locales/de-DE/components.json +1 -0
  27. package/locales/de-DE/file.json +4 -0
  28. package/locales/de-DE/models.json +29 -0
  29. package/locales/de-DE/setting.json +7 -0
  30. package/locales/en-US/common.json +0 -1
  31. package/locales/en-US/components.json +1 -0
  32. package/locales/en-US/file.json +4 -0
  33. package/locales/en-US/models.json +1 -0
  34. package/locales/en-US/setting.json +3 -0
  35. package/locales/es-ES/components.json +1 -0
  36. package/locales/es-ES/file.json +4 -0
  37. package/locales/es-ES/models.json +43 -0
  38. package/locales/es-ES/setting.json +7 -0
  39. package/locales/fa-IR/components.json +1 -0
  40. package/locales/fa-IR/file.json +4 -0
  41. package/locales/fa-IR/models.json +54 -0
  42. package/locales/fa-IR/setting.json +7 -0
  43. package/locales/fr-FR/components.json +1 -0
  44. package/locales/fr-FR/file.json +4 -0
  45. package/locales/fr-FR/models.json +31 -0
  46. package/locales/fr-FR/setting.json +7 -0
  47. package/locales/it-IT/components.json +1 -0
  48. package/locales/it-IT/file.json +4 -0
  49. package/locales/it-IT/models.json +43 -0
  50. package/locales/it-IT/setting.json +7 -0
  51. package/locales/ja-JP/components.json +1 -0
  52. package/locales/ja-JP/file.json +4 -0
  53. package/locales/ja-JP/models.json +28 -0
  54. package/locales/ja-JP/setting.json +7 -0
  55. package/locales/ko-KR/components.json +1 -0
  56. package/locales/ko-KR/file.json +4 -0
  57. package/locales/ko-KR/models.json +37 -0
  58. package/locales/ko-KR/setting.json +7 -0
  59. package/locales/nl-NL/components.json +1 -0
  60. package/locales/nl-NL/file.json +4 -0
  61. package/locales/nl-NL/models.json +13 -0
  62. package/locales/nl-NL/setting.json +7 -0
  63. package/locales/pl-PL/components.json +1 -0
  64. package/locales/pl-PL/file.json +4 -0
  65. package/locales/pl-PL/models.json +13 -0
  66. package/locales/pl-PL/setting.json +7 -0
  67. package/locales/pt-BR/components.json +1 -0
  68. package/locales/pt-BR/file.json +4 -0
  69. package/locales/pt-BR/models.json +29 -0
  70. package/locales/pt-BR/setting.json +7 -0
  71. package/locales/ru-RU/components.json +1 -0
  72. package/locales/ru-RU/file.json +4 -0
  73. package/locales/ru-RU/models.json +1 -0
  74. package/locales/ru-RU/setting.json +7 -0
  75. package/locales/tr-TR/components.json +1 -0
  76. package/locales/tr-TR/file.json +4 -0
  77. package/locales/tr-TR/models.json +29 -0
  78. package/locales/tr-TR/setting.json +7 -0
  79. package/locales/vi-VN/components.json +1 -0
  80. package/locales/vi-VN/file.json +4 -0
  81. package/locales/vi-VN/models.json +1 -0
  82. package/locales/vi-VN/setting.json +7 -0
  83. package/locales/zh-CN/file.json +4 -0
  84. package/locales/zh-CN/models.json +46 -0
  85. package/locales/zh-CN/setting.json +3 -0
  86. package/locales/zh-TW/components.json +1 -0
  87. package/locales/zh-TW/file.json +4 -0
  88. package/locales/zh-TW/models.json +35 -0
  89. package/locales/zh-TW/setting.json +7 -0
  90. package/package.json +5 -5
  91. package/packages/const/src/index.ts +1 -0
  92. package/packages/const/src/lobehubSkill.ts +55 -0
  93. package/packages/types/package.json +1 -1
  94. package/packages/types/src/files/upload.ts +11 -1
  95. package/packages/types/src/message/common/tools.ts +1 -1
  96. package/packages/types/src/serverConfig.ts +1 -0
  97. package/public/not-compatible.html +1296 -0
  98. package/src/app/[variants]/(main)/resource/features/FileDetail.tsx +20 -12
  99. package/src/app/[variants]/(main)/resource/features/modal/FullscreenModal.tsx +2 -4
  100. package/src/app/[variants]/layout.tsx +50 -1
  101. package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +304 -0
  102. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +74 -10
  103. package/src/features/Conversation/Messages/AssistantGroup/Tool/Inspector/ToolTitle.tsx +9 -0
  104. package/src/features/FileViewer/Renderer/Code/index.tsx +224 -0
  105. package/src/features/FileViewer/Renderer/Image/index.tsx +8 -1
  106. package/src/features/FileViewer/Renderer/PDF/index.tsx +3 -1
  107. package/src/features/FileViewer/Renderer/PDF/style.ts +2 -1
  108. package/src/features/FileViewer/index.tsx +135 -24
  109. package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +7 -4
  110. package/src/features/PageEditor/store/initialState.ts +2 -1
  111. package/src/features/ResourceManager/components/Editor/FileContent.tsx +1 -4
  112. package/src/features/ResourceManager/components/Editor/FileCopilot.tsx +64 -0
  113. package/src/features/ResourceManager/components/Editor/index.tsx +98 -31
  114. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +3 -2
  115. package/src/features/ResourceManager/components/Explorer/ListView/ColumnResizeHandle.tsx +119 -0
  116. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +67 -22
  117. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +46 -11
  118. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +140 -81
  119. package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +20 -12
  120. package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +18 -10
  121. package/src/features/ResourceManager/components/UploadDock/Item.tsx +38 -6
  122. package/src/features/ResourceManager/components/UploadDock/index.tsx +62 -41
  123. package/src/features/ResourceManager/index.tsx +1 -0
  124. package/src/helpers/toolEngineering/index.test.ts +3 -0
  125. package/src/helpers/toolEngineering/index.ts +12 -1
  126. package/src/locales/default/file.ts +4 -0
  127. package/src/locales/default/setting.ts +3 -0
  128. package/src/server/globalConfig/index.ts +1 -0
  129. package/src/server/modules/ModelRuntime/index.test.ts +214 -1
  130. package/src/server/modules/ModelRuntime/index.ts +43 -7
  131. package/src/server/routers/lambda/_helpers/resolveContext.ts +8 -8
  132. package/src/server/routers/lambda/agent.ts +1 -1
  133. package/src/server/routers/lambda/aiModel.ts +1 -1
  134. package/src/server/routers/lambda/comfyui.ts +1 -1
  135. package/src/server/routers/lambda/document.ts +44 -0
  136. package/src/server/routers/lambda/exporter.ts +1 -1
  137. package/src/server/routers/lambda/image.ts +13 -13
  138. package/src/server/routers/lambda/klavis.ts +10 -10
  139. package/src/server/routers/lambda/market/index.ts +6 -6
  140. package/src/server/routers/lambda/message.ts +2 -2
  141. package/src/server/routers/lambda/plugin.ts +1 -1
  142. package/src/server/routers/lambda/ragEval.ts +2 -2
  143. package/src/server/routers/lambda/topic.ts +3 -3
  144. package/src/server/routers/lambda/user.ts +10 -10
  145. package/src/server/routers/lambda/userMemories.ts +6 -6
  146. package/src/server/routers/tools/market.ts +261 -0
  147. package/src/server/services/document/index.ts +22 -0
  148. package/src/services/document/index.ts +4 -0
  149. package/src/services/upload.ts +22 -2
  150. package/src/store/chat/slices/plugin/actions/internals.ts +15 -2
  151. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +104 -0
  152. package/src/store/file/slices/fileManager/action.test.ts +9 -3
  153. package/src/store/file/slices/fileManager/action.ts +165 -70
  154. package/src/store/file/slices/upload/action.ts +3 -0
  155. package/src/store/global/actions/general.ts +15 -0
  156. package/src/store/global/initialState.ts +13 -0
  157. package/src/store/serverConfig/selectors.ts +1 -0
  158. package/src/store/tool/initialState.ts +11 -2
  159. package/src/store/tool/selectors/index.ts +1 -0
  160. package/src/store/tool/selectors/tool.ts +3 -1
  161. package/src/store/tool/slices/lobehubSkillStore/action.ts +361 -0
  162. package/src/store/tool/slices/lobehubSkillStore/index.ts +4 -0
  163. package/src/store/tool/slices/lobehubSkillStore/initialState.ts +24 -0
  164. package/src/store/tool/slices/lobehubSkillStore/selectors.ts +145 -0
  165. package/src/store/tool/slices/lobehubSkillStore/types.ts +100 -0
  166. package/src/store/tool/store.ts +8 -2
  167. package/vitest.config.mts +1 -0
  168. package/src/features/FileViewer/Renderer/JavaScript/index.tsx +0 -66
  169. 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
- * @param provider - The model provider
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
- switch (provider) {
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. Build ClientSecretPayload from keyVaults
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(provider, keyVaults);
391
+ const payload = buildPayloadFromKeyVaults(keyVaults, runtimeProvider);
356
392
 
357
- // 3. Initialize ModelRuntime with the payload
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
- * agentId 解析为 sessionId(如果提供了 agentId
20
- * 优先级:agentId > sessionId
19
+ * Resolves agentId to sessionId (if agentId is provided)
20
+ * Priority: agentId > sessionId
21
21
  *
22
- * @param input - 输入的上下文参数
23
- * @param db - 数据库实例
24
- * @param userId - 用户 ID
25
- * @returns 解析后的上下文,sessionId 已从 agentId 解析
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
- // 如果提供了 agentId,优先从 agentsToSessions 表查找对应的 sessionId
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),
@@ -52,7 +52,7 @@ export const aiModelRouter = router({
52
52
  .input(
53
53
  z.object({
54
54
  id: z.string(),
55
- // TODO: 补齐校验 Schema
55
+ // TODO: Complete validation schema
56
56
  models: z.array(z.any()),
57
57
  }),
58
58
  )
@@ -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 }) => {
@@ -156,7 +156,7 @@ const generatePdfFromMarkdown = async (
156
156
  });
157
157
  }
158
158
 
159
- // 完成文档
159
+ // Finalize document
160
160
  doc.end();
161
161
  } catch (error) {
162
162
  reject(
@@ -97,9 +97,9 @@ export const imageRouter = router({
97
97
 
98
98
  log('Starting image creation process, input: %O', input);
99
99
 
100
- // 规范化参考图地址,统一存储 S3 key(避免把会过期的预签名 URL 存进数据库)
100
+ // Normalize reference image addresses, store S3 keys uniformly (avoid storing expiring presigned URLs in database)
101
101
  let configForDatabase = { ...params };
102
- // 1) 处理多图 imageUrls
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) 处理单图 imageUrl
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
- // 防御性检测:确保没有完整URL进入数据库
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
- // 步骤 1: 在事务中原子性地创建所有数据库记录
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. 创建 generationBatch
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. 创建 4 generation(一期固定生成 4 张)
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. 并发为每个 generation 创建 asyncTask(在事务中)
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
- // 在事务中直接创建 asyncTask
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
- // 更新 generation asyncTaskId
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 }) =>