mirror of
https://github.com/vibegif/vibegif.lol.git
synced 2026-04-07 02:12:12 +00:00
Refactor: App orchestration module
This commit is contained in:
177
src/main.js
Normal file
177
src/main.js
Normal file
@@ -0,0 +1,177 @@
|
||||
import './styles.css';
|
||||
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();
|
||||
Reference in New Issue
Block a user