Files
imagen-openrouter/index.html
T
Yusuf İpek 5e8ebd9f1c feat: major UX improvements and polish
- Allow multiple concurrent image generation requests
  - Track pending batches with loading placeholders in gallery
  - Show progress for each batch independently
  - Warn before leaving page with pending generations

- Add clipboard paste support for reference images
  - Paste images directly with Ctrl+V/Cmd+V
  - Works from screenshots, copied images, etc.

- Add download button on image card hover
  - Quick download without opening modal
  - Timestamped filenames for organization

- Replace all emoji icons with clean SVG icons
  - Professional Lucide/Feather-style icons
  - Logo, buttons, loading states, empty states
  - Consistent visual language throughout

- Persist image count setting across refreshes
  - Save to localStorage on change
  - Properly initialize input value on load

- Remove blocking loading indicator
  - Generate button stays enabled during generation
  - Each request shows inline loading placeholders
2026-02-05 09:42:56 +03:00

193 lines
9.8 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Imagen</title>
<link rel="stylesheet" href="src/styles.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="app-container">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<h1 class="logo">
<svg class="logo-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 19l7-7 3 3-7 7-3-3z"></path>
<path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"></path>
<path d="M2 2l7.586 7.586"></path>
<circle cx="11" cy="11" r="2"></circle>
</svg>
Imagen
</h1>
</div>
<!-- Reference Images Section -->
<div class="reference-section">
<h3>Reference Images</h3>
<div class="reference-slots" id="referenceSlots">
<!-- Slots rendered dynamically by JS -->
</div>
<button class="btn btn-ghost" id="clearReferences">Clear All References</button>
</div>
<!-- Model Selection -->
<div class="config-section">
<h3>Model</h3>
<div class="custom-select" id="modelSelectContainer">
<div class="custom-select-trigger" id="modelSelectTrigger">
<span id="modelSelectValue">Gemini 2.5 Flash Image</span>
<span class="custom-select-arrow"></span>
</div>
<div class="custom-select-options" id="modelSelectOptions">
<div class="custom-select-option selected" data-value="google/gemini-2.5-flash-image">Gemini 2.5
Flash Image</div>
<div class="custom-select-option" data-value="google/gemini-2.5-flash-image-preview">Gemini 2.5
Flash (Preview)</div>
<div class="custom-select-option" data-value="google/gemini-3-pro-image-preview">Gemini 3 Pro
(Preview)</div>
<div class="custom-select-option" data-value="openai/gpt-5-image">GPT-5 Image</div>
<div class="custom-select-option" data-value="openai/gpt-5-image-mini">GPT-5 Image Mini</div>
<div class="custom-select-option" data-value="black-forest-labs/flux.2-pro">Flux 2 Pro</div>
<div class="custom-select-option" data-value="black-forest-labs/flux.2-max">Flux 2 Max</div>
<div class="custom-select-option" data-value="black-forest-labs/flux.2-flex">Flux 2 Flex</div>
<div class="custom-select-option" data-value="black-forest-labs/flux.2-klein-4b">Flux 2 Klein 4B
</div>
<div class="custom-select-option" data-value="bytedance-seed/seedream-4.5">Seedream 4.5</div>
<div class="custom-select-option" data-value="sourceful/riverflow-v2-fast-preview">Riverflow V2
Fast</div>
<div class="custom-select-option" data-value="sourceful/riverflow-v2-standard-preview">Riverflow
V2 Standard</div>
<div class="custom-select-option" data-value="sourceful/riverflow-v2-max-preview">Riverflow V2
Max</div>
</div>
</div>
</div>
<!-- Image Quality (Gemini specific) -->
<div class="config-section" id="geminiOptions">
<h3>Image Size</h3>
<div class="button-group">
<button class="btn-toggle active" data-size="1024x1024" data-quality="1K">1K</button>
<button class="btn-toggle" data-size="2048x2048" data-quality="2K">2K</button>
<button class="btn-toggle" data-size="4096x4096" data-quality="4K">4K</button>
</div>
</div>
<!-- Aspect Ratio -->
<div class="config-section">
<h3>Aspect Ratio</h3>
<div class="aspect-grid">
<button class="btn-aspect active" data-ratio="1:1">1:1</button>
<button class="btn-aspect" data-ratio="16:9">16:9</button>
<button class="btn-aspect" data-ratio="9:16">9:16</button>
<button class="btn-aspect" data-ratio="4:3">4:3</button>
<button class="btn-aspect" data-ratio="3:4">3:4</button>
<button class="btn-aspect" data-ratio="3:2">3:2</button>
</div>
</div>
<!-- Number of Images -->
<div class="config-section">
<h3>Number of Images</h3>
<div class="number-input-group">
<button type="button" class="btn-number" id="decreaseCount"></button>
<input type="number" id="imageCount" value="1" min="1" max="8" class="number-input">
<button type="button" class="btn-number" id="increaseCount">+</button>
</div>
</div>
<!-- API Key -->
<div class="config-section">
<h3>OpenRouter API Key</h3>
<input type="password" id="apiKey" placeholder="sk-or-..." class="text-input">
<button class="btn btn-ghost" id="saveApiKey">Save Key</button>
</div>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Prompt Area -->
<div class="prompt-area">
<div class="prompt-container">
<textarea id="promptInput" placeholder="Describe the image you want to generate..."
rows="3"></textarea>
<div class="prompt-actions">
<span class="char-count" id="charCount">0 chars</span>
<button class="btn btn-primary" id="generateBtn">
Generate
</button>
</div>
</div>
</div>
<!-- Gallery -->
<div class="gallery-header">
<h2>Generated Images</h2>
<div class="gallery-actions">
<button class="btn btn-ghost" id="clearGallery">Clear Gallery</button>
</div>
</div>
<div class="gallery" id="gallery">
<div class="gallery-empty" id="galleryEmpty">
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
<p>No images generated yet</p>
<p class="empty-hint">Write a prompt above and click Generate</p>
</div>
</div>
</main>
</div>
<!-- Image Modal -->
<div class="modal" id="imageModal">
<div class="modal-overlay" id="modalOverlay"></div>
<div class="modal-content">
<button class="modal-close" id="modalClose">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<img src="" alt="Full size image" id="modalImage">
<div class="modal-actions">
<button class="btn btn-secondary" id="useAsReference">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
Use as Reference
</button>
<button class="btn btn-secondary" id="recreateImage">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="23 4 23 10 17 10"></polyline>
<polyline points="1 20 1 14 7 14"></polyline>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
</svg>
Recreate
</button>
<button class="btn btn-secondary" id="downloadImage">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Download
</button>
</div>
<div class="modal-metadata" id="modalMetadata">
<!-- Metadata will be injected here -->
</div>
</div>
</div>
<script src="src/app.js"></script>
</body>
</html>