mirror of
https://github.com/vibegif/vibegif.lol.git
synced 2026-04-07 10:12:13 +00:00
Feat: Main orchestrator — generation loop and events
This commit is contained in:
115
app.js
Normal file
115
app.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { getApiKey, setApiKey, hasApiKey } from './storage.js';
|
||||||
|
import { generateFrame, buildFirstMessage, buildNextMessage } from './api.js';
|
||||||
|
import { assembleGif } from './gifmaker.js';
|
||||||
|
import {
|
||||||
|
el, showSetup, showMain, showModal, hideModal,
|
||||||
|
setProgress, addFramePreview, resetProgress,
|
||||||
|
showResult, hideResult, setGenerating,
|
||||||
|
} from './ui.js';
|
||||||
|
|
||||||
|
// --- Init ---
|
||||||
|
function init() {
|
||||||
|
lucide.createIcons();
|
||||||
|
hasApiKey() ? showMain() : showSetup();
|
||||||
|
bindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Events ---
|
||||||
|
function bindEvents() {
|
||||||
|
el.setupSave.addEventListener('click', () => {
|
||||||
|
const k = el.setupKey.value.trim();
|
||||||
|
if (!k) return;
|
||||||
|
setApiKey(k);
|
||||||
|
showMain();
|
||||||
|
});
|
||||||
|
|
||||||
|
el.btnSettings.addEventListener('click', () => showModal(getApiKey()));
|
||||||
|
el.btnCloseModal.addEventListener('click', hideModal);
|
||||||
|
el.modal.addEventListener('click', (e) => { if (e.target === el.modal) hideModal(); });
|
||||||
|
|
||||||
|
el.modalSave.addEventListener('click', () => {
|
||||||
|
const k = el.modalKey.value.trim();
|
||||||
|
if (!k) return;
|
||||||
|
setApiKey(k);
|
||||||
|
hideModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
el.selModel.addEventListener('change', () => {
|
||||||
|
const isGemini = el.selModel.value.startsWith('google/');
|
||||||
|
const opt05 = el.selSize.querySelector('option[value="0.5K"]');
|
||||||
|
if (!isGemini && el.selSize.value === '0.5K') el.selSize.value = '1K';
|
||||||
|
opt05.disabled = !isGemini;
|
||||||
|
});
|
||||||
|
|
||||||
|
el.btnGenerate.addEventListener('click', handleGenerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Generation ---
|
||||||
|
async function handleGenerate() {
|
||||||
|
const prompt = el.inpPrompt.value.trim();
|
||||||
|
if (!prompt) { el.inpPrompt.focus(); return; }
|
||||||
|
|
||||||
|
const model = el.selModel.value;
|
||||||
|
const frameCount = Math.max(2, Math.min(24, parseInt(el.inpFrames.value) || 4));
|
||||||
|
const fps = Math.max(1, Math.min(30, parseInt(el.inpFps.value) || 4));
|
||||||
|
const imageSize = el.selSize.value;
|
||||||
|
const aspectRatio = el.selRatio.value;
|
||||||
|
const WINDOW = 2;
|
||||||
|
|
||||||
|
setGenerating(true);
|
||||||
|
resetProgress();
|
||||||
|
hideResult();
|
||||||
|
|
||||||
|
const allBase64 = [];
|
||||||
|
const fullHistory = []; // all assistant messages for rolling window
|
||||||
|
|
||||||
|
try {
|
||||||
|
// --- Frame 1 ---
|
||||||
|
setProgress(0, `generating frame 1 of ${frameCount}...`);
|
||||||
|
const firstMsg = buildFirstMessage(prompt);
|
||||||
|
const messages1 = [firstMsg];
|
||||||
|
|
||||||
|
const r1 = await generateFrame({ model, messages: messages1, imageSize, aspectRatio });
|
||||||
|
allBase64.push(r1.base64);
|
||||||
|
fullHistory.push(firstMsg, r1.assistantMsg);
|
||||||
|
addFramePreview(r1.base64, 0);
|
||||||
|
setProgress(Math.round(100 / frameCount), `frame 1 of ${frameCount} done`);
|
||||||
|
|
||||||
|
// --- Frames 2..N ---
|
||||||
|
for (let i = 2; i <= frameCount; i++) {
|
||||||
|
setProgress(Math.round(((i - 1) / frameCount) * 100), `generating frame ${i} of ${frameCount}...`);
|
||||||
|
|
||||||
|
const nextUserMsg = buildNextMessage(i, frameCount);
|
||||||
|
|
||||||
|
// Build rolling window: first user msg + last WINDOW (assistant+user) pairs
|
||||||
|
const windowMessages = [firstMsg];
|
||||||
|
const pairCount = fullHistory.length; // includes user+assistant messages
|
||||||
|
const startIdx = Math.max(1, pairCount - WINDOW * 2);
|
||||||
|
for (let j = startIdx; j < pairCount; j++) {
|
||||||
|
windowMessages.push(fullHistory[j]);
|
||||||
|
}
|
||||||
|
windowMessages.push(nextUserMsg);
|
||||||
|
|
||||||
|
const ri = await generateFrame({ model, messages: windowMessages, imageSize, aspectRatio });
|
||||||
|
allBase64.push(ri.base64);
|
||||||
|
fullHistory.push(nextUserMsg, ri.assistantMsg);
|
||||||
|
addFramePreview(ri.base64, i - 1);
|
||||||
|
setProgress(Math.round((i / frameCount) * 100), `frame ${i} of ${frameCount} done`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Assemble GIF ---
|
||||||
|
setProgress(100, 'assembling gif...');
|
||||||
|
const blob = await assembleGif(allBase64, fps);
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
showResult(url);
|
||||||
|
setProgress(100, 'done!');
|
||||||
|
} catch (err) {
|
||||||
|
setProgress(0, `error: ${err.message}`);
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
setGenerating(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Boot ---
|
||||||
|
init();
|
||||||
Reference in New Issue
Block a user