ve2gem / index.html
bibibi12345's picture
faster playback spped
2e113c2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gemini Image Generator</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.input-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
input[type="text"], textarea {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 16px;
box-sizing: border-box;
}
textarea {
height: 100px;
resize: vertical;
}
button {
background-color: #4CAF50;
color: white;
padding: 12px 24px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
width: 100%;
margin-top: 10px;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.loading {
display: none;
text-align: center;
margin: 20px 0;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 2s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.result {
margin-top: 30px;
padding: 20px;
background-color: #f9f9f9;
border-radius: 5px;
display: none;
}
.result h3 {
margin-top: 0;
color: #333;
}
.text-content {
margin-bottom: 20px;
line-height: 1.6;
}
.image-container {
text-align: center;
margin-top: 20px;
}
.generated-image {
max-width: 100%;
height: auto;
border-radius: 5px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.error {
color: #d32f2f;
background-color: #ffebee;
padding: 15px;
border-radius: 5px;
margin-top: 20px;
display: none;
}
.file-upload {
border: 2px dashed #ddd;
border-radius: 5px;
padding: 20px;
text-align: center;
margin-bottom: 10px;
cursor: pointer;
transition: border-color 0.3s;
}
.file-upload:hover {
border-color: #4CAF50;
}
.file-upload.dragover {
border-color: #4CAF50;
background-color: #f0f8f0;
}
.uploaded-images {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
.uploaded-image {
position: relative;
width: 100px;
height: 100px;
}
.uploaded-image img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 5px;
border: 2px solid #ddd;
}
.remove-image {
position: absolute;
top: -5px;
right: -5px;
background: #ff4444;
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.remove-image:hover {
background: #cc0000;
}
</style>
</head>
<body>
<div class="container">
<h1>🎨 Gemini Image Generator</h1>
<div class="input-group">
<label for="vertexKey">Vertex Express Key:</label>
<input type="text" id="vertexKey" placeholder="Enter your Vertex Express key">
</div>
<div class="input-group">
<label for="imageUpload">Upload Images (optional):</label>
<div class="file-upload" id="fileUpload" onclick="document.getElementById('imageUpload').click()">
<p>Click here or drag and drop images to upload</p>
<p style="font-size: 12px; color: #666;">Supports: JPG, PNG, GIF, WebP</p>
</div>
<input type="file" id="imageUpload" multiple accept="image/*" style="display: none;">
<div class="uploaded-images" id="uploadedImages"></div>
</div>
<div class="input-group">
<label for="prompt">Text Prompt:</label>
<textarea id="prompt" placeholder="Describe what you want to do with the images or generate..."></textarea>
</div>
<button onclick="generateImage()">Generate Image</button>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Generating image...</p>
</div>
<div class="error" id="error"></div>
<div class="result" id="result">
<h3>Generated Content:</h3>
<div class="text-content" id="textContent"></div>
<div class="image-container" id="imageContainer"></div>
</div>
</div>
<script>
// Simple fallback for GIF creation if the library fails to load
let GIF_LIBRARY_LOADED = false;
// Try to load gif.js library
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.js';
script.onload = function() {
GIF_LIBRARY_LOADED = true;
};
script.onerror = function() {
console.warn('GIF library failed to load, will show images individually');
};
document.head.appendChild(script);
// Global variable to store uploaded images
let uploadedImages = [];
// File upload handling
document.getElementById('imageUpload').addEventListener('change', handleFileSelect);
const fileUpload = document.getElementById('fileUpload');
fileUpload.addEventListener('dragover', handleDragOver);
fileUpload.addEventListener('drop', handleDrop);
fileUpload.addEventListener('dragleave', handleDragLeave);
function handleFileSelect(event) {
const files = Array.from(event.target.files);
processFiles(files);
}
function handleDragOver(event) {
event.preventDefault();
fileUpload.classList.add('dragover');
}
function handleDrop(event) {
event.preventDefault();
fileUpload.classList.remove('dragover');
const files = Array.from(event.dataTransfer.files);
processFiles(files);
}
function handleDragLeave(event) {
event.preventDefault();
fileUpload.classList.remove('dragover');
}
function processFiles(files) {
files.forEach(file => {
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = function(e) {
const imageData = {
name: file.name,
data: e.target.result.split(',')[1], // Remove data:image/...;base64, prefix
mimeType: file.type
};
uploadedImages.push(imageData);
displayUploadedImage(imageData, uploadedImages.length - 1);
};
reader.readAsDataURL(file);
}
});
}
function displayUploadedImage(imageData, index) {
const container = document.getElementById('uploadedImages');
const imageDiv = document.createElement('div');
imageDiv.className = 'uploaded-image';
imageDiv.innerHTML = `
<img src="data:${imageData.mimeType};base64,${imageData.data}" alt="${imageData.name}">
<button class="remove-image" onclick="removeImage(${index})">×</button>
`;
container.appendChild(imageDiv);
}
function removeImage(index) {
uploadedImages.splice(index, 1);
refreshUploadedImages();
}
function refreshUploadedImages() {
const container = document.getElementById('uploadedImages');
container.innerHTML = '';
uploadedImages.forEach((imageData, index) => {
displayUploadedImage(imageData, index);
});
}
async function generateImage() {
const vertexKey = document.getElementById('vertexKey').value.trim();
const prompt = document.getElementById('prompt').value.trim();
if (!vertexKey) {
showError('Please enter your Vertex Express key');
return;
}
// Check if we have either a prompt or uploaded images
if (!prompt && uploadedImages.length === 0) {
showError('Please enter a prompt or upload at least one image');
return;
}
// Show loading state
document.getElementById('loading').style.display = 'block';
document.getElementById('result').style.display = 'none';
document.getElementById('error').style.display = 'none';
document.querySelector('button').disabled = true;
try {
// Build the parts array with text and images
const parts = [];
// Add text prompt
if (prompt) {
parts.push({
text: prompt
});
}
// Add uploaded images
uploadedImages.forEach(imageData => {
parts.push({
inlineData: {
mimeType: imageData.mimeType,
data: imageData.data
}
});
});
// Ensure we have at least some content
if (parts.length === 0) {
showError('Please enter a prompt or upload at least one image');
return;
}
const response = await fetch('/frontend/v1beta/models/gemini-2.5-flash-image-preview:generateContent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-vertex-express-key': vertexKey
},
body: JSON.stringify({
contents: [{
role: "user",
parts: parts
}]
})
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(`HTTP ${response.status}: ${errorData}`);
}
const data = await response.json();
displayResult(data);
} catch (error) {
console.error('Error:', error);
showError('Error generating image: ' + error.message);
} finally {
// Hide loading state
document.getElementById('loading').style.display = 'none';
document.querySelector('button').disabled = false;
}
}
function displayResult(data) {
const resultDiv = document.getElementById('result');
const textContentDiv = document.getElementById('textContent');
const imageContainerDiv = document.getElementById('imageContainer');
// Clear previous results
textContentDiv.innerHTML = '';
imageContainerDiv.innerHTML = '';
if (data.candidates && data.candidates.length > 0) {
const candidate = data.candidates[0];
const parts = candidate.content.parts;
let textParts = [];
let imageParts = [];
// Separate text and image parts
parts.forEach(part => {
if (part.text) {
textParts.push(part.text);
} else if (part.inlineData && part.inlineData.data) {
imageParts.push(part.inlineData.data);
}
});
// Display text content
if (textParts.length > 0) {
textContentDiv.innerHTML = textParts.join('');
}
// Handle images
if (imageParts.length === 1) {
// Single image - display as is
const img = document.createElement('img');
img.src = `data:image/png;base64,${imageParts[0]}`;
img.className = 'generated-image';
img.alt = 'Generated image';
imageContainerDiv.appendChild(img);
} else if (imageParts.length > 1) {
// Multiple images - try to create animated GIF or show individually
if (GIF_LIBRARY_LOADED && typeof GIF !== 'undefined') {
createAnimatedGif(imageParts, imageContainerDiv);
} else {
// Fallback: show all images individually
showMultipleImages(imageParts, imageContainerDiv);
}
}
resultDiv.style.display = 'block';
} else {
showError('No content generated');
}
}
function createAnimatedGif(imageDataArray, container) {
console.log('Starting GIF creation with', imageDataArray.length, 'images');
try {
// Check if GIF library is available
if (typeof GIF === 'undefined') {
console.error('GIF library not available');
showMultipleImages(imageDataArray, container);
return;
}
console.log('GIF library available, loading first image to get dimensions');
// Load first image to get dimensions
const firstImg = new Image();
firstImg.onload = function() {
const gifWidth = firstImg.naturalWidth;
const gifHeight = firstImg.naturalHeight;
console.log('Using dimensions from first image:', gifWidth + 'x' + gifHeight);
const gif = new GIF({
workers: 2,
quality: 10,
width: gifWidth,
height: gifHeight,
workerScript: './gif.worker.js'
});
let loadedImages = 0;
const totalImages = imageDataArray.length;
// Show loading message for GIF creation
container.innerHTML = '<p>Creating animated GIF... (0/' + totalImages + ' images loaded)</p>';
// Set a timeout to prevent infinite waiting
const timeout = setTimeout(() => {
console.error('GIF creation timeout after 30 seconds');
showMultipleImages(imageDataArray, container);
}, 30000);
console.log('Processing', totalImages, 'images for GIF');
// Define startGifRender function with access to gif variable
function startGifRender() {
console.log('Starting GIF render process');
container.innerHTML = '<p>Rendering animated GIF...</p>';
gif.on('start', function() {
console.log('GIF rendering started');
});
gif.on('progress', function(p) {
console.log('GIF rendering progress:', Math.round(p * 100) + '%');
container.innerHTML = '<p>Rendering animated GIF... ' + Math.round(p * 100) + '%</p>';
});
gif.on('finished', function(blob) {
console.log('GIF rendering finished, blob size:', blob.size, 'bytes');
try {
const url = URL.createObjectURL(blob);
const gifImg = document.createElement('img');
gifImg.src = url;
gifImg.className = 'generated-image';
gifImg.alt = 'Generated animated GIF';
container.innerHTML = '';
container.appendChild(gifImg);
console.log('GIF successfully displayed');
} catch (displayError) {
console.error('Error displaying GIF:', displayError);
showMultipleImages(imageDataArray, container);
}
});
gif.on('abort', function() {
console.error('GIF rendering was aborted');
showMultipleImages(imageDataArray, container);
});
// Set another timeout for the rendering process
const renderTimeout = setTimeout(() => {
console.error('GIF rendering timeout after 60 seconds');
gif.abort();
showMultipleImages(imageDataArray, container);
}, 60000);
gif.on('finished', function() {
clearTimeout(renderTimeout);
});
gif.on('abort', function() {
clearTimeout(renderTimeout);
});
gif.render();
}
imageDataArray.forEach((imageData, index) => {
console.log('Loading image', index + 1, 'of', totalImages);
const img = new Image();
img.onerror = function() {
console.error('Failed to load image', index + 1);
loadedImages++;
container.innerHTML = '<p>Creating animated GIF... (' + loadedImages + '/' + totalImages + ' images loaded)</p>';
if (loadedImages === totalImages) {
clearTimeout(timeout);
console.log('All images processed (some failed), starting GIF render');
startGifRender();
}
};
img.onload = function() {
console.log('Image', index + 1, 'loaded successfully, size:', img.width, 'x', img.height);
try {
// Create a canvas with the actual dimensions
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = gifWidth;
canvas.height = gifHeight;
// Draw image scaled to match first image dimensions
ctx.drawImage(img, 0, 0, gifWidth, gifHeight);
console.log('Image', index + 1, 'drawn to canvas at', gifWidth + 'x' + gifHeight);
// Add frame to GIF with 200ms delay (faster playback)
gif.addFrame(canvas, {delay: 200});
console.log('Frame', index + 1, 'added to GIF');
loadedImages++;
container.innerHTML = '<p>Creating animated GIF... (' + loadedImages + '/' + totalImages + ' images loaded)</p>';
if (loadedImages === totalImages) {
clearTimeout(timeout);
console.log('All images loaded, starting GIF render');
startGifRender();
}
} catch (canvasError) {
console.error('Error processing image', index + 1, ':', canvasError);
loadedImages++;
if (loadedImages === totalImages) {
clearTimeout(timeout);
startGifRender();
}
}
};
img.src = `data:image/png;base64,${imageData}`;
});
};
firstImg.onerror = function() {
console.error('Failed to load first image to determine dimensions, using fallback');
showMultipleImages(imageDataArray, container);
};
firstImg.src = `data:image/png;base64,${imageDataArray[0]}`;
} catch (error) {
console.error('Error creating GIF:', error);
// Fallback to showing individual images
showMultipleImages(imageDataArray, container);
}
}
function showMultipleImages(imageDataArray, container) {
container.innerHTML = '<p>Multiple images generated:</p>';
imageDataArray.forEach((imageData, index) => {
const img = document.createElement('img');
img.src = `data:image/png;base64,${imageData}`;
img.className = 'generated-image';
img.alt = `Generated image ${index + 1}`;
img.style.marginBottom = '10px';
img.style.display = 'block';
container.appendChild(img);
});
}
function showError(message) {
const errorDiv = document.getElementById('error');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
// Allow Enter key to submit
document.getElementById('prompt').addEventListener('keypress', function(e) {
if (e.key === 'Enter' && e.ctrlKey) {
generateImage();
}
});
</script>
</body>
</html>