Files
vibegif.lol/src/main.js

177 lines
4.5 KiB
JavaScript

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 {
ELEMENT_IDS,
grabElements,
show,
hide,
setProgress,
addFramePreview,
resetProgress,
showResult,
hideResult,
setGenerating,
syncSizeByModel
} from './app/dom.js';
const WINDOW = 1;
let el = {};
let currentGifUrl = '';
function showSetup() {
show(el.setupScreen);
hide(el.mainScreen);
}
function showMain() {
hide(el.setupScreen);
show(el.mainScreen);
}
function showModal() {
if (el.modalKey) el.modalKey.value = getApiKey();
show(el.modalSettings);
}
function hideModal() {
hide(el.modalSettings);
}
function clearOldResultUrl() {
if (!currentGifUrl) return;
URL.revokeObjectURL(currentGifUrl);
currentGifUrl = '';
}
async function handleGenerate() {
const prompt = (el.inpPrompt?.value || '').trim();
if (!prompt) {
el.inpPrompt?.focus();
return;
}
if (!hasApiKey()) {
showSetup();
return;
}
const model = el.selModel?.value || 'google/gemini-3.1-flash-image-preview';
const frameCount = Math.max(2, Math.min(24, Number.parseInt(el.inpFrames?.value || '4', 10) || 4));
const fps = Math.max(1, Math.min(30, Number.parseInt(el.inpFps?.value || '4', 10) || 4));
const imageSize = el.selSize?.value || '1K';
const aspectRatio = el.selRatio?.value || '1:1';
setGenerating(el, true);
resetProgress(el);
clearOldResultUrl();
hideResult(el);
const allFrames = [];
const history = [];
const firstMsg = buildFirstMessage(prompt);
try {
setProgress(el, 0, `generating frame 1 of ${frameCount}...`);
const first = await generateFrame({
model,
messages: [firstMsg],
imageSize,
aspectRatio,
apiKey: getApiKey()
});
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`);
for (let i = 2; i <= frameCount; i++) {
setProgress(el, Math.round(((i - 1) / frameCount) * 100), `generating frame ${i} of ${frameCount}...`);
const nextUserMsg = buildNextMessage(i, frameCount);
const startIdx = Math.max(1, history.length - WINDOW * 2);
const windowMessages = [firstMsg, ...history.slice(startIdx), nextUserMsg];
const next = await generateFrame({
model,
messages: windowMessages,
imageSize,
aspectRatio,
apiKey: getApiKey()
});
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, 100, 'assembling gif...');
const blob = await assembleGif(allFrames, fps);
currentGifUrl = URL.createObjectURL(blob);
showResult(el, currentGifUrl);
setProgress(el, 100, 'done! 🎉');
} catch (err) {
const msg = err?.message || String(err);
setProgress(el, 0, `error: ${msg}`);
console.error('vibegif error:', err);
} finally {
setGenerating(el, false);
}
}
function boot() {
el = grabElements(ELEMENT_IDS);
if (window.lucide?.createIcons) {
try {
window.lucide.createIcons();
} catch (e) {
console.warn('lucide init error:', e);
}
}
if (hasApiKey()) showMain();
else showSetup();
syncSizeByModel(el);
el.setupSave?.addEventListener('click', () => {
const key = (el.setupKey?.value || '').trim();
if (!key) return;
setApiKey(key);
if (el.setupKey) el.setupKey.value = '';
showMain();
});
el.btnSettings?.addEventListener('click', showModal);
el.btnCloseModal?.addEventListener('click', hideModal);
el.modalSettings?.addEventListener('click', (e) => {
if (e.target === el.modalSettings) hideModal();
});
el.modalSave?.addEventListener('click', () => {
const key = (el.modalKey?.value || '').trim();
if (!key) return;
setApiKey(key);
hideModal();
});
el.selModel?.addEventListener('change', () => syncSizeByModel(el));
el.btnGenerate?.addEventListener('click', handleGenerate);
el.inpPrompt?.addEventListener('keydown', (e) => {
if (e.key === 'Enter') handleGenerate();
});
window.addEventListener('beforeunload', clearOldResultUrl);
console.log('vibegif.lol booted ✓');
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
else boot();