@lobehub/chat 1.98.2 → 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 +27 -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 +9 -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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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 +15 -0
  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,100 @@
1
+ export default {
2
+ config: {
3
+ aspectRatio: {
4
+ label: '宽高比',
5
+ lock: '锁定宽高比',
6
+ unlock: '解锁宽高比',
7
+ },
8
+ header: {
9
+ desc: '简单描述,即刻创作',
10
+ title: '绘画',
11
+ },
12
+ height: {
13
+ label: '高度',
14
+ },
15
+ imageNum: {
16
+ label: '图片数量',
17
+ },
18
+ imageUrl: {
19
+ label: '参考图',
20
+ },
21
+ imageUrls: {
22
+ label: '参考图',
23
+ },
24
+ model: {
25
+ label: '模型',
26
+ },
27
+ prompt: {
28
+ placeholder: '描述你想要生成的内容',
29
+ },
30
+ seed: {
31
+ label: '种子',
32
+ random: '随机种子',
33
+ },
34
+ size: {
35
+ label: '尺寸',
36
+ },
37
+ steps: {
38
+ label: '步数',
39
+ },
40
+ title: 'AI 绘画',
41
+ width: {
42
+ label: '宽度',
43
+ },
44
+ },
45
+ generation: {
46
+ actions: {
47
+ applySeed: '应用种子',
48
+ copyError: '复制错误信息',
49
+ copyPrompt: '复制提示词',
50
+ copySeed: '复制种子',
51
+ delete: '删除',
52
+ deleteBatch: '删除批次',
53
+ download: '下载',
54
+ downloadFailed: '下载图片失败,请检查网络连接或 S3 存储服务的跨域配置',
55
+ errorCopied: '错误信息已复制到剪贴板',
56
+ errorCopyFailed: '复制错误信息失败',
57
+ generate: '生成',
58
+ promptCopied: '提示词已复制到剪贴板',
59
+ promptCopyFailed: '复制提示词失败',
60
+ reuseSettings: '复用设置',
61
+ seedApplied: '种子已应用到配置',
62
+ seedApplyFailed: '应用种子失败',
63
+ seedCopied: '种子已复制到剪贴板',
64
+ seedCopyFailed: '复制种子失败',
65
+ },
66
+ metadata: {
67
+ count: '{{count}} 张图片',
68
+ },
69
+ status: {
70
+ failed: '生成失败',
71
+ generating: '生成中...',
72
+ },
73
+ },
74
+ notSupportGuide: {
75
+ desc: '当前部署实例为客户端数据库模式,无法使用 AI 图像生成功能。请切换到<1>服务端数据库部署模式</1>,或直接使用 <3>LobeChat Cloud</3>',
76
+ features: {
77
+ fileIntegration: {
78
+ desc: '与文件管理系统深度整合,生成的图片自动保存到文件系统,支持统一管理和组织',
79
+ title: '文件系统互通',
80
+ },
81
+ llmAssisted: {
82
+ desc: '结合大语言模型能力,智能优化和扩展提示词,提升图像生成质量(Coming Soon)',
83
+ title: 'LLM 辅助生图',
84
+ },
85
+ multiProviders: {
86
+ desc: '支持多种 AI 绘画服务商,包括 OpenAI gpt-image-1、Google Imagen、FAL.ai 等,提供丰富的模型选择',
87
+ title: '多 Providers 支持',
88
+ },
89
+ },
90
+ title: '当前部署模式不支持 AI 绘画',
91
+ },
92
+ topic: {
93
+ createNew: '新建主题',
94
+ deleteConfirm: '删除生成主题',
95
+ deleteConfirmDesc: '即将删除该生成主题,删除后将不可恢复,请谨慎操作。',
96
+ empty: '暂无生成主题',
97
+ title: '绘画主题',
98
+ untitled: '默认主题',
99
+ },
100
+ };
@@ -10,6 +10,7 @@ import electron from './electron';
10
10
  import error from './error';
