File size: 20,058 Bytes
901270b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
//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    
}