import React, { useCallback, useState } from 'react'; import { MEDIA_MAX_REGEN_PER_SUGGESTION, RESOURCE_TYPE_ARTICLE_SUGGESTION, } from '../../service/media/media.interface'; import type { MediaGenerateRequest, MediaResponse, } from '../../service/media/media.interface'; import { deleteMedia, downloadMedia, generateMedia, regenerateMedia, } from '../../service/media/media.service'; const EXTRA_PROMPT_MAX: number = 500; interface ArticleImageSuggestion { placement?: string; caption?: string; alt?: string; } interface ImageSuggestionCardProps { articleId: string; suggestionIndex: number; suggestion: ArticleImageSuggestion; media: MediaResponse | null; clientId: string; token: string; onMediaChanged: (media: MediaResponse) => void; onMediaDeleted: (suggestionId: string) => void; onError?: (message: string) => void; } /** * @param {ImageSuggestionCardProps} props * @return {JSX.Element} */ function ImageSuggestionCard({ articleId, suggestionIndex, suggestion, media, clientId, token, onMediaChanged, onMediaDeleted, onError, }: ImageSuggestionCardProps): JSX.Element { const [busy, setBusy] = useState(false); const [downloading, setDownloading] = useState(false); const [deleting, setDeleting] = useState(false); const [extraPrompt, setExtraPrompt] = useState(''); const [includeText, setIncludeText] = useState(false); const hasImage: boolean = !!media?.image_url; const regenCount: number = media?.regen_count ?? 0; const regenExhausted: boolean = regenCount >= MEDIA_MAX_REGEN_PER_SUGGESTION; const buttonDisabled: boolean = busy || (hasImage && regenExhausted); const submit = useCallback(async (): Promise => { setBusy(true); try { const payload: MediaGenerateRequest = { resource_type: RESOURCE_TYPE_ARTICLE_SUGGESTION, resource_id: articleId, suggestion_id: String(suggestionIndex), extra_prompt: extraPrompt.trim() || null, include_text: includeText, }; const fresh: MediaResponse = hasImage ? await regenerateMedia(clientId, token, payload) : await generateMedia(clientId, token, payload); onMediaChanged(fresh); setExtraPrompt(''); } catch (generationError) { onError?.( generationError instanceof Error ? generationError.message : 'Failed to generate image.' ); } finally { setBusy(false); } }, [ articleId, suggestionIndex, extraPrompt, includeText, hasImage, clientId, token, onMediaChanged, onError, ]); const download = useCallback(async (): Promise => { if (!media?.image_url) return; setDownloading(true); try { const blob: Blob = await downloadMedia( clientId, token, RESOURCE_TYPE_ARTICLE_SUGGESTION, articleId, String(suggestionIndex) ); const objectUrl: string = URL.createObjectURL(blob); const anchor: HTMLAnchorElement = document.createElement('a'); anchor.href = objectUrl; anchor.download = `${articleId}-${suggestionIndex}.png`; document.body.appendChild(anchor); anchor.click(); anchor.remove(); URL.revokeObjectURL(objectUrl); } catch (downloadError) { onError?.( downloadError instanceof Error ? downloadError.message : "Couldn't download the image." ); } finally { setDownloading(false); } }, [media?.image_url, articleId, suggestionIndex, clientId, token, onError]); const remove = useCallback(async (): Promise => { if (!media) return; setDeleting(true); try { const suggestionId: string = String(suggestionIndex); await deleteMedia( clientId, token, RESOURCE_TYPE_ARTICLE_SUGGESTION, articleId, suggestionId ); onMediaDeleted(suggestionId); } catch (deleteError) { onError?.( deleteError instanceof Error ? deleteError.message : "Couldn't delete the image." ); } finally { setDeleting(false); } }, [media, clientId, token, articleId, suggestionIndex, onMediaDeleted]); const buttonLabel: string = hasImage ? regenExhausted ? 'No regenerations left' : `Regenerate (${MEDIA_MAX_REGEN_PER_SUGGESTION - regenCount} left)` : 'Generate image'; return (
{hasImage ? ( {suggestion.alt ) : (
)}
{suggestion.placement && ( {suggestion.placement} )} {suggestion.caption && (

{suggestion.caption}

)} {suggestion.alt && (

alt: {suggestion.alt}

)}