@strapi/upload 5.51.1 → 5.52.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 (369) hide show
  1. package/dist/admin/components/AssetCard/VideoPreview.js +1 -1
  2. package/dist/admin/components/AssetCard/VideoPreview.js.map +1 -1
  3. package/dist/admin/components/AssetCard/VideoPreview.mjs +1 -1
  4. package/dist/admin/components/AssetCard/VideoPreview.mjs.map +1 -1
  5. package/dist/admin/components/TableList/TableList.js +1 -1
  6. package/dist/admin/components/TableList/TableList.js.map +1 -1
  7. package/dist/admin/components/TableList/TableList.mjs +1 -1
  8. package/dist/admin/components/TableList/TableList.mjs.map +1 -1
  9. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.js +21 -4
  10. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.js.map +1 -1
  11. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.mjs +21 -4
  12. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.mjs.map +1 -1
  13. package/dist/admin/future/App.js +3 -3
  14. package/dist/admin/future/App.js.map +1 -1
  15. package/dist/admin/future/App.mjs +4 -4
  16. package/dist/admin/future/App.mjs.map +1 -1
  17. package/dist/admin/future/components/BetaNotice.js +72 -0
  18. package/dist/admin/future/components/BetaNotice.js.map +1 -0
  19. package/dist/admin/future/components/BetaNotice.mjs +70 -0
  20. package/dist/admin/future/components/BetaNotice.mjs.map +1 -0
  21. package/dist/admin/future/components/Drawer.js +18 -3
  22. package/dist/admin/future/components/Drawer.js.map +1 -1
  23. package/dist/admin/future/components/Drawer.mjs +18 -3
  24. package/dist/admin/future/components/Drawer.mjs.map +1 -1
  25. package/dist/admin/future/components/UploadProgressDialog.js +200 -27
  26. package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
  27. package/dist/admin/future/components/UploadProgressDialog.mjs +203 -30
  28. package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
  29. package/dist/admin/future/hooks/useAIMetadataEnabled.js +39 -0
  30. package/dist/admin/future/hooks/useAIMetadataEnabled.js.map +1 -0
  31. package/dist/admin/future/hooks/useAIMetadataEnabled.mjs +37 -0
  32. package/dist/admin/future/hooks/useAIMetadataEnabled.mjs.map +1 -0
  33. package/dist/admin/future/hooks/useMediaLibraryPermissions.js +35 -0
  34. package/dist/admin/future/hooks/useMediaLibraryPermissions.js.map +1 -0
  35. package/dist/admin/future/hooks/useMediaLibraryPermissions.mjs +33 -0
  36. package/dist/admin/future/hooks/useMediaLibraryPermissions.mjs.map +1 -0
  37. package/dist/admin/future/pages/Assets/AssetsPage.js +453 -170
  38. package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
  39. package/dist/admin/future/pages/Assets/AssetsPage.mjs +454 -171
  40. package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
  41. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.js +46 -0
  42. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.js.map +1 -0
  43. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.mjs +44 -0
  44. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.mjs.map +1 -0
  45. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js +325 -0
  46. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js.map +1 -0
  47. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs +323 -0
  48. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs.map +1 -0
  49. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js +110 -46
  50. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
  51. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +110 -46
  52. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
  53. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +95 -67
  54. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
  55. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +95 -68
  56. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
  57. package/dist/admin/future/pages/Assets/components/AssetsGrid.js +94 -26
  58. package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
  59. package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +96 -28
  60. package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
  61. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.js +112 -0
  62. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.js.map +1 -0
  63. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.mjs +110 -0
  64. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.mjs.map +1 -0
  65. package/dist/admin/future/pages/Assets/components/AssetsTable.js +172 -56
  66. package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
  67. package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +175 -59
  68. package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
  69. package/dist/admin/future/pages/Assets/components/BulkActionsBar.js +188 -138
  70. package/dist/admin/future/pages/Assets/components/BulkActionsBar.js.map +1 -1
  71. package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs +191 -141
  72. package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs.map +1 -1
  73. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js +123 -76
  74. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js.map +1 -1
  75. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs +126 -79
  76. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs.map +1 -1
  77. package/dist/admin/future/pages/Assets/components/BusyOverlay.js +36 -0
  78. package/dist/admin/future/pages/Assets/components/BusyOverlay.js.map +1 -0
  79. package/dist/admin/future/pages/Assets/components/BusyOverlay.mjs +34 -0
  80. package/dist/admin/future/pages/Assets/components/BusyOverlay.mjs.map +1 -0
  81. package/dist/admin/future/pages/Assets/components/DateRangeCalendar.js +193 -0
  82. package/dist/admin/future/pages/Assets/components/DateRangeCalendar.js.map +1 -0
  83. package/dist/admin/future/pages/Assets/components/DateRangeCalendar.mjs +191 -0
  84. package/dist/admin/future/pages/Assets/components/DateRangeCalendar.mjs.map +1 -0
  85. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.js +131 -0
  86. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.js.map +1 -0
  87. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.mjs +129 -0
  88. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.mjs.map +1 -0
  89. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js +45 -18
  90. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js.map +1 -1
  91. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs +46 -19
  92. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs.map +1 -1
  93. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.js +28 -31
  94. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.js.map +1 -1
  95. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.mjs +28 -31
  96. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.mjs.map +1 -1
  97. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.js +10 -15
  98. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.js.map +1 -1
  99. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.mjs +10 -15
  100. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.mjs.map +1 -1
  101. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.js +13 -3
  102. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.js.map +1 -1
  103. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.mjs +13 -3
  104. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.mjs.map +1 -1
  105. package/dist/admin/future/pages/Assets/components/EmptyState.js +62 -3
  106. package/dist/admin/future/pages/Assets/components/EmptyState.js.map +1 -1
  107. package/dist/admin/future/pages/Assets/components/EmptyState.mjs +62 -4
  108. package/dist/admin/future/pages/Assets/components/EmptyState.mjs.map +1 -1
  109. package/dist/admin/future/pages/Assets/components/FilterBadges.js +417 -0
  110. package/dist/admin/future/pages/Assets/components/FilterBadges.js.map +1 -0
  111. package/dist/admin/future/pages/Assets/components/FilterBadges.mjs +415 -0
  112. package/dist/admin/future/pages/Assets/components/FilterBadges.mjs.map +1 -0
  113. package/dist/admin/future/pages/Assets/components/FilterMenu.js +453 -0
  114. package/dist/admin/future/pages/Assets/components/FilterMenu.js.map +1 -0
  115. package/dist/admin/future/pages/Assets/components/FilterMenu.mjs +447 -0
  116. package/dist/admin/future/pages/Assets/components/FilterMenu.mjs.map +1 -0
  117. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.js +144 -0
  118. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.js.map +1 -0
  119. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.mjs +142 -0
  120. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.mjs.map +1 -0
  121. package/dist/admin/future/pages/Assets/components/{CreateFolderDialog.js → FolderFormDialog.js} +50 -16
  122. package/dist/admin/future/pages/Assets/components/FolderFormDialog.js.map +1 -0
  123. package/dist/admin/future/pages/Assets/components/{CreateFolderDialog.mjs → FolderFormDialog.mjs} +52 -18
  124. package/dist/admin/future/pages/Assets/components/FolderFormDialog.mjs.map +1 -0
  125. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js +125 -148
  126. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
  127. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs +127 -150
  128. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
  129. package/dist/admin/future/pages/Assets/components/SortMenu.js +21 -32
  130. package/dist/admin/future/pages/Assets/components/SortMenu.js.map +1 -1
  131. package/dist/admin/future/pages/Assets/components/SortMenu.mjs +22 -33
  132. package/dist/admin/future/pages/Assets/components/SortMenu.mjs.map +1 -1
  133. package/dist/admin/future/pages/Assets/hooks/useAssetSearch.js +40 -0
  134. package/dist/admin/future/pages/Assets/hooks/useAssetSearch.js.map +1 -0
  135. package/dist/admin/future/pages/Assets/hooks/useAssetSearch.mjs +38 -0
  136. package/dist/admin/future/pages/Assets/hooks/useAssetSearch.mjs.map +1 -0
  137. package/dist/admin/future/pages/Assets/hooks/useAssetSelection.js +21 -5
  138. package/dist/admin/future/pages/Assets/hooks/useAssetSelection.js.map +1 -1
  139. package/dist/admin/future/pages/Assets/hooks/useAssetSelection.mjs +21 -5
  140. package/dist/admin/future/pages/Assets/hooks/useAssetSelection.mjs.map +1 -1
  141. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.js +47 -0
  142. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.js.map +1 -0
  143. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.mjs +44 -0
  144. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.mjs.map +1 -0
  145. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js +33 -12
  146. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js.map +1 -1
  147. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs +33 -12
  148. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs.map +1 -1
  149. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js +222 -45
  150. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js.map +1 -1
  151. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs +223 -46
  152. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs.map +1 -1
  153. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.js +75 -0
  154. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.js.map +1 -0
  155. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.mjs +73 -0
  156. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.mjs.map +1 -0
  157. package/dist/admin/future/pages/Assets/hooks/useListFilters.js +168 -0
  158. package/dist/admin/future/pages/Assets/hooks/useListFilters.js.map +1 -0
  159. package/dist/admin/future/pages/Assets/hooks/useListFilters.mjs +162 -0
  160. package/dist/admin/future/pages/Assets/hooks/useListFilters.mjs.map +1 -0
  161. package/dist/admin/future/pages/Assets/utils/buildAssetFilters.js +201 -0
  162. package/dist/admin/future/pages/Assets/utils/buildAssetFilters.js.map +1 -0
  163. package/dist/admin/future/pages/Assets/utils/buildAssetFilters.mjs +198 -0
  164. package/dist/admin/future/pages/Assets/utils/buildAssetFilters.mjs.map +1 -0
  165. package/dist/admin/future/pages/Assets/utils/listQueryKey.js +2 -2
  166. package/dist/admin/future/pages/Assets/utils/listQueryKey.js.map +1 -1
  167. package/dist/admin/future/pages/Assets/utils/listQueryKey.mjs +2 -2
  168. package/dist/admin/future/pages/Assets/utils/listQueryKey.mjs.map +1 -1
  169. package/dist/admin/future/services/api.js +187 -28
  170. package/dist/admin/future/services/api.js.map +1 -1
  171. package/dist/admin/future/services/api.mjs +187 -30
  172. package/dist/admin/future/services/api.mjs.map +1 -1
  173. package/dist/admin/future/services/assets.js +40 -17
  174. package/dist/admin/future/services/assets.js.map +1 -1
  175. package/dist/admin/future/services/assets.mjs +40 -17
  176. package/dist/admin/future/services/assets.mjs.map +1 -1
  177. package/dist/admin/future/services/folders.js +66 -19
  178. package/dist/admin/future/services/folders.js.map +1 -1
  179. package/dist/admin/future/services/folders.mjs +66 -20
  180. package/dist/admin/future/services/folders.mjs.map +1 -1
  181. package/dist/admin/future/services/settings.js +10 -3
  182. package/dist/admin/future/services/settings.js.map +1 -1
  183. package/dist/admin/future/services/settings.mjs +10 -3
  184. package/dist/admin/future/services/settings.mjs.map +1 -1
  185. package/dist/admin/future/store/uploadProgress.js +109 -2
  186. package/dist/admin/future/store/uploadProgress.js.map +1 -1
  187. package/dist/admin/future/store/uploadProgress.mjs +105 -3
  188. package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
  189. package/dist/admin/future/utils/buildDragSetFromSelection.js +7 -5
  190. package/dist/admin/future/utils/buildDragSetFromSelection.js.map +1 -1
  191. package/dist/admin/future/utils/buildDragSetFromSelection.mjs +7 -5
  192. package/dist/admin/future/utils/buildDragSetFromSelection.mjs.map +1 -1
  193. package/dist/admin/future/utils/formatMoveSuccessMessage.js +12 -1
  194. package/dist/admin/future/utils/formatMoveSuccessMessage.js.map +1 -1
  195. package/dist/admin/future/utils/formatMoveSuccessMessage.mjs +12 -1
  196. package/dist/admin/future/utils/formatMoveSuccessMessage.mjs.map +1 -1
  197. package/dist/admin/future/utils/getRelationId.js +24 -0
  198. package/dist/admin/future/utils/getRelationId.js.map +1 -0
  199. package/dist/admin/future/utils/getRelationId.mjs +22 -0
  200. package/dist/admin/future/utils/getRelationId.mjs.map +1 -0
  201. package/dist/admin/future/utils/isEventFromWithin.js +14 -0
  202. package/dist/admin/future/utils/isEventFromWithin.js.map +1 -0
  203. package/dist/admin/future/utils/isEventFromWithin.mjs +12 -0
  204. package/dist/admin/future/utils/isEventFromWithin.mjs.map +1 -0
  205. package/dist/admin/future/utils/itemLocations.js +40 -0
  206. package/dist/admin/future/utils/itemLocations.js.map +1 -0
  207. package/dist/admin/future/utils/itemLocations.mjs +36 -0
  208. package/dist/admin/future/utils/itemLocations.mjs.map +1 -0
  209. package/dist/admin/future/utils/itemSource.js +36 -0
  210. package/dist/admin/future/utils/itemSource.js.map +1 -0
  211. package/dist/admin/future/utils/itemSource.mjs +32 -0
  212. package/dist/admin/future/utils/itemSource.mjs.map +1 -0
  213. package/dist/admin/future/utils/searchQueryParam.js +11 -0
  214. package/dist/admin/future/utils/searchQueryParam.js.map +1 -0
  215. package/dist/admin/future/utils/searchQueryParam.mjs +9 -0
  216. package/dist/admin/future/utils/searchQueryParam.mjs.map +1 -0
  217. package/dist/admin/hooks/useSelectionState.js +2 -4
  218. package/dist/admin/hooks/useSelectionState.js.map +1 -1
  219. package/dist/admin/hooks/useSelectionState.mjs +2 -4
  220. package/dist/admin/hooks/useSelectionState.mjs.map +1 -1
  221. package/dist/admin/index.js +11 -17
  222. package/dist/admin/index.js.map +1 -1
  223. package/dist/admin/index.mjs +12 -18
  224. package/dist/admin/index.mjs.map +1 -1
  225. package/dist/admin/shared/constants.js +43 -0
  226. package/dist/admin/shared/constants.js.map +1 -0
  227. package/dist/admin/shared/constants.mjs +38 -0
  228. package/dist/admin/shared/constants.mjs.map +1 -0
  229. package/dist/admin/src/future/App.d.ts +1 -1
  230. package/dist/admin/src/future/components/BetaNotice.d.ts +11 -0
  231. package/dist/admin/src/future/components/Drawer.d.ts +5 -1
  232. package/dist/admin/src/future/hooks/useAIMetadataEnabled.d.ts +26 -0
  233. package/dist/admin/src/future/hooks/useMediaLibraryPermissions.d.ts +21 -0
  234. package/dist/admin/src/future/pages/Assets/components/ActionsMenuContent.d.ts +33 -0
  235. package/dist/admin/src/future/pages/Assets/components/AssetActionsMenu.d.ts +24 -0
  236. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetCropEditor.d.ts +7 -1
  237. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.d.ts +16 -0
  238. package/dist/admin/src/future/pages/Assets/components/AssetsSearchInput.d.ts +5 -0
  239. package/dist/admin/src/future/pages/Assets/components/BulkActionsBar.d.ts +21 -1
  240. package/dist/admin/src/future/pages/Assets/components/BulkMoveDialog.d.ts +17 -6
  241. package/dist/admin/src/future/pages/Assets/components/BusyOverlay.d.ts +9 -0
  242. package/dist/admin/src/future/pages/Assets/components/DateRangeCalendar.d.ts +8 -0
  243. package/dist/admin/src/future/pages/Assets/components/DeleteItemsDialog.d.ts +27 -0
  244. package/dist/admin/src/future/pages/Assets/components/Dnd/AssetsDndProvider.d.ts +8 -1
  245. package/dist/admin/src/future/pages/Assets/components/Dnd/buildDragSet.d.ts +18 -3
  246. package/dist/admin/src/future/pages/Assets/components/Dnd/useAssetDnd.d.ts +2 -1
  247. package/dist/admin/src/future/pages/Assets/components/DropZone/UploadDropZoneContext.d.ts +8 -1
  248. package/dist/admin/src/future/pages/Assets/components/EmptyState.d.ts +18 -1
  249. package/dist/admin/src/future/pages/Assets/components/FilterBadges.d.ts +8 -0
  250. package/dist/admin/src/future/pages/Assets/components/FilterMenu.d.ts +26 -0
  251. package/dist/admin/src/future/pages/Assets/components/FolderActionsMenu.d.ts +20 -0
  252. package/dist/admin/src/future/pages/Assets/components/FolderFormDialog.d.ts +16 -0
  253. package/dist/admin/src/future/pages/Assets/components/FolderTree/FolderTree.d.ts +13 -4
  254. package/dist/admin/src/future/pages/Assets/components/SortMenu.d.ts +6 -0
  255. package/dist/admin/src/future/pages/Assets/hooks/useAssetSearch.d.ts +12 -0
  256. package/dist/admin/src/future/pages/Assets/hooks/useAssetSelection.d.ts +8 -1
  257. package/dist/admin/src/future/pages/Assets/hooks/useBusyAssets.d.ts +38 -0
  258. package/dist/admin/src/future/pages/Assets/hooks/useInfiniteAssets.d.ts +27 -3
  259. package/dist/admin/src/future/pages/Assets/hooks/useInfiniteScrollSentinel.d.ts +41 -0
  260. package/dist/admin/src/future/pages/Assets/hooks/useListFilters.d.ts +69 -0
  261. package/dist/admin/src/future/pages/Assets/utils/buildAssetFilters.d.ts +46 -0
  262. package/dist/admin/src/future/pages/Assets/utils/listQueryKey.d.ts +3 -4
  263. package/dist/admin/src/future/services/api.d.ts +53 -5
  264. package/dist/admin/src/future/services/assets.d.ts +9 -1
  265. package/dist/admin/src/future/services/folders.d.ts +9 -1
  266. package/dist/admin/src/future/services/settings.d.ts +2 -2
  267. package/dist/admin/src/future/store/uploadProgress.d.ts +83 -1
  268. package/dist/admin/src/future/utils/buildDragSetFromSelection.d.ts +4 -3
  269. package/dist/admin/src/future/utils/formatMoveSuccessMessage.d.ts +7 -1
  270. package/dist/admin/src/future/utils/getRelationId.d.ts +11 -0
  271. package/dist/admin/src/future/utils/isEventFromWithin.d.ts +11 -0
  272. package/dist/admin/src/future/utils/itemLocations.d.ts +31 -0
  273. package/dist/admin/src/future/utils/itemSource.d.ts +22 -0
  274. package/dist/admin/src/future/utils/searchQueryParam.d.ts +7 -0
  275. package/dist/admin/src/hooks/useAiAvailability.d.ts +1 -1
  276. package/dist/admin/src/hooks/useEditAsset.d.ts +4 -4
  277. package/dist/admin/src/hooks/useEditFolder.d.ts +4 -4
  278. package/dist/admin/src/hooks/useSettings.d.ts +1 -0
  279. package/dist/admin/src/hooks/useTracking.d.ts +1 -1
  280. package/dist/admin/src/hooks/useUpload.d.ts +1 -1
  281. package/dist/admin/translations/en.json.js +73 -2
  282. package/dist/admin/translations/en.json.js.map +1 -1
  283. package/dist/admin/translations/en.json.mjs +73 -2
  284. package/dist/admin/translations/en.json.mjs.map +1 -1
  285. package/dist/admin/utils/urlYupSchema.js +33 -9
  286. package/dist/admin/utils/urlYupSchema.js.map +1 -1
  287. package/dist/admin/utils/urlYupSchema.mjs +33 -9
  288. package/dist/admin/utils/urlYupSchema.mjs.map +1 -1
  289. package/dist/server/config.js +13 -5
  290. package/dist/server/config.js.map +1 -1
  291. package/dist/server/config.mjs +13 -5
  292. package/dist/server/config.mjs.map +1 -1
  293. package/dist/server/constants.js.map +1 -1
  294. package/dist/server/constants.mjs.map +1 -1
  295. package/dist/server/controllers/admin-file.js +30 -0
  296. package/dist/server/controllers/admin-file.js.map +1 -1
  297. package/dist/server/controllers/admin-file.mjs +30 -0
  298. package/dist/server/controllers/admin-file.mjs.map +1 -1
  299. package/dist/server/controllers/admin-settings.js +20 -2
  300. package/dist/server/controllers/admin-settings.js.map +1 -1
  301. package/dist/server/controllers/admin-settings.mjs +21 -3
  302. package/dist/server/controllers/admin-settings.mjs.map +1 -1
  303. package/dist/server/controllers/content-api.js +1 -2
  304. package/dist/server/controllers/content-api.js.map +1 -1
  305. package/dist/server/controllers/content-api.mjs +2 -2
  306. package/dist/server/controllers/content-api.mjs.map +1 -1
  307. package/dist/server/controllers/validation/admin/ai-metadata.js +12 -0
  308. package/dist/server/controllers/validation/admin/ai-metadata.js.map +1 -0
  309. package/dist/server/controllers/validation/admin/ai-metadata.mjs +10 -0
  310. package/dist/server/controllers/validation/admin/ai-metadata.mjs.map +1 -0
  311. package/dist/server/controllers/validation/admin/settings.js +4 -1
  312. package/dist/server/controllers/validation/admin/settings.js.map +1 -1
  313. package/dist/server/controllers/validation/admin/settings.mjs +1 -1
  314. package/dist/server/controllers/validation/admin/settings.mjs.map +1 -1
  315. package/dist/server/routes/admin.js +18 -0
  316. package/dist/server/routes/admin.js.map +1 -1
  317. package/dist/server/routes/admin.mjs +18 -0
  318. package/dist/server/routes/admin.mjs.map +1 -1
  319. package/dist/server/services/ai-metadata.js +108 -11
  320. package/dist/server/services/ai-metadata.js.map +1 -1
  321. package/dist/server/services/ai-metadata.mjs +108 -11
  322. package/dist/server/services/ai-metadata.mjs.map +1 -1
  323. package/dist/server/services/file.js +86 -13
  324. package/dist/server/services/file.js.map +1 -1
  325. package/dist/server/services/file.mjs +87 -14
  326. package/dist/server/services/file.mjs.map +1 -1
  327. package/dist/server/services/image-manipulation.js +4 -4
  328. package/dist/server/services/image-manipulation.js.map +1 -1
  329. package/dist/server/services/image-manipulation.mjs +4 -4
  330. package/dist/server/services/image-manipulation.mjs.map +1 -1
  331. package/dist/server/shared/constants.js +43 -0
  332. package/dist/server/shared/constants.js.map +1 -0
  333. package/dist/server/shared/constants.mjs +38 -0
  334. package/dist/server/shared/constants.mjs.map +1 -0
  335. package/dist/server/src/config.d.ts +2 -0
  336. package/dist/server/src/config.d.ts.map +1 -1
  337. package/dist/server/src/constants.d.ts +2 -1
  338. package/dist/server/src/constants.d.ts.map +1 -1
  339. package/dist/server/src/controllers/admin-file.d.ts +10 -0
  340. package/dist/server/src/controllers/admin-file.d.ts.map +1 -1
  341. package/dist/server/src/controllers/admin-settings.d.ts.map +1 -1
  342. package/dist/server/src/controllers/index.d.ts +1 -0
  343. package/dist/server/src/controllers/index.d.ts.map +1 -1
  344. package/dist/server/src/controllers/validation/admin/ai-metadata.d.ts +2 -0
  345. package/dist/server/src/controllers/validation/admin/ai-metadata.d.ts.map +1 -0
  346. package/dist/server/src/controllers/validation/admin/settings.d.ts +1 -1
  347. package/dist/server/src/controllers/validation/admin/settings.d.ts.map +1 -1
  348. package/dist/server/src/index.d.ts +7 -1
  349. package/dist/server/src/index.d.ts.map +1 -1
  350. package/dist/server/src/routes/admin.d.ts.map +1 -1
  351. package/dist/server/src/services/ai-metadata.d.ts +19 -0
  352. package/dist/server/src/services/ai-metadata.d.ts.map +1 -1
  353. package/dist/server/src/services/file.d.ts +12 -2
  354. package/dist/server/src/services/file.d.ts.map +1 -1
  355. package/dist/server/src/services/image-manipulation.d.ts +0 -5
  356. package/dist/server/src/services/image-manipulation.d.ts.map +1 -1
  357. package/dist/server/src/services/index.d.ts +4 -1
  358. package/dist/server/src/services/index.d.ts.map +1 -1
  359. package/dist/server/src/types.d.ts +3 -0
  360. package/dist/server/src/types.d.ts.map +1 -1
  361. package/dist/shared/constants.d.ts +35 -0
  362. package/dist/shared/constants.d.ts.map +1 -0
  363. package/dist/shared/contracts/files.d.ts +25 -0
  364. package/dist/shared/contracts/files.d.ts.map +1 -1
  365. package/dist/shared/contracts/settings.d.ts +7 -0
  366. package/package.json +10 -10
  367. package/dist/admin/future/pages/Assets/components/CreateFolderDialog.js.map +0 -1
  368. package/dist/admin/future/pages/Assets/components/CreateFolderDialog.mjs.map +0 -1
  369. package/dist/admin/src/future/pages/Assets/components/CreateFolderDialog.d.ts +0 -8
