@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,25 @@
1
+ import prand from 'pure-rand';
2
+
3
+ export const MAX_SEED = 2 ** 31 - 1;
4
+ export function generateUniqueSeeds(seedCount: number): number[] {
5
+ // Use current timestamp as the initial seed
6
+ const initialSeed = Date.now();
7
+
8
+ // Create an instance of Xoroshiro128+ random number generator
9
+ let rng = prand.xoroshiro128plus(initialSeed);
10
+
11
+ const seeds = new Set<number>();
12
+
13
+ while (seeds.size < seedCount) {
14
+ // Generate a 31-bit random integer (PostgreSQL INTEGER compatible)
15
+ const [randomInt, nextRng] = prand.uniformIntDistribution(0, MAX_SEED, rng);
16
+
17
+ // Update RNG state
18
+ rng = nextRng;
19
+
20
+ // Add to the set, ensuring uniqueness
21
+ seeds.add(randomInt);
22
+ }
23
+
24
+ return Array.from(seeds);
25
+ }
@@ -63,11 +63,13 @@ describe('parseDefaultThemeFromCountry', () => {
63
63
  timezones: ['America/New_York'],
64
64
  });
65
65
 
66
- const mockDate = new Date('2025-04-01T12:00:00');
66
+ // 设置UTC时间16:00,这样在纽约时区(EDT,UTC-4)就是12:00
67
+ const mockDate = new Date('2025-04-01T16:00:00.000Z');
67
68
  vi.setSystemTime(mockDate);
68
69
 
69
70
  const request = mockRequest();
70
- expect(parseDefaultThemeFromCountry(request)).toBe('light');
71
+ const result = parseDefaultThemeFromCountry(request);
72
+ expect(result).toBe('light');
71
73
  });
72
74
 
