feat(mockup): rename to Image Mockup Studio and add inline generator selector

This commit is contained in:
airent106 committed 2026-10-29 12:05:52 -06:00
1 parent 3368d7201c
commit 25d24a53a6
2 files changed
+68 -34

No files matched your search

+35 -11
View File
@@ -138,13 +138,17 @@ async function loadSettings() {
const keyStatus = document.getElementById("settings-key-status");
if (keyStatus) {
keyStatus.textContent = data.hasApiKey
? `Active key configured (${data.apiKeySource}: ${data.maskedKey})`
: "No key stored yet — paste your Gemini API key to enable live Nano Banana 2.1 generation.";
? `Active provider configured (${data.apiKeySource}: ${data.maskedKey})`
: "No Gemini API key stored — OpenAI Codex works automatically via OAuth.";
}
const modelSel = document.getElementById("settings-model-select");
if (modelSel && data.nanoBananaModel) {
modelSel.value = data.nanoBananaModel;
}
const studioGenSel = document.getElementById("nb-generator-select");
if (studioGenSel && data.nanoBananaModel) {
studioGenSel.value = data.nanoBananaModel;
}
const bgEl = document.getElementById("settings-user-background");
if (bgEl && data.userBackground !== undefined) {
bgEl.value = data.userBackground;
@@ -439,7 +443,7 @@ function renderColliderStage() {
💾 Log Breakthrough to Idea
</button>
<button type="button" id="btn-collider-to-mockup" class="btn btn-secondary">
🍌 Visualize This Twist in Nano Banana Studio
🖼️ Visualize Twist in Image Mockup Studio
</button>`
: ""
}
@@ -728,8 +732,8 @@ function renderTriageStage() {
<p style="color:var(--text-secondary);">${escapeHtml(item.elevator_pitch || "No elevator pitch yet — open Workbench or deal a Whack card to flesh it out.")}</p>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;padding-top:0.5rem;">
<button type="button" class="btn btn-primary" id="btn-triage-promote">🚀 Promote to ${nextStage.toUpperCase()}</button>
<button type="button" class="btn btn-secondary" id="btn-triage-mockup">🍌 Mock Up Image</button>
<button type="button" class="btn btn-ghost" id="btn-triage-whack">🃏 Whack It</button>
<button type="button" class="btn btn-secondary" id="btn-triage-mockup">🖼️ Mock Up Image</button>
<button type="button" class="btn btn-ghost" id="btn-triage-whack">💡 Prompt Twist</button>
<button type="button" class="btn btn-ghost" id="btn-triage-keep">✓ Keep & Next</button>
<button type="button" class="btn btn-danger-ghost" id="btn-triage-archive">📦 Archive</button>
</div>
@@ -1087,7 +1091,7 @@ function renderDrawerMockupGallery(idea) {
if (!container) return;
if (!idea.mockups || idea.mockups.length === 0) {
container.innerHTML = `<p class="muted-label">No mockups saved for this idea yet. Generate one with 🍌 Nano Banana 2.1 or save a layout from the T-Shirt Compositor!</p>`;
container.innerHTML = `<p class="muted-label">No mockups saved for this idea yet. Generate one in the Image Mockup Studio or save a layout from the T-Shirt Compositor!</p>`;
return;
}
@@ -1104,7 +1108,7 @@ function renderDrawerMockupGallery(idea) {
${m.editInstruction ? `<small class="muted-label">Edited: "${escapeHtml(m.editInstruction)}"</small>` : ""}
<div class="mockup-gallery-actions">
<button type="button" class="btn btn-primary btn-xs btn-mk-refine" data-mk-id="${escapeHtml(m.id)}">
🍌 Refine with Nano Banana
✨ Refine Mockup
</button>
<button type="button" class="btn btn-ghost btn-xs btn-mk-load-canvas" data-mk-url="${escapeHtml(m.imageUrl)}">
👕 Place on Tee Canvas
@@ -1458,7 +1462,7 @@ function renderDrawerActiveWhackStage() {
</label>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;">
<button type="button" id="btn-drawer-save-whack" class="btn btn-primary btn-sm">💾 Save Breakthrough</button>
<button type="button" id="btn-drawer-whack-to-nb" class="btn btn-secondary btn-sm">🍌 Send Twist to Nano Banana Mockup Studio</button>
<button type="button" id="btn-drawer-whack-to-nb" class="btn btn-secondary btn-sm">🖼️ Send Twist to Image Mockup Studio</button>
</div>
</div>
</div>
@@ -2017,22 +2021,42 @@ function bindGlobalEvents() {
document.getElementById("nb-edit-instruction-input").value = "";
});
document.getElementById("nb-generator-select")?.addEventListener("change", async (e) => {
const chosenModel = e.target.value;
state.settings.nanoBananaModel = chosenModel;
const settingsSel = document.getElementById("settings-model-select");
if (settingsSel) settingsSel.value = chosenModel;
await apiFetch("/api/settings", {
method: "PUT",
body: JSON.stringify({ nanoBananaModel: chosenModel })
});
showToast(`Switched Image Mockup Generator to ${e.target.options[e.target.selectedIndex].text.split("—")[0].trim()}`);
});
document.getElementById("btn-generate-nano-banana")?.addEventListener("click", async () => {
if (!state.activeIdeaId) return;
const btn = document.getElementById("btn-generate-nano-banana");
const statusLabel = document.getElementById("nb-generation-status");
const selectedModel =
document.getElementById("nb-generator-select")?.value ||
state.settings.nanoBananaModel ||
"codex-image-gen";
const template = document.getElementById("nb-template-select").value;
const whackCardNumber = document.getElementById("nb-whack-twist-select").value;
const prompt = document.getElementById("nb-prompt-input").value.trim();
const editInstruction = document.getElementById("nb-edit-instruction-input").value.trim();
btn.disabled = true;
statusLabel.textContent = "🎨 Generating high-res AI mockup via OpenAI Codex (takes ~15–25s)...";
statusLabel.textContent =
selectedModel === "codex-image-gen"
? "🤖 Generating high-res PNG via OpenAI Codex (takes ~15–25s)..."
: `✨ Generating mockup via ${selectedModel}...`;
try {
const res = await apiFetch(`/api/ideas/${state.activeIdeaId}/mockups/nano-banana`, {
method: "POST",
body: JSON.stringify({
model: selectedModel,
template,
garmentColor: state.nbGarmentColor,
garmentColorName: state.nbGarmentColorName,
@@ -2046,8 +2070,8 @@ function bindGlobalEvents() {
state.nbEditParentMockup = null;
document.getElementById("nb-edit-mode-box").classList.add("hidden");
document.getElementById("nb-edit-instruction-input").value = "";
statusLabel.textContent = "✓ Saved new Nano Banana mockup to gallery!";
showToast("🍌 Nano Banana 2.1 generated & saved your mockup!");
statusLabel.textContent = `✓ Saved new mockup (${res.mockup.modelUsed}) to gallery!`;
showToast("🖼️ Generated & saved your new mockup!");
await loadIdeas();
} catch (err) {
statusLabel.textContent = `⚠️ ${err.message}`;
+33 -23
View File
@@ -132,7 +132,7 @@
</select>
<label class="filter-checkbox">
<input type="checkbox" id="filter-has-mockup" />
<span>🍌 Has Mockup (<span id="stat-mockups-count">0</span>)</span>
<span>🖼️ Has Mockup (<span id="stat-mockups-count">0</span>)</span>
</label>
<select id="filter-sort" class="filter-select" aria-label="Sort ideas">
<option value="score">Sort: Priority Score</option>
@@ -378,7 +378,7 @@
📝 Workbench & Notes
</button>
<button type="button" class="drawer-tab" data-drawer-tab="mockup">
🎨 Mockup Studio <span id="drawer-mockup-count" class="tab-pill">0</span>
🖼️ Image Mockup Studio <span id="drawer-mockup-count" class="tab-pill">0</span>
</button>
<button type="button" class="drawer-tab" data-drawer-tab="whack">
💡 Brainstorm Prompts <span id="drawer-whack-count" class="tab-pill">0</span>
@@ -526,17 +526,27 @@
</div>
</div>
<!-- TAB 2: NANO BANANA 2.1 VISUAL MOCKUP STUDIO -->
<!-- TAB 2: IMAGE MOCKUP STUDIO -->
<div id="drawer-tab-mockup" class="drawer-tab-panel hidden">
<div class="mockup-studio-layout">
<!-- Left: Generator & Interactive T-Shirt / Product Compositor -->
<div class="mockup-controls-pane">
<div class="studio-card">
<div class="studio-card-header">
<h3>🍌 Nano Banana 2.1 AI Mockup Generator</h3>
<h3>🖼️ Image Mockup Studio</h3>
<button type="button" id="btn-studio-auto-prompt" class="btn btn-ghost btn-xs">✨ Auto-Prompt from Idea</button>
</div>
<div class="field-group">
<label for="nb-generator-select">AI Image Generator Engine</label>
<select id="nb-generator-select" class="filter-select full-width">
<option value="codex-image-gen">🤖 OpenAI Codex (gpt-5.6-sol / Built-in image_gen) — High-Res PNG</option>
<option value="gemini-nano-banana-2.1">✨ Google Gemini (Nano Banana 2.1 / Flash-Lite Vector Fallback)</option>
<option value="gemini-3-pro-image">🎨 Google Gemini Pro Image (Studio Detail)</option>
<option value="gemini-3.1-flash-lite-image">⚡ Google Gemini Flash-Lite Image (Fast Sketch)</option>
</select>
</div>
<div class="form-row-2">
<div>
<label for="nb-template-select">Product / Surface Preset</label>
@@ -550,7 +560,7 @@
</select>
</div>
<div>
<label for="nb-whack-twist-select">Optional Whack Pack Twist</label>
<label for="nb-whack-twist-select">Optional Prompt Twist</label>
<select id="nb-whack-twist-select" class="filter-select full-width">
<option value="">None (Direct Concept)</option>
</select>
@@ -579,20 +589,20 @@
<!-- Iterative Image-to-Image Edit Banner (Active when a mockup is selected for refinement) -->
<div id="nb-edit-mode-box" class="nb-edit-box hidden">
<div class="nb-edit-header">
<span>🍌 <strong>Iterative Edit Mode:</strong> Refining <em id="nb-edit-target-label">Selected Mockup</em></span>
<span>✨ <strong>Iterative Edit Mode:</strong> Refining <em id="nb-edit-target-label">Selected Mockup</em></span>
<button type="button" id="btn-nb-clear-edit" class="btn btn-ghost btn-xs">Cancel Edit Mode</button>
</div>
<input
type="text"
id="nb-edit-instruction-input"
class="text-input"
placeholder="Tell Nano Banana what to change (e.g. 'Move graphic to left-chest pocket on a forest green shirt')"
placeholder="Describe what to change (e.g. 'Move graphic to left-chest pocket on a forest green shirt')"
/>
</div>
<div class="studio-action-row">
<button type="button" id="btn-generate-nano-banana" class="btn btn-primary">
🎨 Generate AI Mockup (Codex / Nano Banana)
🖼️ Generate Image Mockup
</button>
<span id="nb-generation-status" class="muted-label"></span>
</div>
@@ -680,7 +690,7 @@
<div class="studio-card">
<div class="studio-card-header">
<h3>🖼️ Idea Mockup Gallery</h3>
<span class="muted-label">Click 'Refine with 🍌' to iterate on any mockup</span>
<span class="muted-label">Click '✨ Refine' to iterate on any mockup</span>
</div>
<div id="drawer-mockup-gallery" class="drawer-mockup-gallery"></div>
</div>
@@ -719,37 +729,37 @@
</div>
</aside>
<!-- MODAL: NANO BANANA 2.1 SETTINGS -->
<!-- MODAL: AI GENERATOR & PROFILE SETTINGS -->
<div id="settings-modal" class="modal-backdrop hidden">
<div class="modal-card">
<div class="modal-header">
<h3>🍌 Nano Banana 2.1 (Gemini Image API) Settings</h3>
<h3>⚙️ AI Mockup Generator & Profile Settings</h3>
<button type="button" id="btn-close-settings" class="btn btn-ghost btn-sm">✕</button>
</div>
<form id="settings-form" class="modal-body">
<p class="muted-label">
SparkVault uses Google's <strong>Nano Banana 2.1 (`gemini-nano-banana-2.1`)</strong> via the official `@google/genai` Interactions API for text-to-image product mockups and multi-turn conversational image editing.
Choose which AI generator powers the <strong>Image Mockup Studio</strong> (you can also switch generators anytime directly inside the Studio tab).
</p>
<div class="field-group">
<label for="settings-api-key">Gemini API Key (`GEMINI_API_KEY`)</label>
<label for="settings-model-select">Default Image Mockup Generator</label>
<select id="settings-model-select" class="filter-select full-width">
<option value="codex-image-gen">🤖 OpenAI Codex (gpt-5.6-sol + Built-in image_gen) [Recommended]</option>
<option value="gemini-nano-banana-2.1">✨ Google Gemini (Nano Banana 2.1 / Flash-Lite Vector Fallback)</option>
<option value="gemini-3-pro-image">🎨 Google Gemini Pro Image (High-Detail Studio)</option>
<option value="gemini-3.1-flash-lite-image">⚡ Google Gemini Flash-Lite Image (Fast Sketch)</option>
</select>
</div>
<div class="field-group">
<label for="settings-api-key">Optional Google Gemini API Key (`GEMINI_API_KEY`)</label>
<input
type="password"
id="settings-api-key"
class="text-input"
placeholder="Paste your AI Studio / Gemini API key (AIza...)"
placeholder="Only needed if using Google Gemini models (AIza...)"
autocomplete="off"
/>
<small id="settings-key-status" class="muted-label"></small>
</div>
<div class="field-group">
<label for="settings-model-select">AI Mockup Engine / Model</label>
<select id="settings-model-select" class="filter-select full-width">
<option value="codex-image-gen">codex-image-gen — OpenAI Codex (gpt-5.6-sol + Built-in image_gen) [Recommended]</option>
<option value="gemini-nano-banana-2.1">gemini-nano-banana-2.1 — Nano Banana 2.1 (Google Gemini)</option>
<option value="gemini-3-pro-image">gemini-3-pro-image — Nano Banana Pro (High-Detail Studio)</option>
<option value="gemini-3.1-flash-lite-image">gemini-3.1-flash-lite-image — Nano Banana 2 Lite (Ultra-Fast)</option>
</select>
</div>
<div class="field-group">
<label for="settings-user-background">🎲 Personal Background & Tastes (Used by '🎲 Random Idea')</label>
<textarea