@@ -0,0 +1,198 @@
1
+ const MIME_BY_VALUE = {
2
+ picture: 'image',
3
+ audio: 'audio',
4
+ video: 'video'
5
+ };
6
+ const ALL_MIMES = Object.values(MIME_BY_VALUE);
7
+ const PRESET_DELTAS = {
8
+ '1day': {
9
+ days: 1
10
+ },
11
+ '3days': {
12
+ days: 3
13
+ },
14
+ '1week': {
15
+ days: 7
16
+ },
17
+ '1month': {
18
+ months: 1
19
+ },
20
+ '3months': {
21
+ months: 3
22
+ },
23
+ '6months': {
24
+ months: 6
25
+ },
26
+ '1year': {
27
+ years: 1
28
+ }
29
+ };
30
+ const subtractPreset = (now, preset)=>{
31
+ const { days = 0, months = 0, years = 0 } = PRESET_DELTAS[preset];
32
+ const result = new Date(now.getTime());
33
+ if (years || months) {
34
+ // JS date setters overflow at month ends (Mar 31 − 1 month → Feb 31 → Mar 3),
35
+ // so move to the 1st first and clamp the day to the target month's length.
36
+ const dayOfMonth = result.getDate();
37
+ result.setDate(1);
38
+ result.setFullYear(result.getFullYear() - years);
39
+ result.setMonth(result.getMonth() - months);
40
+ const daysInTargetMonth = new Date(result.getFullYear(), result.getMonth() + 1, 0).getDate();
41
+ result.setDate(Math.min(dayOfMonth, daysInTargetMonth));
42
+ }
43
+ result.setDate(result.getDate() - days);
44
+ return result;
45
+ };
46
+ const startOfDay = (date)=>{
47
+ const result = new Date(date.getTime());
48
+ result.setHours(0, 0, 0, 0);
49
+ return result;
50
+ };
51
+ const endOfDay = (date)=>{
52
+ const result = new Date(date.getTime());
53
+ result.setHours(23, 59, 59, 999);
54
+ return result;
55
+ };
56
+ /**
57
+ * Parses `YYYY-MM-DD` as a local calendar date — NOT `new Date(value)`, which
58
+ * treats date-only strings as UTC midnight and shifts the day in every
59
+ * timezone west of UTC. Shared by the query builder, the badge labels and the
60
+ * range calendar so all three agree on which day a range endpoint means.
61
+ */ const parseCalendarDate = (value)=>{
62
+ const [year, month, day] = value.split('-').map(Number);
63
+ return new Date(year, month - 1, day);
64
+ };
65
+ const buildDateClause = (filter, now)=>{
66
+ const { field } = filter;
67
+ if (filter.mode === 'preset') {
68
+ const pivot = subtractPreset(now, filter.preset);
69
+ switch(filter.condition){
70
+ case 'withinLast':
71
+ return {
72
+ [field]: {
73
+ $gte: pivot.toISOString()
74
+ }
75
+ };
76
+ case 'notWithinLast':
77
+ return {
78
+ [field]: {
79
+ $lt: pivot.toISOString()
80
+ }
81
+ };
82
+ case 'isExactly':
83
+ return {
84
+ [field]: {
85
+ $gte: startOfDay(pivot).toISOString(),
86
+ $lte: endOfDay(pivot).toISOString()
87
+ }
88
+ };
89
+ }
90
+ }
91
+ const from = startOfDay(parseCalendarDate(filter.from)).toISOString();
92
+ const to = endOfDay(parseCalendarDate(filter.to)).toISOString();
93
+ if (filter.condition === 'is') {
94
+ return {
95
+ [field]: {
96
+ $gte: from,
97
+ $lte: to
98
+ }
99
+ };
100
+ }
101
+ return {
102
+ $or: [
103
+ {
104
+ [field]: {
105
+ $lt: from
106
+ }
107
+ },
108
+ {
109
+ [field]: {
110
+ $gt: to
111
+ }
112
+ }
113
+ ]
114
+ };
115
+ };
116
+ const buildMimeClause = (filter)=>{
117
+ const fileValues = filter.values.filter((value)=>value !== 'folder');
118
+ if (fileValues.length === 0) {
119
+ return null;
120
+ }
121
+ const branches = fileValues.map((value)=>{
122
+ if (value === 'document') {
123
+ // Catch-all: none of the known mime families.
124
+ return {
125
+ $and: ALL_MIMES.map((mime)=>({
126
+ mime: {
127
+ $notContains: mime
128
+ }
129
+ }))
130
+ };
131
+ }
132
+ return {
133
+ mime: {
134
+ $contains: MIME_BY_VALUE[value]
135
+ }
136
+ };
137
+ });
138
+ if (filter.condition === 'is') {
139
+ return branches.length === 1 ? branches[0] : {
140
+ $or: branches
141
+ };
142
+ }
143
+ // "is not" = matches none of the selected values: negate each branch, AND them.
144
+ const negated = fileValues.map((value)=>{
145
+ if (value === 'document') {
146
+ // Not a document = is one of the known families.
147
+ return {
148
+ $or: ALL_MIMES.map((mime)=>({
149
+ mime: {
150
+ $contains: mime
151
+ }
152
+ }))
153
+ };
154
+ }
155
+ return {
156
+ mime: {
157
+ $notContains: MIME_BY_VALUE[value]
158
+ }
159
+ };
160
+ });
161
+ return negated.length === 1 ? negated[0] : {
162
+ $and: negated
163
+ };
164
+ };
165
+ const buildAssetFilters = (filters, now)=>{
166
+ const fileClauses = [];
167
+ const folderClauses = [];
168
+ let showFolders = true;
169
+ let showFiles = true;
170
+ for (const filter of filters){
171
+ if (filter.kind === 'date') {
172
+ const clause = buildDateClause(filter, now);
173
+ fileClauses.push(clause);
174
+ folderClauses.push(clause);
175
+ continue;
176
+ }
177
+ const includesFolder = filter.values.includes('folder');
178
+ if (filter.condition === 'is' ? !includesFolder : includesFolder) {
179
+ showFolders = false;
180
+ }
181
+ const mimeClause = buildMimeClause(filter);
182
+ if (mimeClause) {
183
+ fileClauses.push(mimeClause);
184
+ } else if (filter.condition === 'is') {
185
+ // `is [folder]` only — no file can match the badge.
186
+ showFiles = false;
187
+ }
188
+ }
189
+ return {
190
+ fileClauses,
191
+ folderClauses,
192
+ showFolders,
193
+ showFiles
194
+ };
195
+ };
196
+
197
+ export { buildAssetFilters, parseCalendarDate };
198
+ //# sourceMappingURL=buildAssetFilters.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildAssetFilters.mjs","sources":["../../../../../../admin/src/future/pages/Assets/utils/buildAssetFilters.ts"],"sourcesContent":["import {\n type DateFilter,\n type DatePreset,\n type ListFilter,\n type TypeFilter,\n type TypeValue,\n} from '../hooks/useListFilters';\n\n/**\n * Translates the URL filter model into Strapi `filters[$and]` entries for the\n * files and folders queries, and decides the structural visibility of both\n * sections.\n *\n * Relative presets resolve against `now` at build time — the URL stays\n * relative (\"within the last week\"), only the outgoing query is absolute.\n *\n * Semantics:\n * - Every badge is one `$and` entry (badges combine with AND, per ticket).\n * - Type: `folder` is not a mime — it acts structurally on the folders\n * section. The remaining values map to mime clauses; `document` is the\n * legacy catch-all (mime contains none of image/audio/video — audio\n * included in the exclusion, fixing the legacy quirk).\n * - Dates apply to files and folders alike (folders have both timestamps).\n * - \"is exactly <preset>\" means the calendar day of `now - delta`.\n * - Ranges are inclusive on both ends (start-of-day .. end-of-day).\n */\n\ntype QueryClause = Record<string, unknown>;\n\nconst MIME_BY_VALUE: Record<Exclude<TypeValue, 'folder' | 'document'>, string> = {\n picture: 'image',\n audio: 'audio',\n video: 'video',\n};\n\nconst ALL_MIMES = Object.values(MIME_BY_VALUE);\n\nconst PRESET_DELTAS: Record<DatePreset, { days?: number; months?: number; years?: number }> = {\n '1day': { days: 1 },\n '3days': { days: 3 },\n '1week': { days: 7 },\n '1month': { months: 1 },\n '3months': { months: 3 },\n '6months': { months: 6 },\n '1year': { years: 1 },\n};\n\nconst subtractPreset = (now: Date, preset: DatePreset): Date => {\n const { days = 0, months = 0, years = 0 } = PRESET_DELTAS[preset];\n const result = new Date(now.getTime());\n\n if (years || months) {\n // JS date setters overflow at month ends (Mar 31 − 1 month → Feb 31 → Mar 3),\n // so move to the 1st first and clamp the day to the target month's length.\n const dayOfMonth = result.getDate();\n result.setDate(1);\n result.setFullYear(result.getFullYear() - years);\n result.setMonth(result.getMonth() - months);\n\n const daysInTargetMonth = new Date(result.getFullYear(), result.getMonth() + 1, 0).getDate();\n result.setDate(Math.min(dayOfMonth, daysInTargetMonth));\n }\n\n result.setDate(result.getDate() - days);\n\n return result;\n};\n\nconst startOfDay = (date: Date): Date => {\n const result = new Date(date.getTime());\n result.setHours(0, 0, 0, 0);\n return result;\n};\n\nconst endOfDay = (date: Date): Date => {\n const result = new Date(date.getTime());\n result.setHours(23, 59, 59, 999);\n return result;\n};\n\n/**\n * Parses `YYYY-MM-DD` as a local calendar date — NOT `new Date(value)`, which\n * treats date-only strings as UTC midnight and shifts the day in every\n * timezone west of UTC. Shared by the query builder, the badge labels and the\n * range calendar so all three agree on which day a range endpoint means.\n */\nexport const parseCalendarDate = (value: string): Date => {\n const [year, month, day] = value.split('-').map(Number);\n return new Date(year, month - 1, day);\n};\n\nconst buildDateClause = (filter: DateFilter, now: Date): QueryClause => {\n const { field } = filter;\n\n if (filter.mode === 'preset') {\n const pivot = subtractPreset(now, filter.preset);\n\n switch (filter.condition) {\n case 'withinLast':\n return { [field]: { $gte: pivot.toISOString() } };\n case 'notWithinLast':\n return { [field]: { $lt: pivot.toISOString() } };\n case 'isExactly':\n return {\n [field]: {\n $gte: startOfDay(pivot).toISOString(),\n $lte: endOfDay(pivot).toISOString(),\n },\n };\n }\n }\n\n const from = startOfDay(parseCalendarDate(filter.from)).toISOString();\n const to = endOfDay(parseCalendarDate(filter.to)).toISOString();\n\n if (filter.condition === 'is') {\n return { [field]: { $gte: from, $lte: to } };\n }\n\n return { $or: [{ [field]: { $lt: from } }, { [field]: { $gt: to } }] };\n};\n\nconst buildMimeClause = (filter: TypeFilter): QueryClause | null => {\n const fileValues = filter.values.filter((value): value is TypeValue => value !== 'folder');\n\n if (fileValues.length === 0) {\n return null;\n }\n\n const branches: QueryClause[] = fileValues.map((value) => {\n if (value === 'document') {\n // Catch-all: none of the known mime families.\n return { $and: ALL_MIMES.map((mime) => ({ mime: { $notContains: mime } })) };\n }\n return { mime: { $contains: MIME_BY_VALUE[value as keyof typeof MIME_BY_VALUE] } };\n });\n\n if (filter.condition === 'is') {\n return branches.length === 1 ? branches[0] : { $or: branches };\n }\n\n // \"is not\" = matches none of the selected values: negate each branch, AND them.\n const negated: QueryClause[] = fileValues.map((value) => {\n if (value === 'document') {\n // Not a document = is one of the known families.\n return { $or: ALL_MIMES.map((mime) => ({ mime: { $contains: mime } })) };\n }\n return { mime: { $notContains: MIME_BY_VALUE[value as keyof typeof MIME_BY_VALUE] } };\n });\n\n return negated.length === 1 ? negated[0] : { $and: negated };\n};\n\nexport interface BuiltFilters {\n /** Extra `$and` entries for the files query. */\n fileClauses: QueryClause[];\n /** Extra `$and` entries for the folders query (dates only). */\n folderClauses: QueryClause[];\n /**\n * Whether the folders section can show at all. A type badge decides it by\n * evaluating \"folder\" like any other value: `is` shows folders only when\n * folder is selected; `is not` hides them only when folder is selected.\n */\n showFolders: boolean;\n /**\n * Whether the files list can show at all — false when an `is` type badge\n * selects only \"folder\" (no file can match).\n */\n showFiles: boolean;\n}\n\nexport const buildAssetFilters = (filters: ListFilter[], now: Date): BuiltFilters => {\n const fileClauses: QueryClause[] = [];\n const folderClauses: QueryClause[] = [];\n let showFolders = true;\n let showFiles = true;\n\n for (const filter of filters) {\n if (filter.kind === 'date') {\n const clause = buildDateClause(filter, now);\n fileClauses.push(clause);\n folderClauses.push(clause);\n continue;\n }\n\n const includesFolder = filter.values.includes('folder');\n if (filter.condition === 'is' ? !includesFolder : includesFolder) {\n showFolders = false;\n }\n\n const mimeClause = buildMimeClause(filter);\n if (mimeClause) {\n fileClauses.push(mimeClause);\n } else if (filter.condition === 'is') {\n // `is [folder]` only — no file can match the badge.\n showFiles = false;\n }\n }\n\n return { fileClauses, folderClauses, showFolders, showFiles };\n};\n"],"names":["MIME_BY_VALUE","picture","audio","video","ALL_MIMES","Object","values","PRESET_DELTAS","days","months","years","subtractPreset","now","preset","result","Date","getTime","dayOfMonth","getDate","setDate","setFullYear","getFullYear","setMonth","getMonth","daysInTargetMonth","Math","min","startOfDay","date","setHours","endOfDay","parseCalendarDate","value","year","month","day","split","map","Number","buildDateClause","filter","field","mode","pivot","condition","$gte","toISOString","$lt","$lte","from","to","$or","$gt","buildMimeClause","fileValues","length","branches","$and","mime","$notContains","$contains","negated","buildAssetFilters","filters","fileClauses","folderClauses","showFolders","showFiles","kind","clause","push","includesFolder","includes","mimeClause"],"mappings":"AA6BA,MAAMA,aAAAA,GAA2E;IAC/EC,OAAAA,EAAS,OAAA;IACTC,KAAAA,EAAO,OAAA;IACPC,KAAAA,EAAO;AACT,CAAA;AAEA,MAAMC,SAAAA,GAAYC,MAAAA,CAAOC,MAAM,CAACN,aAAAA,CAAAA;AAEhC,MAAMO,aAAAA,GAAwF;IAC5F,MAAA,EAAQ;QAAEC,IAAAA,EAAM;AAAE,KAAA;IAClB,OAAA,EAAS;QAAEA,IAAAA,EAAM;AAAE,KAAA;IACnB,OAAA,EAAS;QAAEA,IAAAA,EAAM;AAAE,KAAA;IACnB,QAAA,EAAU;QAAEC,MAAAA,EAAQ;AAAE,KAAA;IACtB,SAAA,EAAW;QAAEA,MAAAA,EAAQ;AAAE,KAAA;IACvB,SAAA,EAAW;QAAEA,MAAAA,EAAQ;AAAE,KAAA;IACvB,OAAA,EAAS;QAAEC,KAAAA,EAAO;AAAE;AACtB,CAAA;AAEA,MAAMC,cAAAA,GAAiB,CAACC,GAAAA,EAAWC,MAAAA,GAAAA;AACjC,IAAA,MAAM,EAAEL,IAAAA,GAAO,CAAC,EAAEC,MAAAA,GAAS,CAAC,EAAEC,KAAAA,GAAQ,CAAC,EAAE,GAAGH,aAAa,CAACM,MAAAA,CAAO;AACjE,IAAA,MAAMC,MAAAA,GAAS,IAAIC,IAAAA,CAAKH,GAAAA,CAAII,OAAO,EAAA,CAAA;AAEnC,IAAA,IAAIN,SAASD,MAAAA,EAAQ;;;QAGnB,MAAMQ,UAAAA,GAAaH,OAAOI,OAAO,EAAA;AACjCJ,QAAAA,MAAAA,CAAOK,OAAO,CAAC,CAAA,CAAA;AACfL,QAAAA,MAAAA,CAAOM,WAAW,CAACN,MAAAA,CAAOO,WAAW,EAAA,GAAKX,KAAAA,CAAAA;AAC1CI,QAAAA,MAAAA,CAAOQ,QAAQ,CAACR,MAAAA,CAAOS,QAAQ,EAAA,GAAKd,MAAAA,CAAAA;QAEpC,MAAMe,iBAAAA,GAAoB,IAAIT,IAAAA,CAAKD,MAAAA,CAAOO,WAAW,EAAA,EAAIP,MAAAA,CAAOS,QAAQ,EAAA,GAAK,CAAA,EAAG,CAAA,CAAA,CAAGL,OAAO,EAAA;AAC1FJ,QAAAA,MAAAA,CAAOK,OAAO,CAACM,IAAAA,CAAKC,GAAG,CAACT,UAAAA,EAAYO,iBAAAA,CAAAA,CAAAA;AACtC,IAAA;AAEAV,IAAAA,MAAAA,CAAOK,OAAO,CAACL,MAAAA,CAAOI,OAAO,EAAA,GAAKV,IAAAA,CAAAA;IAElC,OAAOM,MAAAA;AACT,CAAA;AAEA,MAAMa,aAAa,CAACC,IAAAA,GAAAA;AAClB,IAAA,MAAMd,MAAAA,GAAS,IAAIC,IAAAA,CAAKa,IAAAA,CAAKZ,OAAO,EAAA,CAAA;AACpCF,IAAAA,MAAAA,CAAOe,QAAQ,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,CAAA;IACzB,OAAOf,MAAAA;AACT,CAAA;AAEA,MAAMgB,WAAW,CAACF,IAAAA,GAAAA;AAChB,IAAA,MAAMd,MAAAA,GAAS,IAAIC,IAAAA,CAAKa,IAAAA,CAAKZ,OAAO,EAAA,CAAA;AACpCF,IAAAA,MAAAA,CAAOe,QAAQ,CAAC,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,GAAA,CAAA;IAC5B,OAAOf,MAAAA;AACT,CAAA;AAEA;;;;;IAMO,MAAMiB,iBAAAA,GAAoB,CAACC,KAAAA,GAAAA;IAChC,MAAM,CAACC,IAAAA,EAAMC,KAAAA,EAAOC,GAAAA,CAAI,GAAGH,MAAMI,KAAK,CAAC,GAAA,CAAA,CAAKC,GAAG,CAACC,MAAAA,CAAAA;AAChD,IAAA,OAAO,IAAIvB,IAAAA,CAAKkB,IAAAA,EAAMC,KAAAA,GAAQ,CAAA,EAAGC,GAAAA,CAAAA;AACnC;AAEA,MAAMI,eAAAA,GAAkB,CAACC,MAAAA,EAAoB5B,GAAAA,GAAAA;IAC3C,MAAM,EAAE6B,KAAK,EAAE,GAAGD,MAAAA;IAElB,IAAIA,MAAAA,CAAOE,IAAI,KAAK,QAAA,EAAU;AAC5B,QAAA,MAAMC,KAAAA,GAAQhC,cAAAA,CAAeC,GAAAA,EAAK4B,MAAAA,CAAO3B,MAAM,CAAA;AAE/C,QAAA,OAAQ2B,OAAOI,SAAS;YACtB,KAAK,YAAA;gBACH,OAAO;AAAE,oBAAA,CAACH,QAAQ;AAAEI,wBAAAA,IAAAA,EAAMF,MAAMG,WAAW;AAAG;AAAE,iBAAA;YAClD,KAAK,eAAA;gBACH,OAAO;AAAE,oBAAA,CAACL,QAAQ;AAAEM,wBAAAA,GAAAA,EAAKJ,MAAMG,WAAW;AAAG;AAAE,iBAAA;YACjD,KAAK,WAAA;gBACH,OAAO;AACL,oBAAA,CAACL,QAAQ;wBACPI,IAAAA,EAAMlB,UAAAA,CAAWgB,OAAOG,WAAW,EAAA;wBACnCE,IAAAA,EAAMlB,QAAAA,CAASa,OAAOG,WAAW;AACnC;AACF,iBAAA;AACJ;AACF,IAAA;AAEA,IAAA,MAAMG,OAAOtB,UAAAA,CAAWI,iBAAAA,CAAkBS,MAAAA,CAAOS,IAAI,GAAGH,WAAW,EAAA;AACnE,IAAA,MAAMI,KAAKpB,QAAAA,CAASC,iBAAAA,CAAkBS,MAAAA,CAAOU,EAAE,GAAGJ,WAAW,EAAA;IAE7D,IAAIN,MAAAA,CAAOI,SAAS,KAAK,IAAA,EAAM;QAC7B,OAAO;AAAE,YAAA,CAACH,QAAQ;gBAAEI,IAAAA,EAAMI,IAAAA;gBAAMD,IAAAA,EAAME;AAAG;AAAE,SAAA;AAC7C,IAAA;IAEA,OAAO;QAAEC,GAAAA,EAAK;AAAC,YAAA;AAAE,gBAAA,CAACV,QAAQ;oBAAEM,GAAAA,EAAKE;AAAK;AAAE,aAAA;AAAG,YAAA;AAAE,gBAAA,CAACR,QAAQ;oBAAEW,GAAAA,EAAKF;AAAG;AAAE;AAAE;AAAC,KAAA;AACvE,CAAA;AAEA,MAAMG,kBAAkB,CAACb,MAAAA,GAAAA;IACvB,MAAMc,UAAAA,GAAad,OAAOlC,MAAM,CAACkC,MAAM,CAAC,CAACR,QAA8BA,KAAAA,KAAU,QAAA,CAAA;IAEjF,IAAIsB,UAAAA,CAAWC,MAAM,KAAK,CAAA,EAAG;QAC3B,OAAO,IAAA;AACT,IAAA;AAEA,IAAA,MAAMC,QAAAA,GAA0BF,UAAAA,CAAWjB,GAAG,CAAC,CAACL,KAAAA,GAAAA;AAC9C,QAAA,IAAIA,UAAU,UAAA,EAAY;;YAExB,OAAO;AAAEyB,gBAAAA,IAAAA,EAAMrD,SAAAA,CAAUiC,GAAG,CAAC,CAACqB,QAAU;wBAAEA,IAAAA,EAAM;4BAAEC,YAAAA,EAAcD;AAAK;qBAAE,CAAA;AAAI,aAAA;AAC7E,QAAA;QACA,OAAO;YAAEA,IAAAA,EAAM;gBAAEE,SAAAA,EAAW5D,aAAa,CAACgC,KAAAA;AAAqC;AAAE,SAAA;AACnF,IAAA,CAAA,CAAA;IAEA,IAAIQ,MAAAA,CAAOI,SAAS,KAAK,IAAA,EAAM;AAC7B,QAAA,OAAOY,SAASD,MAAM,KAAK,IAAIC,QAAQ,CAAC,EAAE,GAAG;YAAEL,GAAAA,EAAKK;AAAS,SAAA;AAC/D,IAAA;;AAGA,IAAA,MAAMK,OAAAA,GAAyBP,UAAAA,CAAWjB,GAAG,CAAC,CAACL,KAAAA,GAAAA;AAC7C,QAAA,IAAIA,UAAU,UAAA,EAAY;;YAExB,OAAO;AAAEmB,gBAAAA,GAAAA,EAAK/C,SAAAA,CAAUiC,GAAG,CAAC,CAACqB,QAAU;wBAAEA,IAAAA,EAAM;4BAAEE,SAAAA,EAAWF;AAAK;qBAAE,CAAA;AAAI,aAAA;AACzE,QAAA;QACA,OAAO;YAAEA,IAAAA,EAAM;gBAAEC,YAAAA,EAAc3D,aAAa,CAACgC,KAAAA;AAAqC;AAAE,SAAA;AACtF,IAAA,CAAA,CAAA;AAEA,IAAA,OAAO6B,QAAQN,MAAM,KAAK,IAAIM,OAAO,CAAC,EAAE,GAAG;QAAEJ,IAAAA,EAAMI;AAAQ,KAAA;AAC7D,CAAA;AAoBO,MAAMC,iBAAAA,GAAoB,CAACC,OAAAA,EAAuBnD,GAAAA,GAAAA;AACvD,IAAA,MAAMoD,cAA6B,EAAE;AACrC,IAAA,MAAMC,gBAA+B,EAAE;AACvC,IAAA,IAAIC,WAAAA,GAAc,IAAA;AAClB,IAAA,IAAIC,SAAAA,GAAY,IAAA;IAEhB,KAAK,MAAM3B,UAAUuB,OAAAA,CAAS;QAC5B,IAAIvB,MAAAA,CAAO4B,IAAI,KAAK,MAAA,EAAQ;YAC1B,MAAMC,MAAAA,GAAS9B,gBAAgBC,MAAAA,EAAQ5B,GAAAA,CAAAA;AACvCoD,YAAAA,WAAAA,CAAYM,IAAI,CAACD,MAAAA,CAAAA;AACjBJ,YAAAA,aAAAA,CAAcK,IAAI,CAACD,MAAAA,CAAAA;AACnB,YAAA;AACF,QAAA;AAEA,QAAA,MAAME,cAAAA,GAAiB/B,MAAAA,CAAOlC,MAAM,CAACkE,QAAQ,CAAC,QAAA,CAAA;AAC9C,QAAA,IAAIhC,OAAOI,SAAS,KAAK,IAAA,GAAO,CAAC2B,iBAAiBA,cAAAA,EAAgB;YAChEL,WAAAA,GAAc,KAAA;AAChB,QAAA;AAEA,QAAA,MAAMO,aAAapB,eAAAA,CAAgBb,MAAAA,CAAAA;AACnC,QAAA,IAAIiC,UAAAA,EAAY;AACdT,YAAAA,WAAAA,CAAYM,IAAI,CAACG,UAAAA,CAAAA;AACnB,QAAA,CAAA,MAAO,IAAIjC,MAAAA,CAAOI,SAAS,KAAK,IAAA,EAAM;;YAEpCuB,SAAAA,GAAY,KAAA;AACd,QAAA;AACF,IAAA;IAEA,OAAO;AAAEH,QAAAA,WAAAA;AAAaC,QAAAA,aAAAA;AAAeC,QAAAA,WAAAA;AAAaC,QAAAA;AAAU,KAAA;AAC9D;;;;"}
@@ -11,8 +11,8 @@
11
11
  * same list, so the selection survives the toggle.
