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">
<!-- 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,15 +288,12 @@
});
}
/* ============ 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) {
return fetch(WORKER_CDN).then(function(r) { return r.blob(); }).then(function(blob) {
workerBlobUrl = URL.createObjectURL(blob);
return workerBlobUrl;
});
@@ -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>