mirror of
https://github.com/vibegif/vibegif.lol.git
synced 2026-04-07 02:12:12 +00:00
Fix: image_size enum + rolling window=1
This commit is contained in:
73
index.html
73
index.html
@@ -21,7 +21,6 @@
|
||||
|
||||
<div id="app" class="w-full max-w-xl mx-auto px-4 py-8 flex flex-col items-center gap-6">
|
||||
|
||||
<!-- Header (always visible) -->
|
||||
<header class="w-full flex items-center justify-between">
|
||||
<h1 class="text-4xl tracking-tight">vibegif<span class="text-neutral-400">.lol</span></h1>
|
||||
<button id="btn-settings" class="p-2 rounded-lg hover:bg-neutral-100 transition" title="Settings">
|
||||
@@ -29,14 +28,12 @@
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Setup Screen (visible by default so user sees SOMETHING) -->
|
||||
<div id="setup-screen" class="w-full flex flex-col items-center gap-4 mt-12">
|
||||
<p class="text-neutral-500 text-center text-lg">enter your <a href="https://openrouter.ai/keys" target="_blank" class="underline hover:text-neutral-800">OpenRouter</a> API key to start</p>
|
||||
<input id="setup-key" type="password" placeholder="sk-or-..." class="w-full border border-neutral-300 rounded-lg px-4 py-3 text-center focus:outline-none focus:ring-2 focus:ring-neutral-400">
|
||||
<button id="setup-save" class="bg-neutral-800 text-white rounded-lg px-6 py-3 hover:bg-neutral-700 transition">save & start vibing</button>
|
||||
</div>
|
||||
|
||||
<!-- Main Screen -->
|
||||
<div id="main-screen" class="w-full flex flex-col gap-5 hidden">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-xs text-neutral-400 uppercase tracking-wider">model</label>
|
||||
@@ -67,8 +64,8 @@
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-xs text-neutral-400 uppercase tracking-wider">size</label>
|
||||
<select id="sel-size" class="border border-neutral-300 rounded-lg px-3 py-2 bg-white focus:outline-none focus:ring-2 focus:ring-neutral-400">
|
||||
<option value="1024x1024">1K</option>
|
||||
<option value="512x512">0.5K (Gemini only)</option>
|
||||
<option value="1K">1K</option>
|
||||
<option value="0.5K">0.5K (Gemini only)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
@@ -106,7 +103,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
<div id="modal-settings" class="fixed inset-0 bg-black/30 flex items-center justify-center z-50 hidden">
|
||||
<div class="bg-white rounded-2xl p-6 w-full max-w-sm mx-4 flex flex-col gap-4 shadow-xl">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -123,7 +119,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lucide loaded BEFORE our script, with fallback -->
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.js"></script>
|
||||
|
||||
@@ -131,13 +126,11 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/* ============ STORAGE ============ */
|
||||
var STORAGE_KEY = 'vibegif_api_key';
|
||||
function getApiKey() { return localStorage.getItem(STORAGE_KEY) || ''; }
|
||||
function setApiKey(k) { localStorage.setItem(STORAGE_KEY, (k || '').trim()); }
|
||||
function hasApiKey() { return !!getApiKey(); }
|
||||
|
||||
/* ============ DOM REFS (grabbed once on DOMContentLoaded) ============ */
|
||||
var el = {};
|
||||
|
||||
function grabElements() {
|
||||
@@ -155,7 +148,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ UI HELPERS ============ */
|
||||
function show(node) { if (node) { node.classList.remove('hidden'); node.style.display = ''; } }
|
||||
function hide(node) { if (node) node.classList.add('hidden'); }
|
||||
|
||||
@@ -207,7 +199,6 @@
|
||||
el.btnGenerate.style.cursor = active ? 'not-allowed' : 'pointer';
|
||||
}
|
||||
|
||||
/* ============ API ============ */
|
||||
var API_URL = 'https://openrouter.ai/api/v1/chat/completions';
|
||||
var MASTER_PROMPT = 'minimal black and white line doodle, single stroke, white background, kawaii style';
|
||||
|
||||
@@ -235,12 +226,12 @@
|
||||
modalities: ['image'],
|
||||
image_config: {
|
||||
aspect_ratio: aspectRatio,
|
||||
image_size: imageSize,
|
||||
image_size: imageSize
|
||||
}
|
||||
};
|
||||
|
||||
if (!isGemini && imageSize === '512x512') {
|
||||
body.image_config.image_size = '1024x1024';
|
||||
if (!isGemini && imageSize === '0.5K') {
|
||||
body.image_config.image_size = '1K';
|
||||
}
|
||||
|
||||
return fetch(API_URL, {
|
||||
@@ -262,22 +253,16 @@
|
||||
return res.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
console.log('API response:', JSON.stringify(data).substring(0, 500));
|
||||
|
||||
var choice = data.choices && data.choices[0];
|
||||
if (!choice) throw new Error('No response from model');
|
||||
|
||||
/* Try multiple known response shapes */
|
||||
var b64 = null;
|
||||
|
||||
// Shape 1: choice.message.images[]
|
||||
var images = choice.message && choice.message.images;
|
||||
if (images && images.length) {
|
||||
var imgObj = images[0];
|
||||
b64 = (imgObj.image_url && imgObj.image_url.url) || imgObj.url || imgObj;
|
||||
}
|
||||
|
||||
// Shape 2: multipart content with image_url
|
||||
if (!b64 && choice.message && Array.isArray(choice.message.content)) {
|
||||
for (var i = 0; i < choice.message.content.length; i++) {
|
||||
var part = choice.message.content[i];
|
||||
@@ -288,7 +273,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (!b64) throw new Error('No image in response. Model may have refused or returned text only. Check console.');
|
||||
if (!b64) throw new Error('No image in response. Model may have refused or returned text only.');
|
||||
|
||||
if (typeof b64 === 'string' && b64.indexOf('data:') !== 0) {
|
||||
b64 = 'data:image/png;base64,' + b64;
|
||||
@@ -303,18 +288,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* ============ GIF MAKER ============ */
|
||||
var WORKER_CDN = 'https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.worker.js';
|
||||
var workerBlobUrl = null;
|
||||
|
||||
function getWorkerUrl() {
|
||||
if (workerBlobUrl) return Promise.resolve(workerBlobUrl);
|
||||
return fetch(WORKER_CDN)
|
||||
.then(function(r) { return r.blob(); })
|
||||
.then(function(blob) {
|
||||
workerBlobUrl = URL.createObjectURL(blob);
|
||||
return workerBlobUrl;
|
||||
});
|
||||
return fetch(WORKER_CDN).then(function(r) { return r.blob(); }).then(function(blob) {
|
||||
workerBlobUrl = URL.createObjectURL(blob);
|
||||
return workerBlobUrl;
|
||||
});
|
||||
}
|
||||
|
||||
function loadImage(src) {
|
||||
@@ -364,7 +346,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* ============ GENERATION ORCHESTRATOR ============ */
|
||||
function handleGenerate() {
|
||||
var prompt = (el.inpPrompt.value || '').trim();
|
||||
if (!prompt) { el.inpPrompt.focus(); return; }
|
||||
@@ -374,7 +355,7 @@
|
||||
var fps = Math.max(1, Math.min(30, parseInt(el.inpFps.value) || 4));
|
||||
var imageSize = el.selSize.value;
|
||||
var aspectRatio = el.selRatio.value;
|
||||
var WINDOW = 2;
|
||||
var WINDOW = 1;
|
||||
|
||||
setGenerating(true);
|
||||
resetProgress();
|
||||
@@ -400,13 +381,9 @@
|
||||
setProgress(Math.round(((idx - 1) / frameCount) * 100), 'generating frame ' + idx + ' of ' + frameCount + '...');
|
||||
|
||||
var nextUserMsg = buildNextMessage(idx, frameCount);
|
||||
|
||||
// Rolling window: first msg + last WINDOW*2 history entries + new user msg
|
||||
var windowMessages = [firstMsg];
|
||||
var startIdx = Math.max(1, fullHistory.length - WINDOW * 2);
|
||||
for (var j = startIdx; j < fullHistory.length; j++) {
|
||||
windowMessages.push(fullHistory[j]);
|
||||
}
|
||||
for (var j = startIdx; j < fullHistory.length; j++) windowMessages.push(fullHistory[j]);
|
||||
windowMessages.push(nextUserMsg);
|
||||
|
||||
return generateFrame({ model: model, messages: windowMessages, imageSize: imageSize, aspectRatio: aspectRatio })
|
||||
@@ -439,23 +416,16 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* ============ INIT ON DOM READY ============ */
|
||||
function boot() {
|
||||
grabElements();
|
||||
|
||||
// Init lucide icons safely
|
||||
if (window.lucide && window.lucide.createIcons) {
|
||||
try { window.lucide.createIcons(); } catch(e) { console.warn('lucide init error:', e); }
|
||||
}
|
||||
|
||||
// Route to setup or main
|
||||
if (hasApiKey()) {
|
||||
showMain();
|
||||
} else {
|
||||
showSetup();
|
||||
}
|
||||
if (hasApiKey()) showMain();
|
||||
else showSetup();
|
||||
|
||||
// Setup save
|
||||
if (el.setupSave) el.setupSave.addEventListener('click', function() {
|
||||
var k = (el.setupKey.value || '').trim();
|
||||
if (!k) return;
|
||||
@@ -463,7 +433,6 @@
|
||||
showMain();
|
||||
});
|
||||
|
||||
// Settings modal
|
||||
if (el.btnSettings) el.btnSettings.addEventListener('click', showModal);
|
||||
if (el.btnCloseModal) el.btnCloseModal.addEventListener('click', hideModal);
|
||||
if (el.modalSettings) el.modalSettings.addEventListener('click', function(e) {
|
||||
@@ -476,26 +445,20 @@
|
||||
hideModal();
|
||||
});
|
||||
|
||||
// Model change: disable 0.5K for non-Gemini
|
||||
if (el.selModel) el.selModel.addEventListener('change', function() {
|
||||
var isGemini = el.selModel.value.indexOf('google/') === 0;
|
||||
var opt05 = el.selSize.querySelector('option[value="512x512"]');
|
||||
var opt05 = el.selSize.querySelector('option[value="0.5K"]');
|
||||
if (opt05) opt05.disabled = !isGemini;
|
||||
if (!isGemini && el.selSize.value === '512x512') el.selSize.value = '1024x1024';
|
||||
if (!isGemini && el.selSize.value === '0.5K') el.selSize.value = '1K';
|
||||
});
|
||||
|
||||
// Generate
|
||||
if (el.btnGenerate) el.btnGenerate.addEventListener('click', handleGenerate);
|
||||
|
||||
console.log('vibegif.lol booted ✓');
|
||||
}
|
||||
|
||||
// Defensive: run on DOMContentLoaded OR immediately if already loaded
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', boot);
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
|
||||
else boot();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user