import { jsx, jsxs } from 'react/jsx-runtime'; import * as React from 'react'; import { createContext, useNotification } from '@strapi/admin/strapi-admin'; import { Modal, Button, Flex, Alert } from '@strapi/design-system'; import { produce } from 'immer'; import { useIntl } from 'react-intl'; import { styled } from 'styled-components'; import { AddAssetStep } from '../../components/UploadAssetDialog/AddAssetStep/AddAssetStep.mjs'; import { useBulkEdit } from '../../hooks/useBulkEdit.mjs'; import { useTracking } from '../../hooks/useTracking.mjs'; import { useUpload } from '../../hooks/useUpload.mjs'; import 'byte-size'; import 'date-fns'; import { getTrad } from '../../utils/getTrad.mjs'; import 'qs'; import '../../utils/urlYupSchema.mjs'; import { AIAssetCardSkeletons, AIAssetCard } from './AIAssetCard.mjs'; /* ------------------------------------------------------------------------------------------------- * ModalBody * -----------------------------------------------------------------------------------------------*/ const StyledModalBody = styled(Modal.Body)` padding: 0; display: flex; justify-content: center; [data-radix-scroll-area-viewport] { padding-top: ${({ theme })=>theme.spaces[6]}; padding-bottom: ${({ theme })=>theme.spaces[6]}; padding-left: ${({ theme })=>theme.spaces[7]}; padding-right: ${({ theme })=>theme.spaces[7]}; } `; const StyledAlert = styled(Alert)` & > button { display: none; } `; const ModalContent = ({ onClose })=>{ const { formatMessage } = useIntl(); const { toggleNotification } = useNotification(); const state = useAIUploadModalContext('ModalContent', (s)=>s.state); const dispatch = useAIUploadModalContext('ModalContent', (s)=>s.dispatch); const folderId = useAIUploadModalContext('ModalContent', (s)=>s.folderId); const { upload } = useUpload(); const { edit, isLoading: isSaving } = useBulkEdit(); const [isUploading, setIsUploading] = React.useState(false); const [uploadError, setUploadError] = React.useState(null); const { trackUsage } = useTracking(); const handleCaptionChange = (assetId, caption)=>{ dispatch({ type: 'set_uploaded_asset_caption', payload: { id: assetId, caption } }); }; const handleAltTextChange = (assetId, altText)=>{ dispatch({ type: 'set_uploaded_asset_alt_text', payload: { id: assetId, altText } }); }; const resetState = ()=>{ dispatch({ type: 'set_uploaded_assets', payload: [] }); }; const handleFinish = async ()=>{ if (state.hasUnsavedChanges) { const assetsToUpdate = state.uploadedAssets.filter((asset)=>(asset.wasCaptionChanged || asset.wasAltTextChanged) && asset.file.id); if (assetsToUpdate.length > 0) { if (assetsToUpdate.some((asset)=>asset.wasCaptionChanged)) { trackUsage('didEditAICaption'); } if (assetsToUpdate.some((asset)=>asset.wasAltTextChanged)) { trackUsage('didEditAIAlternativeText'); } // Update assets const updates = assetsToUpdate.map((asset)=>({ id: asset.file.id, fileInfo: { name: asset.file.name, alternativeText: asset.file.alternativeText ?? null, caption: asset.file.caption ?? null, folder: typeof asset.file.folder === 'object' && asset.file.folder !== null ? asset.file.folder.id : asset.file.folder } })); try { await edit(updates); dispatch({ type: 'clear_unsaved_changes' }); } catch (err) { toggleNotification({ type: 'danger', message: (err instanceof Error ? err.message : null) || formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }) }); return; // Don't close modal on error } } } resetState(); onClose(); }; const handleCancel = ()=>{ resetState(); onClose(); }; const handleUpload = async (assets)=>{ dispatch({ type: 'set_assets_to_upload_length', payload: assets.length }); setUploadError(null); setIsUploading(true); try { const assetsForUpload = assets.map((asset)=>({ ...asset, id: asset.id ? Number(asset.id) : undefined })); const uploadedFiles = await upload(assetsForUpload, folderId); const filesWithFolder = uploadedFiles.map((file)=>({ ...file, // The upload API doesn't populate the folder relation, so we add it manually folder: folderId || file.folder })); dispatch({ type: 'set_uploaded_assets', payload: filesWithFolder }); } catch (error) { console.error('Upload failed:', error); setUploadError(error instanceof Error ? error : new Error('Upload failed')); } finally{ setIsUploading(false); } }; if (state.assetsToUploadLength === 0) { return /*#__PURE__*/ jsx(Modal.Content, { children: /*#__PURE__*/ jsx(AddAssetStep, { onClose: onClose, onAddAsset: handleUpload }) }); } if (isUploading || state.assetsToUploadLength > 0 && state.uploadedAssets.length === 0 && !uploadError) { return /*#__PURE__*/ jsxs(Modal.Content, { children: [ /*#__PURE__*/ jsx(Modal.Header, { children: /*#__PURE__*/ jsx(Modal.Title, { children: formatMessage({ id: getTrad('ai.modal.uploading.title'), defaultMessage: 'Uploading and processing with AI...' }) }) }), /*#__PURE__*/ jsx(StyledModalBody, { children: /*#__PURE__*/ jsx(AIAssetCardSkeletons, { count: state.assetsToUploadLength }) }) ] }); } const title = formatMessage({ id: getTrad('ai.modal.title'), defaultMessage: '{count, plural, one {# asset uploaded} other {# assets uploaded}}, review AI generated metadata' }, { count: state.uploadedAssets.length }); if (uploadError) { return /*#__PURE__*/ jsxs(Modal.Content, { children: [ /*#__PURE__*/ jsx(Modal.Header, { children: /*#__PURE__*/ jsx(Modal.Title, { children: title }) }), /*#__PURE__*/ jsx(Modal.Body, { children: /*#__PURE__*/ jsx(StyledAlert, { closeLabel: "", variant: "danger", children: formatMessage({ id: getTrad('ai.modal.error'), defaultMessage: 'Could not generate AI metadata for the uploaded files.' }) }) }), /*#__PURE__*/ jsxs(Modal.Footer, { children: [ /*#__PURE__*/ jsx(Button, { onClick: handleCancel, variant: "tertiary", children: formatMessage({ id: 'cancel', defaultMessage: 'Cancel' }) }), /*#__PURE__*/ jsx(Button, { onClick: handleFinish, loading: isSaving, children: formatMessage({ id: 'global.finish', defaultMessage: 'Finish' }) }) ] }) ] }); } return /*#__PURE__*/ jsxs(Modal.Content, { children: [ /*#__PURE__*/ jsx(Modal.Header, { children: /*#__PURE__*/ jsx(Modal.Title, { children: title }) }), /*#__PURE__*/ jsx(StyledModalBody, { children: /*#__PURE__*/ jsx(Flex, { gap: 6, direction: "column", alignItems: "stretch", children: state.uploadedAssets.map(({ file: asset, wasCaptionChanged, wasAltTextChanged })=>/*#__PURE__*/ jsx(AIAssetCard, { asset: asset, onCaptionChange: (caption)=>asset.id && handleCaptionChange(asset.id, caption), onAltTextChange: (altText)=>asset.id && handleAltTextChange(asset.id, altText), wasCaptionChanged: wasCaptionChanged, wasAltTextChanged: wasAltTextChanged }, asset.id)) }) }), /*#__PURE__*/ jsxs(Modal.Footer, { children: [ /*#__PURE__*/ jsx(Button, { onClick: handleCancel, variant: "tertiary", children: formatMessage({ id: 'cancel', defaultMessage: 'Cancel' }) }), /*#__PURE__*/ jsx(Button, { onClick: handleFinish, loading: isSaving, children: formatMessage({ id: 'global.finish', defaultMessage: 'Finish' }) }) ] }) ] }); }; const [AIUploadModalContext, useAIUploadModalContext] = createContext('AIUploadModalContext'); const reducer = (state, action)=>{ return produce(state, (draft)=>{ if (action.type === 'set_uploaded_assets') { draft.uploadedAssets = action.payload.map((file)=>({ file, wasCaptionChanged: false, wasAltTextChanged: false })); draft.hasUnsavedChanges = false; } if (action.type === 'set_assets_to_upload_length') { draft.assetsToUploadLength = action.payload; } if (action.type === 'set_uploaded_asset_caption') { const asset = draft.uploadedAssets.find((a)=>a.file.id === action.payload.id); if (asset && asset.file.caption !== action.payload.caption) { asset.file.caption = action.payload.caption; asset.wasCaptionChanged = true; draft.hasUnsavedChanges = true; } } if (action.type === 'set_uploaded_asset_alt_text') { const asset = draft.uploadedAssets.find((a)=>a.file.id === action.payload.id); if (asset && asset.file.alternativeText !== action.payload.altText) { asset.file.alternativeText = action.payload.altText; asset.wasAltTextChanged = true; draft.hasUnsavedChanges = true; } } if (action.type === 'remove_uploaded_asset') { draft.uploadedAssets = draft.uploadedAssets.filter((a)=>a.file.id !== action.payload.id); } if (action.type === 'edit_uploaded_asset') { const assetIndex = draft.uploadedAssets.findIndex((a)=>a.file.id === action.payload.editedAsset.id); if (assetIndex !== -1) { draft.uploadedAssets[assetIndex] = { file: action.payload.editedAsset, wasCaptionChanged: draft.uploadedAssets[assetIndex].wasCaptionChanged, wasAltTextChanged: draft.uploadedAssets[assetIndex].wasAltTextChanged }; } } if (action.type === 'clear_unsaved_changes') { draft.hasUnsavedChanges = false; draft.uploadedAssets.forEach((asset)=>{ asset.wasCaptionChanged = false; asset.wasAltTextChanged = false; }); } }); }; const AIUploadModal = ({ open, onClose, folderId = null })=>{ const [state, dispatch] = React.useReducer(reducer, { uploadedAssets: [], assetsToUploadLength: 0, hasUnsavedChanges: false }); const handleClose = React.useCallback(()=>{ // Reset state when modal closes dispatch({ type: 'set_uploaded_assets', payload: [] }); onClose(); }, [ onClose ]); return /*#__PURE__*/ jsx(AIUploadModalContext, { state: state, dispatch: dispatch, folderId: folderId, onClose: handleClose, children: /*#__PURE__*/ jsx(Modal.Root, { open: open, onOpenChange: handleClose, children: /*#__PURE__*/ jsx(ModalContent, { onClose: handleClose }) }) }); }; export { AIUploadModal, useAIUploadModalContext }; //# sourceMappingURL=AIUploadModal.mjs.map