Spaces:
Running
Running
| (() => { | |
| let API_URL = "https://rerun.aswerdlow.com/v1/chat/completions"; | |
| const GRID_SIZE = 8; | |
| window.autoResetOnMaskSelect = true; | |
| window.enable_cache = false; | |
| window.skipHashChecking = true; | |
| let isImageRemoved = false; // Add flag to track if image is removed | |
| let DISABLE_HASH_CHECKING = false; // Add this flag to globally disable hash checking | |
| function getMaskSize() { | |
| const maskSizeInput = document.getElementById('cached-mask-size'); | |
| if (maskSizeInput) { | |
| const size = parseInt(maskSizeInput.value, 10); | |
| // Validate the size is between 2 and GRID_SIZE | |
| return Math.min(Math.max(size, 2), GRID_SIZE); | |
| } | |
| return 6; // Default value if input not found | |
| } | |
| // --- Utility functions --- | |
| async function processImage(imageBytes, targetResolution) { | |
| const img = await createImageBitmap(new Blob([imageBytes], { type: 'image/jpeg' })); | |
| const croppedCanvas = squareCrop(img); | |
| // Resize step | |
| const resizedCanvas = new OffscreenCanvas(targetResolution, targetResolution); | |
| const ctx = resizedCanvas.getContext('2d'); | |
| // Draw the cropped image onto the resized canvas | |
| ctx.drawImage(croppedCanvas, 0, 0, targetResolution, targetResolution); | |
| // Return blob from the resized canvas | |
| return resizedCanvas.convertToBlob({ quality: 0.95, type: 'image/jpeg' }); | |
| } | |
| function squareCrop(img) { | |
| const size = Math.min(img.width, img.height); | |
| const canvas = new OffscreenCanvas(size, size); | |
| const ctx = canvas.getContext('2d'); | |
| ctx.drawImage(img, | |
| (img.width - size) / 2, (img.height - size) / 2, size, size, | |
| 0, 0, size, size | |
| ); | |
| return canvas; | |
| } | |
| function encodeMask(maskArray) { | |
| const rows = maskArray.length; | |
| const cols = maskArray[0].length; | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = cols; | |
| canvas.height = rows; | |
| const ctx = canvas.getContext('2d'); | |
| const imageData = ctx.createImageData(cols, rows); | |
| for (let i = 0; i < maskArray.flat().length; i++) { | |
| const val = maskArray.flat()[i]; | |
| const color = val ? 255 : 0; | |
| const pixelIndex = i * 4; // Each pixel uses 4 bytes in the array | |
| imageData.data[pixelIndex] = color; // R | |
| imageData.data[pixelIndex + 1] = color; // G | |
| imageData.data[pixelIndex + 2] = color; // B | |
| imageData.data[pixelIndex + 3] = color; // A | |
| } | |
| ctx.putImageData(imageData, 0, 0); | |
| const dataURL = canvas.toDataURL("image/png"); | |
| return { | |
| data: dataURL.split(',')[1], | |
| width: cols, | |
| height: rows | |
| }; | |
| } | |
| function isChromeBrowser() { | |
| return /Chrome/.test(navigator.userAgent) && navigator.vendor === "Google Inc."; | |
| } | |
| // --- NEW: Function to get config from UI --- | |
| function getApiConfig() { | |
| const config = { | |
| temperature: parseFloat(document.getElementById('config-temperature').value), | |
| top_p: parseFloat(document.getElementById('config-top_p').value), | |
| maskgit_r_temp: parseFloat(document.getElementById('config-maskgit_r_temp').value), | |
| cfg: parseFloat(document.getElementById('config-cfg').value), | |
| max_tokens: parseInt(document.getElementById('config-max_tokens').value, 10), | |
| resolution: parseInt(document.getElementById('config-resolution').value, 10), | |
| sampling_steps: parseInt(document.getElementById('config-sampling_steps').value, 10), | |
| sampler: document.getElementById('config-sampler').value, | |
| use_reward_models: document.getElementById('config-use_reward_models').checked | |
| }; | |
| console.log("Using API Config:", config); | |
| return config; | |
| } | |
| // --- NEW: Function to update slider value display --- | |
| function setupSliderValueDisplay() { | |
| const sliders = [ | |
| { id: 'config-temperature', displayId: 'config-temperature-value' }, | |
| { id: 'config-top_p', displayId: 'config-top_p-value' }, | |
| { id: 'config-maskgit_r_temp', displayId: 'config-maskgit_r_temp-value' }, | |
| { id: 'config-cfg', displayId: 'config-cfg-value' }, | |
| ]; | |
| sliders.forEach(sliderInfo => { | |
| const slider = document.getElementById(sliderInfo.id); | |
| const display = document.getElementById(sliderInfo.displayId); | |
| if (slider && display) { | |
| // Initial display update | |
| display.textContent = slider.value; | |
| // Update display on input change | |
| slider.addEventListener('input', (event) => { | |
| display.textContent = event.target.value; | |
| }); | |
| } | |
| }); | |
| } | |
| async function callUnidiscAPI(imageBlob, maskArray, sentence, options = {}) { | |
| if (!isChromeBrowser()) { | |
| alert("Warning: The pre-cached demo only works in Chrome due to differences in hashing algorithms."); | |
| } | |
| // Use the global isImageRemoved flag directly. | |
| let customAPIUrl = API_URL; | |
| // Replace <mask> with <m> for API call | |
| const apiSentence = sentence.replace(/<mask>/g, "<m>"); | |
| console.log("Called API with sentence: ", apiSentence); | |
| const messages = [{ | |
| role: "user", | |
| content: [ | |
| ...(apiSentence ? [{ type: "text", text: apiSentence }] : []) | |
| ] | |
| }, | |
| { | |
| role: "assistant", | |
| content: [] | |
| } | |
| ]; | |
| const hasMaskedText = apiSentence.includes("<m") || !apiSentence || apiSentence.trim() === ""; | |
| const hasMaskedImage = maskArray && maskArray.some(row => row.some(cell => cell === true)); | |
| let imageBase64; | |
| let maskData; | |
| // Get target resolution from config *before* processing the image | |
| const resolution = parseInt(document.getElementById('config-resolution').value, 10); | |
| if ((hasMaskedText || hasMaskedImage) && !isImageRemoved) { | |
| // Process the passed imageBlob with the target resolution | |
| const resizedImage = await processImage(await imageBlob.arrayBuffer(), resolution); | |
| imageBase64 = await new Promise(resolve => { | |
| const reader = new FileReader(); | |
| reader.onload = () => resolve(reader.result); | |
| reader.readAsDataURL(resizedImage); | |
| }); | |
| messages[1].content.push({ | |
| type: "image_url", | |
| image_url: { url: imageBase64 }, | |
| is_mask: false | |
| }); | |
| if (maskArray) { | |
| maskData = encodeMask(maskArray); | |
| messages[1].content.push({ | |
| type: "image_url", | |
| image_url: { | |
| url: `data:image/png;base64,${maskData.data}`, | |
| mask_info: JSON.stringify({ | |
| width: maskData.width, | |
| height: maskData.height | |
| }) | |
| }, | |
| is_mask: true | |
| }); | |
| } | |
| } | |
| if (messages.length > 0 && | |
| messages[messages.length - 1].role === 'assistant' && | |
| (!messages[messages.length - 1].content || | |
| messages[messages.length - 1].content.length === 0)) { | |
| console.log("Removing empty assistant message"); | |
| messages.pop(); // Remove the empty assistant message | |
| } | |
| // Create the payload without the hash first. | |
| const payload = { | |
| messages, | |
| model: "unidisc", | |
| ...getApiConfig() // Use the function to get dynamic config | |
| }; | |
| // Caching logic - hash the entire request payload. | |
| let hash = null; | |
| console.log("window.skipHashChecking: ", window.skipHashChecking); | |
| // Skip hash generation and checking if DISABLE_HASH_CHECKING is true | |
| if (!window.skipHashChecking) { | |
| try { | |
| const payloadString = JSON.stringify(payload); | |
| const encoder = new TextEncoder(); | |
| const data = encoder.encode(payloadString); | |
| if (typeof crypto !== 'undefined' && crypto.subtle) { | |
| const hashBuffer = await crypto.subtle.digest('SHA-256', data); | |
| const hashArray = Array.from(new Uint8Array(hashBuffer)); | |
| hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); | |
| console.log("Hash generated from full payload:", hash); | |
| } else { | |
| throw new Error('Web Crypto API is not available. Please ensure you are serving your page over HTTPS or via localhost.'); | |
| } | |
| } catch (error) { | |
| console.error('Error generating hash:', error); | |
| } | |
| } | |
| try { | |
| // Check cache using the hash only if hash checking is enabled | |
| if (hash && !window.skipHashChecking) { | |
| try { | |
| const response = await fetch(`/static/responses/${hash}.json`, { | |
| mode: 'cors', | |
| headers: { | |
| 'Accept': 'application/json' | |
| } | |
| }); | |
| if (response.ok) { | |
| const jsonContent = await response.text(); | |
| console.log("Cache hit!"); | |
| const cachedData = JSON.parse(jsonContent); | |
| console.log("Cached data: ", cachedData); | |
| return { | |
| choices: [{ | |
| index: 0, | |
| message: cachedData, | |
| finish_reason: "stop" | |
| }] | |
| }; | |
| } else { | |
| console.log("Cache miss:", response); | |
| } | |
| } catch (cacheError) { | |
| console.log("Cache access failed:", cacheError); | |
| console.log("Proceeding with direct API call"); | |
| } | |
| console.log("Hash: ", hash); | |
| } | |
| } catch (error) { | |
| console.log("Cache miss:", error) | |
| } | |
| // Only add hash to payload if hash checking is enabled | |
| if (hash && !DISABLE_HASH_CHECKING) { | |
| payload.request_hash = hash; | |
| } | |
| console.log("Payload: ", payload); | |
| try { | |
| const response = await fetch(customAPIUrl, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Access-Control-Allow-Origin': '*', | |
| 'Access-Control-Allow-Methods': 'POST, OPTIONS', | |
| 'Access-Control-Allow-Headers': 'Content-Type' | |
| }, | |
| body: JSON.stringify(payload) | |
| }); | |
| if (!response.ok) throw new Error(`API Error: ${response.status}`); | |
| const data = await response.json(); | |
| console.log("Response: ", data); | |
| return data; | |
| } catch (error) { | |
| console.error('API call failed:', error); | |
| throw error; | |
| } | |
| } | |
| window.callUnidiscAPI = callUnidiscAPI; | |
| const section = document.getElementById('cached-section'); | |
| const grid = section.querySelector('#cached-grid'); | |
| const textInput = section.querySelector('#cached-text-input'); // Get the new text input | |
| const submitButton = section.querySelector('#cached-submit-text'); // Get the new submit button | |
| const responseText = section.querySelector('#cached-response-text'); | |
| const inputImage = section.querySelector('#cached-input-image'); | |
| const outputImage = section.querySelector('#cached-output-image'); | |
| const imageUploadInput = section.querySelector('#cached-image-upload'); // Get the file input | |
| const cells = []; | |
| let currentRow = 0; | |
| let currentCol = 0; | |
| let maskLocked = false; // Add this flag to track if mask is locked in place | |
| let activeMask = null; // Track the currently active mask coordinates | |
| for (let i = 0; i < GRID_SIZE * GRID_SIZE; i++) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'cached-grid-cell'; | |
| cell.dataset.row = Math.floor(i / GRID_SIZE); | |
| cell.dataset.col = i % GRID_SIZE; | |
| grid.appendChild(cell); | |
| cells.push(cell); | |
| } | |
| function createMaskArray(topLeftRow, topLeftCol) { | |
| const maskSize = getMaskSize(); | |
| const maskArray = Array.from({ length: GRID_SIZE }, () => Array(GRID_SIZE).fill(false)); | |
| for (let r = topLeftRow; r < topLeftRow + maskSize && r < GRID_SIZE; r++) { | |
| for (let c = topLeftCol; c < topLeftCol + maskSize && c < GRID_SIZE; c++) { | |
| maskArray[r][c] = true; | |
| } | |
| } | |
| return maskArray; | |
| } | |
| function highlightCells(row, col) { | |
| // If mask is locked, don't update highlights on mousemove | |
| if (maskLocked) return; | |
| cells.forEach(cell => cell.classList.remove('cached-highlighted')); | |
| const maskSize = getMaskSize(); | |
| const offset = Math.floor(maskSize / 2); | |
| const topLeftRow = Math.min(Math.max(row - offset, 0), GRID_SIZE - maskSize); | |
| const topLeftCol = Math.min(Math.max(col - offset, 0), GRID_SIZE - maskSize); | |
| currentRow = row; | |
| currentCol = col; | |
| for (let r = topLeftRow; r < topLeftRow + maskSize && r < GRID_SIZE; r++) { | |
| for (let c = topLeftCol; c < topLeftCol + maskSize && c < GRID_SIZE; c++) { | |
| const cell = cells[r * GRID_SIZE + c]; | |
| if (cell) { | |
| cell.classList.add('cached-highlighted'); | |
| } | |
| } | |
| } | |
| } | |
| grid.addEventListener('mousemove', (e) => { | |
| const rect = grid.getBoundingClientRect(); | |
| const x = e.clientX - rect.left; | |
| const y = e.clientY - rect.top; | |
| const col = Math.floor((x / rect.width) * GRID_SIZE); | |
| const row = Math.floor((y / rect.height) * GRID_SIZE); | |
| highlightCells(row, col); | |
| }); | |
| async function updateOutput() { | |
| try { | |
| const responseText = section.querySelector('#cached-response-text'); | |
| const greyOverlay = section.querySelector('#cached-grey-overlay'); | |
| const outputOverlay = section.querySelector('#cached-output-overlay'); | |
| let maskArray = null; | |
| // Only create a mask if we have an active mask | |
| if (activeMask) { | |
| const [topLeftRow, topLeftCol] = activeMask; | |
| maskArray = createMaskArray(topLeftRow, topLeftCol); | |
| } | |
| // Get sentence from the text input field's value | |
| const sentence = textInput.value.trim(); | |
| if (!sentence) { | |
| // Optionally handle empty input case - maybe show a message? | |
| responseText.textContent = ""; | |
| outputOverlay.style.display = "none"; // Hide overlay if showing message | |
| return; // Don't call API if input is empty | |
| } | |
| const imageBlob = await fetch(inputImage.src).then(res => res.blob()); | |
| responseText.textContent = 'Processing your request...'; // Show loading state | |
| outputOverlay.style.display = "block"; // Show grey overlay while loading | |
| outputOverlay.innerHTML = ""; // Reset any previous custom text in the overlay | |
| console.log("sentence: ", sentence); | |
| const response = await callUnidiscAPI(imageBlob, maskArray, sentence); | |
| const message = response.choices?.[0]?.message; | |
| if (!message) { | |
| throw new Error("No message found in the API response"); | |
| } | |
| // Extract text content if available | |
| let textContent = ''; | |
| if (Array.isArray(message.content)) { | |
| const textPart = message.content.find(part => part.type === "text"); | |
| if (textPart && textPart.text) { | |
| textContent = textPart.text; | |
| } | |
| } else if (typeof message.content === 'string') { | |
| textContent = message.content; | |
| } | |
| if (textContent) { | |
| textContent = textContent.replace(/\s+/g, ' ').replace(/ ([b-zB-Z]) /g, " "); | |
| responseText.textContent = textContent; | |
| } else { | |
| responseText.textContent = 'Image updated successfully!'; | |
| } | |
| // Check if there's an image in the response | |
| let imageUrl = null; | |
| if (Array.isArray(message.content)) { | |
| const imagePart = message.content.find(part => part.type === "image_url"); | |
| if (imagePart && imagePart.image_url && imagePart.image_url.url) { | |
| imageUrl = imagePart.image_url.url; | |
| } | |
| } else if (message.image_url && message.image_url.url) { | |
| imageUrl = message.image_url.url; | |
| } | |
| // Update the output image if we have an image response | |
| if (imageUrl) { | |
| const newImageUrl = imageUrl.startsWith("data:image/jpeg;base64,") | |
| ? imageUrl | |
| : `data:image/jpeg;base64,${imageUrl}`; | |
| outputImage.src = newImageUrl; | |
| // Hide the output overlay when image is ready | |
| outputOverlay.style.display = "none"; | |
| } else { | |
| // No image in response, but API was successful | |
| // Show "Image Fixed" text on the overlay | |
| outputOverlay.style.display = "block"; | |
| outputOverlay.innerHTML = '<div style="display: flex; justify-content: center; align-items: center; height: 100%; color: white; font-size: 24px; font-weight: bold; text-shadow: 1px 1px 3px black;">Image Fixed</div>'; | |
| } | |
| } catch (error) { | |
| console.error('Output update failed:', error); | |
| responseText.textContent = 'Error: ' + error.message; | |
| // Keep the grey overlay visible on error | |
| outputOverlay.style.display = 'block'; // Ensure overlay shows on error | |
| outputOverlay.innerHTML = ''; // Clear any "Image Fixed" text on error | |
| } | |
| } | |
| grid.addEventListener('click', async () => { | |
| const maskSize = getMaskSize(); | |
| const offset = Math.floor(maskSize / 2); | |
| const safeTopLeftRow = Math.min(Math.max(currentRow - offset, 0), GRID_SIZE - maskSize); | |
| const safeTopLeftCol = Math.min(Math.max(currentCol - offset, 0), GRID_SIZE - maskSize); | |
| if (isImageRemoved) { | |
| // If the image is currently removed (greyed out), | |
| // clicking the grid should probably do nothing or maybe restore the image first. | |
| // For now, let's prevent interaction when image is removed. | |
| console.log("Image is removed, grid click ignored."); | |
| return; | |
| } | |
| if (maskLocked && | |
| activeMask && | |
| activeMask[0] === safeTopLeftRow && | |
| activeMask[1] === safeTopLeftCol) { | |
| // If clicking on the same mask area, unlock it | |
| console.log("Clearing mask after clicking on the same mask area"); | |
| maskLocked = false; | |
| activeMask = null; | |
| // Clear highlights | |
| cells.forEach(cell => cell.classList.remove('cached-highlighted')); | |
| // Don't call updateOutput when just removing the mask | |
| return; | |
| } else { | |
| // Lock the mask at current position | |
| maskLocked = true; | |
| activeMask = [safeTopLeftRow, safeTopLeftCol]; | |
| // Ensure the mask area is properly highlighted | |
| cells.forEach(cell => cell.classList.remove('cached-highlighted')); | |
| for (let r = safeTopLeftRow; r < safeTopLeftRow + maskSize && r < GRID_SIZE; r++) { | |
| for (let c = safeTopLeftCol; c < safeTopLeftCol + maskSize && c < GRID_SIZE; c++) { | |
| const cell = cells[r * GRID_SIZE + c]; | |
| if (cell) { | |
| cell.classList.add('cached-highlighted'); | |
| } | |
| } | |
| } | |
| } | |
| try { | |
| // Trigger update when mask is placed/changed | |
| await updateOutput(); | |
| } catch (error) { | |
| console.error('Error updating output after grid click:', error); | |
| } | |
| }); | |
| grid.addEventListener('mouseleave', () => { | |
| // Only clear highlights if mask is not locked | |
| if (!maskLocked) { | |
| cells.forEach(cell => cell.classList.remove('cached-highlighted')); | |
| } | |
| }); | |
| // Initialize highlighting with default values. | |
| highlightCells(1, 1); | |
| // Add event listeners for the reset buttons | |
| const resetImageButton = section.querySelector('#cached-reset-image'); | |
| const clearMaskButton = section.querySelector('#cached-clear-mask'); | |
| const removeImageButton = section.querySelector('#cached-remove-image'); | |
| const greyOverlay = section.querySelector('#cached-grey-overlay'); | |
| const originalImageSrc = "static/images/giraffe.png"; // Store the original image source | |
| // Reset image button functionality | |
| resetImageButton.addEventListener('click', () => { | |
| inputImage.src = originalImageSrc; | |
| inputImage.style.filter = "none"; // Clear any filters | |
| isImageRemoved = false; // Reset the image removed flag | |
| // Hide the grey overlay | |
| greyOverlay.style.display = "none"; | |
| // Also clear the mask when resetting the image | |
| console.log("Clearing mask after resetting the image"); | |
| maskLocked = false; | |
| activeMask = null; | |
| cells.forEach(cell => cell.classList.remove('cached-highlighted')); | |
| // Reset the output image and response text | |
| outputImage.src = originalImageSrc; | |
| responseText.textContent = 'Enter a sentence and interact with the image.'; | |
| // Show the output overlay when resetting | |
| document.querySelector('#cached-output-overlay').style.display = "block"; | |
| document.querySelector('#cached-output-overlay').innerHTML = ""; // Clear potential "Image Fixed" text | |
| }); | |
| // Clear mask button functionality | |
| clearMaskButton.addEventListener('click', () => { | |
| console.log("Clearing mask without affecting the image"); | |
| maskLocked = false; | |
| activeMask = null; | |
| cells.forEach(cell => cell.classList.remove('cached-highlighted')); | |
| // Update the output to reflect that the mask has been cleared | |
| responseText.textContent = 'Mask cleared. Enter a sentence and interact with the image.'; | |
| // Decide if you want to update the output image here or not. | |
| // Maybe call updateOutput() if the text input is not empty? | |
| }); | |
| // Remove image button functionality | |
| removeImageButton.addEventListener('click', async () => { | |
| // Show the solid grey overlay | |
| greyOverlay.style.display = "block"; | |
| isImageRemoved = true; // Set flag to indicate image is removed | |
| // Clear any active mask | |
| console.log("Clearing mask after removing image"); | |
| maskLocked = false; | |
| activeMask = null; | |
| cells.forEach(cell => cell.classList.remove('cached-highlighted')); | |
| // Call the API after fully masking the image | |
| try { | |
| // Update output now uses the text input value | |
| await updateOutput(); | |
| } catch (error) { | |
| console.error('Error updating output after fully masking image:', error); | |
| } | |
| }); | |
| // Add event listener for mask size changes to update the highlight | |
| const maskSizeInput = document.getElementById('cached-mask-size'); | |
| if (maskSizeInput) { | |
| maskSizeInput.addEventListener('change', () => { | |
| // If we have an active mask, clear it as the size has changed | |
| if (maskLocked && activeMask) { | |
| maskLocked = false; | |
| activeMask = null; | |
| cells.forEach(cell => cell.classList.remove('cached-highlighted')); | |
| } | |
| // Update the highlight with the current mouse position | |
| if (currentRow !== undefined && currentCol !== undefined) { | |
| highlightCells(currentRow, currentCol); | |
| } | |
| }); | |
| } | |
| // Add event listener for the new submit button | |
| submitButton.addEventListener('click', async () => { | |
| console.log("Submit button clicked"); | |
| try { | |
| // Trigger update when submit button is clicked | |
| await updateOutput(); | |
| } catch (error) { | |
| console.error('Error updating output after submit click:', error); | |
| } | |
| }); | |
| // Optional: Add event listener for Enter key in the text input | |
| textInput.addEventListener('keypress', async (e) => { | |
| if (e.key === 'Enter') { | |
| console.log("Enter key pressed in text input"); | |
| e.preventDefault(); // Prevent default form submission if inside a form | |
| try { | |
| // Trigger update when Enter is pressed | |
| await updateOutput(); | |
| } catch (error) { | |
| console.error('Error updating output after Enter keypress:', error); | |
| } | |
| } | |
| }); | |
| // Set initial state | |
| textInput.value = "a happy puppy wearing a top hat, cartoon style"; // Set initial text | |
| responseText.textContent = 'Enter a sentence and interact with the image.'; // Initial message | |
| document.querySelector('#cached-output-overlay').style.display = "block"; // Show overlay initially | |
| // Optionally trigger an initial API call on load if desired | |
| // updateOutput(); | |
| setupSliderValueDisplay(); // Call the function to set up slider displays | |
| // --- NEW: Event Listener for Image Upload --- | |
| imageUploadInput.addEventListener('change', (event) => { | |
| const file = event.target.files[0]; | |
| if (file && (file.type === "image/jpeg" || file.type === "image/png")) { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| // Set the input image source to the uploaded image data URL | |
| inputImage.src = e.target.result; | |
| // Reset output image view to match new input | |
| outputImage.src = e.target.result; | |
| // Ensure the image display area is visible and not greyed out | |
| inputImage.style.filter = "none"; | |
| isImageRemoved = false; | |
| greyOverlay.style.display = "none"; | |
| // Clear any existing mask and update status text | |
| clearMaskButton.click(); | |
| responseText.textContent = 'New image uploaded. Interact with the image or enter text.'; | |
| // Reset the output overlay | |
| document.querySelector('#cached-output-overlay').style.display = "block"; | |
| document.querySelector('#cached-output-overlay').innerHTML = ""; | |
| console.log("Image uploaded and displayed."); | |
| } | |
| reader.readAsDataURL(file); | |
| } else if (file) { | |
| alert("Please upload a JPG or PNG image file."); | |
| // Reset the file input value so the same file can be selected again if needed after error | |
| imageUploadInput.value = ""; | |
| } | |
| }); | |
| })(); |