| @@ -1,6 +1,8 @@ | ||
| 1 | 1 | import { AI_HOST } from '@constants'; |
| 2 | +import { renderAiLabelPill, stampAiLabel } from '@shared/lib/ai-label'; | |
| 2 | 3 | import apiFetch from '@wordpress/api-fetch'; |
| 4 | +import { sprintf } from '@wordpress/i18n'; | |
| 3 | 5 | import { addQueryArgs } from '@wordpress/url'; |
| 4 | 6 | |
| 5 | 7 | export const getPlugin = async (slug) => { |
| 6 | 8 | const response = await apiFetch({ |
| @@ -71,8 +73,18 @@ | ||
| 71 | 73 | img.onerror = (e) => reject(e); |
| 72 | 74 | }); |
| 73 | 75 | }; |
| 74 | 76 | |
| 77 | +const shouldDisclose = (metadata) => | |
| 78 | + Boolean(metadata.aiGenerated) && Boolean(metadata.disclose); | |
| 79 | + | |
| 80 | +const altText = (metadata) => { | |
| 81 | + if (!shouldDisclose(metadata)) return metadata.alt ?? ''; | |
| 82 | + const pattern = | |
| 83 | + window.extSharedData?.aiImageAltPattern ?? '%s (AI-generated)'; | |
| 84 | + return sprintf(pattern, metadata.alt ?? '').trim(); | |
| 85 | +}; | |
| 86 | + | |
| 75 | 87 | export const importImage = async (imageUrl, metadata = {}) => { |
| 76 | 88 | const image = new Image(); |
| 77 | 89 | image.src = imageUrl; |
| 78 | 90 | image.crossOrigin = 'anonymous'; |
| @@ -84,8 +96,11 @@ | ||
| 84 | 96 | |
| 85 | 97 | const ctx = canvas.getContext('2d'); |
| 86 | 98 | if (!ctx) return; |
| 87 | 99 | ctx.drawImage(image, 0, 0); |
| 100 | + if (shouldDisclose(metadata)) { | |
| 101 | + stampAiLabel(ctx, canvas.width, canvas.height); | |
| 102 | + } | |
| 88 | 103 | |
| 89 | 104 | const blob = await new Promise((resolve) => { |
| 90 | 105 | canvas.toBlob((blob) => { |
| 91 | 106 | blob && resolve(blob); |
| @@ -93,11 +108,14 @@ | ||
| 93 | 108 | }); |
| 94 | 109 | |
| 95 | 110 | const formData = new FormData(); |
| 96 | 111 | formData.append('file', new File([blob], metadata.filename)); |
| 97 | - formData.append('alt_text', metadata.alt ?? ''); | |
| 112 | + formData.append('alt_text', altText(metadata)); | |
| 98 | 113 | formData.append('caption', metadata.caption ?? ''); |
| 99 | 114 | formData.append('status', 'publish'); |
| 115 | + if (metadata.aiGenerated) { | |
| 116 | + formData.append('meta[extendify_ai_generated]', '1'); | |
| 117 | + } | |
| 100 | 118 | |
| 101 | 119 | return await apiFetch({ |
| 102 | 120 | path: 'wp/v2/media', |
| 103 | 121 | method: 'POST', |
| @@ -108,10 +126,22 @@ | ||
| 108 | 126 | export const importImageServer = async (src, metadata = {}) => { |
| 109 | 127 | const formData = new FormData(); |
| 110 | 128 | formData.append('source', src); |
| 111 | 129 | // Fallback doesn't support custom file_name |
| 112 | - formData.append('alt_text', metadata.alt ?? ''); | |
| 130 | + formData.append('alt_text', altText(metadata)); | |
| 113 | 131 | formData.append('caption', metadata.caption ?? ''); |
| 132 | + if (metadata.aiGenerated) { | |
| 133 | + formData.append('ai_generated', '1'); | |
| 134 | + if (shouldDisclose(metadata)) { | |
| 135 | + const pill = await renderAiLabelPill(); | |
| 136 | + if (pill) { | |
| 137 | + formData.append( | |
| 138 | + 'disclosure_label', | |
| 139 | + new File([pill], 'disclosure-label.png', { type: 'image/png' }), | |
| 140 | + ); | |
| 141 | + } | |
| 142 | + } | |
| 143 | + } | |
| 114 | 144 | |
| 115 | 145 | return await apiFetch({ |
| 116 | 146 | path: '/extendify/v1/draft/upload-image', |
| 117 | 147 | method: 'POST', |
| @@ -126,22 +156,40 @@ | ||
| 126 | 156 | unsplashId, |
| 127 | 157 | metadata = { alt: '', caption: '' }, |
| 128 | 158 | ) => { |
| 129 | 159 | let image; |
| 160 | + const aiGenerated = source === 'ai-generated'; | |
| 130 | 161 | if (unsplashId) { |
| 131 | 162 | await downloadPing(id, source, { unsplashId }); |
| 163 | + } else if (aiGenerated && id) { | |
| 164 | + await downloadPing(id, source, { disclose: metadata.disclose }); | |
| 132 | 165 | } |
| 166 | + | |
| 167 | + let filename = metadata.filename ?? 'image.jpg'; | |
| 168 | + if (aiGenerated && !filename.includes('ai-generated')) { | |
| 169 | + const parts = filename.split('.'); | |
| 170 | + if (parts.length > 1) { | |
| 171 | + const ext = parts.pop(); | |
| 172 | + filename = `${parts.join('.')}-ai-generated.${ext}`; | |
| 173 | + } else { | |
| 174 | + filename = `${filename}-ai-generated`; | |
| 175 | + } | |
| 176 | + } | |
| 177 | + | |
| 133 | 178 | try { |
| 134 | 179 | image = await importImage(src, { |
| 135 | 180 | alt: metadata.alt, |
| 136 | - filename: 'image.jpg', | |
| 181 | + filename, | |
| 137 | 182 | caption: metadata.caption, |
| 183 | + aiGenerated, | |
| 184 | + disclose: metadata.disclose, | |
| 138 | 185 | }); |
| 139 | 186 | } catch (_e) { |
| 140 | 187 | image = await importImageServer(src, { |
| 141 | 188 | alt: metadata.alt, |
| 142 | - filename: 'image.jpg', | |
| 143 | 189 | caption: metadata.caption, |
| 190 | + aiGenerated, | |
| 191 | + disclose: metadata.disclose, | |
| 144 | 192 | }); |
| 145 | 193 | } |
| 146 | 194 | |
| 147 | 195 | return image; |