From cb948b0c1bbc6f9b3f89201a54aeb0a45e1ee5d5 Mon Sep 17 00:00:00 2001 From: AJFrio <20246916+AJFrio@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:02:32 +0000 Subject: [PATCH] perf: parallelize sequential API requests in AddMediaModal Optimize the media upload and reference image fetching processes by replacing sequential `for...of` loops with `Promise.all`. This allows multiple network requests to be executed concurrently, significantly reducing wait time for users. - Refactored `uploadFileAndRecord` to use `Promise.all` for file processing and uploads. - Refactored `generateImage` to use `Promise.all` for fetching reference images. - Added `benchmarks/add-media-modal-upload.js` to establish and verify performance gains (~80% improvement simulated). Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- benchmarks/add-media-modal-upload.js | 111 +++++++++++++++++++++++++ src/components/admin/AddMediaModal.jsx | 20 ++--- 2 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 benchmarks/add-media-modal-upload.js diff --git a/benchmarks/add-media-modal-upload.js b/benchmarks/add-media-modal-upload.js new file mode 100644 index 0000000..5491bce --- /dev/null +++ b/benchmarks/add-media-modal-upload.js @@ -0,0 +1,111 @@ +import { performance } from 'perf_hooks'; + +// Simulated delays +const FILE_TO_DATA_URL_DELAY = 10; +const UPLOAD_API_DELAY = 100; +const MEDIA_API_DELAY = 50; + +// Mock functions +async function fileToDataUrl(f) { + await new Promise(resolve => setTimeout(resolve, FILE_TO_DATA_URL_DELAY)); + return 'data:image/png;base64,mockbase64'; +} + +function parseDataUrl(dataUrl) { + return { mimeType: 'image/png', base64: 'mockbase64' }; +} + +async function adminApiRequest(url, options) { + const delay = url.includes('/storage/upload') ? UPLOAD_API_DELAY : MEDIA_API_DELAY; + await new Promise(resolve => setTimeout(resolve, delay)); + return { + ok: true, + json: async () => ({ + viewUrl: 'http://example.com/image.png', + id: 'mock-id' + }) + }; +} + +// Current sequential implementation +async function uploadFileAndRecordSequential(files) { + const createdItems = []; + for (const f of files) { + const dataUrl = await fileToDataUrl(f); + const { mimeType, base64 } = parseDataUrl(dataUrl); + const uploadRes = await adminApiRequest('/api/admin/storage/upload', { + method: 'POST', + body: JSON.stringify({ mimeType, dataBase64: base64, filename: f.name || 'image.png' }) + }); + const uploaded = await uploadRes.json(); + + const mediaRes = await adminApiRequest('/api/admin/media', { + method: 'POST', + body: JSON.stringify({ + url: uploaded.viewUrl || uploaded.downloadUrl, + source: 'storage', + filename: f.name || 'image', + mimeType: mimeType, + }) + }); + const saved = await mediaRes.json(); + createdItems.push(saved); + } + return createdItems; +} + +// Optimized parallel implementation +async function uploadFileAndRecordParallel(files) { + return Promise.all(files.map(async (f) => { + const dataUrl = await fileToDataUrl(f); + const { mimeType, base64 } = parseDataUrl(dataUrl); + const uploadRes = await adminApiRequest('/api/admin/storage/upload', { + method: 'POST', + body: JSON.stringify({ mimeType, dataBase64: base64, filename: f.name || 'image.png' }) + }); + const uploaded = await uploadRes.json(); + + const mediaRes = await adminApiRequest('/api/admin/media', { + method: 'POST', + body: JSON.stringify({ + url: uploaded.viewUrl || uploaded.downloadUrl, + source: 'storage', + filename: f.name || 'image', + mimeType: mimeType, + }) + }); + return await mediaRes.json(); + })); +} + +async function runBenchmark() { + const files = [ + { name: 'file1.png' }, + { name: 'file2.png' }, + { name: 'file3.png' }, + { name: 'file4.png' }, + { name: 'file5.png' }, + ]; + + console.log(`Running benchmark with ${files.length} files...`); + + const startSeq = performance.now(); + await uploadFileAndRecordSequential(files); + const endSeq = performance.now(); + const seqTime = endSeq - startSeq; + console.log(`Sequential implementation: ${seqTime.toFixed(2)}ms`); + + const startPar = performance.now(); + await uploadFileAndRecordParallel(files); + const endPar = performance.now(); + const parTime = endPar - startPar; + console.log(`Parallel implementation: ${parTime.toFixed(2)}ms`); + + const improvement = ((seqTime - parTime) / seqTime) * 100; + console.log(`Improvement: ${improvement.toFixed(2)}%`); + + // Theoretical sequential time: 5 * (10 + 100 + 50) = 800ms + // Theoretical parallel time: 1 * (10 + 100 + 50) = 160ms +} + +runBenchmark().catch(console.error); diff --git a/src/components/admin/AddMediaModal.jsx b/src/components/admin/AddMediaModal.jsx index f9da65a..a0fa072 100644 --- a/src/components/admin/AddMediaModal.jsx +++ b/src/components/admin/AddMediaModal.jsx @@ -61,8 +61,7 @@ export default function AddMediaModal({ open, onClose, onCreated }) { setUploading(true) setError('') try { - const createdItems = [] - for (const f of files) { + const createdItems = await Promise.all(files.map(async (f) => { const dataUrl = await fileToDataUrl(f) const { mimeType, base64 } = parseDataUrl(dataUrl) const uploadRes = await adminApiRequest('/api/admin/storage/upload', { @@ -83,8 +82,8 @@ export default function AddMediaModal({ open, onClose, onCreated }) { }) const saved = await mediaRes.json() if (!mediaRes.ok) throw new Error(saved.error || 'Failed to save media') - createdItems.push(saved) - } + return saved + })) onCreated?.(createdItems) onClose?.() } catch (e) { @@ -155,17 +154,18 @@ export default function AddMediaModal({ open, onClose, onCreated }) { setError('') try { // Build inputs from selected reference images (up to 3) - const inputs = [] - for (const url of refUrls.slice(0, 3)) { + const inputs = (await Promise.all(refUrls.slice(0, 3).map(async (url) => { try { const proxied = `/api/image-proxy?src=${encodeURIComponent(url)}` const resp = await fetch(proxied) - if (!resp.ok) continue + if (!resp.ok) return null const blob = await resp.blob() const { mimeType, base64 } = await blobToBase64(blob) - if (base64) inputs.push({ mimeType, dataBase64: base64 }) - } catch (_) {} - } + return base64 ? { mimeType, dataBase64: base64 } : null + } catch (_) { + return null + } + }))).filter(Boolean) const res = await adminApiRequest('/api/admin/ai/generate-image', { method: 'POST',