Files
vibegif.lol/assets/js/app.js

147 lines
3.9 KiB
JavaScript

import { generateImageFrame } from "./openrouter.js";
import { buildGifFromFrames } from "./gif.js";
import { firstPrompt, nextFramePrompt, clampForm } from "./ui.js";
const GEMINI_MODEL = "google/gemini-3.1-flash-image-preview";
window.vibeGifApp = function () {
return {
settingsOpen: false,
loading: false,
error: "",
apiKeyInput: "",
apiKey: "",
progressLabel: "",
progressPct: 0,
frames: [],
gifUrl: "",
form: {
model: GEMINI_MODEL,
userPrompt: "",
frameCount: 4,
fps: 6,
imageSize: "1K",
aspectRatio: "1:1"
},
isGeminiModel(model) {
return model === GEMINI_MODEL;
},
refreshIcons() {
const L = window.lucide;
if (!L?.createIcons) return;
try {
// Newer lucide builds can require icons object
L.createIcons({ icons: L.icons });
} catch (_) {
// Backward compatibility fallback
try { L.createIcons(); } catch {}
}
},
init() {
this.apiKey = localStorage.getItem("openrouter_api_key") || "";
this.apiKeyInput = this.apiKey || "";
this.$nextTick(() => this.refreshIcons());
window.addEventListener("load", () => this.refreshIcons());
document.addEventListener("alpine:initialized", () => this.refreshIcons());
this.$watch("settingsOpen", () => {
this.$nextTick(() => this.refreshIcons());
});
this.$watch("form.model", (model) => {
if (!this.isGeminiModel(model) && this.form.imageSize !== "1K") {
this.form.imageSize = "1K";
}
if (this.isGeminiModel(model) && !["1K", "0.5K"].includes(this.form.imageSize)) {
this.form.imageSize = "1K";
}
});
},
saveApiKey() {
this.apiKey = this.apiKeyInput.trim();
localStorage.setItem("openrouter_api_key", this.apiKey);
this.settingsOpen = false;
},
clearApiKey() {
this.apiKey = "";
this.apiKeyInput = "";
localStorage.removeItem("openrouter_api_key");
},
async generate() {
this.error = "";
this.gifUrl = "";
this.frames = [];
this.progressLabel = "";
this.progressPct = 0;
clampForm(this.form);
if (!this.apiKey) {
this.error = "Add your OpenRouter API key first (panel-left icon).";
this.settingsOpen = true;
return;
}
if (!this.form.userPrompt) {
this.error = "Please enter a simple prompt (e.g. rolling cat).";
return;
}
this.loading = true;
try {
const total = this.form.frameCount;
const p1 = firstPrompt(this.form.userPrompt);
this.progressLabel = `Generating frame 1/${total}...`;
const frame1 = await generateImageFrame({
apiKey: this.apiKey,
model: this.form.model,
textPrompt: p1,
previousFrames: [],
imageSize: this.form.imageSize,
aspectRatio: this.form.aspectRatio
});
this.frames.push(frame1);
this.progressPct = Math.round((1 / total) * 100);
for (let i = 2; i <= total; i++) {
this.progressLabel = `Generating frame ${i}/${total}...`;
const next = await generateImageFrame({
apiKey: this.apiKey,
model: this.form.model,
textPrompt: nextFramePrompt(total),
previousFrames: this.frames.slice(-2),
imageSize: this.form.imageSize,
aspectRatio: this.form.aspectRatio
});
this.frames.push(next);
this.progressPct = Math.round((i / total) * 100);
}
this.progressLabel = "Building GIF...";
this.gifUrl = await buildGifFromFrames(this.frames, this.form.fps);
this.progressLabel = "Done.";
this.progressPct = 100;
} catch (e) {
this.error = e?.message || "Failed to generate.";
} finally {
this.loading = false;
}
}
};
};