73
75
  it('should return dark theme during night hours', () => {
@@ -78,7 +80,8 @@ describe('parseDefaultThemeFromCountry', () => {
78
80
  timezones: ['America/New_York'],
79
81
  });
80
82
 
81
- const mockDate = new Date('2025-04-01T22:00:00');
83
+ // 设置UTC时间02:00,这样在纽约时区(EDT,UTC-4)就是22:00
84
+ const mockDate = new Date('2025-04-01T02:00:00.000Z');
82
85
  vi.setSystemTime(mockDate);
83
86
 
84
87
  const request = mockRequest();
@@ -0,0 +1,152 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { setNamespace } from './storeDebug';
4
+
5
+ describe('storeDebug utilities', () => {
6
+ describe('setNamespace', () => {
7
+ it('should create a namespaced action creator', () => {
8
+ const createAction = setNamespace('user');
9
+
10
+ expect(typeof createAction).toBe('function');
11
+ });
12
+
13
+ it('should return string action type when no payload provided', () => {
14
+ const createAction = setNamespace('user');
15
+ const actionType = createAction('login');
16
+
17
+ expect(actionType).toBe('user/login');
18
+ expect(typeof actionType).toBe('string');
19
+ });
20
+
21
+ it('should return action object when payload provided', () => {
22
+ const createAction = setNamespace('user');
23
+ const action = createAction('login', { email: 'test@example.com' });
24
+
25
+ expect(action).toEqual({
26
+ type: 'user/login',
27
+ payload: { email: 'test@example.com' },
28
+ });
29
+ expect(typeof action).toBe('object');
30
+ });
31
+
32
+ it('should handle empty namespace', () => {
33
+ const createAction = setNamespace('');
34
+ const actionType = createAction('action');
35
+
36
+ expect(actionType).toBe('action');
37
+ });
38
+
39
+ it('should handle truthy payload values (return objects)', () => {
40
+ const createAction = setNamespace('data');
41
+
42
+ // String payload
43
+ const stringAction = createAction('update', 'hello');
44
+ expect(stringAction).toEqual({
45
+ type: 'data/update',
46
+ payload: 'hello',
47
+ });
48
+
49
+ // Number payload
50
+ const numberAction = createAction('increment', 42);
51
+ expect(numberAction).toEqual({
52
+ type: 'data/increment',
53
+ payload: 42,
54
+ });
55
+
56
+ // Boolean payload
57
+ const booleanAction = createAction('toggle', true);
58
+ expect(booleanAction).toEqual({
59
+ type: 'data/toggle',
60
+ payload: true,
61
+ });
62
+
63
+ // Array payload
64
+ const arrayAction = createAction('batch', [1, 2, 3]);
65
+ expect(arrayAction).toEqual({
66
+ type: 'data/batch',
67
+ payload: [1, 2, 3],
68
+ });
69
+ });
70
+
71
+ it('should handle falsy payload values (return strings)', () => {
72
+ const createAction = setNamespace('data');
73
+
74
+ // Null payload (falsy)
75
+ const nullAction = createAction('reset', null);
76
+ expect(nullAction).toBe('data/reset');
77
+
78
+ // Undefined payload (falsy)
79
+ const undefinedAction = createAction('clear', undefined);
80
+ expect(undefinedAction).toBe('data/clear');
81
+
82
+ // Zero payload (falsy)
83
+ const zeroAction = createAction('setValue', 0);
84
+ expect(zeroAction).toBe('data/setValue');
85
+
86
+ // Empty string payload (falsy)
87
+ const emptyStringAction = createAction('clearField', '');
88
+ expect(emptyStringAction).toBe('data/clearField');
89
+
90
+ // False payload (falsy)
91
+ const falseAction = createAction('disable', false);
92
+ expect(falseAction).toBe('data/disable');
93
+ });
94
+
95
+ it('should handle complex nested object payload', () => {
96
+ const createAction = setNamespace('app');
97
+ const complexPayload = {
98
+ user: {
99
+ id: 123,
100
+ profile: {
101
+ name: 'John Doe',
102
+ settings: {
103
+ theme: 'dark',
104
+ notifications: true,
105
+ },
106
+ },
107
+ },
108
+ metadata: {
109
+ timestamp: Date.now(),
110
+ source: 'api',
111
+ },
112
+ };
113
+
114
+ const action = createAction('userUpdate', complexPayload);
115
+
116
+ expect(action).toEqual({
117
+ type: 'app/userUpdate',
118
+ payload: complexPayload,
119
+ });
120
+ });
121
+
122
+ it('should create unique namespaces for different instances', () => {
123
+ const userActions = setNamespace('user');
124
+ const postActions = setNamespace('post');
125
+
126
+ const userLogin = userActions('login');
127
+ const postCreate = postActions('create');
128
+
129
+ expect(userLogin).toBe('user/login');
130
+ expect(postCreate).toBe('post/create');
131
+ });
132
+
133
+ it('should handle special characters in namespace and type', () => {
134
+ const createAction = setNamespace('my-feature_v2');
135
+ const actionType = createAction('save-data_with-id');
136
+
137
+ expect(actionType).toBe('my-feature_v2/save-data_with-id');
138
+ });
139
+
140
+ it('should maintain type consistency across calls', () => {
141
+ const createAction = setNamespace('counter');
142
+
143
+ // Multiple calls should work consistently
144
+ const increment1 = createAction('increment');
145
+ const increment2 = createAction('increment');
146
+
147
+ expect(increment1).toBe('counter/increment');
148
+ expect(increment2).toBe('counter/increment');
149
+ expect(increment1).toBe(increment2);
150
+ });
151
+ });
152
+ });
@@ -1,11 +1,20 @@
1
1
  export const setNamespace = (namespace: string) => {
2
- return (type: string, payload?: any) => {
2
+ return <P = undefined>(type: string, payload?: P) => {
3
3
  const name = [namespace, type].filter(Boolean).join('/');
4
- return payload
5
- ? {
6
- payload,
7
- type: name,
8
- }
9
- : name;
4
+ return (
5
+ payload
6
+ ? {
7
+ payload,
8
+ type: name,
9
+ }
10
+ : name
11
+ ) as P extends undefined ? string : { payload: P; type: string };
10
12
  };
11
13
  };
14
+
15
+ export type Action =
16
+ | string
17
+ | {
18
+ [x: string | number | symbol]: unknown;
19
+ type: string;
20
+ };
@@ -0,0 +1,259 @@
1
+ import dayjs from 'dayjs';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import {
5
+ daysAgo,
6
+ getYYYYmmddHHMMss,
7
+ hoursAgo,
8
+ lastMonth,
9
+ monthsAgo,
10
+ thisMonth,
11
+ thisQuarter,
12
+ thisWeek,
13
+ thisYear,
14
+ today,
15
+ weeksAgo,
16
+ } from './time';
17
+
18
+ describe('time utilities', () => {
19
+ beforeEach(() => {
20
+ vi.useFakeTimers();
21
+ // Set a fixed date for consistent testing: 2024-06-15 14:30:45 (local time)
22
+ vi.setSystemTime(new Date('2024-06-15T14:30:45.123'));
23
+ });
24
+
25
+ afterEach(() => {
26
+ vi.useRealTimers();
27
+ });
28
+
29
+ describe('today', () => {
30
+ it('should return the start of the current day', () => {
31
+ const result = today();
32
+
33
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-06-15 00:00:00');
34
+ });
35
+ });
36
+
37
+ describe('thisWeek', () => {
38
+ it('should return the start of the current week', () => {
39
+ const result = thisWeek();
40
+
41
+ // dayjs starts week on Sunday by default
42
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-06-09 00:00:00');
43
+ });
44
+ });
45
+
46
+ describe('thisMonth', () => {
47
+ it('should return the start of the current month', () => {
48
+ const result = thisMonth();
49
+
50
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-06-01 00:00:00');
51
+ });
52
+ });
53
+
54
+ describe('thisQuarter', () => {
55
+ it('should return the start of the current quarter (Q2)', () => {
56
+ const result = thisQuarter();
57
+
58
+ // June is in Q2, which starts in April (month 3, 0-indexed)
59
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-04-01 00:00:00');
60
+ });
61
+
62
+ it('should work correctly for Q1 (January)', () => {
63
+ vi.setSystemTime(new Date('2024-01-15T14:30:45.123'));
64
+ const result = thisQuarter();
65
+
66
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-01-01 00:00:00');
67
+ });
68
+
69
+ it('should work correctly for Q3 (September)', () => {
70
+ vi.setSystemTime(new Date('2024-09-15T14:30:45.123'));
71
+ const result = thisQuarter();
72
+
73
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-07-01 00:00:00');
74
+ });
75
+
76
+ it('should work correctly for Q4 (December)', () => {
77
+ vi.setSystemTime(new Date('2024-12-15T14:30:45.123'));
78
+ const result = thisQuarter();
79
+
80
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-10-01 00:00:00');
81
+ });
82
+ });
83
+
84
+ describe('thisYear', () => {
85
+ it('should return the start of the current year', () => {
86
+ const result = thisYear();
87
+
88
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-01-01 00:00:00');
89
+ });
90
+ });
91
+
92
+ describe('hoursAgo', () => {
93
+ it('should return the correct time hours ago', () => {
94
+ const result = hoursAgo(3);
95
+
96
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-06-15 11:00:00');
97
+ });
98
+
99
+ it('should handle 0 hours ago', () => {
100
+ const result = hoursAgo(0);
101
+
102
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-06-15 14:00:00');
103
+ });
104
+
105
+ it('should handle large hour values (cross day boundary)', () => {
106
+ const result = hoursAgo(25);
107
+
108
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-06-14 13:00:00');
109
+ });
110
+ });
111
+
112
+ describe('daysAgo', () => {
113
+ it('should return the correct date days ago', () => {
114
+ const result = daysAgo(7);
115
+
116
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-06-08 00:00:00');
117
+ });
118
+
119
+ it('should handle 0 days ago (today)', () => {
120
+ const result = daysAgo(0);
121
+
122
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-06-15 00:00:00');
123
+ });
124
+
125
+ it('should handle large day values (cross month boundary)', () => {
126
+ const result = daysAgo(30);
127
+
128
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-05-16 00:00:00');
129
+ });
130
+ });
131
+
132
+ describe('weeksAgo', () => {
133
+ it('should return the correct week weeks ago', () => {
134
+ const result = weeksAgo(2);
135
+
136
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-05-26 00:00:00');
137
+ });
138
+
139
+ it('should handle 0 weeks ago (this week)', () => {
140
+ const result = weeksAgo(0);
141
+
142
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-06-09 00:00:00');
143
+ });
144
+ });
145
+
146
+ describe('monthsAgo', () => {
147
+ it('should return the correct month months ago', () => {
148
+ const result = monthsAgo(3);
149
+
150
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-03-01 00:00:00');
151
+ });
152
+
153
+ it('should handle 0 months ago (this month)', () => {
154
+ const result = monthsAgo(0);
155
+
156
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-06-01 00:00:00');
157
+ });
158
+
159
+ it('should handle cross year boundary', () => {
160
+ const result = monthsAgo(12);
161
+
162
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2023-06-01 00:00:00');
163
+ });
164
+ });
165
+
166
+ describe('lastMonth', () => {
167
+ it('should return the end of last month', () => {
168
+ const result = lastMonth();
169
+
170
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-05-31 23:59:59');
171
+ });
172
+
173
+ it('should work correctly for January (December of previous year)', () => {
174
+ vi.setSystemTime(new Date('2024-01-15T14:30:45.123'));
175
+ const result = lastMonth();
176
+
177
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2023-12-31 23:59:59');
178
+ });
179
+
180
+ it('should work correctly for March (February with 29 days in leap year)', () => {
181
+ vi.setSystemTime(new Date('2024-03-15T14:30:45.123'));
182
+ const result = lastMonth();
183
+
184
+ expect(result.format('YYYY-MM-DD HH:mm:ss')).toBe('2024-02-29 23:59:59');
185
+ });
186
+ });
187
+
188
+ describe('getYYYYmmddHHMMss', () => {
189
+ it('should format date to YYYYMMDD_HHmmss format', () => {
190
+ const date = new Date('2024-01-01T23:59:59.123');
191
+ const result = getYYYYmmddHHMMss(date);
192
+
193
+ expect(result).toBe('20240101_235959');
194
+ });
195
+
196
+ it('should format date with zero padding', () => {
197
+ const date = new Date('2024-02-05T01:02:03.456');
198
+ const result = getYYYYmmddHHMMss(date);
199
+
200
+ expect(result).toBe('20240205_010203');
201
+ });
202
+
203
+ it('should handle end of year date', () => {
204
+ const date = new Date('2024-12-31T00:00:00.000');
205
+ const result = getYYYYmmddHHMMss(date);
206
+
207
+ expect(result).toBe('20241231_000000');
208
+ });
209
+
210
+ it('should handle leap year date', () => {
211
+ const date = new Date('2024-02-29T12:30:45.789');
212
+ const result = getYYYYmmddHHMMss(date);
213
+
214
+ expect(result).toBe('20240229_123045');
215
+ });
216
+
217
+ it('should work with the current mocked time', () => {
218
+ const date = new Date('2024-06-15T14:30:45.123');
219
+ const result = getYYYYmmddHHMMss(date);
220
+
221
+ expect(result).toBe('20240615_143045');
222
+ });
223
+ });
224
+
225
+ describe('edge cases and integration', () => {
226
+ it('should all return dayjs objects for date functions', () => {
227
+ expect(dayjs.isDayjs(today())).toBe(true);
228
+ expect(dayjs.isDayjs(thisWeek())).toBe(true);
229
+ expect(dayjs.isDayjs(thisMonth())).toBe(true);
230
+ expect(dayjs.isDayjs(thisQuarter())).toBe(true);
231
+ expect(dayjs.isDayjs(thisYear())).toBe(true);
232
+ expect(dayjs.isDayjs(hoursAgo(1))).toBe(true);
233
+ expect(dayjs.isDayjs(daysAgo(1))).toBe(true);
234
+ expect(dayjs.isDayjs(weeksAgo(1))).toBe(true);
235
+ expect(dayjs.isDayjs(monthsAgo(1))).toBe(true);
236
+ expect(dayjs.isDayjs(lastMonth())).toBe(true);
237
+ });
238
+
239
+ it('should maintain consistency between different time functions', () => {
240
+ const todayResult = today();
241
+ const daysAgoResult = daysAgo(0);
242
+
243
+ expect(todayResult.format('YYYY-MM-DD')).toBe(daysAgoResult.format('YYYY-MM-DD'));
244
+ });
245
+
246
+ it('should work with different time zones in getYYYYmmddHHMMss', () => {
247
+ // Note: dayjs uses the local timezone by default
248
+ const localDate = new Date('2024-06-15T14:30:45.000');
249
+ const result = getYYYYmmddHHMMss(localDate);
250
+
251
+ // The exact result depends on the test environment's timezone
252
+ // But it should always be a valid format
253
+ expect(result).toMatch(/^\d{8}_\d{6}$/);
254
+ expect(result).toHaveLength(15);
255
+ // Should match the local time format
256
+ expect(result).toBe('20240615_143045');
257
+ });
258
+ });
259
+ });
package/src/utils/time.ts CHANGED
@@ -21,3 +21,21 @@ export const weeksAgo = (weeks: number) => dayjs().subtract(weeks, 'week').start
21
21
  export const monthsAgo = (months: number) => dayjs().subtract(months, 'month').startOf('month');
22
22
 
23
23
  export const lastMonth = () => monthsAgo(1).endOf('month');
24
+
25
+ /**
26
+ * Get the date in the format of YYYYMMdd_HHmmss like 20240101_235959
27
+ *
28
+ * @example
29
+ *
30
+ * ```ts
31
+ * getYYYYmmddHHMMss(new Date('2024-01-01 23:59:59')); // returns '20240101_235959'
32
+ * getYYYYmmddHHMMss(new Date('2024-12-31 00:00:00')); // returns '20241231_000000'
33
+ * ```
34
+ *
35
+ * @param date - The date to format
36
+ * @returns A string in YYYYMMdd_HHmmss format with underscore separator between date and time
37
+ * @see https://day.js.org/docs/en/display/format
38
+ */
39
+ export function getYYYYmmddHHMMss(date: Date) {
40
+ return dayjs(date).format('YYYYMMDD_HHmmss');
41
+ }
@@ -0,0 +1,61 @@
1
+ // ------------------------ Time -----------------------------
2
+
3
+ export const MS = 1;
4
+
5
+ /**
6
+ * `1000ms`
7
+ */
8
+ export const SECOND = 1000 * MS;
9
+
10
+ /**
11
+ * `60s`
12
+ */
13
+ export const MINUTE = 60 * SECOND;
14
+
15
+ /**
16
+ * `3600s`
17
+ */
18
+ export const HOUR = 60 * MINUTE;
19
+
20
+ /**
21
+ * `86400s`
22
+ */
23
+ export const DAY = 24 * HOUR;
24
+
25
+ /**
26
+ * `2592000s`
27
+ */
28
+ export const MONTH = 30 * DAY;
29
+
30
+ /**
31
+ * `31536000s`
32
+ */
33
+ export const YEAR = 365 * DAY;
34
+
35
+ export const API_TEST_TIMEOUT = 10 * SECOND;
36
+
37
+ // ------------------------ Size -----------------------------
38
+
39
+ /**
40
+ * `1024 bytes`
41
+ */
42
+ export const KiB = 1024;
43
+
44
+ /**
45
+ * `1024 KiB` (1,048,576 bytes)
46
+ */
47
+ export const MiB = 1024 * KiB;
48
+
49
+ /**
50
+ * `1024 MiB` (1,073,741,824 bytes)
51
+ */
52
+ export const GiB = 1024 * MiB;
53
+
54
+ /**
55
+ * `1024 GiB` (1,099,511,627,776 bytes)
56
+ */
57
+ export const TiB = 1024 * GiB;
58
+
59
+ export const DOLLAR_PER_CREDIT = 0.003;
60
+ export const DOLLAR_PER_RMB = 0.14;
61
+ export const RMB_PER_CREDIT = DOLLAR_PER_RMB / DOLLAR_PER_CREDIT;