@lobehub/chat 1.98.1 → 1.99.0

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 (456) hide show
  1. package/.cursor/rules/backend-architecture.mdc +93 -17
  2. package/.cursor/rules/cursor-ux.mdc +45 -35
  3. package/.cursor/rules/project-introduce.mdc +72 -6
  4. package/.cursor/rules/rules-attach.mdc +16 -7
  5. package/.eslintrc.js +10 -0
  6. package/CHANGELOG.md +52 -0
  7. package/apps/desktop/README.md +7 -0
  8. package/apps/desktop/electron-builder.js +5 -0
  9. package/apps/desktop/package.json +2 -1
  10. package/apps/desktop/src/main/const/dir.ts +3 -0
  11. package/apps/desktop/src/main/controllers/UploadFileCtr.ts +13 -8
  12. package/apps/desktop/src/main/core/App.ts +8 -0
  13. package/apps/desktop/src/main/core/StaticFileServerManager.ts +221 -0
  14. package/apps/desktop/src/main/services/fileSrv.ts +231 -44
  15. package/apps/desktop/src/main/utils/next-electron-rsc.ts +36 -5
  16. package/changelog/v1.json +18 -0
  17. package/docs/development/database-schema.dbml +70 -0
  18. package/locales/ar/common.json +2 -0
  19. package/locales/ar/components.json +35 -0
  20. package/locales/ar/error.json +2 -0
  21. package/locales/ar/image.json +100 -0
  22. package/locales/ar/metadata.json +4 -0
  23. package/locales/ar/modelProvider.json +1 -0
  24. package/locales/ar/models.json +51 -9
  25. package/locales/ar/plugin.json +22 -0
  26. package/locales/ar/providers.json +3 -0
  27. package/locales/ar/setting.json +5 -0
  28. package/locales/bg-BG/common.json +2 -0
  29. package/locales/bg-BG/components.json +35 -0
  30. package/locales/bg-BG/error.json +2 -0
  31. package/locales/bg-BG/image.json +100 -0
  32. package/locales/bg-BG/metadata.json +4 -0
  33. package/locales/bg-BG/modelProvider.json +1 -0
  34. package/locales/bg-BG/models.json +51 -9
  35. package/locales/bg-BG/plugin.json +22 -0
  36. package/locales/bg-BG/providers.json +3 -0
  37. package/locales/bg-BG/setting.json +5 -0
  38. package/locales/de-DE/common.json +2 -0
  39. package/locales/de-DE/components.json +35 -0
  40. package/locales/de-DE/error.json +2 -0
  41. package/locales/de-DE/image.json +100 -0
  42. package/locales/de-DE/metadata.json +4 -0
  43. package/locales/de-DE/modelProvider.json +1 -0
  44. package/locales/de-DE/models.json +51 -9
  45. package/locales/de-DE/plugin.json +22 -0
  46. package/locales/de-DE/providers.json +3 -0
  47. package/locales/de-DE/setting.json +5 -0
  48. package/locales/en-US/common.json +2 -0
  49. package/locales/en-US/components.json +35 -0
  50. package/locales/en-US/error.json +2 -0
  51. package/locales/en-US/image.json +100 -0
  52. package/locales/en-US/metadata.json +4 -0
  53. package/locales/en-US/modelProvider.json +1 -0
  54. package/locales/en-US/models.json +51 -9
  55. package/locales/en-US/plugin.json +22 -0
  56. package/locales/en-US/providers.json +3 -0
  57. package/locales/en-US/setting.json +5 -0
  58. package/locales/es-ES/common.json +2 -0
  59. package/locales/es-ES/components.json +35 -0
  60. package/locales/es-ES/error.json +2 -0
  61. package/locales/es-ES/image.json +100 -0
  62. package/locales/es-ES/metadata.json +4 -0
  63. package/locales/es-ES/modelProvider.json +1 -0
  64. package/locales/es-ES/models.json +51 -9
  65. package/locales/es-ES/plugin.json +22 -0
  66. package/locales/es-ES/providers.json +3 -0
  67. package/locales/es-ES/setting.json +5 -0
  68. package/locales/fa-IR/common.json +2 -0
  69. package/locales/fa-IR/components.json +35 -0
  70. package/locales/fa-IR/error.json +2 -0
  71. package/locales/fa-IR/image.json +100 -0
  72. package/locales/fa-IR/metadata.json +4 -0
  73. package/locales/fa-IR/modelProvider.json +1 -0
  74. package/locales/fa-IR/models.json +51 -9
  75. package/locales/fa-IR/plugin.json +22 -0
  76. package/locales/fa-IR/providers.json +3 -0
  77. package/locales/fa-IR/setting.json +5 -0
  78. package/locales/fr-FR/common.json +2 -0
  79. package/locales/fr-FR/components.json +35 -0
  80. package/locales/fr-FR/error.json +2 -0
  81. package/locales/fr-FR/image.json +100 -0
  82. package/locales/fr-FR/metadata.json +4 -0
  83. package/locales/fr-FR/modelProvider.json +1 -0
  84. package/locales/fr-FR/models.json +51 -9
  85. package/locales/fr-FR/plugin.json +22 -0
  86. package/locales/fr-FR/providers.json +3 -0
  87. package/locales/fr-FR/setting.json +5 -0
  88. package/locales/it-IT/common.json +2 -0
  89. package/locales/it-IT/components.json +35 -0
  90. package/locales/it-IT/error.json +2 -0
  91. package/locales/it-IT/image.json +100 -0
  92. package/locales/it-IT/metadata.json +4 -0
  93. package/locales/it-IT/modelProvider.json +1 -0
  94. package/locales/it-IT/models.json +51 -9
  95. package/locales/it-IT/plugin.json +22 -0
  96. package/locales/it-IT/providers.json +3 -0
  97. package/locales/it-IT/setting.json +5 -0
  98. package/locales/ja-JP/common.json +2 -0
  99. package/locales/ja-JP/components.json +35 -0
  100. package/locales/ja-JP/error.json +2 -0
  101. package/locales/ja-JP/image.json +100 -0
  102. package/locales/ja-JP/metadata.json +4 -0
  103. package/locales/ja-JP/modelProvider.json +1 -0
  104. package/locales/ja-JP/models.json +51 -9
  105. package/locales/ja-JP/plugin.json +22 -0
  106. package/locales/ja-JP/providers.json +3 -0
  107. package/locales/ja-JP/setting.json +5 -0
  108. package/locales/ko-KR/common.json +2 -0
  109. package/locales/ko-KR/components.json +35 -0
  110. package/locales/ko-KR/error.json +2 -0
  111. package/locales/ko-KR/image.json +100 -0
  112. package/locales/ko-KR/metadata.json +4 -0
  113. package/locales/ko-KR/modelProvider.json +1 -0
  114. package/locales/ko-KR/models.json +51 -9
  115. package/locales/ko-KR/plugin.json +22 -0
  116. package/locales/ko-KR/providers.json +3 -0
  117. package/locales/ko-KR/setting.json +5 -0
  118. package/locales/nl-NL/common.json +2 -0
  119. package/locales/nl-NL/components.json +35 -0
  120. package/locales/nl-NL/error.json +2 -0
  121. package/locales/nl-NL/image.json +100 -0
  122. package/locales/nl-NL/metadata.json +4 -0
  123. package/locales/nl-NL/modelProvider.json +1 -0
  124. package/locales/nl-NL/models.json +51 -9
  125. package/locales/nl-NL/plugin.json +22 -0
  126. package/locales/nl-NL/providers.json +3 -0
  127. package/locales/nl-NL/setting.json +5 -0
  128. package/locales/pl-PL/common.json +2 -0
  129. package/locales/pl-PL/components.json +35 -0
  130. package/locales/pl-PL/error.json +2 -0
  131. package/locales/pl-PL/image.json +100 -0
  132. package/locales/pl-PL/metadata.json +4 -0
  133. package/locales/pl-PL/modelProvider.json +1 -0
  134. package/locales/pl-PL/models.json +51 -9
  135. package/locales/pl-PL/plugin.json +22 -0
  136. package/locales/pl-PL/providers.json +3 -0
  137. package/locales/pl-PL/setting.json +5 -0
  138. package/locales/pt-BR/common.json +2 -0
  139. package/locales/pt-BR/components.json +35 -0
  140. package/locales/pt-BR/error.json +2 -0
  141. package/locales/pt-BR/image.json +100 -0
  142. package/locales/pt-BR/metadata.json +4 -0
  143. package/locales/pt-BR/modelProvider.json +1 -0
  144. package/locales/pt-BR/models.json +51 -9
  145. package/locales/pt-BR/plugin.json +22 -0
  146. package/locales/pt-BR/providers.json +3 -0
  147. package/locales/pt-BR/setting.json +5 -0
  148. package/locales/ru-RU/common.json +2 -0
  149. package/locales/ru-RU/components.json +35 -0
  150. package/locales/ru-RU/error.json +2 -0
  151. package/locales/ru-RU/image.json +100 -0
  152. package/locales/ru-RU/metadata.json +4 -0
  153. package/locales/ru-RU/modelProvider.json +1 -0
  154. package/locales/ru-RU/models.json +51 -9
  155. package/locales/ru-RU/plugin.json +22 -0
  156. package/locales/ru-RU/providers.json +3 -0
  157. package/locales/ru-RU/setting.json +5 -0
  158. package/locales/tr-TR/common.json +2 -0
  159. package/locales/tr-TR/components.json +35 -0
  160. package/locales/tr-TR/error.json +2 -0
  161. package/locales/tr-TR/image.json +100 -0
  162. package/locales/tr-TR/metadata.json +4 -0
  163. package/locales/tr-TR/modelProvider.json +1 -0
  164. package/locales/tr-TR/models.json +51 -9
  165. package/locales/tr-TR/plugin.json +22 -0
  166. package/locales/tr-TR/providers.json +3 -0
  167. package/locales/tr-TR/setting.json +5 -0
  168. package/locales/vi-VN/common.json +2 -0
  169. package/locales/vi-VN/components.json +35 -0
  170. package/locales/vi-VN/error.json +2 -0
  171. package/locales/vi-VN/image.json +100 -0
  172. package/locales/vi-VN/metadata.json +4 -0
  173. package/locales/vi-VN/modelProvider.json +1 -0
  174. package/locales/vi-VN/models.json +51 -9
  175. package/locales/vi-VN/plugin.json +22 -0
  176. package/locales/vi-VN/providers.json +3 -0
  177. package/locales/vi-VN/setting.json +5 -0
  178. package/locales/zh-CN/common.json +2 -0
  179. package/locales/zh-CN/components.json +35 -0
  180. package/locales/zh-CN/error.json +2 -0
  181. package/locales/zh-CN/image.json +100 -0
  182. package/locales/zh-CN/metadata.json +4 -0
  183. package/locales/zh-CN/modelProvider.json +1 -0
  184. package/locales/zh-CN/models.json +51 -9
  185. package/locales/zh-CN/plugin.json +22 -0
  186. package/locales/zh-CN/providers.json +3 -0
  187. package/locales/zh-CN/setting.json +5 -0
  188. package/locales/zh-TW/common.json +2 -0
  189. package/locales/zh-TW/components.json +35 -0
  190. package/locales/zh-TW/error.json +2 -0
  191. package/locales/zh-TW/image.json +100 -0
  192. package/locales/zh-TW/metadata.json +4 -0
  193. package/locales/zh-TW/modelProvider.json +1 -0
  194. package/locales/zh-TW/models.json +51 -9
  195. package/locales/zh-TW/plugin.json +22 -0
  196. package/locales/zh-TW/providers.json +3 -0
  197. package/locales/zh-TW/setting.json +5 -0
  198. package/package.json +11 -4
  199. package/packages/electron-server-ipc/src/events/file.ts +3 -1
  200. package/packages/electron-server-ipc/src/types/file.ts +15 -0
  201. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/TopActions.tsx +11 -1
  202. package/src/app/[variants]/(main)/image/@menu/components/AspectRatioSelect/index.tsx +73 -0
  203. package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +39 -0
  204. package/src/app/[variants]/(main)/image/@menu/components/SizeSelect/index.tsx +89 -0
  205. package/src/app/[variants]/(main)/image/@menu/default.tsx +11 -0
  206. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/AspectRatioSelect.tsx +24 -0
  207. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/DimensionControlGroup.tsx +107 -0
  208. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageNum.tsx +290 -0
  209. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUpload.tsx +504 -0
  210. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrl.tsx +18 -0
  211. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrlsUpload.tsx +19 -0
  212. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ModelSelect.tsx +155 -0
  213. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/ImageManageModal.tsx +415 -0
  214. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/index.tsx +732 -0
  215. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SeedNumberInput.tsx +24 -0
  216. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSelect.tsx +17 -0
  217. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSliderInput.tsx +15 -0
  218. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/StepsSliderInput.tsx +11 -0
  219. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/constants.ts +1 -0
  220. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +93 -0
  221. package/src/app/[variants]/(main)/image/@topic/default.tsx +17 -0
  222. package/src/app/[variants]/(main)/image/@topic/features/Topics/NewTopicButton.tsx +64 -0
  223. package/src/app/[variants]/(main)/image/@topic/features/Topics/SkeletonList.tsx +34 -0
  224. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItem.tsx +136 -0
  225. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItemContainer.tsx +91 -0
  226. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicList.tsx +57 -0
  227. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicUrlSync.tsx +37 -0
  228. package/src/app/[variants]/(main)/image/@topic/features/Topics/index.tsx +19 -0
  229. package/src/app/[variants]/(main)/image/NotSupportClient.tsx +153 -0
  230. package/src/app/[variants]/(main)/image/_layout/Desktop/Container.tsx +35 -0
  231. package/src/app/[variants]/(main)/image/_layout/Desktop/RegisterHotkeys.tsx +10 -0
  232. package/src/app/[variants]/(main)/image/_layout/Desktop/index.tsx +30 -0
  233. package/src/app/[variants]/(main)/image/_layout/Mobile/index.tsx +14 -0
  234. package/src/app/[variants]/(main)/image/_layout/type.ts +7 -0
  235. package/src/app/[variants]/(main)/image/features/GenerationFeed/BatchItem.tsx +196 -0
  236. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/ActionButtons.tsx +60 -0
  237. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/ElapsedTime.tsx +90 -0
  238. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/ErrorState.tsx +65 -0
  239. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/LoadingState.tsx +43 -0
  240. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/SuccessState.tsx +49 -0
  241. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/index.tsx +156 -0
  242. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/styles.ts +51 -0
  243. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/types.ts +39 -0
  244. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/utils.ts +11 -0
  245. package/src/app/[variants]/(main)/image/features/GenerationFeed/index.tsx +97 -0
  246. package/src/app/[variants]/(main)/image/features/ImageWorkspace/Content.tsx +48 -0
  247. package/src/app/[variants]/(main)/image/features/ImageWorkspace/EmptyState.tsx +37 -0
  248. package/src/app/[variants]/(main)/image/features/ImageWorkspace/SkeletonList.tsx +50 -0
  249. package/src/app/[variants]/(main)/image/features/ImageWorkspace/index.tsx +23 -0
  250. package/src/app/[variants]/(main)/image/features/PromptInput/Title.tsx +38 -0
  251. package/src/app/[variants]/(main)/image/features/PromptInput/index.tsx +114 -0
  252. package/src/app/[variants]/(main)/image/layout.tsx +19 -0
  253. package/src/app/[variants]/(main)/image/loading.tsx +3 -0
  254. package/src/app/[variants]/(main)/image/page.tsx +47 -0
  255. package/src/app/[variants]/(main)/settings/system-agent/index.tsx +2 -1
  256. package/src/chains/summaryGenerationTitle.ts +25 -0
  257. package/src/components/ImageItem/index.tsx +9 -6
  258. package/src/{features/Conversation/Error → components/InvalidAPIKey}/APIKeyForm/Bedrock.tsx +3 -4
  259. package/src/{features/Conversation/Error → components/InvalidAPIKey}/APIKeyForm/ProviderApiKeyForm.tsx +5 -4
  260. package/src/components/InvalidAPIKey/APIKeyForm/index.tsx +108 -0
  261. package/src/{features/Conversation/Error → components/InvalidAPIKey}/APIKeyForm/useApiKey.ts +2 -1
  262. package/src/components/InvalidAPIKey/index.tsx +30 -0
  263. package/src/components/KeyValueEditor/index.tsx +203 -0
  264. package/src/components/KeyValueEditor/utils.ts +42 -0
  265. package/src/config/aiModels/fal.ts +52 -0
  266. package/src/config/aiModels/index.ts +3 -0
  267. package/src/config/aiModels/openai.ts +20 -6
  268. package/src/config/llm.ts +6 -0
  269. package/src/config/modelProviders/fal.ts +21 -0
  270. package/src/config/modelProviders/index.ts +3 -0
  271. package/src/config/paramsSchemas/fal/flux-kontext-dev.ts +8 -0
  272. package/src/config/paramsSchemas/fal/flux-pro-kontext.ts +11 -0
  273. package/src/config/paramsSchemas/fal/flux-schnell.ts +9 -0
  274. package/src/config/paramsSchemas/fal/imagen4.ts +10 -0
  275. package/src/config/paramsSchemas/openai/gpt-image-1.ts +10 -0
  276. package/src/const/hotkeys.ts +2 -2
  277. package/src/const/image.ts +6 -0
  278. package/src/const/settings/systemAgent.ts +1 -0
  279. package/src/database/client/migrations.json +27 -0
  280. package/src/database/migrations/0026_add_autovacuum_tuning.sql +2 -0
  281. package/src/database/migrations/0027_ai_image.sql +47 -0
  282. package/src/database/migrations/meta/0027_snapshot.json +6003 -0
  283. package/src/database/migrations/meta/_journal.json +7 -0
  284. package/src/database/models/__tests__/asyncTask.test.ts +7 -5
  285. package/src/database/models/__tests__/file.test.ts +287 -0
  286. package/src/database/models/__tests__/generation.test.ts +786 -0
  287. package/src/database/models/__tests__/generationBatch.test.ts +614 -0
  288. package/src/database/models/__tests__/generationTopic.test.ts +411 -0
  289. package/src/database/models/aiModel.ts +2 -0
  290. package/src/database/models/asyncTask.ts +1 -1
  291. package/src/database/models/file.ts +28 -20
  292. package/src/database/models/generation.ts +197 -0
  293. package/src/database/models/generationBatch.ts +212 -0
  294. package/src/database/models/generationTopic.ts +131 -0
  295. package/src/database/repositories/aiInfra/index.test.ts +151 -1
  296. package/src/database/repositories/aiInfra/index.ts +28 -19
  297. package/src/database/repositories/tableViewer/index.test.ts +1 -1
  298. package/src/database/schemas/file.ts +8 -0
  299. package/src/database/schemas/generation.ts +127 -0
  300. package/src/database/schemas/index.ts +1 -0
  301. package/src/database/schemas/relations.ts +45 -1
  302. package/src/database/type.ts +2 -0
  303. package/src/database/utils/idGenerator.ts +3 -0
  304. package/src/features/Conversation/Error/ChatInvalidApiKey.tsx +39 -0
  305. package/src/features/Conversation/Error/InvalidAccessCode.tsx +2 -2
  306. package/src/features/Conversation/Error/index.tsx +3 -3
  307. package/src/features/ImageSidePanel/index.tsx +83 -0
  308. package/src/features/ImageTopicPanel/index.tsx +79 -0
  309. package/src/features/PluginDevModal/MCPManifestForm/CollapsibleSection.tsx +62 -0
  310. package/src/features/PluginDevModal/MCPManifestForm/QuickImportSection.tsx +158 -0
  311. package/src/features/PluginDevModal/MCPManifestForm/index.tsx +99 -155
  312. package/src/features/PluginStore/McpList/Detail/Settings/index.tsx +5 -2
  313. package/src/hooks/useDownloadImage.ts +31 -0
  314. package/src/hooks/useFetchGenerationTopics.ts +13 -0
  315. package/src/hooks/useHotkeys/imageScope.ts +48 -0
  316. package/src/libs/mcp/client.ts +55 -22
  317. package/src/libs/mcp/types.ts +42 -6
  318. package/src/libs/model-runtime/BaseAI.ts +3 -1
  319. package/src/libs/model-runtime/ModelRuntime.test.ts +80 -0
  320. package/src/libs/model-runtime/ModelRuntime.ts +15 -1
  321. package/src/libs/model-runtime/UniformRuntime/index.ts +4 -1
  322. package/src/libs/model-runtime/fal/index.test.ts +442 -0
  323. package/src/libs/model-runtime/fal/index.ts +88 -0
  324. package/src/libs/model-runtime/openai/index.test.ts +396 -2
  325. package/src/libs/model-runtime/openai/index.ts +129 -3
  326. package/src/libs/model-runtime/runtimeMap.ts +2 -0
  327. package/src/libs/model-runtime/types/image.ts +25 -0
  328. package/src/libs/model-runtime/types/type.ts +1 -0
  329. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +10 -0
  330. package/src/libs/standard-parameters/index.ts +1 -0
  331. package/src/libs/standard-parameters/meta-schema.test.ts +214 -0
  332. package/src/libs/standard-parameters/meta-schema.ts +147 -0
  333. package/src/libs/swr/index.ts +1 -0
  334. package/src/libs/trpc/async/asyncAuth.ts +29 -8
  335. package/src/libs/trpc/async/context.ts +42 -4
  336. package/src/libs/trpc/async/index.ts +17 -4
  337. package/src/libs/trpc/async/init.ts +8 -0
  338. package/src/libs/trpc/client/lambda.ts +19 -2
  339. package/src/locales/default/common.ts +2 -0
  340. package/src/locales/default/components.ts +35 -0
  341. package/src/locales/default/error.ts +2 -0
  342. package/src/locales/default/image.ts +100 -0
  343. package/src/locales/default/index.ts +2 -0
  344. package/src/locales/default/metadata.ts +4 -0
  345. package/src/locales/default/modelProvider.ts +2 -0
  346. package/src/locales/default/plugin.ts +22 -0
  347. package/src/locales/default/setting.ts +5 -0
  348. package/src/middleware.ts +1 -0
  349. package/src/server/modules/ElectronIPCClient/index.ts +9 -1
  350. package/src/server/modules/S3/index.ts +15 -0
  351. package/src/server/routers/async/caller.ts +9 -1
  352. package/src/server/routers/async/image.ts +253 -0
  353. package/src/server/routers/async/index.ts +2 -0
  354. package/src/server/routers/lambda/aiProvider.test.ts +1 -0
  355. package/src/server/routers/lambda/generation.test.ts +267 -0
  356. package/src/server/routers/lambda/generation.ts +86 -0
  357. package/src/server/routers/lambda/generationBatch.test.ts +376 -0
  358. package/src/server/routers/lambda/generationBatch.ts +56 -0
  359. package/src/server/routers/lambda/generationTopic.test.ts +508 -0
  360. package/src/server/routers/lambda/generationTopic.ts +93 -0
  361. package/src/server/routers/lambda/image.ts +248 -0
  362. package/src/server/routers/lambda/index.ts +8 -0
  363. package/src/server/routers/tools/mcp.ts +15 -0
  364. package/src/server/services/file/__tests__/index.test.ts +135 -0
  365. package/src/server/services/file/impls/local.test.ts +153 -52
  366. package/src/server/services/file/impls/local.ts +70 -46
  367. package/src/server/services/file/impls/s3.test.ts +114 -0
  368. package/src/server/services/file/impls/s3.ts +40 -0
  369. package/src/server/services/file/impls/type.ts +10 -0
  370. package/src/server/services/file/index.ts +14 -0
  371. package/src/server/services/generation/index.ts +239 -0
  372. package/src/server/services/mcp/index.ts +20 -2
  373. package/src/services/__tests__/generation.test.ts +40 -0
  374. package/src/services/__tests__/generationBatch.test.ts +36 -0
  375. package/src/services/__tests__/generationTopic.test.ts +72 -0
  376. package/src/services/electron/file.ts +3 -1
  377. package/src/services/generation.ts +16 -0
  378. package/src/services/generationBatch.ts +25 -0
  379. package/src/services/generationTopic.ts +28 -0
  380. package/src/services/image.ts +33 -0
  381. package/src/services/mcp.ts +12 -7
  382. package/src/services/upload.ts +43 -9
  383. package/src/store/aiInfra/slices/aiProvider/action.ts +25 -5
  384. package/src/store/aiInfra/slices/aiProvider/initialState.ts +1 -0
  385. package/src/store/aiInfra/slices/aiProvider/selectors.ts +3 -0
  386. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +5 -5
  387. package/src/store/chat/slices/message/action.ts +2 -2
  388. package/src/store/chat/slices/translate/action.ts +1 -1
  389. package/src/store/global/initialState.ts +9 -0
  390. package/src/store/global/selectors/systemStatus.ts +8 -0
  391. package/src/store/image/index.ts +2 -0
  392. package/src/store/image/initialState.ts +25 -0
  393. package/src/store/image/selectors.ts +4 -0
  394. package/src/store/image/slices/createImage/action.test.ts +330 -0
  395. package/src/store/image/slices/createImage/action.ts +134 -0
  396. package/src/store/image/slices/createImage/initialState.ts +9 -0
  397. package/src/store/image/slices/createImage/selectors.test.ts +114 -0
  398. package/src/store/image/slices/createImage/selectors.ts +9 -0
  399. package/src/store/image/slices/generationBatch/action.test.ts +495 -0
  400. package/src/store/image/slices/generationBatch/action.ts +303 -0
  401. package/src/store/image/slices/generationBatch/initialState.ts +13 -0
  402. package/src/store/image/slices/generationBatch/reducer.test.ts +568 -0
  403. package/src/store/image/slices/generationBatch/reducer.ts +101 -0
  404. package/src/store/image/slices/generationBatch/selectors.test.ts +307 -0
  405. package/src/store/image/slices/generationBatch/selectors.ts +36 -0
  406. package/src/store/image/slices/generationConfig/action.test.ts +351 -0
  407. package/src/store/image/slices/generationConfig/action.ts +295 -0
  408. package/src/store/image/slices/generationConfig/hooks.test.ts +304 -0
  409. package/src/store/image/slices/generationConfig/hooks.ts +118 -0
  410. package/src/store/image/slices/generationConfig/index.ts +1 -0
  411. package/src/store/image/slices/generationConfig/initialState.ts +37 -0
  412. package/src/store/image/slices/generationConfig/selectors.test.ts +204 -0
  413. package/src/store/image/slices/generationConfig/selectors.ts +25 -0
  414. package/src/store/image/slices/generationTopic/action.test.ts +687 -0
  415. package/src/store/image/slices/generationTopic/action.ts +319 -0
  416. package/src/store/image/slices/generationTopic/index.ts +2 -0
  417. package/src/store/image/slices/generationTopic/initialState.ts +14 -0
  418. package/src/store/image/slices/generationTopic/reducer.test.ts +198 -0
  419. package/src/store/image/slices/generationTopic/reducer.ts +66 -0
  420. package/src/store/image/slices/generationTopic/selectors.test.ts +103 -0
  421. package/src/store/image/slices/generationTopic/selectors.ts +15 -0
  422. package/src/store/image/store.ts +42 -0
  423. package/src/store/image/utils/size.ts +51 -0
  424. package/src/store/tool/slices/customPlugin/action.ts +10 -1
  425. package/src/store/tool/slices/mcpStore/action.ts +6 -4
  426. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +4 -0
  427. package/src/store/user/slices/settings/selectors/systemAgent.ts +2 -0
  428. package/src/types/aiModel.ts +8 -3
  429. package/src/types/aiProvider.ts +1 -0
  430. package/src/types/asyncTask.ts +2 -0
  431. package/src/types/files/index.ts +5 -0
  432. package/src/types/generation/index.ts +80 -0
  433. package/src/types/hotkey.ts +2 -0
  434. package/src/types/plugins/mcp.ts +2 -6
  435. package/src/types/tool/plugin.ts +8 -0
  436. package/src/types/user/settings/keyVaults.ts +5 -0
  437. package/src/types/user/settings/systemAgent.ts +1 -0
  438. package/src/utils/client/downloadFile.ts +33 -4
  439. package/src/utils/number.test.ts +105 -0
  440. package/src/utils/number.ts +25 -0
  441. package/src/utils/server/__tests__/geo.test.ts +6 -3
  442. package/src/utils/storeDebug.test.ts +152 -0
  443. package/src/utils/storeDebug.ts +16 -7
  444. package/src/utils/time.test.ts +259 -0
  445. package/src/utils/time.ts +18 -0
  446. package/src/utils/units.ts +61 -0
  447. package/src/utils/url.test.ts +358 -9
  448. package/src/utils/url.ts +105 -3
  449. package/{vitest.server.config.ts → vitest.config.server.ts} +3 -0
  450. package/.cursor/rules/i18n/i18n-auto-attached.mdc +0 -6
  451. package/src/features/Conversation/Error/APIKeyForm/index.tsx +0 -105
  452. package/src/features/Conversation/Error/InvalidAPIKey.tsx +0 -16
  453. package/src/features/PluginDevModal/MCPManifestForm/EnvEditor.tsx +0 -227
  454. /package/.cursor/rules/{i18n/i18n.mdc → i18n.mdc} +0 -0
  455. /package/src/app/[variants]/(main)/settings/system-agent/features/{createForm.tsx → SystemAgentForm.tsx} +0 -0
  456. /package/src/{features/Conversation/Error → components/InvalidAPIKey}/APIKeyForm/LoadingContext.ts +0 -0
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { ImageStore } from '@/store/image';
4
+ import { initialState } from '@/store/image/initialState';
5
+ import { ImageGenerationTopic } from '@/types/generation';
6
+ import { merge } from '@/utils/merge';
7
+
8
+ import { generationTopicSelectors } from './selectors';
9
+
10
+ const initialStore = initialState as ImageStore;
11
+
12
+ const mockGenerationTopics: ImageGenerationTopic[] = [
13
+ {
14
+ id: 'gt_aBc123xYz456',
15
+ title: 'Generation Topic 1',
16
+ coverUrl: 'https://example.com/cover1.jpg',
17
+ createdAt: new Date('2023-01-01'),
18
+ updatedAt: new Date('2023-01-01'),
19
+ },
20
+ {
21
+ id: 'gt_dEf789uVw012',
22
+ title: 'Generation Topic 2',
23
+ coverUrl: null,
24
+ createdAt: new Date('2023-01-02'),
25
+ updatedAt: new Date('2023-01-02'),
26
+ },
27
+ ];
28
+
29
+ describe('generationTopicSelectors', () => {
30
+ describe('activeGenerationTopicId', () => {
31
+ it('should return null when no active topic is set', () => {
32
+ const activeId = generationTopicSelectors.activeGenerationTopicId(initialStore);
33
+ expect(activeId).toBeNull();
34
+ });
35
+
36
+ it('should return the active generation topic id', () => {
37
+ const state = merge(initialStore, { activeGenerationTopicId: 'gt_aBc123xYz456' });
38
+ const activeId = generationTopicSelectors.activeGenerationTopicId(state);
39
+ expect(activeId).toBe('gt_aBc123xYz456');
40
+ });
41
+ });
42
+
43
+ describe('generationTopics', () => {
44
+ it('should return empty array when no topics exist', () => {
45
+ const topics = generationTopicSelectors.generationTopics(initialStore);
46
+ expect(topics).toEqual([]);
47
+ });
48
+
49
+ it('should return all generation topics from the store', () => {
50
+ const state = merge(initialStore, { generationTopics: mockGenerationTopics });
51
+ const topics = generationTopicSelectors.generationTopics(state);
52
+ expect(topics).toEqual(mockGenerationTopics);
53
+ });
54
+ });
55
+
56
+ describe('getGenerationTopicById', () => {
57
+ it('should return undefined when topic is not found', () => {
58
+ const state = merge(initialStore, { generationTopics: mockGenerationTopics });
59
+ const topic = generationTopicSelectors.getGenerationTopicById('nonexistent')(state);
60
+ expect(topic).toBeUndefined();
61
+ });
62
+
63
+ it('should return the topic with the given id', () => {
64
+ const state = merge(initialStore, { generationTopics: mockGenerationTopics });
65
+ const topic = generationTopicSelectors.getGenerationTopicById('gt_aBc123xYz456')(state);
66
+ expect(topic).toEqual(mockGenerationTopics[0]);
67
+ });
68
+
69
+ it('should return the correct topic when multiple topics exist', () => {
70
+ const state = merge(initialStore, { generationTopics: mockGenerationTopics });
71
+ const topic = generationTopicSelectors.getGenerationTopicById('gt_dEf789uVw012')(state);
72
+ expect(topic).toEqual(mockGenerationTopics[1]);
73
+ });
74
+ });
75
+
76
+ describe('isLoadingGenerationTopic', () => {
77
+ it('should return false when no topics are loading', () => {
78
+ const isLoading =
79
+ generationTopicSelectors.isLoadingGenerationTopic('gt_aBc123xYz456')(initialStore);
80
+ expect(isLoading).toBe(false);
81
+ });
82
+
83
+ it('should return false when topic is not in loading list', () => {
84
+ const state = merge(initialStore, { loadingGenerationTopicIds: ['gt_dEf789uVw012'] });
85
+ const isLoading = generationTopicSelectors.isLoadingGenerationTopic('gt_aBc123xYz456')(state);
86
+ expect(isLoading).toBe(false);
87
+ });
88
+
89
+ it('should return true when topic is in loading list', () => {
90
+ const state = merge(initialStore, {
91
+ loadingGenerationTopicIds: ['gt_aBc123xYz456', 'gt_dEf789uVw012'],
92
+ });
93
+ const isLoading = generationTopicSelectors.isLoadingGenerationTopic('gt_aBc123xYz456')(state);
94
+ expect(isLoading).toBe(true);
95
+ });
96
+
97
+ it('should return true when topic is the only one in loading list', () => {
98
+ const state = merge(initialStore, { loadingGenerationTopicIds: ['gt_aBc123xYz456'] });
99
+ const isLoading = generationTopicSelectors.isLoadingGenerationTopic('gt_aBc123xYz456')(state);
100
+ expect(isLoading).toBe(true);
101
+ });
102
+ });
103
+ });
@@ -0,0 +1,15 @@
1
+ import { ImageStoreState } from '../../initialState';
2
+
3
+ const activeGenerationTopicId = (s: ImageStoreState) => s.activeGenerationTopicId;
4
+ const generationTopics = (s: ImageStoreState) => s.generationTopics;
5
+ const getGenerationTopicById = (id: string) => (s: ImageStoreState) =>
6
+ s.generationTopics.find((topic) => topic.id === id);
7
+ const isLoadingGenerationTopic = (id: string) => (s: ImageStoreState) =>
8
+ s.loadingGenerationTopicIds.includes(id);
9
+
10
+ export const generationTopicSelectors = {
11
+ activeGenerationTopicId,
12
+ generationTopics,
13
+ getGenerationTopicById,
14
+ isLoadingGenerationTopic,
15
+ };
@@ -0,0 +1,42 @@
1
+ import { subscribeWithSelector } from 'zustand/middleware';
2
+ import { shallow } from 'zustand/shallow';
3
+ import { createWithEqualityFn } from 'zustand/traditional';
4
+ import { StateCreator } from 'zustand/vanilla';
5
+
6
+ import { createDevtools } from '../middleware/createDevtools';
7
+ import { ImageStoreState, initialState } from './initialState';
8
+ import { CreateImageAction, createCreateImageSlice } from './slices/createImage/action';
9
+ import { GenerationBatchAction, createGenerationBatchSlice } from './slices/generationBatch/action';
10
+ import {
11
+ GenerationConfigAction,
12
+ createGenerationConfigSlice,
13
+ } from './slices/generationConfig/action';
14
+ import { GenerationTopicAction, createGenerationTopicSlice } from './slices/generationTopic/action';
15
+
16
+ // =============== aggregate createStoreFn ============ //
17
+
18
+ export interface ImageStore
19
+ extends GenerationConfigAction,
20
+ GenerationTopicAction,
21
+ GenerationBatchAction,
22
+ CreateImageAction,
23
+ ImageStoreState {}
24
+
25
+ const createStore: StateCreator<ImageStore, [['zustand/devtools', never]]> = (...parameters) => ({
26
+ ...initialState,
27
+ ...createGenerationConfigSlice(...parameters),
28
+ ...createGenerationTopicSlice(...parameters),
29
+ ...createGenerationBatchSlice(...parameters),
30
+ ...createCreateImageSlice(...parameters),
31
+ });
32
+
33
+ // =============== implement useStore ============ //
34
+
35
+ const devtools = createDevtools('image');
36
+
37
+ export const useImageStore = createWithEqualityFn<ImageStore>()(
38
+ subscribeWithSelector(devtools(createStore)),
39
+ shallow,
40
+ );
41
+
42
+ export const getImageStoreState = () => useImageStore.getState();
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 解析比例字符串,例如 "16:9" -> 1.777
3
+ * @param ratio - 格式为 "width:height" 的比例字符串
4
+ * @returns 比例数值,出现错误时返回 1:1 比例
5
+ */
6
+ export function parseRatio(ratio: string): number {
7
+ if (!ratio || typeof ratio !== 'string') return 1;
8
+
9
+ const parts = ratio.split(':');
10
+ if (parts.length !== 2) return 1;
11
+
12
+ const [widthStr, heightStr] = parts;
13
+ const width = Number(widthStr);
14
+ const height = Number(heightStr);
15
+
16
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
17
+ return 1;
18
+ }
19
+
20
+ return width / height;
21
+ }
22
+
23
+ /**
24
+ * 根据目标比例和默认尺寸,计算最合适的尺寸
25
+ * @param ratio - 目标宽高比例
26
+ * @param defaultWidth - 默认宽度
27
+ * @param defaultHeight - 默认高度
28
+ * @returns 计算出的尺寸对象
29
+ */
30
+ export function adaptSizeToRatio(ratio: number, defaultWidth: number, defaultHeight: number) {
31
+ // 验证输入参数
32
+ if (!Number.isFinite(ratio) || ratio <= 0) {
33
+ throw new Error('Invalid ratio: must be a positive finite number');
34
+ }
35
+ if (!Number.isFinite(defaultWidth) || defaultWidth <= 0) {
36
+ throw new Error('Invalid defaultWidth: must be a positive finite number');
37
+ }
38
+ if (!Number.isFinite(defaultHeight) || defaultHeight <= 0) {
39
+ throw new Error('Invalid defaultHeight: must be a positive finite number');
40
+ }
41
+
42
+ const currentRatio = defaultWidth / defaultHeight;
43
+
44
+ if (ratio > currentRatio) {
45
+ // 目标比例更宽,保持宽度,调整高度
46
+ return { width: defaultWidth, height: Math.round(defaultWidth / ratio) };
47
+ } else {
48
+ // 目标比例更高,保持高度,调整宽度
49
+ return { width: Math.round(defaultHeight * ratio), height: defaultHeight };
50
+ }
51
+ }
@@ -51,7 +51,16 @@ export const createCustomPluginSlice: StateCreator<
51
51
  const url = plugin.customParams?.mcp?.url;
