@lobehub/chat 1.98.2 → 1.99.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/backend-architecture.mdc +93 -17
- package/.cursor/rules/cursor-ux.mdc +45 -35
- package/.cursor/rules/project-introduce.mdc +72 -6
- package/.cursor/rules/rules-attach.mdc +16 -7
- package/.eslintrc.js +10 -0
- package/CHANGELOG.md +44 -0
- package/apps/desktop/README.md +7 -0
- package/apps/desktop/electron-builder.js +5 -0
- package/apps/desktop/package.json +2 -1
- package/apps/desktop/src/main/appBrowsers.ts +1 -1
- package/apps/desktop/src/main/const/dir.ts +3 -0
- package/apps/desktop/src/main/controllers/UploadFileCtr.ts +13 -8
- package/apps/desktop/src/main/core/App.ts +8 -0
- package/apps/desktop/src/main/core/BrowserManager.ts +5 -2
- package/apps/desktop/src/main/core/StaticFileServerManager.ts +221 -0
- package/apps/desktop/src/main/services/fileSrv.ts +231 -44
- package/apps/desktop/src/main/utils/next-electron-rsc.ts +36 -5
- package/changelog/v1.json +14 -0
- package/docs/development/database-schema.dbml +70 -0
- package/locales/ar/common.json +2 -0
- package/locales/ar/components.json +35 -0
- package/locales/ar/error.json +2 -0
- package/locales/ar/image.json +100 -0
- package/locales/ar/metadata.json +4 -0
- package/locales/ar/modelProvider.json +1 -0
- package/locales/ar/models.json +15 -0
- package/locales/ar/plugin.json +22 -0
- package/locales/ar/providers.json +3 -0
- package/locales/ar/setting.json +5 -0
- package/locales/bg-BG/common.json +2 -0
- package/locales/bg-BG/components.json +35 -0
- package/locales/bg-BG/error.json +2 -0
- package/locales/bg-BG/image.json +100 -0
- package/locales/bg-BG/metadata.json +4 -0
- package/locales/bg-BG/modelProvider.json +1 -0
- package/locales/bg-BG/models.json +15 -0
- package/locales/bg-BG/plugin.json +22 -0
- package/locales/bg-BG/providers.json +3 -0
- package/locales/bg-BG/setting.json +5 -0
- package/locales/de-DE/common.json +2 -0
- package/locales/de-DE/components.json +35 -0
- package/locales/de-DE/error.json +2 -0
- package/locales/de-DE/image.json +100 -0
- package/locales/de-DE/metadata.json +4 -0
- package/locales/de-DE/modelProvider.json +1 -0
- package/locales/de-DE/models.json +15 -0
- package/locales/de-DE/plugin.json +22 -0
- package/locales/de-DE/providers.json +3 -0
- package/locales/de-DE/setting.json +5 -0
- package/locales/en-US/common.json +2 -0
- package/locales/en-US/components.json +35 -0
- package/locales/en-US/error.json +2 -0
- package/locales/en-US/image.json +100 -0
- package/locales/en-US/metadata.json +4 -0
- package/locales/en-US/modelProvider.json +1 -0
- package/locales/en-US/models.json +15 -0
- package/locales/en-US/plugin.json +22 -0
- package/locales/en-US/providers.json +3 -0
- package/locales/en-US/setting.json +5 -0
- package/locales/es-ES/common.json +2 -0
- package/locales/es-ES/components.json +35 -0
- package/locales/es-ES/error.json +2 -0
- package/locales/es-ES/image.json +100 -0
- package/locales/es-ES/metadata.json +4 -0
- package/locales/es-ES/modelProvider.json +1 -0
- package/locales/es-ES/models.json +15 -0
- package/locales/es-ES/plugin.json +22 -0
- package/locales/es-ES/providers.json +3 -0
- package/locales/es-ES/setting.json +5 -0
- package/locales/fa-IR/common.json +2 -0
- package/locales/fa-IR/components.json +35 -0
- package/locales/fa-IR/error.json +2 -0
- package/locales/fa-IR/image.json +100 -0
- package/locales/fa-IR/metadata.json +4 -0
- package/locales/fa-IR/modelProvider.json +1 -0
- package/locales/fa-IR/models.json +15 -0
- package/locales/fa-IR/plugin.json +22 -0
- package/locales/fa-IR/providers.json +3 -0
- package/locales/fa-IR/setting.json +5 -0
- package/locales/fr-FR/common.json +2 -0
- package/locales/fr-FR/components.json +35 -0
- package/locales/fr-FR/error.json +2 -0
- package/locales/fr-FR/image.json +100 -0
- package/locales/fr-FR/metadata.json +4 -0
- package/locales/fr-FR/modelProvider.json +1 -0
- package/locales/fr-FR/models.json +15 -0
- package/locales/fr-FR/plugin.json +22 -0
- package/locales/fr-FR/providers.json +3 -0
- package/locales/fr-FR/setting.json +5 -0
- package/locales/it-IT/common.json +2 -0
- package/locales/it-IT/components.json +35 -0
- package/locales/it-IT/error.json +2 -0
- package/locales/it-IT/image.json +100 -0
- package/locales/it-IT/metadata.json +4 -0
- package/locales/it-IT/modelProvider.json +1 -0
- package/locales/it-IT/models.json +15 -0
- package/locales/it-IT/plugin.json +22 -0
- package/locales/it-IT/providers.json +3 -0
- package/locales/it-IT/setting.json +5 -0
- package/locales/ja-JP/common.json +2 -0
- package/locales/ja-JP/components.json +35 -0
- package/locales/ja-JP/error.json +2 -0
- package/locales/ja-JP/image.json +100 -0
- package/locales/ja-JP/metadata.json +4 -0
- package/locales/ja-JP/modelProvider.json +1 -0
- package/locales/ja-JP/models.json +15 -0
- package/locales/ja-JP/plugin.json +22 -0
- package/locales/ja-JP/providers.json +3 -0
- package/locales/ja-JP/setting.json +5 -0
- package/locales/ko-KR/common.json +2 -0
- package/locales/ko-KR/components.json +35 -0
- package/locales/ko-KR/error.json +2 -0
- package/locales/ko-KR/image.json +100 -0
- package/locales/ko-KR/metadata.json +4 -0
- package/locales/ko-KR/modelProvider.json +1 -0
- package/locales/ko-KR/models.json +15 -0
- package/locales/ko-KR/plugin.json +22 -0
- package/locales/ko-KR/providers.json +3 -0
- package/locales/ko-KR/setting.json +5 -0
- package/locales/nl-NL/common.json +2 -0
- package/locales/nl-NL/components.json +35 -0
- package/locales/nl-NL/error.json +2 -0
- package/locales/nl-NL/image.json +100 -0
- package/locales/nl-NL/metadata.json +4 -0
- package/locales/nl-NL/modelProvider.json +1 -0
- package/locales/nl-NL/models.json +15 -0
- package/locales/nl-NL/plugin.json +22 -0
- package/locales/nl-NL/providers.json +3 -0
- package/locales/nl-NL/setting.json +5 -0
- package/locales/pl-PL/common.json +2 -0
- package/locales/pl-PL/components.json +35 -0
- package/locales/pl-PL/error.json +2 -0
- package/locales/pl-PL/image.json +100 -0
- package/locales/pl-PL/metadata.json +4 -0
- package/locales/pl-PL/modelProvider.json +1 -0
- package/locales/pl-PL/models.json +15 -0
- package/locales/pl-PL/plugin.json +22 -0
- package/locales/pl-PL/providers.json +3 -0
- package/locales/pl-PL/setting.json +5 -0
- package/locales/pt-BR/common.json +2 -0
- package/locales/pt-BR/components.json +35 -0
- package/locales/pt-BR/error.json +2 -0
- package/locales/pt-BR/image.json +100 -0
- package/locales/pt-BR/metadata.json +4 -0
- package/locales/pt-BR/modelProvider.json +1 -0
- package/locales/pt-BR/models.json +15 -0
- package/locales/pt-BR/plugin.json +22 -0
- package/locales/pt-BR/providers.json +3 -0
- package/locales/pt-BR/setting.json +5 -0
- package/locales/ru-RU/common.json +2 -0
- package/locales/ru-RU/components.json +35 -0
- package/locales/ru-RU/error.json +2 -0
- package/locales/ru-RU/image.json +100 -0
- package/locales/ru-RU/metadata.json +4 -0
- package/locales/ru-RU/modelProvider.json +1 -0
- package/locales/ru-RU/models.json +15 -0
- package/locales/ru-RU/plugin.json +22 -0
- package/locales/ru-RU/providers.json +3 -0
- package/locales/ru-RU/setting.json +5 -0
- package/locales/tr-TR/common.json +2 -0
- package/locales/tr-TR/components.json +35 -0
- package/locales/tr-TR/error.json +2 -0
- package/locales/tr-TR/image.json +100 -0
- package/locales/tr-TR/metadata.json +4 -0
- package/locales/tr-TR/modelProvider.json +1 -0
- package/locales/tr-TR/models.json +15 -0
- package/locales/tr-TR/plugin.json +22 -0
- package/locales/tr-TR/providers.json +3 -0
- package/locales/tr-TR/setting.json +5 -0
- package/locales/vi-VN/common.json +2 -0
- package/locales/vi-VN/components.json +35 -0
- package/locales/vi-VN/error.json +2 -0
- package/locales/vi-VN/image.json +100 -0
- package/locales/vi-VN/metadata.json +4 -0
- package/locales/vi-VN/modelProvider.json +1 -0
- package/locales/vi-VN/models.json +15 -0
- package/locales/vi-VN/plugin.json +22 -0
- package/locales/vi-VN/providers.json +3 -0
- package/locales/vi-VN/setting.json +5 -0
- package/locales/zh-CN/common.json +2 -0
- package/locales/zh-CN/components.json +35 -0
- package/locales/zh-CN/error.json +2 -0
- package/locales/zh-CN/image.json +100 -0
- package/locales/zh-CN/metadata.json +4 -0
- package/locales/zh-CN/modelProvider.json +1 -0
- package/locales/zh-CN/models.json +15 -0
- package/locales/zh-CN/plugin.json +22 -0
- package/locales/zh-CN/providers.json +3 -0
- package/locales/zh-CN/setting.json +5 -0
- package/locales/zh-TW/common.json +2 -0
- package/locales/zh-TW/components.json +35 -0
- package/locales/zh-TW/error.json +2 -0
- package/locales/zh-TW/image.json +100 -0
- package/locales/zh-TW/metadata.json +4 -0
- package/locales/zh-TW/modelProvider.json +1 -0
- package/locales/zh-TW/models.json +15 -0
- package/locales/zh-TW/plugin.json +22 -0
- package/locales/zh-TW/providers.json +3 -0
- package/locales/zh-TW/setting.json +5 -0
- package/package.json +11 -4
- package/packages/electron-server-ipc/src/events/file.ts +3 -1
- package/packages/electron-server-ipc/src/types/file.ts +15 -0
- package/src/app/[variants]/(main)/_layout/Desktop/SideBar/TopActions.tsx +11 -1
- package/src/app/[variants]/(main)/image/@menu/components/AspectRatioSelect/index.tsx +73 -0
- package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +39 -0
- package/src/app/[variants]/(main)/image/@menu/components/SizeSelect/index.tsx +89 -0
- package/src/app/[variants]/(main)/image/@menu/default.tsx +11 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/AspectRatioSelect.tsx +24 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/DimensionControlGroup.tsx +107 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageNum.tsx +290 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUpload.tsx +504 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrl.tsx +18 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrlsUpload.tsx +19 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ModelSelect.tsx +155 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/ImageManageModal.tsx +415 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/index.tsx +732 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SeedNumberInput.tsx +24 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSelect.tsx +17 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSliderInput.tsx +15 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/StepsSliderInput.tsx +11 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/constants.ts +1 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +93 -0
- package/src/app/[variants]/(main)/image/@topic/default.tsx +17 -0
- package/src/app/[variants]/(main)/image/@topic/features/Topics/NewTopicButton.tsx +64 -0
- package/src/app/[variants]/(main)/image/@topic/features/Topics/SkeletonList.tsx +34 -0
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItem.tsx +136 -0
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItemContainer.tsx +91 -0
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicList.tsx +57 -0
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicUrlSync.tsx +37 -0
- package/src/app/[variants]/(main)/image/@topic/features/Topics/index.tsx +19 -0
- package/src/app/[variants]/(main)/image/NotSupportClient.tsx +153 -0
- package/src/app/[variants]/(main)/image/_layout/Desktop/Container.tsx +35 -0
- package/src/app/[variants]/(main)/image/_layout/Desktop/RegisterHotkeys.tsx +10 -0
- package/src/app/[variants]/(main)/image/_layout/Desktop/index.tsx +30 -0
- package/src/app/[variants]/(main)/image/_layout/Mobile/index.tsx +14 -0
- package/src/app/[variants]/(main)/image/_layout/type.ts +7 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/BatchItem.tsx +196 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/ActionButtons.tsx +60 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/ElapsedTime.tsx +90 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/ErrorState.tsx +65 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/LoadingState.tsx +44 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/SuccessState.tsx +49 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/index.tsx +154 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/styles.ts +51 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/types.ts +39 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/utils.ts +11 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/index.tsx +97 -0
- package/src/app/[variants]/(main)/image/features/ImageWorkspace/Content.tsx +48 -0
- package/src/app/[variants]/(main)/image/features/ImageWorkspace/EmptyState.tsx +37 -0
- package/src/app/[variants]/(main)/image/features/ImageWorkspace/SkeletonList.tsx +50 -0
- package/src/app/[variants]/(main)/image/features/ImageWorkspace/index.tsx +23 -0
- package/src/app/[variants]/(main)/image/features/PromptInput/Title.tsx +38 -0
- package/src/app/[variants]/(main)/image/features/PromptInput/index.tsx +114 -0
- package/src/app/[variants]/(main)/image/layout.tsx +19 -0
- package/src/app/[variants]/(main)/image/loading.tsx +3 -0
- package/src/app/[variants]/(main)/image/page.tsx +47 -0
- package/src/app/[variants]/(main)/settings/system-agent/index.tsx +2 -1
- package/src/chains/summaryGenerationTitle.ts +25 -0
- package/src/components/ImageItem/index.tsx +9 -6
- package/src/{features/Conversation/Error → components/InvalidAPIKey}/APIKeyForm/Bedrock.tsx +3 -4
- package/src/{features/Conversation/Error → components/InvalidAPIKey}/APIKeyForm/ProviderApiKeyForm.tsx +5 -4
- package/src/components/InvalidAPIKey/APIKeyForm/index.tsx +108 -0
- package/src/{features/Conversation/Error → components/InvalidAPIKey}/APIKeyForm/useApiKey.ts +2 -1
- package/src/components/InvalidAPIKey/index.tsx +30 -0
- package/src/components/KeyValueEditor/index.tsx +203 -0
- package/src/components/KeyValueEditor/utils.ts +42 -0
- package/src/config/aiModels/fal.ts +52 -0
- package/src/config/aiModels/index.ts +3 -0
- package/src/config/aiModels/openai.ts +20 -6
- package/src/config/llm.ts +6 -0
- package/src/config/modelProviders/fal.ts +21 -0
- package/src/config/modelProviders/index.ts +3 -0
- package/src/config/paramsSchemas/fal/flux-kontext-dev.ts +8 -0
- package/src/config/paramsSchemas/fal/flux-pro-kontext.ts +11 -0
- package/src/config/paramsSchemas/fal/flux-schnell.ts +9 -0
- package/src/config/paramsSchemas/fal/imagen4.ts +10 -0
- package/src/config/paramsSchemas/openai/gpt-image-1.ts +10 -0
- package/src/const/hotkeys.ts +2 -2
- package/src/const/image.ts +6 -0
- package/src/const/settings/systemAgent.ts +1 -0
- package/src/database/client/migrations.json +27 -0
- package/src/database/migrations/0026_add_autovacuum_tuning.sql +2 -0
- package/src/database/migrations/0027_ai_image.sql +47 -0
- package/src/database/migrations/meta/0027_snapshot.json +6003 -0
- package/src/database/migrations/meta/_journal.json +7 -0
- package/src/database/models/__tests__/asyncTask.test.ts +7 -5
- package/src/database/models/__tests__/file.test.ts +287 -0
- package/src/database/models/__tests__/generation.test.ts +786 -0
- package/src/database/models/__tests__/generationBatch.test.ts +614 -0
- package/src/database/models/__tests__/generationTopic.test.ts +411 -0
- package/src/database/models/aiModel.ts +2 -0
- package/src/database/models/asyncTask.ts +1 -1
- package/src/database/models/file.ts +28 -20
- package/src/database/models/generation.ts +197 -0
- package/src/database/models/generationBatch.ts +212 -0
- package/src/database/models/generationTopic.ts +131 -0
- package/src/database/repositories/aiInfra/index.test.ts +157 -1
- package/src/database/repositories/aiInfra/index.ts +37 -19
- package/src/database/repositories/tableViewer/index.test.ts +1 -1
- package/src/database/schemas/file.ts +8 -0
- package/src/database/schemas/generation.ts +127 -0
- package/src/database/schemas/index.ts +1 -0
- package/src/database/schemas/relations.ts +45 -1
- package/src/database/type.ts +2 -0
- package/src/database/utils/idGenerator.ts +3 -0
- package/src/features/Conversation/Error/ChatInvalidApiKey.tsx +39 -0
- package/src/features/Conversation/Error/InvalidAccessCode.tsx +2 -2
- package/src/features/Conversation/Error/index.tsx +3 -3
- package/src/features/ImageSidePanel/index.tsx +83 -0
- package/src/features/ImageTopicPanel/index.tsx +79 -0
- package/src/features/PluginDevModal/MCPManifestForm/CollapsibleSection.tsx +62 -0
- package/src/features/PluginDevModal/MCPManifestForm/QuickImportSection.tsx +158 -0
- package/src/features/PluginDevModal/MCPManifestForm/index.tsx +99 -155
- package/src/features/PluginStore/McpList/Detail/Settings/index.tsx +5 -2
- package/src/hooks/useDownloadImage.ts +31 -0
- package/src/hooks/useFetchGenerationTopics.ts +13 -0
- package/src/hooks/useHotkeys/imageScope.ts +48 -0
- package/src/libs/mcp/client.ts +55 -22
- package/src/libs/mcp/types.ts +42 -6
- package/src/libs/model-runtime/BaseAI.ts +3 -1
- package/src/libs/model-runtime/ModelRuntime.test.ts +80 -0
- package/src/libs/model-runtime/ModelRuntime.ts +15 -1
- package/src/libs/model-runtime/UniformRuntime/index.ts +4 -1
- package/src/libs/model-runtime/fal/index.test.ts +442 -0
- package/src/libs/model-runtime/fal/index.ts +88 -0
- package/src/libs/model-runtime/openai/index.test.ts +396 -2
- package/src/libs/model-runtime/openai/index.ts +129 -3
- package/src/libs/model-runtime/runtimeMap.ts +2 -0
- package/src/libs/model-runtime/types/image.ts +25 -0
- package/src/libs/model-runtime/types/type.ts +1 -0
- package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +10 -0
- package/src/libs/standard-parameters/index.ts +1 -0
- package/src/libs/standard-parameters/meta-schema.test.ts +214 -0
- package/src/libs/standard-parameters/meta-schema.ts +147 -0
- package/src/libs/swr/index.ts +1 -0
- package/src/libs/trpc/async/asyncAuth.ts +29 -8
- package/src/libs/trpc/async/context.ts +42 -4
- package/src/libs/trpc/async/index.ts +17 -4
- package/src/libs/trpc/async/init.ts +8 -0
- package/src/libs/trpc/client/lambda.ts +19 -2
- package/src/locales/default/common.ts +2 -0
- package/src/locales/default/components.ts +35 -0
- package/src/locales/default/error.ts +2 -0
- package/src/locales/default/image.ts +100 -0
- package/src/locales/default/index.ts +2 -0
- package/src/locales/default/metadata.ts +4 -0
- package/src/locales/default/modelProvider.ts +2 -0
- package/src/locales/default/plugin.ts +22 -0
- package/src/locales/default/setting.ts +5 -0
- package/src/middleware.ts +1 -0
- package/src/server/modules/ElectronIPCClient/index.ts +9 -1
- package/src/server/modules/S3/index.ts +15 -0
- package/src/server/routers/async/caller.ts +9 -1
- package/src/server/routers/async/image.ts +253 -0
- package/src/server/routers/async/index.ts +2 -0
- package/src/server/routers/lambda/aiProvider.test.ts +2 -0
- package/src/server/routers/lambda/generation.test.ts +267 -0
- package/src/server/routers/lambda/generation.ts +86 -0
- package/src/server/routers/lambda/generationBatch.test.ts +376 -0
- package/src/server/routers/lambda/generationBatch.ts +56 -0
- package/src/server/routers/lambda/generationTopic.test.ts +508 -0
- package/src/server/routers/lambda/generationTopic.ts +93 -0
- package/src/server/routers/lambda/image.ts +248 -0
- package/src/server/routers/lambda/index.ts +8 -0
- package/src/server/routers/tools/mcp.ts +15 -0
- package/src/server/services/file/__tests__/index.test.ts +135 -0
- package/src/server/services/file/impls/local.test.ts +153 -52
- package/src/server/services/file/impls/local.ts +70 -46
- package/src/server/services/file/impls/s3.test.ts +114 -0
- package/src/server/services/file/impls/s3.ts +40 -0
- package/src/server/services/file/impls/type.ts +10 -0
- package/src/server/services/file/index.ts +14 -0
- package/src/server/services/generation/index.ts +239 -0
- package/src/server/services/mcp/index.ts +20 -2
- package/src/services/__tests__/generation.test.ts +40 -0
- package/src/services/__tests__/generationBatch.test.ts +36 -0
- package/src/services/__tests__/generationTopic.test.ts +72 -0
- package/src/services/electron/file.ts +3 -1
- package/src/services/generation.ts +16 -0
- package/src/services/generationBatch.ts +25 -0
- package/src/services/generationTopic.ts +28 -0
- package/src/services/image.ts +33 -0
- package/src/services/mcp.ts +12 -7
- package/src/services/upload.ts +43 -9
- package/src/store/aiInfra/slices/aiProvider/action.ts +31 -6
- package/src/store/aiInfra/slices/aiProvider/initialState.ts +1 -0
- package/src/store/aiInfra/slices/aiProvider/selectors.ts +3 -0
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +5 -5
- package/src/store/chat/slices/message/action.ts +2 -2
- package/src/store/chat/slices/translate/action.ts +1 -1
- package/src/store/global/initialState.ts +9 -0
- package/src/store/global/selectors/systemStatus.ts +8 -0
- package/src/store/image/index.ts +2 -0
- package/src/store/image/initialState.ts +25 -0
- package/src/store/image/selectors.ts +4 -0
- package/src/store/image/slices/createImage/action.test.ts +330 -0
- package/src/store/image/slices/createImage/action.ts +134 -0
- package/src/store/image/slices/createImage/initialState.ts +9 -0
- package/src/store/image/slices/createImage/selectors.test.ts +114 -0
- package/src/store/image/slices/createImage/selectors.ts +9 -0
- package/src/store/image/slices/generationBatch/action.test.ts +495 -0
- package/src/store/image/slices/generationBatch/action.ts +303 -0
- package/src/store/image/slices/generationBatch/initialState.ts +13 -0
- package/src/store/image/slices/generationBatch/reducer.test.ts +568 -0
- package/src/store/image/slices/generationBatch/reducer.ts +101 -0
- package/src/store/image/slices/generationBatch/selectors.test.ts +307 -0
- package/src/store/image/slices/generationBatch/selectors.ts +36 -0
- package/src/store/image/slices/generationConfig/action.test.ts +351 -0
- package/src/store/image/slices/generationConfig/action.ts +295 -0
- package/src/store/image/slices/generationConfig/hooks.test.ts +304 -0
- package/src/store/image/slices/generationConfig/hooks.ts +118 -0
- package/src/store/image/slices/generationConfig/index.ts +1 -0
- package/src/store/image/slices/generationConfig/initialState.ts +37 -0
- package/src/store/image/slices/generationConfig/selectors.test.ts +204 -0
- package/src/store/image/slices/generationConfig/selectors.ts +25 -0
- package/src/store/image/slices/generationTopic/action.test.ts +687 -0
- package/src/store/image/slices/generationTopic/action.ts +319 -0
- package/src/store/image/slices/generationTopic/index.ts +2 -0
- package/src/store/image/slices/generationTopic/initialState.ts +14 -0
- package/src/store/image/slices/generationTopic/reducer.test.ts +198 -0
- package/src/store/image/slices/generationTopic/reducer.ts +66 -0
- package/src/store/image/slices/generationTopic/selectors.test.ts +103 -0
- package/src/store/image/slices/generationTopic/selectors.ts +15 -0
- package/src/store/image/store.ts +42 -0
- package/src/store/image/utils/size.ts +51 -0
- package/src/store/tool/slices/customPlugin/action.ts +10 -1
- package/src/store/tool/slices/mcpStore/action.ts +6 -4
- package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +4 -0
- package/src/store/user/slices/settings/selectors/systemAgent.ts +2 -0
- package/src/types/aiModel.ts +8 -3
- package/src/types/aiProvider.ts +2 -0
- package/src/types/asyncTask.ts +2 -0
- package/src/types/files/index.ts +5 -0
- package/src/types/generation/index.ts +80 -0
- package/src/types/hotkey.ts +2 -0
- package/src/types/plugins/mcp.ts +2 -6
- package/src/types/tool/plugin.ts +8 -0
- package/src/types/user/settings/keyVaults.ts +5 -0
- package/src/types/user/settings/systemAgent.ts +1 -0
- package/src/utils/client/downloadFile.ts +33 -4
- package/src/utils/number.test.ts +105 -0
- package/src/utils/number.ts +25 -0
- package/src/utils/server/__tests__/geo.test.ts +6 -3
- package/src/utils/storeDebug.test.ts +152 -0
- package/src/utils/storeDebug.ts +16 -7
- package/src/utils/time.test.ts +259 -0
- package/src/utils/time.ts +18 -0
- package/src/utils/units.ts +61 -0
- package/src/utils/url.test.ts +358 -9
- package/src/utils/url.ts +105 -3
- package/{vitest.server.config.ts → vitest.config.server.ts} +3 -0
- package/.cursor/rules/i18n/i18n-auto-attached.mdc +0 -6
- package/src/features/Conversation/Error/APIKeyForm/index.tsx +0 -105
- package/src/features/Conversation/Error/InvalidAPIKey.tsx +0 -16
- package/src/features/PluginDevModal/MCPManifestForm/EnvEditor.tsx +0 -227
- /package/.cursor/rules/{i18n/i18n.mdc → i18n.mdc} +0 -0
- /package/src/app/[variants]/(main)/settings/system-agent/features/{createForm.tsx → SystemAgentForm.tsx} +0 -0
- /package/src/{features/Conversation/Error → components/InvalidAPIKey}/APIKeyForm/LoadingContext.ts +0 -0
@@ -0,0 +1,411 @@
|
|
1
|
+
// @vitest-environment node
|
2
|
+
import { eq } from 'drizzle-orm/expressions';
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
4
|
+
|
5
|
+
import { LobeChatDatabase } from '@/database/type';
|
6
|
+
import { FileService } from '@/server/services/file';
|
7
|
+
import { ImageGenerationTopic } from '@/types/generation';
|
8
|
+
|
9
|
+
import { generationBatches, generationTopics, generations, users } from '../../schemas';
|
10
|
+
import { GenerationTopicItem } from '../../schemas/generation';
|
11
|
+
import { GenerationTopicModel } from '../generationTopic';
|
12
|
+
import { getTestDB } from './_util';
|
13
|
+
|
14
|
+
// Mock FileService
|
15
|
+
const mockGetFullFileUrl = vi.fn();
|
16
|
+
vi.mock('@/server/services/file', () => ({
|
17
|
+
FileService: vi.fn().mockImplementation(() => ({
|
18
|
+
getFullFileUrl: mockGetFullFileUrl,
|
19
|
+
})),
|
20
|
+
}));
|
21
|
+
|
22
|
+
const serverDB: LobeChatDatabase = await getTestDB();
|
23
|
+
|
24
|
+
const userId = 'generation-topic-test-user';
|
25
|
+
const otherUserId = 'other-user';
|
26
|
+
const generationTopicModel = new GenerationTopicModel(serverDB, userId);
|
27
|
+
|
28
|
+
beforeEach(async () => {
|
29
|
+
await serverDB.delete(users);
|
30
|
+
await serverDB.insert(users).values([{ id: userId }, { id: otherUserId }]);
|
31
|
+
|
32
|
+
// Reset mocks before each test
|
33
|
+
vi.clearAllMocks();
|
34
|
+
mockGetFullFileUrl.mockImplementation((url: string) => `https://example.com/${url}`);
|
35
|
+
});
|
36
|
+
|
37
|
+
afterEach(async () => {
|
38
|
+
await serverDB.delete(users);
|
39
|
+
});
|
40
|
+
|
41
|
+
describe('GenerationTopicModel', () => {
|
42
|
+
describe('create', () => {
|
43
|
+
it('should create a new generation topic', async () => {
|
44
|
+
const title = 'Test Generation Topic';
|
45
|
+
|
46
|
+
const result = await generationTopicModel.create(title);
|
47
|
+
|
48
|
+
expect(result.id).toBeDefined();
|
49
|
+
expect(result.title).toBe(title);
|
50
|
+
expect(result.userId).toBe(userId);
|
51
|
+
expect(result.coverUrl).toBeNull();
|
52
|
+
expect(result.createdAt).toBeInstanceOf(Date);
|
53
|
+
expect(result.updatedAt).toBeInstanceOf(Date);
|
54
|
+
|
55
|
+
// Verify it's saved in database
|
56
|
+
const topic = await serverDB.query.generationTopics.findFirst({
|
57
|
+
where: eq(generationTopics.id, result.id),
|
58
|
+
});
|
59
|
+
expect(topic).toMatchObject({ title, userId });
|
60
|
+
});
|
61
|
+
|
62
|
+
it('should create a topic with empty title', async () => {
|
63
|
+
const result = await generationTopicModel.create('');
|
64
|
+
|
65
|
+
expect(result.id).toBeDefined();
|
66
|
+
expect(result.title).toBe('');
|
67
|
+
expect(result.userId).toBe(userId);
|
68
|
+
});
|
69
|
+
});
|
70
|
+
|
71
|
+
describe('queryAll', () => {
|
72
|
+
it('should return all topics for the user ordered by updatedAt desc', async () => {
|
73
|
+
// Create test data with different timestamps
|
74
|
+
const now = new Date();
|
75
|
+
const earlier = new Date(now.getTime() - 60000); // 1 minute earlier
|
76
|
+
const earliest = new Date(now.getTime() - 120000); // 2 minutes earlier
|
77
|
+
|
78
|
+
await serverDB.insert(generationTopics).values([
|
79
|
+
{
|
80
|
+
id: 'topic1',
|
81
|
+
userId,
|
82
|
+
title: 'Topic 1',
|
83
|
+
updatedAt: earliest,
|
84
|
+
},
|
85
|
+
{
|
86
|
+
id: 'topic2',
|
87
|
+
userId,
|
88
|
+
title: 'Topic 2',
|
89
|
+
updatedAt: now,
|
90
|
+
},
|
91
|
+
{
|
92
|
+
id: 'topic3',
|
93
|
+
userId,
|
94
|
+
title: 'Topic 3',
|
95
|
+
updatedAt: earlier,
|
96
|
+
},
|
97
|
+
{
|
98
|
+
id: 'topic4',
|
99
|
+
userId: otherUserId,
|
100
|
+
title: 'Other User Topic',
|
101
|
+
updatedAt: now,
|
102
|
+
},
|
103
|
+
]);
|
104
|
+
|
105
|
+
const result = await generationTopicModel.queryAll();
|
106
|
+
|
107
|
+
// Should return only topics for current user, ordered by updatedAt desc
|
108
|
+
expect(result).toHaveLength(3);
|
109
|
+
expect(result[0].id).toBe('topic2'); // most recent
|
110
|
+
expect(result[1].id).toBe('topic3'); // middle
|
111
|
+
expect(result[2].id).toBe('topic1'); // oldest
|
112
|
+
});
|
113
|
+
|
114
|
+
it('should process cover URLs through FileService', async () => {
|
115
|
+
await serverDB.insert(generationTopics).values([
|
116
|
+
{
|
117
|
+
id: 'topic1',
|
118
|
+
userId,
|
119
|
+
title: 'Topic with cover',
|
120
|
+
coverUrl: 'cover-image-key',
|
121
|
+
},
|
122
|
+
{
|
123
|
+
id: 'topic2',
|
124
|
+
userId,
|
125
|
+
title: 'Topic without cover',
|
126
|
+
coverUrl: null,
|
127
|
+
},
|
128
|
+
]);
|
129
|
+
|
130
|
+
const result = await generationTopicModel.queryAll();
|
131
|
+
|
132
|
+
expect(result).toHaveLength(2);
|
133
|
+
expect(result[0].coverUrl).toBe('https://example.com/cover-image-key');
|
134
|
+
expect(result[1].coverUrl).toBeNull();
|
135
|
+
|
136
|
+
// Verify FileService was called for the topic with coverUrl
|
137
|
+
expect(mockGetFullFileUrl).toHaveBeenCalledWith('cover-image-key');
|
138
|
+
expect(mockGetFullFileUrl).toHaveBeenCalledTimes(1);
|
139
|
+
});
|
140
|
+
|
141
|
+
it('should return empty array if no topics exist', async () => {
|
142
|
+
const result = await generationTopicModel.queryAll();
|
143
|
+
expect(result).toHaveLength(0);
|
144
|
+
});
|
145
|
+
});
|
146
|
+
|
147
|
+
describe('update', () => {
|
148
|
+
it('should update a generation topic', async () => {
|
149
|
+
// Create a test topic
|
150
|
+
const { id } = await generationTopicModel.create('Original Title');
|
151
|
+
|
152
|
+
const updateData: Partial<ImageGenerationTopic> = {
|
153
|
+
title: 'Updated Title',
|
154
|
+
coverUrl: 'new-cover-key',
|
155
|
+
};
|
156
|
+
|
157
|
+
const result = await generationTopicModel.update(id, updateData);
|
158
|
+
|
159
|
+
expect(result).toBeDefined();
|
160
|
+
expect(result!.id).toBe(id);
|
161
|
+
expect(result!.title).toBe('Updated Title');
|
162
|
+
expect(result!.coverUrl).toBe('new-cover-key');
|
163
|
+
expect(result!.updatedAt).toBeInstanceOf(Date);
|
164
|
+
|
165
|
+
// Verify in database
|
166
|
+
const updatedTopic = await serverDB.query.generationTopics.findFirst({
|
167
|
+
where: eq(generationTopics.id, id),
|
168
|
+
});
|
169
|
+
expect(updatedTopic).toMatchObject(updateData);
|
170
|
+
});
|
171
|
+
|
172
|
+
it('should not update topics of other users', async () => {
|
173
|
+
// Create a topic for another user
|
174
|
+
const [otherUserTopic] = await serverDB
|
175
|
+
.insert(generationTopics)
|
176
|
+
.values({ id: 'other-topic', userId: otherUserId, title: 'Other User Topic' })
|
177
|
+
.returning();
|
178
|
+
|
179
|
+
const updateData: Partial<ImageGenerationTopic> = {
|
180
|
+
title: 'Hacked Title',
|
181
|
+
};
|
182
|
+
|
183
|
+
// Attempt to update should not affect other user's topic
|
184
|
+
const result = await generationTopicModel.update(otherUserTopic.id, updateData);
|
185
|
+
|
186
|
+
// Should return undefined or empty result because of user permission check
|
187
|
+
expect(result).toBeUndefined();
|
188
|
+
|
189
|
+
// Verify the topic remains unchanged in the database
|
190
|
+
const unchangedTopic = await serverDB.query.generationTopics.findFirst({
|
191
|
+
where: eq(generationTopics.id, otherUserTopic.id),
|
192
|
+
});
|
193
|
+
expect(unchangedTopic?.title).toBe('Other User Topic');
|
194
|
+
});
|
195
|
+
|
196
|
+
it('should update only specified fields', async () => {
|
197
|
+
const { id } = await generationTopicModel.create('Original Title');
|
198
|
+
|
199
|
+
// Update only title
|
200
|
+
const result = await generationTopicModel.update(id, { title: 'Only Title Updated' });
|
201
|
+
|
202
|
+
expect(result).toBeDefined();
|
203
|
+
expect(result!.title).toBe('Only Title Updated');
|
204
|
+
expect(result!.coverUrl).toBeNull(); // Should remain unchanged
|
205
|
+
});
|
206
|
+
});
|
207
|
+
|
208
|
+
describe('delete', () => {
|
209
|
+
it('should delete a generation topic', async () => {
|
210
|
+
const { id } = await generationTopicModel.create('Topic to Delete');
|
211
|
+
|
212
|
+
const result = await generationTopicModel.delete(id);
|
213
|
+
|
214
|
+
expect(result).toBeDefined();
|
215
|
+
const deleteResult = result!;
|
216
|
+
expect(deleteResult.deletedTopic.id).toBe(id);
|
217
|
+
expect(deleteResult.deletedTopic.title).toBe('Topic to Delete');
|
218
|
+
expect(deleteResult.filesToDelete).toEqual([]);
|
219
|
+
|
220
|
+
// Verify it's deleted from database
|
221
|
+
const deletedTopic = await serverDB.query.generationTopics.findFirst({
|
222
|
+
where: eq(generationTopics.id, id),
|
223
|
+
});
|
224
|
+
expect(deletedTopic).toBeUndefined();
|
225
|
+
});
|
226
|
+
|
227
|
+
it('should not delete topics of other users', async () => {
|
228
|
+
// Create a topic for another user
|
229
|
+
const [otherUserTopic] = await serverDB
|
230
|
+
.insert(generationTopics)
|
231
|
+
.values({ id: 'other-topic', userId: otherUserId, title: 'Other User Topic' })
|
232
|
+
.returning();
|
233
|
+
|
234
|
+
// Attempt to delete should not affect other user's topic
|
235
|
+
const result = await generationTopicModel.delete(otherUserTopic.id);
|
236
|
+
|
237
|
+
// Should return undefined because of user permission check
|
238
|
+
expect(result).toBeUndefined();
|
239
|
+
|
240
|
+
// The topic should still exist in the database
|
241
|
+
const stillExistsTopic = await serverDB.query.generationTopics.findFirst({
|
242
|
+
where: eq(generationTopics.id, otherUserTopic.id),
|
243
|
+
});
|
244
|
+
expect(stillExistsTopic).toBeDefined();
|
245
|
+
expect(stillExistsTopic?.title).toBe('Other User Topic');
|
246
|
+
});
|
247
|
+
|
248
|
+
it('should return deleted topic data', async () => {
|
249
|
+
const { id } = await generationTopicModel.create('Topic with Data');
|
250
|
+
|
251
|
+
// Add some data to the topic
|
252
|
+
await generationTopicModel.update(id, {
|
253
|
+
title: 'Updated Topic',
|
254
|
+
coverUrl: 'cover-key',
|
255
|
+
});
|
256
|
+
|
257
|
+
const result = await generationTopicModel.delete(id);
|
258
|
+
|
259
|
+
expect(result).toBeDefined();
|
260
|
+
const deleteResult = result!;
|
261
|
+
expect(deleteResult.deletedTopic).toMatchObject({
|
262
|
+
id,
|
263
|
+
title: 'Updated Topic',
|
264
|
+
coverUrl: 'cover-key',
|
265
|
+
userId,
|
266
|
+
});
|
267
|
+
expect(deleteResult.filesToDelete).toContain('cover-key');
|
268
|
+
});
|
269
|
+
|
270
|
+
it('should return undefined when trying to delete non-existent topic', async () => {
|
271
|
+
const nonExistentId = 'non-existent-topic-id';
|
272
|
+
|
273
|
+
const result = await generationTopicModel.delete(nonExistentId);
|
274
|
+
|
275
|
+
// Should return undefined because topic doesn't exist
|
276
|
+
expect(result).toBeUndefined();
|
277
|
+
});
|
278
|
+
|
279
|
+
it('should return undefined when trying to delete topic with invalid format id', async () => {
|
280
|
+
const invalidId = 'invalid-format-id';
|
281
|
+
|
282
|
+
const result = await generationTopicModel.delete(invalidId);
|
283
|
+
|
284
|
+
// Should return undefined because topic doesn't exist with this invalid ID
|
285
|
+
expect(result).toBeUndefined();
|
286
|
+
});
|
287
|
+
|
288
|
+
it('should collect file URLs from batches and generations when deleting topic with data', async () => {
|
289
|
+
// Create a topic with cover image
|
290
|
+
const { id: topicId } = await generationTopicModel.create(
|
291
|
+
'Topic with batches and generations',
|
292
|
+
);
|
293
|
+
await generationTopicModel.update(topicId, { coverUrl: 'topic-cover.jpg' });
|
294
|
+
|
295
|
+
// Create a generation batch associated with this topic
|
296
|
+
const [batch] = await serverDB
|
297
|
+
.insert(generationBatches)
|
298
|
+
.values({
|
299
|
+
userId,
|
300
|
+
generationTopicId: topicId,
|
301
|
+
provider: 'test-provider',
|
302
|
+
model: 'test-model',
|
303
|
+
prompt: 'Test generation prompt',
|
304
|
+
width: 1024,
|
305
|
+
height: 1024,
|
306
|
+
})
|
307
|
+
.returning();
|
308
|
+
|
309
|
+
// Create generations with asset data containing thumbnail URLs
|
310
|
+
await serverDB.insert(generations).values([
|
311
|
+
{
|
312
|
+
userId,
|
313
|
+
generationBatchId: batch.id,
|
314
|
+
asyncTaskId: null,
|
315
|
+
fileId: null,
|
316
|
+
seed: 12345,
|
317
|
+
asset: {
|
318
|
+
type: 'image',
|
319
|
+
thumbnailUrl: 'thumbnail1.jpg',
|
320
|
+
originalUrl: 'original1.jpg',
|
321
|
+
width: 1024,
|
322
|
+
height: 1024,
|
323
|
+
},
|
324
|
+
},
|
325
|
+
{
|
326
|
+
userId,
|
327
|
+
generationBatchId: batch.id,
|
328
|
+
asyncTaskId: null,
|
329
|
+
fileId: null,
|
330
|
+
seed: 12346,
|
331
|
+
asset: {
|
332
|
+
type: 'image',
|
333
|
+
thumbnailUrl: 'thumbnail2.jpg',
|
334
|
+
originalUrl: 'original2.jpg',
|
335
|
+
width: 1024,
|
336
|
+
height: 1024,
|
337
|
+
},
|
338
|
+
},
|
339
|
+
]);
|
340
|
+
|
341
|
+
// Now delete the topic - this should collect all file URLs from cover + generations
|
342
|
+
const result = await generationTopicModel.delete(topicId);
|
343
|
+
|
344
|
+
expect(result).toBeDefined();
|
345
|
+
const deleteResult = result!;
|
346
|
+
expect(deleteResult.deletedTopic.id).toBe(topicId);
|
347
|
+
|
348
|
+
// Should collect cover URL and thumbnail URLs from generations (lines 111-117)
|
349
|
+
expect(deleteResult.filesToDelete).toContain('topic-cover.jpg');
|
350
|
+
expect(deleteResult.filesToDelete).toContain('thumbnail1.jpg');
|
351
|
+
expect(deleteResult.filesToDelete).toContain('thumbnail2.jpg');
|
352
|
+
expect(deleteResult.filesToDelete).toHaveLength(3);
|
353
|
+
|
354
|
+
// Verify topic is actually deleted from database
|
355
|
+
const deletedTopic = await serverDB.query.generationTopics.findFirst({
|
356
|
+
where: eq(generationTopics.id, topicId),
|
357
|
+
});
|
358
|
+
expect(deletedTopic).toBeUndefined();
|
359
|
+
});
|
360
|
+
});
|
361
|
+
|
362
|
+
describe('user isolation', () => {
|
363
|
+
it('should only operate on topics belonging to the user', async () => {
|
364
|
+
// Create topics for different users
|
365
|
+
await serverDB.insert(generationTopics).values([
|
366
|
+
{ id: 'user1-topic1', userId, title: 'User 1 Topic 1' },
|
367
|
+
{ id: 'user1-topic2', userId, title: 'User 1 Topic 2' },
|
368
|
+
{ id: 'user2-topic1', userId: otherUserId, title: 'User 2 Topic 1' },
|
369
|
+
]);
|
370
|
+
|
371
|
+
const result = await generationTopicModel.queryAll();
|
372
|
+
|
373
|
+
// Should only return topics for the current user
|
374
|
+
expect(result).toHaveLength(2);
|
375
|
+
expect(result.every((topic) => topic.userId === userId)).toBe(true);
|
376
|
+
expect(result.some((topic) => topic.title === 'User 2 Topic 1')).toBe(false);
|
377
|
+
});
|
378
|
+
});
|
379
|
+
|
380
|
+
describe('edge cases', () => {
|
381
|
+
it('should handle topics with null titles', async () => {
|
382
|
+
await serverDB.insert(generationTopics).values({
|
383
|
+
id: 'null-title-topic',
|
384
|
+
userId,
|
385
|
+
title: null,
|
386
|
+
});
|
387
|
+
|
388
|
+
const result = await generationTopicModel.queryAll();
|
389
|
+
|
390
|
+
expect(result).toHaveLength(1);
|
391
|
+
expect(result[0].title).toBeNull();
|
392
|
+
});
|
393
|
+
|
394
|
+
it('should handle topics with null coverUrl', async () => {
|
395
|
+
const { id } = await generationTopicModel.create('Topic');
|
396
|
+
|
397
|
+
const result = await generationTopicModel.update(id, { coverUrl: null });
|
398
|
+
|
399
|
+
expect(result).toBeDefined();
|
400
|
+
expect(result!.coverUrl).toBeNull();
|
401
|
+
});
|
402
|
+
|
403
|
+
it('should return undefined when updating non-existent topic', async () => {
|
404
|
+
const nonExistentId = 'non-existent-topic';
|
405
|
+
|
406
|
+
const updateResult = await generationTopicModel.update(nonExistentId, { title: 'New Title' });
|
407
|
+
|
408
|
+
expect(updateResult).toBeUndefined();
|
409
|
+
});
|
410
|
+
});
|
411
|
+
});
|
@@ -67,6 +67,7 @@ export class AiModelModel {
|
|
67
67
|
displayName: aiModels.displayName,
|
68
68
|
enabled: aiModels.enabled,
|
69
69
|
id: aiModels.id,
|
70
|
+
parameters: aiModels.parameters,
|
70
71
|
pricing: aiModels.pricing,
|
71
72
|
releasedAt: aiModels.releasedAt,
|
72
73
|
source: aiModels.source,
|
@@ -93,6 +94,7 @@ export class AiModelModel {
|
|
93
94
|
displayName: aiModels.displayName,
|
94
95
|
enabled: aiModels.enabled,
|
95
96
|
id: aiModels.id,
|
97
|
+
parameters: aiModels.parameters,
|
96
98
|
providerId: aiModels.providerId,
|
97
99
|
sort: aiModels.sort,
|
98
100
|
source: aiModels.source,
|
@@ -2,7 +2,7 @@ import { count, sum } from 'drizzle-orm';
|
|
2
2
|
import { and, asc, desc, eq, ilike, inArray, like, notExists, or } from 'drizzle-orm/expressions';
|
3
3
|
import type { PgTransaction } from 'drizzle-orm/pg-core';
|
4
4
|
|
5
|
-
import { LobeChatDatabase } from '@/database/type';
|
5
|
+
import { LobeChatDatabase, Transaction } from '@/database/type';
|
6
6
|
import { FilesTabs, QueryFileListParams, SortType } from '@/types/files';
|
7
7
|
|
8
8
|
import {
|
@@ -29,10 +29,11 @@ export class FileModel {
|
|
29
29
|
create = async (
|
30
30
|
params: Omit<NewFile, 'id' | 'userId'> & { knowledgeBaseId?: string },
|
31
31
|
insertToGlobalFiles?: boolean,
|
32
|
+
trx?: Transaction,
|
32
33
|
) => {
|
33
|
-
const
|
34
|
+
const executeInTransaction = async (tx: Transaction) => {
|
34
35
|
if (insertToGlobalFiles) {
|
35
|
-
await
|
36
|
+
await tx.insert(globalFiles).values({
|
36
37
|
creator: this.userId,
|
37
38
|
fileType: params.fileType,
|
38
39
|
hashId: params.fileHash!,
|
@@ -42,7 +43,7 @@ export class FileModel {
|
|
42
43
|
});
|
43
44
|
}
|
44
45
|
|
45
|
-
const result = await
|
46
|
+
const result = await tx
|
46
47
|
.insert(files)
|
47
48
|
.values({ ...params, userId: this.userId })
|
48
49
|
.returning();
|
@@ -50,7 +51,7 @@ export class FileModel {
|
|
50
51
|
const item = result[0];
|
51
52
|
|
52
53
|
if (params.knowledgeBaseId) {
|
53
|
-
await
|
54
|
+
await tx.insert(knowledgeBaseFiles).values({
|
54
55
|
fileId: item.id,
|
55
56
|
knowledgeBaseId: params.knowledgeBaseId,
|
56
57
|
userId: this.userId,
|
@@ -58,8 +59,11 @@ export class FileModel {
|
|
58
59
|
}
|
59
60
|
|
60
61
|
return item;
|
61
|
-
}
|
62
|
+
};
|
62
63
|
|
64
|
+
const result = await (trx
|
65
|
+
? executeInTransaction(trx)
|
66
|
+
: this.db.transaction(executeInTransaction));
|
63
67
|
return { id: result.id };
|
64
68
|
};
|
65
69
|
|
@@ -82,20 +86,21 @@ export class FileModel {
|
|
82
86
|
};
|
83
87
|
};
|
84
88
|
|
85
|
-
delete = async (id: string, removeGlobalFile: boolean = true) => {
|
86
|
-
const
|
87
|
-
|
89
|
+
delete = async (id: string, removeGlobalFile: boolean = true, trx?: Transaction) => {
|
90
|
+
const executeInTransaction = async (tx: Transaction) => {
|
91
|
+
// pglite 环境下不能再 transaction 中使用非事务操作,会阻塞住
|
92
|
+
const file = await this.findById(id, tx);
|
93
|
+
if (!file) return;
|
88
94
|
|
89
|
-
|
95
|
+
const fileHash = file.fileHash!;
|
90
96
|
|
91
|
-
|
92
|
-
|
93
|
-
await this.deleteFileChunks(trx as any, [id]);
|
97
|
+
// 2. Delete related chunks
|
98
|
+
await this.deleteFileChunks(tx as any, [id]);
|
94
99
|
|
95
|
-
//
|
96
|
-
await
|
100
|
+
// 3. Delete file record
|
101
|
+
await tx.delete(files).where(and(eq(files.id, id), eq(files.userId, this.userId)));
|
97
102
|
|
98
|
-
const result = await
|
103
|
+
const result = await tx
|
99
104
|
.select({ count: count() })
|
100
105
|
.from(files)
|
101
106
|
.where(and(eq(files.fileHash, fileHash)));
|
@@ -105,11 +110,13 @@ export class FileModel {
|
|
105
110
|
// delete the file from global file if it is not used by other files
|
106
111
|
// if `DISABLE_REMOVE_GLOBAL_FILE` is true, we will not remove the global file
|
107
112
|
if (fileCount === 0 && removeGlobalFile) {
|
108
|
-
await
|
113
|
+
await tx.delete(globalFiles).where(eq(globalFiles.hashId, fileHash));
|
109
114
|
|
110
115
|
return file;
|
111
116
|
}
|
112
|
-
}
|
117
|
+
};
|
118
|
+
|
119
|
+
return await (trx ? executeInTransaction(trx) : this.db.transaction(executeInTransaction));
|
113
120
|
};
|
114
121
|
|
115
122
|
deleteGlobalFile = async (hashId: string) => {
|
@@ -261,8 +268,9 @@ export class FileModel {
|
|
261
268
|
});
|
262
269
|
};
|
263
270
|
|
264
|
-
findById = async (id: string) => {
|
265
|
-
|
271
|
+
findById = async (id: string, trx?: Transaction) => {
|
272
|
+
const database = trx || this.db;
|
273
|
+
return database.query.files.findFirst({
|
266
274
|
where: and(eq(files.id, id), eq(files.userId, this.userId)),
|
267
275
|
});
|
268
276
|
};
|