Spaces:
Sleeping
Sleeping
| // ---------- Glowing cursor effect ---------- | |
| const glowingEffect = document.createElement('div'); | |
| glowingEffect.classList.add('glowing-effect', 'js-generated'); | |
| document.body.appendChild(glowingEffect); | |
| // Update CSS custom properties for the glow position (mouse + touch) | |
| function updateGlow(x, y) { | |
| glowingEffect.style.setProperty('--x', `${x}px`); | |
| glowingEffect.style.setProperty('--y', `${y}px`); | |
| } | |
| document.addEventListener('mousemove', (e) => updateGlow(e.clientX, e.clientY)); | |
| document.addEventListener('touchmove', (e) => { | |
| if (e.touches && e.touches[0]) { | |
| updateGlow(e.touches[0].clientX, e.touches[0].clientY); | |
| } | |
| }, { passive: true }); | |
| // ---------- Helper utilities ---------- | |
| const qs = (id) => document.getElementById(id); | |
| const showOutput = (html) => { | |
| const outputDiv = qs('output') || (() => { | |
| const d = document.createElement('div'); | |
| d.id = 'output'; | |
| document.body.appendChild(d); | |
| return d; | |
| })(); | |
| outputDiv.innerHTML = html; | |
| }; | |
| const createDownloadLink = (url, label = 'Download') => { | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.textContent = label; | |
| a.download = ''; | |
| a.className = 'download-link'; | |
| return a; | |
| }; | |
| const createImgPreview = (src, alt = 'preview') => { | |
| const img = document.createElement('img'); | |
| img.src = src; | |
| img.alt = alt; | |
| img.style.maxWidth = '320px'; | |
| img.style.height = 'auto'; | |
| img.style.display = 'block'; | |
| img.style.marginTop = '12px'; | |
| img.style.borderRadius = '8px'; | |
| return img; | |
| }; | |
| // ---------- Elements (gracefully handle missing elements) ---------- | |
| const imageInput = qs('image') || qs('image-input'); // caption image | |
| const foregroundInput = qs('foreground'); // foreground for change bg / remove bg | |
| const backgroundInput = qs('background'); // background for change bg (optional) | |
| const generateCaptionBtn = qs('generate-caption-btn'); | |
| const removeBackgroundBtn = qs('remove-background-btn'); | |
| const changeBackgroundBtn = qs('change-background-btn'); | |
| // ---------- Core request functions using FormData ---------- | |
| async function postFormData(url, formData) { | |
| const res = await fetch(url, { | |
| method: 'POST', | |
| body: formData, // do NOT set Content-Type; browser will add the correct boundary | |
| }); | |
| if (!res.ok) { | |
| const text = await res.text().catch(() => ''); | |
| throw new Error(`Server responded ${res.status}: ${text}`); | |
| } | |
| return res.json().catch(() => { throw new Error('Invalid JSON response'); }); | |
| } | |
| // ---------- Generate Caption ---------- | |
| if (generateCaptionBtn) { | |
| generateCaptionBtn.addEventListener('click', async (e) => { | |
| e.preventDefault(); | |
| try { | |
| const fileInput = imageInput; | |
| if (!fileInput || !fileInput.files || fileInput.files.length === 0) { | |
| showOutput('<span style="color:#b00">Please choose an image to generate caption.</span>'); | |
| return; | |
| } | |
| showOutput('Generating caption…'); | |
| const fd = new FormData(); | |
| fd.append('image', fileInput.files[0]); | |
| const data = await postFormData('/generate_caption', fd); | |
| // Expecting JSON like: { caption: "...", image_url: "..." } but handle flexibly | |
| const caption = data.caption || data.text || ''; | |
| const imageUrl = data.image_url || data.result_url || data.output_url || null; | |
| let html = `<strong>Generated Caption:</strong><div style="margin-top:8px">${caption || '<em>(no caption returned)</em>'}</div>`; | |
| if (imageUrl) { | |
| const img = createImgPreview(imageUrl, 'Generated caption preview'); | |
| html += '<div></div>'; // separator | |
| // we will set outputDiv innerHTML, then append image node and download link | |
| } | |
| showOutput(html); | |
| if (imageUrl) { | |
| const outDiv = qs('output'); | |
| outDiv.appendChild(createImgPreview(imageUrl, 'Generated caption preview')); | |
| outDiv.appendChild(createDownloadLink(imageUrl, 'Download Result Image')); | |
| // Optional: add "Copy caption" button | |
| if (caption) { | |
| const copyBtn = document.createElement('button'); | |
| copyBtn.textContent = 'Copy Caption'; | |
| copyBtn.style.marginLeft = '8px'; | |
| copyBtn.addEventListener('click', () => { | |
| navigator.clipboard?.writeText(caption).then(() => { | |
| copyBtn.textContent = 'Copied!'; | |
| setTimeout(() => copyBtn.textContent = 'Copy Caption', 1500); | |
| }).catch(() => { | |
| copyBtn.textContent = 'Copy failed'; | |
| }); | |
| }); | |
| outDiv.appendChild(copyBtn); | |
| } | |
| } | |
| } catch (err) { | |
| console.error(err); | |
| showOutput(`<span style="color:#b00">Error generating caption: ${err.message}</span>`); | |
| } | |
| }); | |
| } | |
| // ---------- Remove Background ---------- | |
| if (removeBackgroundBtn) { | |
| removeBackgroundBtn.addEventListener('click', async (e) => { | |
| e.preventDefault(); | |
| try { | |
| // Prefer explicit foreground input; fallback to generic image input | |
| const fileInput = foregroundInput || imageInput; | |
| if (!fileInput || !fileInput.files || fileInput.files.length === 0) { | |
| showOutput('<span style="color:#b00">Please choose an image to remove background.</span>'); | |
| return; | |
| } | |
| showOutput('Removing background…'); | |
| const fd = new FormData(); | |
| fd.append('image', fileInput.files[0]); | |
| const data = await postFormData('/remove_background', fd); | |
| // Expect JSON like: { output_url: "..." } or { output_path: "..." } | |
| const outUrl = data.output_url || data.output_path || data.result_url || null; | |
| if (outUrl) { | |
| const outHtml = '<strong>Removed Background:</strong>'; | |
| showOutput(outHtml); | |
| const outDiv = qs('output'); | |
| outDiv.appendChild(createImgPreview(outUrl, 'Result - background removed')); | |
| outDiv.appendChild(createDownloadLink(outUrl, 'Download Result Image')); | |
| } else { | |
| showOutput('<span style="color:#b00">Background removed but server did not return a URL.</span>'); | |
| } | |
| } catch (err) { | |
| console.error(err); | |
| showOutput(`<span style="color:#b00">Error removing background: ${err.message}</span>`); | |
| } | |
| }); | |
| } | |
| // ---------- Change Background ---------- | |
| if (changeBackgroundBtn) { | |
| changeBackgroundBtn.addEventListener('click', async (e) => { | |
| e.preventDefault(); | |
| try { | |
| const fgInput = foregroundInput || imageInput; | |
| if (!fgInput || !fgInput.files || fgInput.files.length === 0) { | |
| showOutput('<span style="color:#b00">Please choose a foreground image.</span>'); | |
| return; | |
| } | |
| // Option A: use a second file input for background if available | |
| // Option B: fallback to a hard-coded URL (if your server supports that) | |
| const bgFile = (backgroundInput && backgroundInput.files && backgroundInput.files[0]) || null; | |
| const FALLBACK_BACKGROUND_URL = null; // set a URL string here only if your server supports background URLs | |
| if (!bgFile && !FALLBACK_BACKGROUND_URL) { | |
| showOutput('<span style="color:#b00">Please choose a background image (or configure FALLBACK_BACKGROUND_URL in the script).</span>'); | |
| return; | |
| } | |
| showOutput('Changing background…'); | |
| const fd = new FormData(); | |
| fd.append('foreground', fgInput.files[0]); | |
| if (bgFile) { | |
| fd.append('background', bgFile); | |
| } else { | |
| fd.append('background_url', FALLBACK_BACKGROUND_URL); | |
| } | |
| const data = await postFormData('/change_background', fd); | |
| const outUrl = data.output_url || data.output_path || data.result_url || null; | |
| if (outUrl) { | |
| showOutput('<strong>Changed Background:</strong>'); | |
| const outDiv = qs('output'); | |
| outDiv.appendChild(createImgPreview(outUrl, 'Result - background changed')); | |
| outDiv.appendChild(createDownloadLink(outUrl, 'Download Result Image')); | |
| } else { | |
| showOutput('<span style="color:#b00">Background changed but server did not return a URL.</span>'); | |
| } | |
| } catch (err) { | |
| console.error(err); | |
| showOutput(`<span style="color:#b00">Error changing background: ${err.message}</span>`); | |
| } | |
| }); | |
| } | |
| // ---------- Accessibility: enable Enter key on focused file inputs when needed ---------- | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') { | |
| // try to trigger caption generation if that button exists and is visible | |
| const active = document.activeElement; | |
| if (active && (active.tagName === 'INPUT' && active.type === 'file')) { | |
| // don't auto-submit; just do nothing (files require user action) | |
| return; | |
| } | |
| } | |
| }); | |
| // ---------- Optional: add minimal CSS for the glowing effect if not already in CSS ---------- | |
| (function ensureGlowCss() { | |
| const id = 'glow-css'; | |
| if (document.getElementById(id)) return; | |
| const style = document.createElement('style'); | |
| style.id = id; | |
| style.textContent = ` | |
| .glowing-effect { | |
| position: fixed; | |
| left: 0; | |
| top: 0; | |
| width: 240px; | |
| height: 240px; | |
| pointer-events: none; | |
| transform: translate(calc(var(--x, 0) - 50%), calc(var(--y, 0) - 50%)); | |
| mix-blend-mode: screen; | |
| filter: blur(40px); | |
| z-index: 9999; | |
| transition: transform 0.04s linear; | |
| background: radial-gradient(circle at center, rgba(0,150,255,0.18) 0%, rgba(0,150,255,0.06) 40%, transparent 70%); | |
| } | |
| .glowing-effect.js-generated { opacity: 0.95; } | |
| #output { margin: 14px auto; max-width: 720px; text-align: left; } | |
| .download-link { display:inline-block; margin-top:8px; padding:8px 12px; background:#007bff; color:#fff; border-radius:6px; text-decoration:none; } | |
| .download-link:hover { background:#0056b3; } | |
| `; | |
| document.head.appendChild(style); | |
| })(); | |