52
52
  if (!url) return;
53
53
 
54
- manifest = await mcpService.getStreamableMcpServerManifest(plugin.identifier, url);
54
+ manifest = await mcpService.getStreamableMcpServerManifest({
55
+ auth: plugin.customParams.mcp.auth,
56
+ headers: plugin.customParams.mcp.headers,
57
+ identifier: plugin.identifier,
58
+ metadata: {
59
+ avatar: plugin.customParams.avatar,
60
+ description: plugin.customParams.description,
61
+ },
62
+ url,
63
+ });
55
64
  } else {
56
65
  manifest = await toolService.getToolManifest(
57
66
  plugin.customParams?.manifestUrl,
@@ -229,11 +229,13 @@ export const createMCPPluginStoreSlice: StateCreator<
229
229
  }
230
230
  if (connection?.type === 'http') {
231
231
  manifest = await mcpService.getStreamableMcpServerManifest(
232
- identifier,
233
- connection.url!,
234
232
  {
235
- avatar: plugin.icon,
236
- description: plugin.description,
233
+ identifier,
234
+ metadata: {
235
+ avatar: plugin.icon,
236
+ description: plugin.description,
237
+ },
238
+ url: connection.url!,
237
239
  },
238
240
  abortController.signal,
239
241
  );
@@ -55,6 +55,10 @@ exports[`settingsSelectors > currentSystemAgent > should merge DEFAULT_SYSTEM_AG
55
55
  "provider": "openai",
56
56
  },
57
57
  "enableAutoReply": true,
58
+ "generationTopic": {
59
+ "model": "gpt-4.1-mini",
60
+ "provider": "openai",
61
+ },
58
62
  "historyCompress": {
59
63
  "model": "gpt-4.1-mini",
60
64
  "provider": "openai",
@@ -13,9 +13,11 @@ const thread = (s: UserStore) => currentSystemAgent(s).thread;
13
13
  const agentMeta = (s: UserStore) => currentSystemAgent(s).agentMeta;
14
14
  const queryRewrite = (s: UserStore) => currentSystemAgent(s).queryRewrite;
15
15
  const historyCompress = (s: UserStore) => currentSystemAgent(s).historyCompress;
16
+ const generationTopic = (s: UserStore) => currentSystemAgent(s).generationTopic;
16
17
 
17
18
  export const systemAgentSelectors = {
18
19
  agentMeta,
20
+ generationTopic,
19
21
  historyCompress,
20
22
  queryRewrite,
21
23
  thread,
@@ -1,5 +1,7 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ import { ModelParamsSchema } from '@/libs/standard-parameters';
4
+
3
5
  export type ModelPriceCurrency = 'CNY' | 'USD';
4
6
 
5
7
  export const AiModelSourceEnum = {
@@ -186,15 +188,16 @@ export interface AIEmbeddingModelCard extends AIBaseModelCard {
186
188
  type: 'embedding';
187
189
  }
188
190
 
189
- export interface AIText2ImageModelCard extends AIBaseModelCard {
191
+ export interface AIImageModelCard extends AIBaseModelCard {
192
+ parameters?: ModelParamsSchema;
190
193
  pricing?: {
191
194
  /**
192
195
  * the currency of the pricing
193
196
  * @default USD
194
197
  */
195
198
  currency?: ModelPriceCurrency;
196
- } & Record<string, number>; // [resolution: string]: number;
197
- resolutions: string[];
199
+ } & Record<string, number>;
200
+ resolutions?: string[];
198
201
  type: 'image';
199
202
  }
200
203
 
@@ -299,6 +302,7 @@ export interface AiProviderModelListItem {
299
302
  displayName?: string;
300
303
  enabled: boolean;
301
304
  id: string;
305
+ parameters?: Record<string, any>;
302
306
  pricing?: ChatModelPricing;
303
307
  releasedAt?: string;
304
308
  settings?: AiModelSettings;
@@ -350,6 +354,7 @@ export interface EnabledAiModel {
350
354
  displayName?: string;
351
355
  enabled?: boolean;
352
356
  id: string;
357
+ parameters?: Record<string, any>;
353
358
  providerId: string;
354
359
  settings?: AiModelSettings;
355
360
  sort?: number;
@@ -255,5 +255,6 @@ export interface AiProviderRuntimeConfig {
255
255
  export interface AiProviderRuntimeState {
256
256
  enabledAiModels: EnabledAiModel[];
257
257
  enabledAiProviders: EnabledProvider[];
258
+ enabledImageAiProviders: EnabledProvider[];
258
259
  runtimeConfig: Record<string, AiProviderRuntimeConfig>;
259
260
  }
@@ -1,6 +1,7 @@
1
1
  export enum AsyncTaskType {
2
2
  Chunking = 'chunk',
3
3
  Embedding = 'embedding',
4
+ ImageGeneration = 'image_generation',
4
5
  }
5
6
 
6
7
  export enum AsyncTaskStatus {
@@ -12,6 +13,7 @@ export enum AsyncTaskStatus {
12
13
 
13
14
  export enum AsyncTaskErrorType {
14
15
  EmbeddingError = 'EmbeddingError',
16
+ InvalidProviderAPIKey = 'InvalidProviderAPIKey',
15
17
  /**
16
18
  * the chunk parse result it empty
17
19
  */
@@ -7,12 +7,17 @@ export enum FilesTabs {
7
7
  Websites = 'websites',
8
8
  }
9
9
 
10
+ export enum FileSource {
11
+ ImageGeneration = 'image_generation',
12
+ }
13
+
10
14
  export interface FileItem {
11
15
  createdAt: Date;
12
16
  enabled?: boolean;
13
17
  id: string;
14
18
  name: string;
15
19
  size: number;
20
+ source?: FileSource | null;
16
21
  type: string;
17
22
  updatedAt: Date;
18
23
  url: string;
@@ -0,0 +1,80 @@
1
+ import { AsyncTaskError, AsyncTaskStatus } from '../asyncTask';
2
+
3
+ export interface ImageGenerationTopic {
4
+ id: string;
5
+ title?: string | null;
6
+ coverUrl?: string | null;
7
+ createdAt: Date;
8
+ updatedAt: Date;
9
+ }
10
+
11
+ export interface BaseGenerationAsset {
12
+ type: string;
13
+ }
14
+
15
+ export interface ImageGenerationAsset extends BaseGenerationAsset {
16
+ /**
17
+ * api provider 家的 cdn url,一般很快就会失效
18
+ */
19
+ originalUrl?: string;
20
+ /**
21
+ * 存到自己 oss 的 url, 只存了 key, 完整的 url 需要使用 FileService.getFullFileUrl 获取
22
+ */
23
+ url?: string;
24
+ /**
25
+ * 缩略图,图片那就是尺寸裁剪过的,视频那就是封面的缩略图
26
+ */
27
+ thumbnailUrl?: string;
28
+ /**
29
+ * 图片/视频的宽度
30
+ */
31
+ width?: number;
32
+ /**
33
+ * 图片/视频的高度
34
+ */
35
+ height?: number;
36
+ }
37
+
38
+ export type GenerationAsset = ImageGenerationAsset;
39
+
40
+ export interface GenerationConfig {
41
+ prompt: string;
42
+ imageUrls?: string[];
43
+ width?: number;
44
+ height?: number;
45
+ aspectRatio?: string;
46
+ size?: string;
47
+ steps?: number;
48
+ cfg?: number;
49
+ }
50
+
51
+ export interface GenerationAsyncTask {
52
+ id: string;
53
+ status: AsyncTaskStatus;
54
+ error?: AsyncTaskError;
55
+ }
56
+
57
+ export interface Generation {
58
+ id: string;
59
+ /**
60
+ * The asset associated with the generation, containing image URLs and dimensions.
61
+ */
62
+ asset?: GenerationAsset | null;
63
+ seed?: number | null;
64
+ createdAt: Date;
65
+ asyncTaskId: string | null;
66
+
67
+ task: GenerationAsyncTask;
68
+ }
69
+
70
+ export interface GenerationBatch {
71
+ id: string;
72
+ provider: string;
73
+ model: string;
74
+ prompt: string;
75
+ width?: number | null;
76
+ height?: number | null;
77
+ config?: GenerationConfig;
78
+ createdAt: Date;
79
+ generations: Generation[];
80
+ }
@@ -84,6 +84,8 @@ export const HotkeyScopeEnum = {
84
84
  // 默认全局注册的快捷键 scope
85
85
  // https://react-hotkeys-hook.vercel.app/docs/documentation/hotkeys-provider
86
86
  Global: 'global',
87
+
88
+ Image: 'image',
87
89
  } as const;
88
90
 
89
91
  export type HotkeyId = (typeof HotkeyEnum)[keyof typeof HotkeyEnum];
@@ -1,5 +1,6 @@
1
1
  import { PluginQueryParams, SystemDependency } from '@lobehub/market-sdk';
2
2
 
3
+ import { MCPErrorType } from '@/libs/mcp';
3
4
  import { MCPInstallStep } from '@/store/tool/slices/mcpStore';
4
5
 
5
6
  export interface CheckMcpInstallParams {
@@ -147,12 +148,7 @@ export interface MCPErrorInfo {
147
148
  /**
148
149
  * 错误类型
149
150
  */
150
- type:
151
- | 'CONNECTION_FAILED'
152
- | 'PROCESS_SPAWN_ERROR'
153
- | 'INITIALIZATION_TIMEOUT'
154
- | 'VALIDATION_ERROR'
155
- | 'UNKNOWN_ERROR';
151
+ type: MCPErrorType;
156
152
  }
157
153
 
158
154
  export interface MCPInstallProgress {
@@ -27,6 +27,14 @@ export interface CustomPluginParams {
27
27
  command?: string;
28
28
  type: 'http' | 'stdio';
29
29
  url?: string;
30
+ // 新增认证配置支持
31
+ auth?: {
32
+ type: 'none' | 'bearer' | 'oauth2';
33
+ token?: string; // Bearer Token
34
+ accessToken?: string; // OAuth2 Access Token
35
+ };
36
+ // 新增 headers 配置支持
37
+ headers?: Record<string, string>;
30
38
  };
31
39
  avatar?: string;
32
40
  description?: string;
@@ -3,6 +3,10 @@ export interface OpenAICompatibleKeyVault {
3
3
  baseURL?: string;
4
4
  }
5
5
 
6
+ export interface FalKeyVault {
7
+ apiKey?: string;
8
+ }
9
+
6
10
  export interface AzureOpenAIKeyVault {
7
11
  apiKey?: string;
8
12
  apiVersion?: string;
@@ -43,6 +47,7 @@ export interface UserKeyVaults extends SearchEngineKeyVaults {
43
47
  cloudflare?: CloudflareKeyVault;
44
48
  cohere?: OpenAICompatibleKeyVault;
45
49
  deepseek?: OpenAICompatibleKeyVault;
50
+ fal?: FalKeyVault;
46
51
  fireworksai?: OpenAICompatibleKeyVault;
47
52
  giteeai?: OpenAICompatibleKeyVault;
48
53
  github?: OpenAICompatibleKeyVault;
@@ -11,6 +11,7 @@ export interface QueryRewriteSystemAgent extends Omit<SystemAgentItem, 'enabled'
11
11
 
12
12
  export interface UserSystemAgentConfig {
13
13
  agentMeta: SystemAgentItem;
14
+ generationTopic: SystemAgentItem;
14
15
  historyCompress: SystemAgentItem;
15
16
  queryRewrite: QueryRewriteSystemAgent;
16
17
  thread: SystemAgentItem;
@@ -1,19 +1,48 @@
1
- export const downloadFile = async (url: string, fileName: string) => {
1
+ export const downloadFile = async (
2
+ url: string,
3
+ fileName: string,
4
+ fallbackToOpen: boolean = true,
5
+ ) => {
2
6
  try {
3
- const res = await fetch(url);
4
- const blob = await res.blob();
7
+ // Use better CORS handling similar to download-image.ts
8
+ const response = await fetch(url, {
9
+ // Avoid image disk cache which can cause incorrect CORS headers
10
+ cache: 'no-store',
5
11
 
12
+ credentials: 'omit',
13
+
14
+ mode: 'cors',
15
+ });
16
+
17
+ if (!response.ok) {
18
+ throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
19
+ }
20
+
21
+ const blob = await response.blob();
22
+
23
+ // Create download link
6
24
  const blobUrl = window.URL.createObjectURL(blob);
7
25
  const link = document.createElement('a');
8
26
  link.href = blobUrl;
9
27
  link.download = fileName;
10
28
  link.style.display = 'none';
29
+
30
+ // Trigger download
11
31
  document.body.append(link);
12
32
  link.click();
33
+
34
+ // Cleanup
13
35
  link.remove();
14
36
  window.URL.revokeObjectURL(blobUrl);
15
37
  } catch (error) {
16
38
  console.log('Download failed:', error);
17
- window.open(url);
39
+
40
+ // Fallback: open in new tab if enabled
41
+ if (fallbackToOpen) {
42
+ window.open(url);
43
+ } else {
44
+ // Re-throw the error if fallback is disabled
45
+ throw error;
46
+ }
18
47
  }
19
48
  };
@@ -0,0 +1,105 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import { MAX_SEED, generateUniqueSeeds } from './number';
4
+
5
+ describe('number utilities', () => {
6
+ describe('MAX_SEED constant', () => {
7
+ it('should be 2^31 - 1', () => {
8
+ expect(MAX_SEED).toBe(2147483647);
9
+ expect(MAX_SEED).toBe(2 ** 31 - 1);
10
+ });
11
+ });
12
+
13
+ describe('generateUniqueSeeds', () => {
14
+ it('should generate the correct number of seeds', () => {
15
+ const seedCount = 5;
16
+ const seeds = generateUniqueSeeds(seedCount);
17
+
18
+ expect(seeds).toHaveLength(seedCount);
19
+ });
20
+
21
+ it('should generate unique seeds', () => {
22
+ const seedCount = 10;
23
+ const seeds = generateUniqueSeeds(seedCount);
24
+
25
+ // Convert to Set to check uniqueness
26
+ const uniqueSeeds = new Set(seeds);
27
+ expect(uniqueSeeds.size).toBe(seedCount);
28
+ });
29
+
30
+ it('should generate seeds within valid range', () => {
31
+ const seedCount = 20;
32
+ const seeds = generateUniqueSeeds(seedCount);
33
+
34
+ seeds.forEach((seed) => {
35
+ expect(seed).toBeGreaterThanOrEqual(0);
36
+ expect(seed).toBeLessThanOrEqual(MAX_SEED);
37
+ expect(Number.isInteger(seed)).toBe(true);
38
+ });
39
+ });
40
+
41
+ it('should handle edge case of 0 seeds', () => {
42
+ const seeds = generateUniqueSeeds(0);
43
+
44
+ expect(seeds).toHaveLength(0);
45
+ expect(Array.isArray(seeds)).toBe(true);
46
+ });
47
+
48
+ it('should handle edge case of 1 seed', () => {
49
+ const seeds = generateUniqueSeeds(1);
50
+
51
+ expect(seeds).toHaveLength(1);
52
+ expect(seeds[0]).toBeGreaterThanOrEqual(0);
53
+ expect(seeds[0]).toBeLessThanOrEqual(MAX_SEED);
54
+ });
55
+
56
+ it('should generate different results on different calls', () => {
57
+ const seedCount = 5;
58
+
59
+ // Generate seeds at different times to ensure different timestamps
60
+ const seeds1 = generateUniqueSeeds(seedCount);
61
+
62
+ // Add a small delay to ensure different timestamp
63
+ vi.useFakeTimers();
64
+ vi.advanceTimersByTime(1);
65
+ const seeds2 = generateUniqueSeeds(seedCount);
66
+ vi.useRealTimers();
67
+
68
+ // The arrays should not be identical (very low probability of collision)
69
+ expect(seeds1).not.toEqual(seeds2);
70
+ });
71
+
72
+ it('should handle larger seed counts', () => {
73
+ const seedCount = 100;
74
+ const seeds = generateUniqueSeeds(seedCount);
75
+
76
+ expect(seeds).toHaveLength(seedCount);
77
+
78
+ // Verify uniqueness
79
+ const uniqueSeeds = new Set(seeds);
80
+ expect(uniqueSeeds.size).toBe(seedCount);
81
+
82
+ // Verify all are in valid range
83
+ seeds.forEach((seed) => {
84
+ expect(seed).toBeGreaterThanOrEqual(0);
85
+ expect(seed).toBeLessThanOrEqual(MAX_SEED);
86
+ });
87
+ });
88
+
89
+ it('should use current timestamp as initial seed', () => {
90
+ const mockTimestamp = 1234567890123;
91
+ vi.useFakeTimers();
92
+ vi.setSystemTime(mockTimestamp);
93
+
94
+ // Mock Date.now to verify it's being used
95
+ const dateSpy = vi.spyOn(Date, 'now').mockReturnValue(mockTimestamp);
96
+
97
+ generateUniqueSeeds(3);
98
+
99
+ expect(dateSpy).toHaveBeenCalled();
100
+
101
+ dateSpy.mockRestore();
102
+ vi.useRealTimers();
103
+ });
104
+ });
105
+ });