11
11
  import file from './file';
12
12
  import hotkey from './hotkey';
13
+ import image from './image';
13
14
  import knowledgeBase from './knowledgeBase';
14
15
  import metadata from './metadata';
15
16
  import migration from './migration';
@@ -39,6 +40,7 @@ const resources = {
39
40
  error,
40
41
  file,
41
42
  hotkey,
43
+ image,
42
44
  knowledgeBase,
43
45
  metadata,
44
46
  migration,
@@ -37,6 +37,10 @@ export default {
37
37
  search: '搜索',
38
38
  title: '发现',
39
39
  },
40
+ image: {
41
+ description: '{{appName}} 带给你最好的 GPT Image, Flux, Midjourney, Stable Diffusion 使用体验',
42
+ title: 'AI 绘画',
43
+ },
40
44
  plugins: {
41
45
  description:
42
46
  '搜素、图表生成、学术、图像生成、视频生成、语音生成、自动化工作流,定制 ChatGPT / Claude 专属的 ToolCall 插件能力',
@@ -68,6 +68,8 @@ export default {
68
68
  customSessionToken: '自定义 Session Token',
69
69
  description:
70
70
  '输入你的 AWS AccessKeyId / SecretAccessKey 即可开始会话。应用不会记录你的鉴权配置',
71
+ imageGenerationDescription:
72
+ '输入你的 AWS AccessKeyId / SecretAccessKey 即可开始生成。应用不会记录你的鉴权配置',
71
73
  title: '使用自定义 Bedrock 鉴权信息',
72
74
  },
73
75
  },
@@ -57,12 +57,28 @@ export default {
57
57
  },
58
58
  },