12
12
  * - Infinite scroll (load more) does NOT change any segment → selection persists.
13
13
  *
14
- * Search/filter use stable defaults until their header controls are wired;
15
- * follow-up PRs only pass real values — no selection logic changes needed.
14
+ * Filter uses a stable default until its header control is wired; that follow-up
15
+ * only passes a real value — no selection logic changes needed.
16
16
  */ /**
17
17
  * Builds a stable fingerprint for the current asset list query.
18
18
  *
@@ -1 +1 @@
1
- {"version":3,"file":"listQueryKey.js","sources":["../../../../../../admin/src/future/pages/Assets/utils/listQueryKey.ts"],"sourcesContent":["/**\n * Fingerprint for \"which asset list the user is looking at\".\n *\n * Selection clears when this key changes (see ClearSelectionOnChange in AssetsPage).\n *\n * Hybrid rule\n * - Folder, search, sort, filter changes → new key → selection clears.\n * - Switching table/grid view does NOT change the key — both views render the\n * same list, so the selection survives the toggle.\n * - Infinite scroll (load more) does NOT change any segment → selection persists.\n *\n * Search/filter use stable defaults until their header controls are wired;\n * follow-up PRs only pass real values — no selection logic changes needed.\n */\nexport interface ListQueryKeyInput {\n folderId: number | null;\n /** Empty string until search UI ships. */\n search: string;\n /** Null until sort UI ships. */\n sort: string | null;\n /** Null until filter UI ships. */\n filter: string | null;\n}\n\n/**\n * Builds a stable fingerprint for the current asset list query.\n *\n * Selection clears when this key changes. Infinite scroll does not change any\n * segment, so selection persists across load-more.\n */\nexport const getListQueryKey = ({ folderId, search, sort, filter }: ListQueryKeyInput): string =>\n JSON.stringify({ folderId, search, sort, filter });\n"],"names":["getListQueryKey","folderId","search","sort","filter","JSON","stringify"],"mappings":";;AAAA;;;;;;;;;;;;;;;;;;AA6BC,IACM,MAAMA,eAAAA,GAAkB,CAAC,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,IAAI,EAAEC,MAAM,EAAqB,GACnFC,IAAAA,CAAKC,SAAS,CAAC;AAAEL,QAAAA,QAAAA;AAAUC,QAAAA,MAAAA;AAAQC,QAAAA,IAAAA;AAAMC,QAAAA;KAAO;;;;"}
1
+ {"version":3,"file":"listQueryKey.js","sources":["../../../../../../admin/src/future/pages/Assets/utils/listQueryKey.ts"],"sourcesContent":["/**\n * Fingerprint for \"which asset list the user is looking at\".\n *\n * Selection clears when this key changes (see ClearSelectionOnChange in AssetsPage).\n *\n * Hybrid rule\n * - Folder, search, sort, filter changes → new key → selection clears.\n * - Switching table/grid view does NOT change the key — both views render the\n * same list, so the selection survives the toggle.\n * - Infinite scroll (load more) does NOT change any segment → selection persists.\n *\n * Filter uses a stable default until its header control is wired; that follow-up\n * only passes a real value — no selection logic changes needed.\n */\nexport interface ListQueryKeyInput {\n folderId: number | null;\n search: string;\n /** Composite of asset sort and folder position — both change the render order. */\n sort: string | null;\n /** Null until filter UI ships. */\n filter: string | null;\n}\n\n/**\n * Builds a stable fingerprint for the current asset list query.\n *\n * Selection clears when this key changes. Infinite scroll does not change any\n * segment, so selection persists across load-more.\n */\nexport const getListQueryKey = ({ folderId, search, sort, filter }: ListQueryKeyInput): string =>\n JSON.stringify({ folderId, search, sort, filter });\n"],"names":["getListQueryKey","folderId","search","sort","filter","JSON","stringify"],"mappings":";;AAAA;;;;;;;;;;;;;;;;;;AA4BC,IACM,MAAMA,eAAAA,GAAkB,CAAC,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,IAAI,EAAEC,MAAM,EAAqB,GACnFC,IAAAA,CAAKC,SAAS,CAAC;AAAEL,QAAAA,QAAAA;AAAUC,QAAAA,MAAAA;AAAQC,QAAAA,IAAAA;AAAMC,QAAAA;KAAO;;;;"}
@@ -9,8 +9,8 @@
9
9
  * same list, so the selection survives the toggle.
