Fix: image_size enum + rolling window=1

This commit is contained in:
2026-03-20 22:32:17 -07:00
parent 304360c647
commit be7307de94

View File

@@ -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"> <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"> <header class="w-full flex items-center justify-between">
<h1 class="text-4xl tracking-tight">vibegif<span class="text-neutral-400">.lol</span></h1> <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"> <button id="btn-settings" class="p-2 rounded-lg hover:bg-neutral-100 transition" title="Settings">
@@ -29,14 +28,12 @@
</button> </button>
</header> </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"> <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> <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"> <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> <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> </div>
<!-- Main Screen -->
<div id="main-screen" class="w-full flex flex-col gap-5 hidden"> <div id="main-screen" class="w-full flex flex-col gap-5 hidden">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<label class="text-xs text-neutral-400 uppercase tracking-wider">model</label> <label class="text-xs text-neutral-400 uppercase tracking-wider">model</label>
@@ -67,8 +64,8 @@
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<label class="text-xs text-neutral-400 uppercase tracking-wider">size</label> <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"> <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="1K">1K</option>
<option value="512x512">0.5K (Gemini only)</option> <option value="0.5K">0.5K (Gemini only)</option>
</select> </select>
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
@@ -106,7 +103,6 @@
</div> </div>
</div> </div>
<!-- Settings Modal -->
<div id="modal-settings" class="fixed inset-0 bg-black/30 flex items-center justify-center z-50 hidden"> <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="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"> <div class="flex items-center justify-between">
@@ -123,7 +119,6 @@
</div> </div>
</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://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> <script src="https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.js"></script>
@@ -131,13 +126,11 @@
(function() { (function() {
'use strict'; 'use strict';
/* ============ STORAGE ============ */
var STORAGE_KEY = 'vibegif_api_key'; var STORAGE_KEY = 'vibegif_api_key';
function getApiKey() { return localStorage.getItem(STORAGE_KEY) || ''; } function getApiKey() { return localStorage.getItem(STORAGE_KEY) || ''; }
function setApiKey(k) { localStorage.setItem(STORAGE_KEY, (k || '').trim()); } function setApiKey(k) { localStorage.setItem(STORAGE_KEY, (k || '').trim()); }
function hasApiKey() { return !!getApiKey(); } function hasApiKey() { return !!getApiKey(); }
/* ============ DOM REFS (grabbed once on DOMContentLoaded) ============ */
var el = {}; var el = {};
function grabElements() { function grabElements() {
@@ -155,7 +148,6 @@
} }
} }
/* ============ UI HELPERS ============ */
function show(node) { if (node) { node.classList.remove('hidden'); node.style.display = ''; } } function show(node) { if (node) { node.classList.remove('hidden'); node.style.display = ''; } }
function hide(node) { if (node) node.classList.add('hidden'); } function hide(node) { if (node) node.classList.add('hidden'); }
@@ -207,7 +199,6 @@
el.btnGenerate.style.cursor = active ? 'not-allowed' : 'pointer'; el.btnGenerate.style.cursor = active ? 'not-allowed' : 'pointer';
} }
/* ============ API ============ */
var API_URL = 'https://openrouter.ai/api/v1/chat/completions'; 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'; var MASTER_PROMPT = 'minimal black and white line doodle, single stroke, white background, kawaii style';
@@ -235,12 +226,12 @@
modalities: ['image'], modalities: ['image'],
image_config: { image_config: {
aspect_ratio: aspectRatio, aspect_ratio: aspectRatio,
image_size: imageSize, image_size: imageSize
} }
}; };
if (!isGemini && imageSize === '512x512') { if (!isGemini && imageSize === '0.5K') {
body.image_config.image_size = '1024x1024'; body.image_config.image_size = '1K';
} }
return fetch(API_URL, { return fetch(API_URL, {
@@ -262,22 +253,16 @@
return res.json(); return res.json();
}) })
.then(function(data) { .then(function(data) {
console.log('API response:', JSON.stringify(data).substring(0, 500));
var choice = data.choices && data.choices[0]; var choice = data.choices && data.choices[0];
if (!choice) throw new Error('No response from model'); if (!choice) throw new Error('No response from model');
/* Try multiple known response shapes */
var b64 = null; var b64 = null;
// Shape 1: choice.message.images[]
var images = choice.message && choice.message.images; var images = choice.message && choice.message.images;
if (images && images.length) { if (images && images.length) {
var imgObj = images[0]; var imgObj = images[0];
b64 = (imgObj.image_url && imgObj.image_url.url) || imgObj.url || imgObj; 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)) { if (!b64 && choice.message && Array.isArray(choice.message.content)) {
for (var i = 0; i < choice.message.content.length; i++) { for (var i = 0; i < choice.message.content.length; i++) {
var part = choice.message.content[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) { if (typeof b64 === 'string' && b64.indexOf('data:') !== 0) {
b64 = 'data:image/png;base64,' + b64; 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 WORKER_CDN = 'https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.worker.js';
var workerBlobUrl = null; var workerBlobUrl = null;
function getWorkerUrl() { function getWorkerUrl() {
if (workerBlobUrl) return Promise.resolve(workerBlobUrl); if (workerBlobUrl) return Promise.resolve(workerBlobUrl);
return fetch(WORKER_CDN) return fetch(WORKER_CDN).then(function(r) { return r.blob(); }).then(function(blob) {
.then(function(r) { return r.blob(); }) workerBlobUrl = URL.createObjectURL(blob);
.then(function(blob) { return workerBlobUrl;
workerBlobUrl = URL.createObjectURL(blob); });
return workerBlobUrl;
});
} }
function loadImage(src) { function loadImage(src) {
@@ -364,7 +346,6 @@
}); });
} }
/* ============ GENERATION ORCHESTRATOR ============ */
function handleGenerate() { function handleGenerate() {
var prompt = (el.inpPrompt.value || '').trim(); var prompt = (el.inpPrompt.value || '').trim();
if (!prompt) { el.inpPrompt.focus(); return; } if (!prompt) { el.inpPrompt.focus(); return; }
@@ -374,7 +355,7 @@
var fps = Math.max(1, Math.min(30, parseInt(el.inpFps.value) || 4)); var fps = Math.max(1, Math.min(30, parseInt(el.inpFps.value) || 4));
var imageSize = el.selSize.value; var imageSize = el.selSize.value;
var aspectRatio = el.selRatio.value; var aspectRatio = el.selRatio.value;
var WINDOW = 2; var WINDOW = 1;
setGenerating(true); setGenerating(true);
resetProgress(); resetProgress();
@@ -400,13 +381,9 @@
setProgress(Math.round(((idx - 1) / frameCount) * 100), 'generating frame ' + idx + ' of ' + frameCount + '...'); setProgress(Math.round(((idx - 1) / frameCount) * 100), 'generating frame ' + idx + ' of ' + frameCount + '...');
var nextUserMsg = buildNextMessage(idx, frameCount); var nextUserMsg = buildNextMessage(idx, frameCount);
// Rolling window: first msg + last WINDOW*2 history entries + new user msg
var windowMessages = [firstMsg]; var windowMessages = [firstMsg];
var startIdx = Math.max(1, fullHistory.length - WINDOW * 2); var startIdx = Math.max(1, fullHistory.length - WINDOW * 2);
for (var j = startIdx; j < fullHistory.length; j++) { for (var j = startIdx; j < fullHistory.length; j++) windowMessages.push(fullHistory[j]);
windowMessages.push(fullHistory[j]);
}
windowMessages.push(nextUserMsg); windowMessages.push(nextUserMsg);
return generateFrame({ model: model, messages: windowMessages, imageSize: imageSize, aspectRatio: aspectRatio }) return generateFrame({ model: model, messages: windowMessages, imageSize: imageSize, aspectRatio: aspectRatio })
@@ -439,23 +416,16 @@
}); });
} }
/* ============ INIT ON DOM READY ============ */
function boot() { function boot() {
grabElements(); grabElements();
// Init lucide icons safely
if (window.lucide && window.lucide.createIcons) { if (window.lucide && window.lucide.createIcons) {
try { window.lucide.createIcons(); } catch(e) { console.warn('lucide init error:', e); } try { window.lucide.createIcons(); } catch(e) { console.warn('lucide init error:', e); }
} }
// Route to setup or main if (hasApiKey()) showMain();
if (hasApiKey()) { else showSetup();
showMain();
} else {
showSetup();
}
// Setup save
if (el.setupSave) el.setupSave.addEventListener('click', function() { if (el.setupSave) el.setupSave.addEventListener('click', function() {
var k = (el.setupKey.value || '').trim(); var k = (el.setupKey.value || '').trim();
if (!k) return; if (!k) return;
@@ -463,7 +433,6 @@
showMain(); showMain();
}); });
// Settings modal
if (el.btnSettings) el.btnSettings.addEventListener('click', showModal); if (el.btnSettings) el.btnSettings.addEventListener('click', showModal);
if (el.btnCloseModal) el.btnCloseModal.addEventListener('click', hideModal); if (el.btnCloseModal) el.btnCloseModal.addEventListener('click', hideModal);
if (el.modalSettings) el.modalSettings.addEventListener('click', function(e) { if (el.modalSettings) el.modalSettings.addEventListener('click', function(e) {
@@ -476,26 +445,20 @@
hideModal(); hideModal();
}); });
// Model change: disable 0.5K for non-Gemini
if (el.selModel) el.selModel.addEventListener('change', function() { if (el.selModel) el.selModel.addEventListener('change', function() {
var isGemini = el.selModel.value.indexOf('google/') === 0; 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 (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); if (el.btnGenerate) el.btnGenerate.addEventListener('click', handleGenerate);
console.log('vibegif.lol booted ✓'); console.log('vibegif.lol booted ✓');
} }
// Defensive: run on DOMContentLoaded OR immediately if already loaded if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
if (document.readyState === 'loading') { else boot();
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})(); })();
</script> </script>
</body> </body>