Feat: semi-transparent mode with animated webp

This commit is contained in:
2026-03-21 00:36:15 -07:00
parent 23ee7642ae
commit 4de18f3825

View File

@@ -2,6 +2,8 @@ import { getApiKey, setApiKey, hasApiKey } from './core/storage.js';
import { buildFirstMessage, buildNextMessage } from './core/messages.js';
import { generateFrame } from './services/openrouter.js';
import { assembleGif } from './services/gif.js';
import { removeBackgroundFrames } from './services/background.js';
import { assembleAnimatedWebp } from './services/webp.js';
import {
ELEMENT_IDS,
grabElements,
@@ -62,6 +64,9 @@ async function handleGenerate() {
const maxSize = Math.max(32, Math.min(1024, Number.parseInt(el.inpMaxSize?.value || '256', 10) || 256));
const imageSize = '1K';
const aspectRatio = el.selRatio?.value || '1:1';
const semiTransparent = !!el.chkSemiTransparent?.checked;
const generationMaxPct = semiTransparent ? 55 : 100;
setGenerating(el, true);
resetProgress(el);
@@ -86,10 +91,10 @@ async function handleGenerate() {
allFrames.push(first.base64);
history.push(firstMsg, first.assistantMsg);
addFramePreview(el, first.base64, 0);
setProgress(el, Math.round(100 / frameCount), `frame 1 of ${frameCount} done`);
setProgress(el, Math.round((1 / frameCount) * generationMaxPct), `frame 1 of ${frameCount} done`);
for (let i = 2; i <= frameCount; i++) {
setProgress(el, Math.round(((i - 1) / frameCount) * 100), `generating frame ${i} of ${frameCount}...`);
setProgress(el, Math.round(((i - 1) / frameCount) * generationMaxPct), `generating frame ${i} of ${frameCount}...`);
const nextUserMsg = buildNextMessage(i, frameCount);
const startIdx = Math.max(1, history.length - WINDOW * 2);
@@ -106,14 +111,35 @@ async function handleGenerate() {
allFrames.push(next.base64);
history.push(nextUserMsg, next.assistantMsg);
addFramePreview(el, next.base64, i - 1);
setProgress(el, Math.round((i / frameCount) * 100), `frame ${i} of ${frameCount} done`);
setProgress(el, Math.round((i / frameCount) * generationMaxPct), `frame ${i} of ${frameCount} done`);
}
if (!semiTransparent) {
setProgress(el, 100, 'assembling gif...');
const blob = await assembleGif(allFrames, fps, maxSize);
currentGifUrl = URL.createObjectURL(blob);
showResult(el, currentGifUrl);
showResult(el, currentGifUrl, 'vibegif.gif');
setProgress(el, 100, 'done! 🎉');
return;
}
setProgress(el, 56, 'starting background removal...');
const transparentFrames = await removeBackgroundFrames(allFrames, {
onProgress: ({ pct, text }) => {
const mapped = Math.round(55 + (Math.max(0, Math.min(100, pct)) * 0.30));
setProgress(el, mapped, text || 'removing backgrounds...');
}
});
setProgress(el, 86, 'assembling animated webp...');
const webpBlob = await assembleAnimatedWebp(transparentFrames, fps, maxSize, (pct, text) => {
const mapped = Math.round(85 + (Math.max(0, Math.min(100, pct)) * 0.15));
setProgress(el, mapped, text || 'assembling animated webp...');
});
currentGifUrl = URL.createObjectURL(webpBlob);
showResult(el, currentGifUrl, 'vibegif.webp');
setProgress(el, 100, 'done! transparent animated webp ready ✨');
} catch (err) {
const msg = err?.message || String(err);
setProgress(el, 0, `error: ${msg}`);