10
10
  * - Infinite scroll (load more) does NOT change any segment → selection persists.
11
11
  *
12
- * Search/filter use stable defaults until their header controls are wired;
13
- * follow-up PRs only pass real values — no selection logic changes needed.
12
+ * Filter uses a stable default until its header control is wired; that follow-up
13
+ * only passes a real value — no selection logic changes needed.
14
14
  */ /**
15
15
  * Builds a stable fingerprint for the current asset list query.
16
16
  *
@@ -1 +1 @@
1
- {"version":3,"file":"listQueryKey.mjs","sources":["../../../../../../admin/src/future/pages/Assets/utils/listQueryKey.ts"],"sourcesContent":["/**\n * Fingerprint for \"which asset list the user is looking at\".\n *\n * Selection clears when this key changes (see ClearSelectionOnChange in AssetsPage).\n *\n * Hybrid rule\n * - Folder, search, sort, filter changes → new key → selection clears.\n * - Switching table/grid view does NOT change the key — both views render the\n * same list, so the selection survives the toggle.\n * - Infinite scroll (load more) does NOT change any segment → selection persists.\n *\n * Search/filter use stable defaults until their header controls are wired;\n * follow-up PRs only pass real values — no selection logic changes needed.\n */\nexport interface ListQueryKeyInput {\n folderId: number | null;\n /** Empty string until search UI ships. */\n search: string;\n /** Null until sort UI ships. */\n sort: string | null;\n /** Null until filter UI ships. */\n filter: string | null;\n}\n\n/**\n * Builds a stable fingerprint for the current asset list query.\n *\n * Selection clears when this key changes. Infinite scroll does not change any\n * segment, so selection persists across load-more.\n */\nexport const getListQueryKey = ({ folderId, search, sort, filter }: ListQueryKeyInput): string =>\n JSON.stringify({ folderId, search, sort, filter });\n"],"names":["getListQueryKey","folderId","search","sort","filter","JSON","stringify"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;AA6BC,IACM,MAAMA,eAAAA,GAAkB,CAAC,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,IAAI,EAAEC,MAAM,EAAqB,GACnFC,IAAAA,CAAKC,SAAS,CAAC;AAAEL,QAAAA,QAAAA;AAAUC,QAAAA,MAAAA;AAAQC,QAAAA,IAAAA;AAAMC,QAAAA;KAAO;;;;"}
1
+ {"version":3,"file":"listQueryKey.mjs","sources":["../../../../../../admin/src/future/pages/Assets/utils/listQueryKey.ts"],"sourcesContent":["/**\n * Fingerprint for \"which asset list the user is looking at\".\n *\n * Selection clears when this key changes (see ClearSelectionOnChange in AssetsPage).\n *\n * Hybrid rule\n * - Folder, search, sort, filter changes → new key → selection clears.\n * - Switching table/grid view does NOT change the key — both views render the\n * same list, so the selection survives the toggle.\n * - Infinite scroll (load more) does NOT change any segment → selection persists.\n *\n * Filter uses a stable default until its header control is wired; that follow-up\n * only passes a real value — no selection logic changes needed.\n */\nexport interface ListQueryKeyInput {\n folderId: number | null;\n search: string;\n /** Composite of asset sort and folder position — both change the render order. */\n sort: string | null;\n /** Null until filter UI ships. */\n filter: string | null;\n}\n\n/**\n * Builds a stable fingerprint for the current asset list query.\n *\n * Selection clears when this key changes. Infinite scroll does not change any\n * segment, so selection persists across load-more.\n */\nexport const getListQueryKey = ({ folderId, search, sort, filter }: ListQueryKeyInput): string =>\n JSON.stringify({ folderId, search, sort, filter });\n"],"names":["getListQueryKey","folderId","search","sort","filter","JSON","stringify"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;AA4BC,IACM,MAAMA,eAAAA,GAAkB,CAAC,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,IAAI,EAAEC,MAAM,EAAqB,GACnFC,IAAAA,CAAKC,SAAS,CAAC;AAAEL,QAAAA,QAAAA;AAAUC,QAAAA,MAAAA;AAAQC,QAAAA,IAAAA;AAAMC,QAAAA;KAAO;;;;"}
@@ -7,10 +7,14 @@ var files = require('../utils/files.js');
7
7
  var uploadFileViaXHR = require('./uploadFileViaXHR.js');
8
8
 
9
9
  /**
10
- * Stores the original upload entries for a batch (keyed by uploadId) to enable retry.
10
+ * Stores the original upload batch (keyed by uploadId) to enable retry.
11
11
  */ const uploadRegistry = new Map();
12
- const registerUploadEntries = (uploadId, entries)=>{
13
- uploadRegistry.set(uploadId, entries);
12
+ const registerUploadEntries = (uploadId, entries, generateAiMetadata, concurrency)=>{
13
+ uploadRegistry.set(uploadId, {
14
+ entries,
15
+ generateAiMetadata,
16
+ concurrency
17
+ });
14
18
  };
15
19
  const getUploadEntries = (uploadId)=>{
16
20
  return uploadRegistry.get(uploadId);
@@ -39,6 +43,54 @@ const unregisterAbortController = (uploadId)=>{
39
43
  unregisterAbortController(uploadId);
40
44
  }
41
45
  };
46
+ /** Maps a server per-file outcome onto the row's terminal metadata status. */ const METADATA_STATUS_BY_RESULT = {
47
+ success: 'generated',
48
+ skipped: 'skipped',
49
+ error: 'failed'
50
+ };
51
+ /**
52
+ * Kicks off AI metadata generation for a freshly uploaded file.
53
+ *
54
+ * Deliberately fire-and-forget — the caller must NOT await it:
55
+ * - generation for file N overlaps the upload of file N+1;
56
+ * - a generation failure can never affect the upload result (the upload already
57
+ * succeeded), which is why the promise is fully swallowed here.
58
+ *
59
+ * Every uploaded file is sent, including non-images: the server already classifies
60
+ * them (`ai-metadata.generateForFiles` returns `skipped` for anything whose mime
61
+ * isn't `image/*`) and duplicating that rule here would let the two drift apart.
62
+ * The upshot is that a non-image now reports "Metadata generation skipped" rather
63
+ * than silently showing no metadata state at all.
64
+ */ const maybeGenerateMetadata = ({ file, index, uploadId, enabled, dispatch })=>{
65
+ if (!enabled || typeof file.id !== 'number') {
66
+ return;
67
+ }
68
+ dispatch(uploadProgress.setFileMetadataGenerating({
69
+ index,
70
+ uploadId
71
+ }));
72
+ // `uploadApi` is declared below but only read at call time, long after the module
73
+ // has finished evaluating.
74
+ const promise = dispatch(uploadApi.endpoints.generateAiMetadata.initiate({
75
+ fileIds: [
76
+ file.id
77
+ ]
78
+ }));
79
+ promise.unwrap().then((results)=>{
80
+ const result = results?.find((r)=>r.id === file.id) ?? results?.[0];
81
+ dispatch(uploadProgress.setFileMetadataResult({
82
+ index,
83
+ uploadId,
84
+ status: result ? METADATA_STATUS_BY_RESULT[result.status] : 'failed'
85
+ }));
86
+ }).catch(()=>{
87
+ dispatch(uploadProgress.setFileMetadataResult({
88
+ index,
89
+ uploadId,
90
+ status: 'failed'
91
+ }));
92
+ });
93
+ };
42
94
  /**
43
95
  * Uploads the given entry indices one at a time through the single-file endpoint.
44
96
  *
@@ -46,16 +98,19 @@ const unregisterAbortController = (uploadId)=>{
46
98
  * per-frame batcher to `setFileProgress`, awaits the XHR, then dispatches
47
99
  * "complete" or "error". Each file is wrapped in its own try/catch so one failure
48
100
  * does not stop the batch. An abort stops the loop without starting further files.
49
- */ const runSequentialUpload = async ({ entries, indices, token, uploadId, abortController, dispatch })=>{
101
+ */ const runUploadPool = async ({ entries, indices, token, uploadId, abortController, dispatch, concurrency = 1, generateAiMetadata })=>{
50
102
  const url = `${window.strapi.backendURL}/upload/unstable/upload-file`;
51
103
  const uploaded = [];
52
- for (const index of indices){
53
- if (abortController.signal.aborted) {
54
- break;
55
- }
104
+ // Shared queue: each worker shifts the next index when it frees up, so N
105
+ // files are in flight at any moment (not N-sized waves). With concurrency 1
106
+ // this degenerates to the original strictly sequential loop.
107
+ const queue = [
108
+ ...indices
109
+ ];
110
+ const uploadOne = async (index)=>{
56
111
  const entry = entries[index];
57
112
  if (!entry) {
58
- continue;
113
+ return;
59
114
  }
60
115
  const fileName = entry.fileInfo?.name ?? entry.file.name;
61
116
  dispatch(uploadProgress.setFileUploading({
@@ -81,10 +136,20 @@ const unregisterAbortController = (uploadId)=>{
81
136
  index,
82
137
  file
83
138
  }));
139
+ // Not awaited: overlaps with the next file's upload and can't fail the batch.
140
+ maybeGenerateMetadata({
141
+ file,
142
+ index,
143
+ uploadId,
144
+ enabled: generateAiMetadata,
145
+ dispatch
146
+ });
84
147
  } catch (err) {
85
148
  batcher.cancel();
86
149
  if (err instanceof uploadFileViaXHR.UploadAbortedError) {
87
- break;
150
+ // Batch was cancelled — the worker loop checks the signal and stops.
151
+ // cancelUpload (dispatched from the dialog) marks the remaining rows.
152
+ return;
88
153
  }
89
154
  const message = err instanceof Error ? err.message : 'Upload failed';
90
155
  dispatch(uploadProgress.setFileError({
@@ -93,7 +158,28 @@ const unregisterAbortController = (uploadId)=>{
93
158
  message
94
159
  }));
95
160
  }
96
- }
161
+ };
162
+ // Guard non-finite/invalid values (NaN, Infinity, <1): `Math.floor(NaN)`
163
+ // would propagate through Math.max to `Array.from({ length: NaN })` = zero
164
+ // workers, silently uploading nothing. Anything invalid falls back to 1.
165
+ const requested = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;
166
+ const workerCount = Math.min(Math.max(1, requested), queue.length);
167
+ const worker = async ()=>{
168
+ while(queue.length > 0){
169
+ if (abortController.signal.aborted) {
170
+ break;
171
+ }
172
+ const index = queue.shift();
173
+ if (index === undefined) {
174
+ break;
175
+ }
176
+ // eslint-disable-next-line no-await-in-loop
177
+ await uploadOne(index);
178
+ }
179
+ };
180
+ await Promise.all(Array.from({
181
+ length: workerCount
182
+ }, ()=>worker()));
97
183
  unregisterAbortController(uploadId);
98
184
  return {
99
185
  data: uploaded
@@ -154,7 +240,7 @@ const unregisterAbortController = (uploadId)=>{
154
240
  /**
155
241
  * Processes an SSE stream from the URL upload endpoint.
156
242
  * Dispatches Redux actions for each file event and returns the final result.
157
- */ const processSSEStream = async ({ response, dispatch })=>{
243
+ */ const processSSEStream = async ({ response, dispatch, uploadId, generateAiMetadata })=>{
158
244
  const reader = response.body.getReader();
159
245
  const decoder = new TextDecoder();
160
246
  let streamResult = null;
@@ -206,6 +292,14 @@ const unregisterAbortController = (uploadId)=>{
206
292
  index,
207
293
  file: payload.file
208
294
  }));
295
+ // Same fire-and-forget semantics as the file flow.
296
+ maybeGenerateMetadata({
297
+ file: payload.file,
298
+ index,
299
+ uploadId,
300
+ enabled: generateAiMetadata,
301
+ dispatch
302
+ });
209
303
  break;
210
304
  }
211
305
  case 'file:error':
@@ -242,10 +336,12 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
242
336
  }).injectEndpoints({
243
337
  endpoints: (builder)=>({
244
338
  /**
245
- * Upload files sequentially, one request per file, to `/upload/unstable/upload-file`.
246
- * Real per-file byte progress comes from `XHR.upload.onprogress`.
339
+ * Upload files to `/upload/unstable/upload-file`, one request per file,
340
+ * through a worker pool of `concurrency` parallel requests (default 1 =
341
+ * sequential). Real per-file byte progress comes from
342
+ * `XHR.upload.onprogress`.
247
343
  */ uploadFiles: builder.mutation({
248
- queryFn: async ({ formData, totalFiles }, { dispatch, getState })=>{
344
+ queryFn: async ({ formData, totalFiles, concurrency = 1, generateAiMetadata }, { dispatch, getState })=>{
249
345
  const token = getState().admin_app?.token;
250
346
  // Extract the original files and their per-file fileInfo from the combined FormData.
251
347
  const files = formData.getAll('files');
@@ -271,23 +367,31 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
271
367
  // Get the uploadId from state after dispatching
272
368
  const uploadId = getState().uploadProgress.uploadId;
273
369
  // Store original entries for retry functionality
274
- registerUploadEntries(uploadId, entries);
370
+ registerUploadEntries(uploadId, entries, generateAiMetadata, concurrency);
275
371
  // One AbortController per batch
276
372
  const abortController = new AbortController();
277
373
  registerAbortController(uploadId, abortController);
278
- return runSequentialUpload({
374
+ return runUploadPool({
279
375
  entries,
280
376
  indices: entries.map((_, index)=>index),
281
377
  token,
282
378
  uploadId,
283
379
  abortController,
284
- dispatch
380
+ dispatch,
381
+ concurrency,
382
+ generateAiMetadata
285
383
  });
286
384
  },
385
+ // `Folder, LIST` refreshes the folder header count, which changes when
386
+ // files are added to it.
287
387
  invalidatesTags: [
288
388
  {
289
389
  type: 'Asset',
290
390
  id: 'LIST'
391
+ },
392
+ {
393
+ type: 'Folder',
394
+ id: 'LIST'
291
395
  }
292
396
  ]
293
397
  }),
@@ -319,19 +423,25 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
319
423
  };
320
424
  }
321
425
  },
426
+ // `Folder, LIST` refreshes the folder header count, which changes when
427
+ // files are added to it.
322
428
  invalidatesTags: [
323
429
  {
324
430
  type: 'Asset',
325
431
  id: 'LIST'
432
+ },
433
+ {
434
+ type: 'Folder',
435
+ id: 'LIST'
326
436
  }
327
437
  ]
328
438
  }),
329
439
  /**
330
440
  * Retry uploading cancelled files.
331
441
  * Maps cancelled rows back to their original entries and re-runs only those
332
- * through the same sequential loop with a fresh AbortController.
442
+ * through the same upload pool with a fresh AbortController.
333
443
  */ retryCancelledFiles: builder.mutation({
334
- queryFn: async (_, { dispatch, getState })=>{
444
+ queryFn: async (_arg, { dispatch, getState })=>{
335
445
  const { uploadId, files: stateFiles } = getState().uploadProgress;
336
446
  const token = getState().admin_app?.token;
337
447
  const cancelledIndices = stateFiles.filter((f)=>f.status === 'cancelled').map((f)=>f.index);
@@ -343,8 +453,8 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
343
453
  }
344
454
  };
345
455
  }
346
- const entries = getUploadEntries(uploadId);
347
- if (!entries) {
456
+ const batch = getUploadEntries(uploadId);
457
+ if (!batch) {
348
458
  return {
349
459
  error: {
350
460
  name: 'UnknownError',
@@ -357,19 +467,28 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
357
467
  // Fresh AbortController for the retry run
358
468
  const abortController = new AbortController();
359
469
  registerAbortController(uploadId, abortController);
360
- return runSequentialUpload({
361
- entries,
470
+ return runUploadPool({
471
+ entries: batch.entries,
362
472
  indices: cancelledIndices,
363
473
  token,
364
474
  uploadId,
365
475
  abortController,
366
- dispatch
476
+ dispatch,
477
+ // Replay the retried rows with the flags the batch was started with.
478
+ concurrency: batch.concurrency,
479
+ generateAiMetadata: batch.generateAiMetadata
367
480
  });
368
481
  },
482
+ // `Folder, LIST` refreshes the folder header count, which changes when
483
+ // files are added to it.
369
484
  invalidatesTags: [
370
485
  {
371
486
  type: 'Asset',
372
487
  id: 'LIST'
488
+ },
489
+ {
490
+ type: 'Folder',
491
+ id: 'LIST'
373
492
  }
374
493
  ]
375
494
  }),
@@ -377,7 +496,7 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
377
496
  * Upload files from URLs.
378
497
  * Sends URLs to the server which fetches and uploads them (SSE flow, unchanged).
379
498
  */ uploadFromUrls: builder.mutation({
380
- queryFn: async ({ urls, folderId }, { dispatch, getState })=>{
499
+ queryFn: async ({ urls, folderId, generateAiMetadata }, { dispatch, getState })=>{
381
500
  const token = getState().admin_app?.token;
382
501
  // Extract filenames from URLs for the progress dialog
383
502
  const fileNames = urls.map((url)=>files.getFilenameFromUrl(url));
@@ -426,7 +545,9 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
426
545
  // Process SSE stream from server
427
546
  const streamResult = await processSSEStream({
428
547
  response,
429
- dispatch
548
+ dispatch,
549
+ uploadId,
550
+ generateAiMetadata
430
551
  });
431
552
  unregisterAbortController(uploadId);
432
553
  if (streamResult && streamResult.data.length > 0) {
@@ -462,19 +583,57 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
462
583
  };
463
584
  }
464
585
  },
586
+ // `Folder, LIST` refreshes the folder header count, which changes when
587
+ // files are added to it.
465
588
  invalidatesTags: [
466
589
  {
467
590
  type: 'Asset',
468
591
  id: 'LIST'
592
+ },
593
+ {
594
+ type: 'Folder',
595
+ id: 'LIST'
469
596
  }
470
597
  ]
598
+ }),
599
+ /**
600
+ * Generate AI metadata (alt text + caption) for the given assets.
601
+ * Synchronous: resolves once every file has been processed and reports the
602
+ * outcome per file, so non-images and individual failures don't fail the
603
+ * whole batch. Existing alt text and captions are never overwritten.
604
+ *
605
+ * Lives here rather than in `assets.ts` because the upload flows dispatch it
606
+ * directly via `initiate()` after each file completes, and `assets.ts` already
607
+ * imports from this module — defining it there would be a circular import.
608
+ * Re-exported from `assets.ts` for the components that consume the hook.
609
+ */ generateAiMetadata: builder.mutation({
610
+ query: ({ fileIds })=>({
611
+ url: '/upload/unstable/generate-ai-metadata',
612
+ method: 'POST',
613
+ data: {
614
+ fileIds
615
+ }
616
+ }),
617
+ transformResponse: (response)=>response.data,
618
+ invalidatesTags: (_result, _error, { fileIds })=>[
619
+ ...fileIds.map((id)=>({
620
+ type: 'Asset',
621
+ id
622
+ })),
623
+ {
624
+ type: 'Asset',
625
+ id: 'LIST'
626
+ }
627
+ ]
471
628
  })
472
629
  })
473
630
  });
474
- const { useUploadFilesMutation, useUploadFileSilentlyMutation, useRetryCancelledFilesMutation, useUploadFromUrlsMutation } = uploadApi;
631
+ const { useUploadFilesMutation, useUploadFileSilentlyMutation, useRetryCancelledFilesMutation, useUploadFromUrlsMutation, useGenerateAiMetadataMutation } = uploadApi;
475
632
 
476
633
  exports.abortUpload = abortUpload;
634
+ exports.maybeGenerateMetadata = maybeGenerateMetadata;
477
635
  exports.uploadApi = uploadApi;
636
+ exports.useGenerateAiMetadataMutation = useGenerateAiMetadataMutation;
478
637
  exports.useRetryCancelledFilesMutation = useRetryCancelledFilesMutation;
479
638
  exports.useUploadFileSilentlyMutation = useUploadFileSilentlyMutation;
480
639
  exports.useUploadFilesMutation = useUploadFilesMutation;