Gertie01's picture
Upload app.py with huggingface_hub
8814063 verified
=== index.html ===
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Remix images with text prompts using AI models like Gemini and GPT. Drag and drop your images into three spots for creative combinations.">
<meta name="keywords" content="image remixer, AI art, Gemini, GPT, image generation, drag and drop, creative tool">
<meta name="author" content="Anycoder">
<title>AI Image Remixer</title>
<link rel="stylesheet" href="assets/css/styles.css">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext x='50%' y='50%' style='dominant-baseline:central;text-anchor:middle;font-size:80px;'%3E🎨%3C/text%3E%3C/svg%3E" type="image/svg+xml">
</head>
<body>
<header class="site-header">
<div class="container">
<h1>AI Image Remixer 🎨</h1>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank" rel="noopener noreferrer">Built with anycoder</a>
</div>
</header>
<main class="container">
<section class="upload-section">
<h2>1. Upload or Drop Images</h2>
<div id="upload-dropzone" class="dropzone">
<p>Drag & drop images here, or click to upload</p>
<input type="file" id="image-upload" accept="image/*" multiple style="display: none;">
<button id="upload-button" class="btn">Browse Files</button>
</div>
<div id="uploaded-previews" class="uploaded-previews">
<!-- Uploaded image previews will go here -->
</div>
</section>
<section class="remix-spots-section">
<h2>2. Place Images in Spots</h2>
<p class="instruction-text">Drag images from the "Uploads" section into these three blank spots.</p>
<div class="remix-spots">
<div class="image-spot" id="spot-1" data-index="0">
<span class="spot-label">Spot 1</span>
<img class="spot-image" src="assets/img/placeholder.png" alt="Image spot 1">
</div>
<div class="image-spot" id="spot-2" data-index="1">
<span class="spot-label">Spot 2</span>
<img class="spot-image" src="assets/img/placeholder.png" alt="Image spot 2">
</div>
<div class="image-spot" id="spot-3" data-index="2">
<span class="spot-label">Spot 3</span>
<img class="spot-image" src="assets/img/placeholder.png" alt="Image spot 3">
</div>
</div>
</section>
<section class="prompt-model-section">
<h2>3. Add a Prompt & Select Model</h2>
<div class="prompt-input-group">
<label for="text-prompt">Text Prompt:</label>
<textarea id="text-prompt" placeholder="Describe the remix, e.g., 'A surreal landscape with elements of all three images, painted in an impressionistic style.'" rows="4"></textarea>
</div>
<div class="model-selection">
<h3>Choose AI Model:</h3>
<div class="model-options">
<label class="model-radio">
<input type="radio" name="ai-model" value="gemini-2" checked>
Gemini-2
<span class="tooltip">Advanced multimodal reasoning.</span>
</label>
<label class="model-radio">
<input type="radio" name="ai-model" value="gpt-image-1">
GPT Image-1
<span class="tooltip">Powerful image generation.</span>
</label>
</div>
</div>
<button id="remix-button" class="btn btn-primary">Remix Images</button>
<div id="status-message" class="status-message" aria-live="polite"></div>
</section>
<section class="result-section">
<h2>4. Your Remixed Image</h2>
<div id="remix-result" class="remix-result">
<img id="result-image" src="assets/img/initial-result.png" alt="Remixed image output" style="display: none;">
<p id="result-placeholder">Your remixed image will appear here.</p>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container">
<p>&copy; 2023 AI Image Remixer. All rights reserved.</p>
</div>
</footer>
<script src="assets/js/script.js"></script>
</body>
</html>
=== assets/css/styles.css ===
/* Basic Reset & Box Sizing */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* Base Styles */
:root {
--primary-color: #6a0dad; /* Dark purple */
--primary-hover: #5a0ca0;
--secondary-color: #007bff; /* Blue for highlights */
--text-color: #333;
--light-text-color: #f4f4f4;
--bg-color: #f9f9f9;
--card-bg: #ffffff;
--border-color: #e0e0e0;
--shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
--border-radius: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
}
body {
font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--bg-color);
min-height: 100vh;
display: flex;
flex-direction: column;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: var(--spacing-md);
}
/* Header */
.site-header {
background-color: var(--primary-color);
color: var(--light-text-color);
padding: var(--spacing-md) 0;
box-shadow: var(--shadow);
}
.site-header .container {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap; /* Allow wrapping on smaller screens */
}
.site-header h1 {
font-size: 1.8em;
margin: 0;
padding-bottom: 5px; /* Add some padding below for mobile */
}
.anycoder-link {
color: var(--light-text-color);
text-decoration: none;
font-weight: bold;
font-size: 0.9em;
padding: 5px 10px;
border: 1px solid var(--light-text-color);
border-radius: 5px;
transition: background-color 0.3s ease, color 0.3s ease;
}
.anycoder-link:hover {
background-color: var(--light-text-color);
color: var(--primary-color);
}
/* Main Content Sections */
main {
flex: 1; /* Allows main content to grow and push footer down */
padding-top: var(--spacing-lg);
padding-bottom: var(--spacing-lg);
}
section {
background-color: var(--card-bg);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
}
h2 {
color: var(--primary-color);
font-size: 1.5em;
margin-bottom: var(--spacing-md);
border-bottom: 2px solid var(--border-color);
padding-bottom: 10px;
}
/* Upload Section */
.upload-section {
text-align: center;
}
.dropzone {
border: 2px dashed var(--border-color);
border-radius: var(--border-radius);
padding: var(--spacing-lg);
text-align: center;
cursor: pointer;
transition: background-color 0.3s ease, border-color 0.3s ease;
margin-bottom: var(--spacing-md);
position: relative;
}
.dropzone.drag-over {
background-color: #f0e6fa; /* Lighter primary color */
border-color: var(--primary-color);
}
.dropzone p {
margin-bottom: var(--spacing-md);
font-size: 1.1em;
color: #666;
}
.btn {
display: inline-block;
background-color: var(--primary-color);
color: var(--light-text-color);
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.btn:hover {
background-color: var(--primary-hover);
transform: translateY(-2px);
}
.btn-primary {
background-color: var(--secondary-color);
}
.btn-primary:hover {
background-color: #0056b3;
}
.uploaded-previews {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-md);
justify-content: center;
margin-top: var(--spacing-md);
}
.uploaded-image-wrapper {
position: relative;
width: 100px;
height: 100px;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
overflow: hidden;
cursor: grab;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.uploaded-image-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.remove-image {
position: absolute;
top: 5px;
right: 5px;
background-color: rgba(255, 0, 0, 0.7);
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
font-size: 0.8em;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.2s ease;
}
.remove-image:hover {
background-color: rgba(255, 0, 0, 1);
}
/* Remix Spots Section */
.instruction-text {
font-style: italic;
color: #666;
margin-bottom: var(--spacing-md);
text-align: center;
}
.remix-spots {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-md);
margin-top: var(--spacing-md);
}
.image-spot {
border: 2px dashed var(--border-color);
border-radius: var(--border-radius);
min-height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: var(--spacing-md);
transition: background-color 0.3s ease, border-color 0.3s ease;
background-color: #fefefe;
position: relative;
overflow: hidden; /* Ensure image doesn't overflow */
}
.image-spot.drag-over {
background-color: #e6f0fa; /* Lighter blue for spot drag-over */
border-color: var(--secondary-color);
}
.image-spot .spot-label {
position: absolute;
top: 10px;
left: 10px;
background-color: rgba(0, 0, 0, 0.6);
color: white;
padding: 3px 8px;
border-radius: 5px;
font-size: 0.8em;
z-index: 10;
}
.image-spot .spot-image {
max-width: 100%;
max-height: 180px; /* Constrain image height */
object-fit: contain;
display: block;
margin-top: 20px; /* Space for label */
filter: grayscale(80%); /* Placeholder initial look */
transition: filter 0.3s ease;
}
.image-spot.has-image .spot-image {
filter: grayscale(0%); /* Full color when an image is dropped */
}
/* Prompt & Model Section */
.prompt-input-group {
margin-bottom: var(--spacing-lg);
}
.prompt-input-group label {
display: block;
font-weight: bold;
margin-bottom: 8px;
color: var(--primary-color);
}
textarea#text-prompt {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
font-size: 1em;
font-family: inherit;
resize: vertical;
min-height: 100px;
transition: border-color 0.3s ease;
}
textarea#text-prompt:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(106, 13, 173, 0.2);
}
.model-selection h3 {
font-size: 1.2em;
color: var(--primary-color);
margin-bottom: var(--spacing-md);
}
.model-options {
display: flex;
gap: var(--spacing-md);
flex-wrap: wrap;
margin-bottom: var(--spacing-lg);
}
.model-radio {
display: flex;
align-items: center;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
padding: 10px 15px;
cursor: pointer;
transition: background-color 0.3s ease, border-color 0.3s ease;
background-color: var(--card-bg);
position: relative;
overflow: visible; /* Allow tooltip to show */
}
.model-radio:hover {
background-color: #f0f0f0;
}
.model-radio input[type="radio"] {
margin-right: 8px;
accent-color: var(--primary-color); /* Custom color for radio button */
}
.model-radio input[type="radio"]:checked + span {
font-weight: bold;
color: var(--primary-color);
}
/* Tooltip for model description */
.model-radio .tooltip {
visibility: hidden;
opacity: 0;
width: 180px;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
text-align: center;
border-radius: 6px;
padding: 8px 0;
position: absolute;
z-index: 1;
bottom: 125%; /* Position above the text */
left: 50%;
margin-left: -90px; /* Center the tooltip */
font-size: 0.8em;
transition: opacity 0.3s;
}
.model-radio .tooltip::after {
content: " ";
position: absolute;
top: 100%; /* At the bottom of the tooltip */
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: rgba(0, 0, 0, 0.8) transparent transparent transparent;
}
.model-radio:hover .tooltip {
visibility: visible;
opacity: 1;
}
/* Status Message */
.status-message {
margin-top: var(--spacing-md);
padding: 10px 15px;
border-radius: var(--border-radius);
font-weight: bold;
text-align: center;
display: none; /* Hidden by default */
}
.status-message.loading {
background-color: #e0f7fa; /* Light blue */
color: #007bff;
border: 1px solid #007bff;
display: block;
}
.status-message.error {
background-color: #ffebee; /* Light red */
color: #d32f2f;
border: 1px solid #d32f2f;
display: block;
}
/* Result Section */
.remix-result {
min-height: 250px;
display: flex;
justify-content: center;
align-items: center;
border: 2px dashed var(--border-color);
border-radius: var(--border-radius);
background-color: #fefefe;
overflow: hidden;
padding: var(--spacing-md);
}
.remix-result img#result-image {
max-width: 100%;
max-height: 500px; /* Max height for the result image */
height: auto;
border-radius: var(--border-radius);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.remix-result p#result-placeholder {
color: #888;
font-style: italic;
}
/* Footer */
.site-footer {
background-color: var(--text-color);
color: var(--light-text-color);
text-align: center;
padding: var(--spacing-md) 0;
margin-top: var(--spacing-lg);
}
.site-footer p {
font-size: 0.9em;
}
/* Responsive Adjustments */
@media (max-width: 768px) {
.site-header .container {
flex-direction: column;
text-align: center;
}
.site-header h1 {
margin-bottom: var(--spacing-md);
}
.remix-spots {
grid-template-columns: 1fr; /* Stack spots vertically */
}
.model-options {
flex-direction: column; /* Stack model options vertically */
}
}
@media (max-width: 480px) {
.container {
padding: var(--spacing-md);
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.3em;
}
.btn {
width: 100%;
margin-top: var(--spacing-md);
}
.uploaded-image-wrapper {
width: 80px;
height: 80px;
}
}
=== assets/js/script.js ===
document.addEventListener('DOMContentLoaded', () => {
const uploadInput = document.getElementById('image-upload');
const uploadButton = document.getElementById('upload-button');
const uploadDropzone = document.getElementById('upload-dropzone');
const uploadedPreviews = document.getElementById('uploaded-previews');
const imageSpots = Array.from(document.querySelectorAll('.image-spot'));
const textPrompt = document.getElementById('text-prompt');
const remixButton = document.getElementById('remix-button');
const statusMessage = document.getElementById('status-message');
const remixResult = document.getElementById('remix-result');
const resultImage = document.getElementById('result-image');
const resultPlaceholder = document.getElementById('result-placeholder');
// Store base64 data for images in spots
const imageSpotData = Array(3).fill(null);
// Store original draggable elements created from uploaded images
const draggableImages = new Map(); // Map<id, {element, src}>
let uploadedImageCounter = 0;
// --- Utility Functions ---
const showStatus = (message, type = 'loading') => {
statusMessage.textContent = message;
statusMessage.className = `status-message ${type}`;
statusMessage.style.display = 'block';
};
const hideStatus = () => {
statusMessage.style.display = 'none';
statusMessage.textContent = '';
};
const clearResult = () => {
resultImage.style.display = 'none';
resultImage.src = '';
resultPlaceholder.style.display = 'block';
};
// --- File Upload Logic ---
uploadButton.addEventListener('click', () => uploadInput.click());
uploadInput.addEventListener('change', (event) => {
handleFiles(event.target.files);
});
uploadDropzone.addEventListener('dragover', (event) => {
event.preventDefault();
uploadDropzone.classList.add('drag-over');
});
uploadDropzone.addEventListener('dragleave', () => {
uploadDropzone.classList.remove('drag-over');
});
uploadDropzone.addEventListener('drop', (event) => {
event.preventDefault();
uploadDropzone.classList.remove('drag-over');
handleFiles(event.dataTransfer.files);
});
function handleFiles(files) {
if (files.length === 0) return;
Array.from(files).forEach(file => {
if (!file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = (e) => {
const imgId = `uploaded-img-${uploadedImageCounter++}`;
createDraggableImage(e.target.result, imgId);
};
reader.readAsDataURL(file);
});
}
function createDraggableImage(src, id) {
const wrapper = document.createElement('div');
wrapper.className = 'uploaded-image-wrapper';
wrapper.draggable = true;
wrapper.id = id;
const img = document.createElement('img');
img.src = src;
img.alt = `Uploaded image ${id}`;
const removeBtn = document.createElement('button');
removeBtn.className = 'remove-image';
removeBtn.textContent = 'x';
removeBtn.title = 'Remove image';
removeBtn.addEventListener('click', (e) => {
e.stopPropagation(); // Prevent drag event from firing
wrapper.remove();
draggableImages.delete(id);
// Also check if this image was in any spot and remove it
for (let i = 0; i < imageSpotData.length; i++) {
if (imageSpotData[i] && imageSpotData[i].id === id) {
clearImageSpot(imageSpots[i], i);
}
}
});
wrapper.appendChild(img);
wrapper.appendChild(removeBtn);
uploadedPreviews.appendChild(wrapper);
draggableImages.set(id, { element: wrapper, src: src });
wrapper.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', id);
e.dataTransfer.effectAllowed = 'copyMove';
// Optional: add a class to the original to make it transparent or similar
// e.target.classList.add('dragging');
});
// wrapper.addEventListener('dragend', (e) => {
// e.target.classList.remove('dragging');
// });
}
// --- Image Spot Drag & Drop Logic ---
imageSpots.forEach((spot) => {
spot.addEventListener('dragover', (event) => {
event.preventDefault();
spot.classList.add('drag-over');
event.dataTransfer.dropEffect = 'copy';
});
spot.addEventListener('dragleave', () => {
spot.classList.remove('drag-over');
});
spot.addEventListener('drop', (event) => {
event.preventDefault();
spot.classList.remove('drag-over');
const draggedId = event.dataTransfer.getData('text/plain');
const draggedImageData = draggableImages.get(draggedId);
if (draggedImageData) {
const spotIndex = parseInt(spot.dataset.index);
renderImageInSpot(spot, draggedImageData.src);
imageSpotData[spotIndex] = { src: draggedImageData.src, id: draggedId };
}
});
});
function renderImageInSpot(spotElement, imgSrc) {
const imgElement = spotElement.querySelector('.spot-image');
imgElement.src = imgSrc;
spotElement.classList.add('has-image');
imgElement.alt = `Image in spot ${spotElement.dataset.index + 1}`;
// Remove old image's alt if it exists and wasn't placeholder.
}
function clearImageSpot(spotElement, spotIndex) {
const imgElement = spotElement.querySelector('.spot-image');
imgElement.src = 'assets/img/placeholder.png'; // Reset to placeholder
spotElement.classList.remove('has-image');
imgElement.alt = `Image spot ${spotIndex + 1}`;
imageSpotData[spotIndex] = null;
}
// --- Remix Logic ---
remixButton.addEventListener('click', remixImages);
async function remixImages() {
hideStatus();
clearResult();
const images = imageSpotData.filter(data => data !== null);
const prompt = textPrompt.value.trim();
const selectedModel = document.querySelector('input[name="ai-model"]:checked').value;
if (images.length === 0) {
showStatus('Please drag at least one image into a spot.', 'error');
return;
}
if (!prompt) {
showStatus('Please enter a text prompt.', 'error');
return;
}
showStatus(`Remixing with ${selectedModel}...`, 'loading');
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 3000)); // Simulate network delay
// In a real application, you would send `images` (base64 or URLs), `prompt`, and `selectedModel`
// to a backend API that integrates with Gemini-2 or GPT Image-1.
// The backend would then return the generated image's URL or base64.
// For simulation, we'll use a placeholder image based on the model
const placeholderResult = selectedModel === 'gemini-2'
? 'https://picsum.photos/seed/gemini/800/600?grayscale' // A bit more abstract
: 'https://picsum.photos/seed/gpt/800/600'; // Standard realistic
resultImage.src = placeholderResult;
resultImage.style.display = 'block';
resultPlaceholder.style.display = 'none';
showStatus('Remix successful!', 'success');
statusMessage.style.backgroundColor = '#d4edda'; // Light green for success
statusMessage.style.color = '#155724';
statusMessage.style.borderColor = '#28a745';
} catch (error) {
console.error('Remix error:', error);
showStatus('Failed to remix images. Please try again.', 'error');
}
}
});
// Initial state for result section
document.addEventListener('DOMContentLoaded', () => {
const resultImage = document.getElementById('result-image');
const resultPlaceholder = document.getElementById('result-placeholder');
resultImage.style.display = 'none';
resultPlaceholder.style.display = 'block';
});
=== assets/img/placeholder.png ===
iVBORw0KGgoAAAANSUhEUgAAASwAAAEsAQMAAABHRxZqAAAAA1BMVEWAgIBN1FfXAAAAFUlEQVR4XnOgWAAAAqIApQAAAD8JvL4AAAAASUVORK5CYII=
=== assets/img/initial-result.png ===
iVBORw0KGgoAAAANSUhEUgAAASwAAAEsAQMAAABHRxZqAAAAA1BMVEWAgIBN1FfXAAAAFUlEQVR4XnOgWAAAAqIApQAAAD8JvL4AAAAASUVORK5CYII=