Spaces:
Running
Running
| //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 | |
| } | |