Repository navigation
v2.3.0: Upload. Measure. Persist.
PromptLab v2.3.0 integrates direct Gemini Files API integration for uploading media up to 2 GB with two upload paths and a built-in file browser, a comprehensive token usage and cost estimation system with real-time per-model pricing badges, and smarter preset state persistence that survives page refreshes. This release also delivers intelligent processing state polling, automated Base64 fallback when Files API assets expire, and hardened error handling across the entire upload and generation pipeline.
✨ Highlights
📤 Gemini Files API — Direct Uploads & Smart Browser
Upload images and videos up to 2 GB directly to Google's Gemini Files API via two upload paths — a high-speed direct resumable stream and a reliable chunked proxy — plus a "Select Existing" file browser for reusing previously uploaded assets without re-uploading.
- ⚡ High-Speed Direct Resumable Upload (
handleDirectResumableUploadinAddFilesApiModal.tsx) — requests a resumable upload session header (X-Goog-Upload-URL) from a newaction: "resumable_session"backend endpoint and streams binary file bytes directly from the browser to Google Cloud viaXMLHttpRequestwith real-time percentage progress tracking (30%–95%), completely bypassing Vercel's 4.5 MB request body limit and execution timeouts for large media files up to 2 GB - 🔁 Smart CORS Verification — if browser CORS policies block reading the raw completion response, a background
action: "list"call automatically checks Gemini Files API storage to confirm file creation without raising CORS error popups or duplicating uploads. If direct upload is blocked entirely, the system seamlessly falls back to the chunked proxy pipeline - 📦 Chunked Proxy Upload — splits large files into 2 MB chunks uploaded sequentially with a session
uploadId, staged in system temp storage (start,chunk,finish,cancelactions), then reassembled and streamed to Gemini Files API via@google/genai - 🔍 "Select Existing" Tab — browse, search, and attach previously uploaded files stored on Google's Gemini Files API without re-uploading. The backend route supports
action: "list"andaction: "delete"commands usingai.files.list()andai.files.delete(). Each file card displays filename, size, MIME type badge,ACTIVE/PROCESSINGstatus, 48-hour expiration countdown,fileUriwith a quick copy button, custom reference label input, and an instant "Delete" button with an in-modal confirmation banner (replacing nativeconfirm()dialogs frequently blocked in iframe environments) - 🔄 Processing State Polling — both server-side and client-side automatically poll media files stuck in
PROCESSINGstate until they reachACTIVE. The generation route pollsai.files.get()up to 15 times at 2-second intervals per image/video reference before invoking generation. The upload modal polls up to 8 times post-upload (direct path) and up to 10 verify + 8 processing iterations (CORS verification path), showing "Processing media tracks on Google Cloud..." status feedback. Errors now differentiate between PROCESSING ("wait a few seconds and try again"), FAILED ("re-upload or select a different asset"), and expired/inaccessible states ("48-hour expiry or API key mismatch") with specific resolution guidance for each - 🛡️ Pre-Verification & Automatic Base64 Fallback — the generation pipeline pre-verifies every
fileUriresource withai.files.get()before invokinggenerateContentStream(). Inaccessible Files API assets (48-hour expiration, API key mismatch, orFAILEDstate) automatically fall back to inline Base64 data if available in the workspace asset payload. When fallback is impossible, it reports a clear asset-specific error identifying the exact@imageN/@videoNtag and resource ID with step-by-step resolution instructions - 🏷️ FILES API Badge & History — all Files API assets display an emerald-green
FILES APIbadge on their asset cards (VisualAssetCardandVideoAssetCard) with local blob URL preview playback. References persist inHistoryItemobjects withisFilesApi,fileUri, andexpirationTimemetadata, allowing historical generations to recall and re-run using active Files API URIs within their 48-hour lifecycle
💰 Token Usage Tracking & Cost Estimation
A comprehensive token tracking and cost estimation system gives you real-time visibility into generation costs, with per-model pricing badges in the engine controls and persistent cost data in your history.
- 📊 Real-Time Token Display — the output panel header shows
TOKENS: {total} ({prompt} IN [{cached} CACHED] / {candidates} OUT), updated live as the server broadcastsusageMetadata(prompt, candidates, total, and cached content token counts) via SSEusageevents - 💵 Estimated Cost Badge — an emerald-green cost badge appears alongside the character count, computed by
calculateEstimatedCost(selectedModel, tokenUsage)from the newlib/pricing.tsmodule. Costs display in USD ($0.001234or< $0.000001for sub-micro amounts) - 🗂️ 6-Model Pricing Table —
lib/pricing.tscovers Gemini 3.6 Flash, 3.5 Flash, 3.5 Flash-Lite, 3.1 Pro Preview (tiered ≤200K / >200K), 3.1 Flash-Lite, and 3 Flash Preview, with audio input rate support and model alias resolution viaMODEL_ALIASES - 🎛️ Pricing Rate Badges in Engine Controls — each model selection card in
EngineControlsModalnow displays per-modelIN: $X.XX / 1M/OUT: $X.XX / 1Mpricing rates in its footer, sourced fromgetModelPricingSummary(). Tiered pricing models display a range (e.g.$2.00–$4.00 / 1M) - 💾 Persistent Cost History —
tokenUsageandestimatedCostare stored inHistoryItemobjects at generation time viacalculateEstimatedCost()and preserved across history JSON import/export. Cost display inHistoryCardSummaryandHistoryViewerModalprefers the storedestimatedCostfield over recomputation, keeping your historical cost data stable even if pricing rates change in the future - 🔁 Session-Surviving Token State — active
tokenUsagepersists tolocalStorageunderprompt_generator_token_usageso your token counts survive page refreshes alongside other session state, restoring automatically on mount
💾 Preset Persistence & Error Hardening
- 📌 Preset Selection Persistence — the active
loadedPresetIdis now persisted tolocalStorageunderprompt_generator_loaded_preset_idand restored on application load. The loaded preset selection stays visible even when editor content diverges, with an amber[EDIT]badge on the preset list item indicating modified content alongside a[Deselect]option to clear the association - 🚫 413 Prevention — the chunked proxy upload pipeline completely eliminates HTTP 413 "Request Entity Too Large" errors for files up to 2 GB
- 🧹 SSE Error Unwrapping — both server-side and client-side now properly unwrap stringified nested JSON error objects from the GoogleGenAI SDK into clean, human-readable text.
403 PERMISSION_DENIEDerrors on expired Files API assets now provide clear actionable guidance explaining 48-hour expiry and API key binding - 🖼️ Empty
srcSanitization — six components (VisualAssetCard,VideoAssetCard,AssetLibrarySidebar,AssetImportModal,HistoryViewerModal,VideoPlayerModal) now rigorously sanitize image and video sources, rendering clean fallback placeholder UI instead of passing empty string""values to<img>,<video>, or<iframe>srcattributes, eliminating browser console warnings and unwanted network requests
✨ What's New
- 📤 Gemini Files API integration — upload images/videos up to 2 GB via direct resumable upload or 2 MB chunked proxy
- ⚡ High-speed direct resumable upload — streams bytes directly to Google Cloud via XHR, bypassing Vercel's 4.5 MB limit
- 🔁 Smart CORS verification — background file listing confirms upload completion without error popups
- 🔍 "Select Existing" file browser — browse, search, and attach previously uploaded Files API assets
- 🔄 Processing state polling — server-side (15× at 2s) and client-side (8-18×) polling until files reach
ACTIVE - 🛡️ Pre-verification with Base64 fallback — auto-fallback when Files API assets expire or are inaccessible
- 🏷️ FILES API badge on asset cards with local blob URL preview playback
- 💰 Token usage tracking — real-time token counts (prompt/candidates/cached/total) via SSE events
- 💵 Estimated cost badge — live USD cost computed per-model from
lib/pricing.tswith 6 models covered - 🎛️ Pricing rate badges in Engine Controls model selector cards
- 💾 Cost history persistence —
tokenUsage+ stableestimatedCoststored inHistoryItemobjects - 📌 Preset selection persistence —
loadedPresetIdsurvives page refreshes;[EDIT]badge on modified presets - 🚫 413 "Request Entity Too Large" prevention in Files API uploader
- 🧹 SSE error unwrapping —
403 PERMISSION_DENIEDon expired Files API assets now shows clear guidance - 🖼️ Empty
srcsanitization — six components prevent emptysrcattributes on media elements
🐛 Fixed
- Files API Pre-Verification & Automatic Base64 Fallback — inaccessible
fileUriresources automatically fall back to inline Base64 data if available; clear asset-specific errors when fallback isn't possible - Gemini Stream Error Formatting & 403 Permission Error Handling — SSE stream errors like
403 PERMISSION_DENIEDon expired Files API file references are no longer swallowed internally; server and client both unwrap stringified nested JSON error objects with actionable guidance explaining 48-hour expiry and API key binding - In-Modal Deletion Confirmation for Gemini Files API Resources — native browser
confirm()/alert()dialogs replaced with inline in-modal confirmation banner that works reliably inside preview iframe environments - Empty String
srcAttribute Prevention for Media Elements — six image/video components rigorously sanitize sources to prevent empty""values on<img>,<video>,<iframe>tags, eliminating browser console warnings and unwanted network requests for Files API assets without inline Base64 streams - Base64 Decoding Error on Files API Media References — server-side generation proxy now correctly checks
fileUriandisFilesApiflags to construct properfileDataparts instead of attempting to parse temporary client-sideblob:preview URLs as Base64 data - 413 Request Entity Too Large Prevention for Large File Uploads — 2 MB chunked pipeline with session
uploadIdandstart/chunk/finish/cancelactions eliminates HTTP 413 errors for uploads up to 2 GB - Preset Edit Indicator and Selection Preservation — loaded preset selection now persists even when editor content diverges, with an amber
[EDIT]badge and[Deselect]option;loadedPresetIdpersisted tolocalStoragefor cross-refresh continuity