//gosh... this is a mess... I'm sorry... I'll clean it up later... I promise... function my_tag_autocomplete() { function dark_theme() { const url = new URL(window.location); if (url.searchParams.get('__theme') !== 'dark') { url.searchParams.set('__theme', 'dark'); window.location.href = url.href; } } function setupSuggestionSystem() { // Select all textarea elements with specific IDs const textboxes = document.querySelectorAll( '#custom_prompt_text textarea, ' + '#positive_prompt_text textarea, ' + '#negative_prompt_text textarea, ' + '#ai_prompt_text textarea, ' + '#prompt_ban_text textarea' ); // Log the number of textboxes found //console.log("Found specific textboxes:", textboxes.length); textboxes.forEach(textbox => { // Skip if the suggestion system is already set up for this textbox if (textbox.dataset.suggestionSetup) return; // Log that the suggestion system is being set up for this textbox console.log("Setting up suggestion system for", textbox); let suggestionBox = document.createElement('div'); suggestionBox.className = 'suggestion-box'; // Hide the suggestion box initially suggestionBox.style.display = 'none'; // Append the suggestion box to the body element document.body.appendChild(suggestionBox); let selectedIndex = -1; // Index of the currently selected suggestion item let currentSuggestions = []; // Array to store the current suggestion items // Handle input events on the textbox textbox.addEventListener('input', async function () { const value = textbox.value; // Current value of the textbox const cursorPosition = textbox.selectionStart; // Current cursor position in the textbox // Extract the word to send for suggestions let wordToSend = ''; if (cursorPosition === value.length) { // If cursor is at the end, extract the word after the last comma const lastCommaIndex = value.lastIndexOf(','); wordToSend = value.slice(lastCommaIndex + 1).trim(); } else { // If cursor is not at the end, extract the word between the nearest commas const beforeCursor = value.slice(0, cursorPosition); const afterCursor = value.slice(cursorPosition); const lastCommaBeforeCursor = beforeCursor.lastIndexOf(','); const firstCommaAfterCursor = afterCursor.indexOf(','); const start = lastCommaBeforeCursor >= 0 ? lastCommaBeforeCursor + 1 : 0; // Start position for word extraction const end = firstCommaAfterCursor >= 0 ? cursorPosition + firstCommaAfterCursor : value.length; // End position for word extraction wordToSend = value.slice(start, end).trim(); } // If no word is extracted, hide the suggestion box and skip the API request if (!wordToSend) { //console.log("Skipping API request due to empty word."); suggestionBox.style.display = 'none'; return; } // Log the word being sent for the initial API request //console.log("Sending initial API request with word:", wordToSend); let eventId; // Variable to store the event ID from the API response try { // Make the first API request to get an event ID const initialResponse = await fetch('/gradio_api/call/update_suggestions_js', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fn_index: 0, data: [wordToSend] // Send the extracted word instead of the full textbox content }) }); // Log the status of the initial API response //console.log("Initial API response status:", initialResponse.status); // Check if the initial API request failed if (!initialResponse.ok) { console.error("Initial API request failed:", initialResponse.status, initialResponse.statusText); return; } const initialResult = await initialResponse.json(); // Log the data received from the initial API response //console.log("Initial API response data:", initialResult); // Extract the event ID from the response eventId = initialResult.event_id; if (!eventId) { console.error("No event_id found in initial API response:", initialResult); return; } // Log the extracted event ID //console.log("Extracted event_id:", eventId); } catch (error) { // Log any errors that occur during the initial API request console.error("Error during initial API request:", error); return; } let suggestions; // Variable to store the suggestion data try { // Make the second API request to get suggestion data using the event ID const suggestionResponse = await fetch(`/gradio_api/call/update_suggestions_js/${eventId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); // Log the status of the suggestion API response //console.log("Suggestion API response status:", suggestionResponse.status); // Check if the suggestion API request failed if (!suggestionResponse.ok) { console.error("Suggestion API request failed:", suggestionResponse.status, suggestionResponse.statusText); return; } // Log the full suggestion API response object //console.log("Suggestion API response object:", suggestionResponse); // Get the raw suggestion data as text const rawSuggestions = await suggestionResponse.text(); // Log the raw suggestion data received //console.log("Raw suggestions received:", rawSuggestions); // Parse the Python-formatted list into a JavaScript array const lines = rawSuggestions.split('\n'); // Split the response into lines let dataLine = lines.find(line => line.startsWith('data:')); // Find the line starting with "data:" if (!dataLine) { console.error("No data line found in raw suggestions:", rawSuggestions); return; } // Remove the "data:" prefix and parse the JSON string into an array const jsonString = dataLine.replace('data:', '').trim(); suggestions = JSON.parse(jsonString); // Log the parsed suggestion data //console.log("Parsed suggestions:", suggestions); } catch (error) { // Log any errors that occur during the suggestion API request console.error("Error during suggestion API request:", error); return; } // Clear the suggestion box content suggestionBox.innerHTML = ''; currentSuggestions = []; // Reset the current suggestions array // Check if there are no valid suggestions to display if (!suggestions || suggestions.length === 0 || suggestions.every(suggestion => suggestion.length === 0)) { //console.log("No suggestions available."); suggestionBox.style.display = 'none'; return; } // Calculate the width of the longest suggestion item let maxWidth = 0; const tempDiv = document.createElement('div'); // Temporary div to measure text width tempDiv.style.position = 'absolute'; tempDiv.style.visibility = 'hidden'; tempDiv.style.whiteSpace = 'nowrap'; document.body.appendChild(tempDiv); // Bind click events to suggestion items during input event suggestions.forEach((suggestion, index) => { if (!Array.isArray(suggestion) || suggestion.length === 0) { console.warn(`Invalid suggestion format at index ${index}:`, suggestion); return; } suggestion.forEach(element => { const item = document.createElement('div'); item.className = 'suggestion-item'; item.textContent = element; item.dataset.value = element; tempDiv.textContent = element; maxWidth = Math.max(maxWidth, tempDiv.offsetWidth); currentSuggestions.push({ prompt: element }); item.addEventListener('click', () => applySuggestion(element)); suggestionBox.appendChild(item); }); }); // Remove the temporary div after measuring document.body.removeChild(tempDiv); // Update the suggestion box position if it is already visible if (suggestionBox.style.display !== 'none') { updateSuggestionBoxPosition(); } // Set the width of the suggestion box setSuggestionBoxWidth(maxWidth); // Log the set width of the suggestion box //console.log("Set suggestionBox width:", suggestionBox.style.width); // Log the actual rendered width of the suggestion box //console.log("Actual suggestionBox width:", suggestionBox.offsetWidth); selectedIndex = -1; // Reset the selected index // Log that the suggestions have been successfully displayed //console.log("Suggestions successfully displayed."); }); // Handle keyboard navigation for the suggestion box textbox.addEventListener('keydown', function (e) { if (suggestionBox.style.display === 'none') return; // Exit if the suggestion box is not visible const items = suggestionBox.querySelectorAll('.suggestion-item'); if (items.length === 0) return; // Exit if there are no suggestion items if (e.key === 'Tab' || e.key === 'Enter') { e.preventDefault(); // Prevent default behavior if (selectedIndex >= 0 && selectedIndex < currentSuggestions.length) { applySuggestion(currentSuggestions[selectedIndex].prompt); // Apply the selected suggestion } else if (items.length > 0) { applySuggestion(currentSuggestions[0].prompt); // Apply the first suggestion if none selected } } else if (e.key === 'ArrowDown') { e.preventDefault(); // Prevent default scrolling selectedIndex = Math.min(selectedIndex + 1, items.length - 1); // Move selection down items.forEach((item, idx) => item.classList.toggle('selected', idx === selectedIndex)); if (selectedIndex >= 0) items[selectedIndex].scrollIntoView({ block: 'nearest' }); textbox.focus(); // Keep focus on the textbox } else if (e.key === 'ArrowUp') { e.preventDefault(); // Prevent default scrolling selectedIndex = Math.max(selectedIndex - 1, 0); // Move selection up items.forEach((item, idx) => item.classList.toggle('selected', idx === selectedIndex)); if (selectedIndex >= 0) items[selectedIndex].scrollIntoView({ block: 'nearest' }); textbox.focus(); // Keep focus on the textbox } else if (e.key === 'Escape') { suggestionBox.style.display = 'none'; // Hide the suggestion box } }); // Hide the suggestion box when clicking outside document.addEventListener('click', function(e) { if (!suggestionBox.contains(e.target) && e.target !== textbox) { suggestionBox.style.display = 'none'; // Hide if click is outside textbox and suggestion box } }); function setSuggestionBoxWidth(maxWidth) { suggestionBox.style.display = 'block'; // Show the suggestion box suggestionBox.style.width = `${Math.min(maxWidth + 20, 600)}px`; // Set width based on max suggestion width suggestionBox.style.minWidth = '0px'; // Remove minimum width restriction suggestionBox.style.maxWidth = 'none'; // Remove maximum width restriction suggestionBox.offsetWidth; // Force a reflow to apply styles } function formatSuggestion(suggestion) { // Remove popularity info (number in parentheses) from the suggestion const withoutHeat = suggestion.replace(/\s\(\d+\)$/, ''); // Replace underscores with spaces while preserving parentheses content let formatted = withoutHeat.replace(/_/g, ' ').replace(/:/g, ' '); // Escape parentheses formatted = formatted.replace(/\(/g, '\\(').replace(/\)/g, '\\)'); return formatted; } function applySuggestion(promptText) { // Log the prompt text before formatting for debugging //console.log("Debug: promptText before formatting:", promptText); const formattedText = formatSuggestion(promptText[0]); // Format the suggestion text const cursorPosition = textbox.selectionStart; // Get the current cursor position const value = textbox.value; // Get the current textbox value // Split the text around the cursor const beforeCursor = value.slice(0, cursorPosition); const afterCursor = value.slice(cursorPosition); // Find the position of the last comma before the cursor const lastCommaIndex = beforeCursor.lastIndexOf(','); // Determine if a comma is needed after the suggestion const needsComma = afterCursor.trim().length === 0; // Insert the suggestion, replacing the text after the last comma or at the start const newValue = lastCommaIndex >= 0 ? beforeCursor.slice(0, lastCommaIndex + 1) + ` ${formattedText}${needsComma ? ',' : ''}` + afterCursor : `${formattedText}${needsComma ? ',' : ''} ${afterCursor}`; textbox.value = newValue.trim(); // Update the textbox with the new value // Clear the current suggestions and hide the suggestion box currentSuggestions = []; suggestionBox.style.display = 'none'; // Trigger an input event to notify other listeners textbox.dispatchEvent(new Event('input', { bubbles: true })); textbox.focus(); // Refocus the textbox } // Update the position of the suggestion box dynamically function updateSuggestionBoxPosition() { const rect = textbox.getBoundingClientRect(); // Get the textbox's position and size suggestionBox.style.top = `${rect.bottom + window.scrollY}px`; // Position below the textbox const cursorPosition = textbox.selectionStart; // Get the cursor position const textBeforeCursor = textbox.value.substring(0, cursorPosition); // Text before the cursor // Create a temporary span to measure the cursor position const tempSpan = document.createElement('span'); tempSpan.style.position = 'absolute'; tempSpan.style.visibility = 'hidden'; tempSpan.style.font = window.getComputedStyle(textbox).font; // Match the textbox font tempSpan.textContent = textBeforeCursor; document.body.appendChild(tempSpan); // Calculate the offset of the cursor const cursorOffset = tempSpan.offsetWidth; document.body.removeChild(tempSpan); // Remove the temporary span // Set the left position of the suggestion box based on cursor offset let newLeft = rect.left + window.scrollX + cursorOffset; // Prevent the suggestion box from overflowing the right edge of the viewport const suggestionWidth = suggestionBox.offsetWidth; const windowWidth = window.innerWidth; if (newLeft + suggestionWidth > windowWidth) { newLeft = windowWidth - suggestionWidth; } // Prevent the suggestion box from going beyond the left edge if (newLeft < 0) { newLeft = 0; } suggestionBox.style.left = `${newLeft}px`; // Apply the calculated left position // Force a reflow to ensure the position updates suggestionBox.style.transform = 'translateZ(0)'; } // Update the suggestion box position on input textbox.addEventListener('input', function () { updateSuggestionBoxPosition(); }); // Update the suggestion box position on scroll document.addEventListener('scroll', function () { if (suggestionBox.style.display !== 'none') { updateSuggestionBoxPosition(); } }, true); textbox.dataset.suggestionSetup = 'true'; // Mark the textbox as having the suggestion system set up }); } // Log that the script has loaded and attempt initial setup console.log("Auto Tag JS: Script loaded, attempting initial setup"); setupSuggestionSystem(); // Initialize the suggestion system immediately dark_theme(); // Apply the dark theme }