59
59
  mcp: {
60
+ advanced: {
61
+ title: '高级设置',
62
+ },
60
63
  args: {
61
64
  desc: '传递给执行命令的参数列表,一般在这里输入 MCP 服务器名称,或启动脚本路径',
62
65
  label: '命令参数',
63
66
  placeholder: '例如:mcp-hello-world',
64
67
  required: '请输入启动参数',
65
68
  },
69
+ auth: {
70
+ bear: 'API Key',
71
+ desc: '选择 MCP 服务器的认证方式',
72
+ label: '认证类型',
73
+ none: '无需认证',
74
+ placeholder: '请选择认证类型',
75
+ token: {
76
+ desc: '输入你的 API Key 或 Bearer Token',
77
+ label: 'API Key',
78
+ placeholder: 'sk-xxxxx',
79
+ required: '请输入认证令牌',
80
+ },
81
+ },
66
82
  avatar: {
67
83
  label: '插件图标',
68
84
  },
@@ -90,6 +106,11 @@ export default {
90
106
  label: 'MCP Server 环境变量',
91
107
  stringifyError: '无法序列化参数,请检查参数格式',
92
108
  },
109
+ headers: {
110
+ add: '新增一行',
111
+ desc: '输入请求头',
112
+ label: 'HTTP Headers',
113
+ },
93
114
  identifier: {
94
115
  desc: '为你的 MCP 插件指定一个名称,需要使用英文字符',
95
116
  invalid: '标识符只能包含字母、数字、连字符和下划线',
@@ -271,6 +292,7 @@ export default {
271
292
  showDetails: '查看详情',
272
293
  },
273
294
  errorTypes: {
295
+ AUTHORIZATION_ERROR: '授权验证错误',
274
296
  CONNECTION_FAILED: '连接失败',
275
297
  INITIALIZATION_TIMEOUT: '初始化超时',
276
298
  PROCESS_SPAWN_ERROR: '进程启动失败',
@@ -488,6 +488,11 @@ export default {
488
488
  placeholder: '请输入自定义提示词',
489
489
  title: '自定义提示词',
490
490
  },
491
+ generationTopic: {
492
+ label: 'AI 绘画话题命名模型',
493
+ modelDesc: '指定用于 AI 绘画自动命名话题的模型',
494
+ title: 'AI 绘画自动命名话题',
495
+ },
491
496
  helpInfo: '当创建新助手时,将以默认助手设置作为预设值。',
492
497
  historyCompress: {
493
498
  label: '会话历史模型',
package/src/middleware.ts CHANGED
@@ -37,6 +37,7 @@ export const config = {
37
37
  '/chat(.*)',
38
38
  '/changelog(.*)',
39
39
  '/settings(.*)',
40
+ '/image',
40
41
  '/files',
41
42
  '/files(.*)',
42
43
  '/repos(.*)',
@@ -1,4 +1,4 @@
1
- import { ElectronIpcClient } from '@lobechat/electron-server-ipc';
1
+ import { CreateFileParams, ElectronIpcClient, FileMetadata } from '@lobechat/electron-server-ipc';
2
2
 
3
3
  import packageJSON from '@/../apps/desktop/package.json';
4
4
 
@@ -27,12 +27,20 @@ class LobeHubElectronIpcClient extends ElectronIpcClient {
27
27
  return this.sendRequest<string>('getStaticFilePath', id);
28
28
  };
29
29
 
30
+ getFileHTTPURL = async (path: string) => {
31
+ return this.sendRequest<string>('getFileHTTPURL', path);
32
+ };
33
+
30
34
  deleteFiles = async (paths: string[]) => {
31
35
  return this.sendRequest<{ errors?: { message: string; path: string }[]; success: boolean }>(
32
36
  'deleteFiles',
33
37
  paths,
34
38
  );
35
39
  };
40
+
41
+ createFile = async (params: CreateFileParams) => {
42
+ return this.sendRequest<{ metadata: FileMetadata; success: boolean }>('createFile', params);
43
+ };
36
44
  }
37
45
 
38
46
  export const electronIpcClient = new LobeHubElectronIpcClient(packageJSON.name);
@@ -9,6 +9,8 @@ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
9
9
  import { z } from 'zod';
10
10
 
11
11
  import { fileEnv } from '@/config/file';
12
+ import { YEAR } from '@/utils/units';
13
+ import { inferContentTypeFromImageUrl } from '@/utils/url';
12
14
 
13
15
  export const fileSchema = z.object({
14
16
  Key: z.string(),
@@ -142,4 +144,17 @@ export class S3 {
142
144
 
143
145
  return this.client.send(command);
144
146
  }
147
+
148
+ public async uploadMedia(key: string, buffer: Buffer) {
149
+ const command = new PutObjectCommand({
150
+ ACL: this.setAcl ? 'public-read' : undefined,
151
+ Body: buffer,
152
+ Bucket: this.bucket,
153
+ CacheControl: `public, max-age=${YEAR}`,
154
+ ContentType: inferContentTypeFromImageUrl(key)!,
155
+ Key: key,
156
+ });
157
+
158
+ await this.client.send(command);
159
+ }
145
160
  }
@@ -1,4 +1,5 @@
1
1
  import { createTRPCClient, httpLink } from '@trpc/client';
2
+ import debug from 'debug';
2
3
  import superjson from 'superjson';
3
4
  import urlJoin from 'url-join';
4
5
 
@@ -13,7 +14,11 @@ import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
13
14
  import { asyncRouter } from './index';
14
15
  import type { AsyncRouter } from './index';
15
16
 
17
+ const log = debug('lobe-image:async-caller');
18
+
16
19
  export const createAsyncServerClient = async (userId: string, payload: JWTPayload) => {
20
+ log('Creating async server client for userId: %s', userId);
21
+
17
22
  const gateKeeper = await KeyVaultsGateKeeper.initWithEnvKey();
18
23
  const headers: Record<string, string> = {
19
24
  Authorization: `Bearer ${serverDBEnv.KEY_VAULTS_SECRET}`,
@@ -24,7 +29,7 @@ export const createAsyncServerClient = async (userId: string, payload: JWTPayloa
24
29
  headers['x-vercel-protection-bypass'] = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
25
30
  }
26
31
 
27
- return createTRPCClient<AsyncRouter>({
32
+ const client = createTRPCClient<AsyncRouter>({
28
33
  links: [
29
34
  httpLink({
30
35
  headers,
@@ -33,6 +38,9 @@ export const createAsyncServerClient = async (userId: string, payload: JWTPayloa
33
38
  }),
34
39
  ],
35
40
  });
41
+
42
+ log('Async server client created successfully for userId: %s', userId);
43
+ return client;
36
44
  };
37
45
 
38
46
  /**
@@ -0,0 +1,253 @@
1
+ import debug from 'debug';
2
+ import { z } from 'zod';
3
+
4
+ import { ASYNC_TASK_TIMEOUT, AsyncTaskModel } from '@/database/models/asyncTask';
5
+ import { FileModel } from '@/database/models/file';
6
+ import { GenerationModel } from '@/database/models/generation';
7
+ import { AgentRuntimeErrorType } from '@/libs/model-runtime/error';
8
+ import { RuntimeImageGenParams } from '@/libs/standard-parameters/meta-schema';
9
+ import { asyncAuthedProcedure, asyncRouter as router } from '@/libs/trpc/async';
10
+ import { initAgentRuntimeWithUserPayload } from '@/server/modules/AgentRuntime';
11
+ import { GenerationService } from '@/server/services/generation';
12
+ import { AsyncTaskError, AsyncTaskErrorType, AsyncTaskStatus } from '@/types/asyncTask';
13
+
14
+ const log = debug('lobe-image:async');
15
+
16
+ // Constants for better maintainability
17
+ const FILENAME_MAX_LENGTH = 50;
18
+ const IMAGE_URL_PREVIEW_LENGTH = 100;
19
+
20
+ const imageProcedure = asyncAuthedProcedure.use(async (opts) => {
21
+ const { ctx } = opts;
22
+
23
+ return opts.next({
24
+ ctx: {
25
+ asyncTaskModel: new AsyncTaskModel(ctx.serverDB, ctx.userId),
26
+ fileModel: new FileModel(ctx.serverDB, ctx.userId),
27
+ generationModel: new GenerationModel(ctx.serverDB, ctx.userId),
28
+ generationService: new GenerationService(ctx.serverDB, ctx.userId),
29
+ },
30
+ });
31
+ });
32
+
33
+ const createImageInputSchema = z.object({
34
+ generationId: z.string(),
35
+ model: z.string(),
36
+ params: z
37
+ .object({
38
+ cfg: z.number().optional(),
39
+ height: z.number().optional(),
40
+ imageUrls: z.array(z.string()).optional(),
41
+ prompt: z.string(),
42
+ seed: z.number().nullable().optional(),
43
+ steps: z.number().optional(),
44
+ width: z.number().optional(),
45
+ })
46
+ .passthrough(),
47
+ provider: z.string(),
48
+ taskId: z.string(),
49
+ });
50
+
51
+ /**
52
+ * Checks if the abort signal has been triggered and throws an error if so
53
+ */
54
+ const checkAbortSignal = (signal: AbortSignal) => {
55
+ if (signal.aborted) {
56
+ throw new Error('Operation was aborted');
57
+ }
58
+ };
59
+
60
+ /**
61
+ * Categorizes errors into appropriate AsyncTaskErrorType
62
+ */
63
+ const categorizeError = (
64
+ error: any,
65
+ isAborted: boolean,
66
+ ): { errorMessage: string; errorType: AsyncTaskErrorType } => {
67
+ // FIXME: 401 的问题应该放到 agentRuntime 中处理会更好
68
+ if (error.errorType === AgentRuntimeErrorType.InvalidProviderAPIKey || error?.status === 401) {
69
+ return {
70
+ errorMessage: 'Invalid provider API key, please check your API key',
71
+ errorType: AsyncTaskErrorType.InvalidProviderAPIKey,
72
+ };
73
+ }
74
+
75
+ if (error instanceof AsyncTaskError) {
76
+ return {
77
+ errorMessage: typeof error.body === 'string' ? error.body : error.body.detail,
78
+ errorType: error.name as AsyncTaskErrorType,
79
+ };
80
+ }
81
+
82
+ if (isAborted || error.message?.includes('aborted')) {
83
+ return {
84
+ errorMessage: 'Image generation task timed out, please try again',
85
+ errorType: AsyncTaskErrorType.Timeout,
86
+ };
87
+ }
88
+
89
+ if (error.message?.includes('timeout') || error.name === 'TimeoutError') {
90
+ return {
91
+ errorMessage: 'Image generation task timed out, please try again',
92
+ errorType: AsyncTaskErrorType.Timeout,
93
+ };
94
+ }
95
+
96
+ if (error.message?.includes('network') || error.name === 'NetworkError') {
97
+ return {
98
+ errorMessage: error.message || 'Network error occurred during image generation',
99
+ errorType: AsyncTaskErrorType.ServerError,
100
+ };
101
+ }
102
+
103
+ return {
104
+ errorMessage: error.message || 'Unknown error occurred during image generation',
105
+ errorType: AsyncTaskErrorType.ServerError,
106
+ };
107
+ };
108
+
109
+ export const imageRouter = router({
110
+ createImage: imageProcedure.input(createImageInputSchema).mutation(async ({ input, ctx }) => {
111
+ const { taskId, generationId, provider, model, params } = input;
112
+
113
+ log('Starting async image generation: %O', {
114
+ generationId,
115
+ imageParams: { height: params.height, steps: params.steps, width: params.width },
116
+ model,
117
+ prompt: params.prompt,
118
+ provider,
119
+ taskId,
120
+ });
121
+
122
+ log('Updating task status to Processing: %s', taskId);
123
+ await ctx.asyncTaskModel.update(taskId, { status: AsyncTaskStatus.Processing });
124
+
125
+ // Use AbortController to prevent resource leaks
126
+ const abortController = new AbortController();
127
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
128
+
129
+ try {
130
+ const imageGenerationPromise = async (signal: AbortSignal) => {
131
+ log('Initializing agent runtime for provider: %s', provider);
132
+ const agentRuntime = await initAgentRuntimeWithUserPayload(provider, ctx.jwtPayload);
133
+
134
+ // Check if operation has been cancelled
135
+ checkAbortSignal(signal);
136
+
137
+ log('Agent runtime initialized, calling createImage');
138
+ const response = await agentRuntime.createImage({
139
+ model,
140
+ params: params as unknown as RuntimeImageGenParams,
141
+ });
142
+
143
+ if (!response) {
144
+ log('Create image response is empty');
145
+ throw new Error('Create image response is empty');
146
+ }
147
+
148
+ // Check if operation has been cancelled
149
+ checkAbortSignal(signal);
150
+
151
+ log('Image generation successful: %O', {
152
+ height: response.height,
153
+ imageUrl: response.imageUrl.startsWith('data:')
154
+ ? response.imageUrl.slice(0, IMAGE_URL_PREVIEW_LENGTH) + '...'
155
+ : response.imageUrl,
156
+ width: response.width,
157
+ });
158
+
159
+ log('Transforming image for generation');
160
+ const { imageUrl, width, height } = response;
161
+ const { image, thumbnailImage } =
162
+ await ctx.generationService.transformImageForGeneration(imageUrl);
163
+
164
+ // Check if operation has been cancelled
165
+ checkAbortSignal(signal);
166
+
167
+ log('Uploading image for generation');
168
+ const { imageUrl: uploadedImageUrl, thumbnailImageUrl } =
169
+ await ctx.generationService.uploadImageForGeneration(image, thumbnailImage);
170
+
171
+ // Check if operation has been cancelled
172
+ checkAbortSignal(signal);
173
+
174
+ log('Updating generation asset and file');
175
+ await ctx.generationModel.createAssetAndFile(
176
+ generationId,
177
+ {
178
+ height: height ?? image.height,
179
+ originalUrl: imageUrl,
180
+ thumbnailUrl: thumbnailImageUrl,
181
+ type: 'image',
182
+ url: uploadedImageUrl,
183
+ width: width ?? image.width,
184
+ },
185
+ {
186
+ fileHash: image.hash,
187
+ fileType: image.mime,
188
+ metadata: {
189
+ generationId,
190
+ height: image.height,
191
+ width: image.width,
192
+ },
193
+ name: `${params.prompt.slice(0, FILENAME_MAX_LENGTH)}.${image.extension}`,
194
+ // Use first 50 characters of prompt as filename
195
+ size: image.size,
196
+ url: uploadedImageUrl,
197
+ },
198
+ );
199
+
200
+ log('Updating task status to Success: %s', taskId);
201
+ await ctx.asyncTaskModel.update(taskId, {
202
+ status: AsyncTaskStatus.Success,
203
+ });
204
+
205
+ log('Async image generation completed successfully: %s', taskId);
206
+ return { success: true };
207
+ };
208
+
209
+ // Set timeout to cancel operation and prevent resource leaks
210
+ timeoutId = setTimeout(() => {
211
+ log('Image generation timeout, aborting operation: %s', taskId);
212
+ abortController.abort();
213
+ }, ASYNC_TASK_TIMEOUT);
214
+
215
+ const result = await imageGenerationPromise(abortController.signal);
216
+
217
+ // Clean up timeout timer
218
+ if (timeoutId) {
219
+ clearTimeout(timeoutId);
220
+ timeoutId = null;
221
+ }
222
+
223
+ return result;
224
+ } catch (error: any) {
225
+ // Clean up timeout timer
226
+ if (timeoutId) {
227
+ clearTimeout(timeoutId);
228
+ timeoutId = null;
229
+ }
230
+
231
+ log('Async image generation failed: %O', {
232
+ error: error.message || error,
233
+ generationId,
234
+ taskId,
235
+ });
236
+
237
+ // Improved error categorization logic
238
+ const { errorType, errorMessage } = categorizeError(error, abortController.signal.aborted);
239
+
240
+ await ctx.asyncTaskModel.update(taskId, {
241
+ error: new AsyncTaskError(errorType, errorMessage),
242
+ status: AsyncTaskStatus.Error,
243
+ });
244
+
245
+ log('Task status updated to Error: %s, errorType: %s', taskId, errorType);
246
+
247
+ return {
248
+ message: `Image generation ${taskId} failed: ${errorMessage}`,
249
+ success: false,
250
+ };
251
+ }
252
+ }),
253
+ });
@@ -1,11 +1,13 @@
1
1
  import { publicProcedure, asyncRouter as router } from '@/libs/trpc/async';
2
2
 
3
3
  import { fileRouter } from './file';
4
+ import { imageRouter } from './image';
4
5
  import { ragEvalRouter } from './ragEval';
5
6
 
6
7
  export const asyncRouter = router({
7
8
  file: fileRouter,
8
9
  healthcheck: publicProcedure.query(() => "i'm live!"),
10
+ image: imageRouter,
9
11
  ragEval: ragEvalRouter,
10
12
  });
11
13
 
@@ -38,6 +38,7 @@ describe('aiProviderRouter', () => {
38
38
  enabledAiModels: [],
39
39
  enabledAiProviders: [],
40
40
  runtimeConfig: {},
41
+ enabledImageAiProviders: [],
41
42
  };
42
43
 
43
44
  beforeEach(() => {