APP-Backend / src /views /chat /image-editor.js
Luca448's picture
Savestate: Integrate Nano Banana 2 image generation via Pollinations and custom UI editor
8b950cf
Raw History Blame Contribute Delete
5.97 kB
// image-editor.js - Nano Banana / Pollinations UI overlay for Image Editing
export function initImageEditor() {
if (document.getElementById('image-editor-modal')) return;
const modalHtml = `
<div id="image-editor-modal" style="display: none; position: fixed; inset: 0; z-index: 9999; background: rgba(0,0,0,0.8); backdrop-filter: blur(10px); flex-direction: column; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s ease;">
<div style="position: absolute; top: 20px; right: 20px; z-index: 10;">
<button id="close-image-editor" style="background: rgba(255,255,255,0.2); border: none; width: 40px; height: 40px; border-radius: 20px; color: white; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center;">
<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<div style="width: 90%; max-width: 600px; display: flex; flex-direction: column; gap: 16px;">
<div style="position: relative; width: 100%; aspect-ratio: 1/1; background: #111; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.5);">
<img id="editor-base-image" style="width: 100%; height: 100%; object-fit: contain; pointer-events: none;" />
<canvas id="editor-canvas" style="position: absolute; inset: 0; width: 100%; height: 100%; cursor: crosshair; touch-action: none;"></canvas>
</div>
<div style="display: flex; gap: 8px; background: rgba(255,255,255,0.05); padding: 8px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1);">
<input type="text" id="editor-prompt" placeholder="Was möchtest du ändern? (z.B. 'Mach den Himmel blauer')" style="flex: 1; background: transparent; border: none; color: white; padding: 12px; font-size: 16px; outline: none; min-width: 0;" />
<button id="editor-submit" style="background: white; color: black; border: none; padding: 0 24px; border-radius: 8px; font-weight: 600; cursor: pointer;">Ändern</button>
</div>
<div style="text-align: center; color: rgba(255,255,255,0.5); font-size: 12px;">Hinweis: Maskierung (Inpainting) erfordert Nano Banana 2 (Premium). Aktuell wird der reine Text-Prompt verarbeitet.</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
const modal = document.getElementById('image-editor-modal');
const closeBtn = document.getElementById('close-image-editor');
const canvas = document.getElementById('editor-canvas');
const promptInput = document.getElementById('editor-prompt');
const submitBtn = document.getElementById('editor-submit');
const baseImg = document.getElementById('editor-base-image');
let ctx = canvas.getContext('2d');
let isDrawing = false;
let currentPromptContext = '';
function resizeCanvas() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.lineWidth = 30;
ctx.strokeStyle = 'rgba(255, 50, 50, 0.5)';
}
window.addEventListener('resize', () => {
if (modal.style.display !== 'none') resizeCanvas();
});
closeBtn.onclick = () => {
modal.style.opacity = '0';
setTimeout(() => modal.style.display = 'none', 300);
};
// Drawing Logic
const startDrawing = (e) => {
isDrawing = true;
draw(e);
};
const stopDrawing = () => {
isDrawing = false;
ctx.beginPath();
};
const draw = (e) => {
if (!isDrawing) return;
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
const x = clientX - rect.left;
const y = clientY - rect.top;
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
};
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
canvas.addEventListener('touchstart', startDrawing, { passive: false });
canvas.addEventListener('touchmove', draw, { passive: false });
canvas.addEventListener('touchend', stopDrawing);
submitBtn.onclick = () => {
const editInstruction = promptInput.value.trim();
if (!editInstruction) return;
// For Pollinations (Option A), we just append the edit to the prompt or overwrite it.
// In a real API, we'd send baseImg.src and canvas.toDataURL() as a mask.
const newPrompt = `${currentPromptContext}, modified: ${editInstruction}`;
// Send a message into the chat conceptually
const input = document.querySelector('#chat-input');
const sendBtn = document.querySelector('#send-button');
if (input && sendBtn) {
input.value = `Bitte generiere das Bild mit der Änderung: "${editInstruction}". Neuer Prompt: ${newPrompt}`;
sendBtn.click();
}
closeBtn.click();
};
window.openImageEditor = (url, originalPromptEncoded) => {
baseImg.src = url;
currentPromptContext = decodeURIComponent(originalPromptEncoded);
promptInput.value = '';
modal.style.display = 'flex';
// Force reflow
void modal.offsetWidth;
modal.style.opacity = '1';
setTimeout(() => {
resizeCanvas();
ctx.clearRect(0, 0, canvas.width, canvas.height);
}, 100);
};
}
// Auto-init on load if not a module or manually called
if (typeof document !== 'undefined') {
document.addEventListener('DOMContentLoaded', initImageEditor);
// Just in case it's loaded dynamically after DOMContentLoaded
if (document.readyState === 'complete' || document.readyState === 'interactive') {
initImageEditor();
}
}