mirabarukaso commited on
Commit
901270b
·
1 Parent(s): e60fe6a

Update Tag Complete JS

Browse files
Files changed (5) hide show
  1. app.py +11 -7
  2. scripts/lib.css +52 -0
  3. scripts/lib.js +373 -0
  4. scripts/lib.py +19 -24
  5. scripts/tag_autocomplete.py +8 -124
app.py CHANGED
@@ -2,10 +2,10 @@ import gradio as gr
2
  import sys
3
  sys.path.append("scripts/")
4
  from lib import init, refresh_character_thumb_image, get_prompt_manager
5
- from lib import JAVA_SCRIPT, CSS_SCRIPT, TITLE
6
 
7
  if __name__ == '__main__':
8
- character_list, character_list_cn, LANG = init()
9
 
10
  with gr.Blocks(js=JAVA_SCRIPT, css=CSS_SCRIPT, title=TITLE) as ui:
11
  with gr.Row():
@@ -30,13 +30,15 @@ if __name__ == '__main__':
30
  allow_custom_value=False,
31
  )
32
 
 
 
 
33
  with gr.Row(elem_classes='main_row'):
34
  with gr.Column(elem_classes='column_prompts'):
35
  thumb_image = gr.Gallery(type="pil", columns=3, show_download_button=False, object_fit='contain', label="Thumb")
36
  with gr.Row():
37
  with gr.Column():
38
- custom_prompt = gr.Textbox(value='', label='Semi-auto tag complete test. Try tag* *tag *tag* (e621_sfw.csv@DominikDoom)')
39
- suggestions = gr.Dataset(components=[custom_prompt], samples_per_page=30, samples=[])
40
  with gr.Column():
41
  gr.Markdown("<h2><span style=\"color:orangered\">Thumb Image create by waiNSFWIllustrious_v120.safetensors with ComfyUI</span></h2>",)
42
  gr.Markdown(f"<a href='https://github.com/mirabarukaso/character_select_stand_alone_app'>Character Select SAA</a>")
@@ -51,7 +53,9 @@ if __name__ == '__main__':
51
  inputs=[character1,character2,character3],
52
  outputs=[thumb_image])
53
 
54
- custom_prompt.change(fn=get_prompt_manager().update_suggestions, inputs=[custom_prompt], outputs=[suggestions])
55
- suggestions.select(fn=get_prompt_manager().apply_suggestion, inputs=[suggestions, custom_prompt], outputs=[custom_prompt, suggestions])
 
 
56
 
57
- ui.launch()
 
2
  import sys
3
  sys.path.append("scripts/")
4
  from lib import init, refresh_character_thumb_image, get_prompt_manager
5
+ from lib import TITLE
6
 
7
  if __name__ == '__main__':
8
+ character_list, character_list_cn, LANG, JAVA_SCRIPT, CSS_SCRIPT = init()
9
 
10
  with gr.Blocks(js=JAVA_SCRIPT, css=CSS_SCRIPT, title=TITLE) as ui:
11
  with gr.Row():
 
30
  allow_custom_value=False,
31
  )
32
 
33
+ dummy_dropdown = gr.Dropdown(visible=False, allow_custom_value=True)
34
+ dummy_textbox = gr.Textbox(visible=False)
35
+
36
  with gr.Row(elem_classes='main_row'):
37
  with gr.Column(elem_classes='column_prompts'):
38
  thumb_image = gr.Gallery(type="pil", columns=3, show_download_button=False, object_fit='contain', label="Thumb")
39
  with gr.Row():
40
  with gr.Column():
41
+ custom_prompt = gr.Textbox(value='', label='Semi-auto tag complete test. Try tag* *tag *tag* (e621_sfw.csv@DominikDoom)', elem_id="custom_prompt_text")
 
42
  with gr.Column():
43
  gr.Markdown("<h2><span style=\"color:orangered\">Thumb Image create by waiNSFWIllustrious_v120.safetensors with ComfyUI</span></h2>",)
44
  gr.Markdown(f"<a href='https://github.com/mirabarukaso/character_select_stand_alone_app'>Character Select SAA</a>")
 
53
  inputs=[character1,character2,character3],
54
  outputs=[thumb_image])
55
 
56
+ # Prompt Auto Complete JS
57
+ # Have to use dummy components
58
+ # Use custom_prompt, the stupid js console will always report "api_info.ts:423 Too many arguments provided for the endpoint."
59
+ dummy_textbox.change(fn=get_prompt_manager().update_suggestions_js, inputs=[dummy_textbox], outputs=[dummy_dropdown])
60
 
61
+ ui.launch(server_name='0.0.0.0')
scripts/lib.css ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #custom_prompt_text textarea {color: darkorange}
2
+ #positive_prompt_text textarea {color: greenyellow}
3
+ #negative_prompt_text textarea {color: red}
4
+ #ai_prompt_text textarea {color: hotpink}
5
+ #prompt_ban_text textarea {color: Khaki}
6
+
7
+ .suggestion-box {
8
+ position: absolute;
9
+ border: 1px solid #888;
10
+ background-color: #333;
11
+ color: #ccc;
12
+ z-index: 1000;
13
+ max-height: 300px;
14
+ overflow-y: auto;
15
+ overflow-x: hidden;
16
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
17
+ padding: 5px;
18
+ border-radius: 5px;
19
+ width: auto !important;
20
+ min-width: 0 !important;
21
+ max-width: none !important;
22
+ pointer-events: auto;
23
+ }
24
+
25
+ .suggestion-box::-webkit-scrollbar {
26
+ width: 10px;
27
+ }
28
+
29
+ .suggestion-box::-webkit-scrollbar-track {
30
+ background: #333;
31
+ border-radius: 5px;
32
+ }
33
+
34
+ .suggestion-box::-webkit-scrollbar-thumb {
35
+ background: linear-gradient(45deg, #555, #888);
36
+ border-radius: 5px;
37
+ }
38
+
39
+ .suggestion-box::-webkit-scrollbar-thumb:hover {
40
+ background: linear-gradient(45deg, #777, #aaa);
41
+ }
42
+
43
+ .suggestion-item {
44
+ padding: 8px 12px;
45
+ cursor: pointer;
46
+ white-space: nowrap;
47
+ pointer-events: auto;
48
+ }
49
+
50
+ .suggestion-item:hover, .suggestion-item.selected {
51
+ background-color: #555;
52
+ }
scripts/lib.js ADDED
@@ -0,0 +1,373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //gosh... this is a mess... I'm sorry... I'll clean it up later... I promise...
2
+ function my_tag_autocomplete() {
3
+ function dark_theme() {
4
+ const url = new URL(window.location);
5
+ if (url.searchParams.get('__theme') !== 'dark') {
6
+ url.searchParams.set('__theme', 'dark');
7
+ window.location.href = url.href;
8
+ }
9
+ }
10
+
11
+ function setupSuggestionSystem() {
12
+ // Select all textarea elements with specific IDs
13
+ const textboxes = document.querySelectorAll(
14
+ '#custom_prompt_text textarea, ' +
15
+ '#positive_prompt_text textarea, ' +
16
+ '#negative_prompt_text textarea, ' +
17
+ '#ai_prompt_text textarea, ' +
18
+ '#prompt_ban_text textarea'
19
+ );
20
+ // Log the number of textboxes found
21
+ //console.log("Found specific textboxes:", textboxes.length);
22
+
23
+ textboxes.forEach(textbox => {
24
+ // Skip if the suggestion system is already set up for this textbox
25
+ if (textbox.dataset.suggestionSetup) return;
26
+
27
+ // Log that the suggestion system is being set up for this textbox
28
+ console.log("Setting up suggestion system for", textbox);
29
+
30
+ let suggestionBox = document.createElement('div');
31
+ suggestionBox.className = 'suggestion-box';
32
+ // Hide the suggestion box initially
33
+ suggestionBox.style.display = 'none';
34
+
35
+ // Append the suggestion box to the body element
36
+ document.body.appendChild(suggestionBox);
37
+
38
+ let selectedIndex = -1; // Index of the currently selected suggestion item
39
+ let currentSuggestions = []; // Array to store the current suggestion items
40
+
41
+ // Handle input events on the textbox
42
+ textbox.addEventListener('input', async function () {
43
+ const value = textbox.value; // Current value of the textbox
44
+ const cursorPosition = textbox.selectionStart; // Current cursor position in the textbox
45
+
46
+ // Extract the word to send for suggestions
47
+ let wordToSend = '';
48
+ if (cursorPosition === value.length) {
49
+ // If cursor is at the end, extract the word after the last comma
50
+ const lastCommaIndex = value.lastIndexOf(',');
51
+ wordToSend = value.slice(lastCommaIndex + 1).trim();
52
+ } else {
53
+ // If cursor is not at the end, extract the word between the nearest commas
54
+ const beforeCursor = value.slice(0, cursorPosition);
55
+ const afterCursor = value.slice(cursorPosition);
56
+
57
+ const lastCommaBeforeCursor = beforeCursor.lastIndexOf(',');
58
+ const firstCommaAfterCursor = afterCursor.indexOf(',');
59
+
60
+ const start = lastCommaBeforeCursor >= 0 ? lastCommaBeforeCursor + 1 : 0; // Start position for word extraction
61
+ const end = firstCommaAfterCursor >= 0 ? cursorPosition + firstCommaAfterCursor : value.length; // End position for word extraction
62
+
63
+ wordToSend = value.slice(start, end).trim();
64
+ }
65
+
66
+ // If no word is extracted, hide the suggestion box and skip the API request
67
+ if (!wordToSend) {
68
+ //console.log("Skipping API request due to empty word.");
69
+ suggestionBox.style.display = 'none';
70
+ return;
71
+ }
72
+
73
+ // Log the word being sent for the initial API request
74
+ //console.log("Sending initial API request with word:", wordToSend);
75
+
76
+ let eventId; // Variable to store the event ID from the API response
77
+ try {
78
+ // Make the first API request to get an event ID
79
+ const initialResponse = await fetch('/gradio_api/call/update_suggestions_js', {
80
+ method: 'POST',
81
+ headers: { 'Content-Type': 'application/json' },
82
+ body: JSON.stringify({
83
+ fn_index: 0,
84
+ data: [wordToSend] // Send the extracted word instead of the full textbox content
85
+ })
86
+ });
87
+
88
+ // Log the status of the initial API response
89
+ //console.log("Initial API response status:", initialResponse.status);
90
+
91
+ // Check if the initial API request failed
92
+ if (!initialResponse.ok) {
93
+ console.error("Initial API request failed:", initialResponse.status, initialResponse.statusText);
94
+ return;
95
+ }
96
+
97
+ const initialResult = await initialResponse.json();
98
+ // Log the data received from the initial API response
99
+ //console.log("Initial API response data:", initialResult);
100
+
101
+ // Extract the event ID from the response
102
+ eventId = initialResult.event_id;
103
+ if (!eventId) {
104
+ console.error("No event_id found in initial API response:", initialResult);
105
+ return;
106
+ }
107
+
108
+ // Log the extracted event ID
109
+ //console.log("Extracted event_id:", eventId);
110
+ } catch (error) {
111
+ // Log any errors that occur during the initial API request
112
+ console.error("Error during initial API request:", error);
113
+ return;
114
+ }
115
+
116
+ let suggestions; // Variable to store the suggestion data
117
+ try {
118
+ // Make the second API request to get suggestion data using the event ID
119
+ const suggestionResponse = await fetch(`/gradio_api/call/update_suggestions_js/${eventId}`, {
120
+ method: 'GET',
121
+ headers: { 'Content-Type': 'application/json' }
122
+ });
123
+
124
+ // Log the status of the suggestion API response
125
+ //console.log("Suggestion API response status:", suggestionResponse.status);
126
+
127
+ // Check if the suggestion API request failed
128
+ if (!suggestionResponse.ok) {
129
+ console.error("Suggestion API request failed:", suggestionResponse.status, suggestionResponse.statusText);
130
+ return;
131
+ }
132
+
133
+ // Log the full suggestion API response object
134
+ //console.log("Suggestion API response object:", suggestionResponse);
135
+
136
+ // Get the raw suggestion data as text
137
+ const rawSuggestions = await suggestionResponse.text();
138
+ // Log the raw suggestion data received
139
+ //console.log("Raw suggestions received:", rawSuggestions);
140
+
141
+ // Parse the Python-formatted list into a JavaScript array
142
+ const lines = rawSuggestions.split('\n'); // Split the response into lines
143
+ let dataLine = lines.find(line => line.startsWith('data:')); // Find the line starting with "data:"
144
+
145
+ if (!dataLine) {
146
+ console.error("No data line found in raw suggestions:", rawSuggestions);
147
+ return;
148
+ }
149
+
150
+ // Remove the "data:" prefix and parse the JSON string into an array
151
+ const jsonString = dataLine.replace('data:', '').trim();
152
+ suggestions = JSON.parse(jsonString);
153
+ // Log the parsed suggestion data
154
+ //console.log("Parsed suggestions:", suggestions);
155
+ } catch (error) {
156
+ // Log any errors that occur during the suggestion API request
157
+ console.error("Error during suggestion API request:", error);
158
+ return;
159
+ }
160
+
161
+ // Clear the suggestion box content
162
+ suggestionBox.innerHTML = '';
163
+ currentSuggestions = []; // Reset the current suggestions array
164
+
165
+ // Check if there are no valid suggestions to display
166
+ if (!suggestions || suggestions.length === 0 || suggestions.every(suggestion => suggestion.length === 0)) {
167
+ //console.log("No suggestions available.");
168
+ suggestionBox.style.display = 'none';
169
+ return;
170
+ }
171
+
172
+ // Calculate the width of the longest suggestion item
173
+ let maxWidth = 0;
174
+ const tempDiv = document.createElement('div'); // Temporary div to measure text width
175
+ tempDiv.style.position = 'absolute';
176
+ tempDiv.style.visibility = 'hidden';
177
+ tempDiv.style.whiteSpace = 'nowrap';
178
+ document.body.appendChild(tempDiv);
179
+
180
+ // Bind click events to suggestion items during input event
181
+ suggestions.forEach((suggestion, index) => {
182
+ if (!Array.isArray(suggestion) || suggestion.length === 0) {
183
+ console.warn(`Invalid suggestion format at index ${index}:`, suggestion);
184
+ return;
185
+ }
186
+ suggestion.forEach(element => {
187
+ const item = document.createElement('div');
188
+ item.className = 'suggestion-item';
189
+ item.textContent = element;
190
+ item.dataset.value = element;
191
+ tempDiv.textContent = element;
192
+ maxWidth = Math.max(maxWidth, tempDiv.offsetWidth);
193
+ currentSuggestions.push({ prompt: element });
194
+ item.addEventListener('click', () => applySuggestion(element));
195
+ suggestionBox.appendChild(item);
196
+ });
197
+ });
198
+
199
+ // Remove the temporary div after measuring
200
+ document.body.removeChild(tempDiv);
201
+
202
+ // Update the suggestion box position if it is already visible
203
+ if (suggestionBox.style.display !== 'none') {
204
+ updateSuggestionBoxPosition();
205
+ }
206
+
207
+ // Set the width of the suggestion box
208
+ setSuggestionBoxWidth(maxWidth);
209
+
210
+ // Log the set width of the suggestion box
211
+ //console.log("Set suggestionBox width:", suggestionBox.style.width);
212
+ // Log the actual rendered width of the suggestion box
213
+ //console.log("Actual suggestionBox width:", suggestionBox.offsetWidth);
214
+
215
+ selectedIndex = -1; // Reset the selected index
216
+
217
+ // Log that the suggestions have been successfully displayed
218
+ //console.log("Suggestions successfully displayed.");
219
+ });
220
+
221
+ // Handle keyboard navigation for the suggestion box
222
+ textbox.addEventListener('keydown', function (e) {
223
+ if (suggestionBox.style.display === 'none')
224
+ return; // Exit if the suggestion box is not visible
225
+
226
+ const items = suggestionBox.querySelectorAll('.suggestion-item');
227
+ if (items.length === 0) return; // Exit if there are no suggestion items
228
+
229
+ if (e.key === 'Tab' || e.key === 'Enter') {
230
+ e.preventDefault(); // Prevent default behavior
231
+ if (selectedIndex >= 0 && selectedIndex < currentSuggestions.length) {
232
+ applySuggestion(currentSuggestions[selectedIndex].prompt); // Apply the selected suggestion
233
+ } else if (items.length > 0) {
234
+ applySuggestion(currentSuggestions[0].prompt); // Apply the first suggestion if none selected
235
+ }
236
+ } else if (e.key === 'ArrowDown') {
237
+ e.preventDefault(); // Prevent default scrolling
238
+ selectedIndex = Math.min(selectedIndex + 1, items.length - 1); // Move selection down
239
+ items.forEach((item, idx) => item.classList.toggle('selected', idx === selectedIndex));
240
+ if (selectedIndex >= 0) items[selectedIndex].scrollIntoView({ block: 'nearest' });
241
+ textbox.focus(); // Keep focus on the textbox
242
+ } else if (e.key === 'ArrowUp') {
243
+ e.preventDefault(); // Prevent default scrolling
244
+ selectedIndex = Math.max(selectedIndex - 1, 0); // Move selection up
245
+ items.forEach((item, idx) => item.classList.toggle('selected', idx === selectedIndex));
246
+ if (selectedIndex >= 0) items[selectedIndex].scrollIntoView({ block: 'nearest' });
247
+ textbox.focus(); // Keep focus on the textbox
248
+ } else if (e.key === 'Escape') {
249
+ suggestionBox.style.display = 'none'; // Hide the suggestion box
250
+ }
251
+ });
252
+
253
+ // Hide the suggestion box when clicking outside
254
+ document.addEventListener('click', function(e) {
255
+ if (!suggestionBox.contains(e.target) && e.target !== textbox) {
256
+ suggestionBox.style.display = 'none'; // Hide if click is outside textbox and suggestion box
257
+ }
258
+ });
259
+
260
+ function setSuggestionBoxWidth(maxWidth) {
261
+ suggestionBox.style.display = 'block'; // Show the suggestion box
262
+ suggestionBox.style.width = `${Math.min(maxWidth + 20, 600)}px`; // Set width based on max suggestion width
263
+ suggestionBox.style.minWidth = '0px'; // Remove minimum width restriction
264
+ suggestionBox.style.maxWidth = 'none'; // Remove maximum width restriction
265
+ suggestionBox.offsetWidth; // Force a reflow to apply styles
266
+ }
267
+
268
+ function formatSuggestion(suggestion) {
269
+ // Remove popularity info (number in parentheses) from the suggestion
270
+ const withoutHeat = suggestion.replace(/\s\(\d+\)$/, '');
271
+
272
+ // Replace underscores with spaces while preserving parentheses content
273
+ let formatted = withoutHeat.replace(/_/g, ' ').replace(/:/g, ' ');
274
+
275
+ // Escape parentheses
276
+ formatted = formatted.replace(/\(/g, '\\(').replace(/\)/g, '\\)');
277
+
278
+ return formatted;
279
+ }
280
+
281
+ function applySuggestion(promptText) {
282
+ // Log the prompt text before formatting for debugging
283
+ //console.log("Debug: promptText before formatting:", promptText);
284
+ const formattedText = formatSuggestion(promptText[0]); // Format the suggestion text
285
+ const cursorPosition = textbox.selectionStart; // Get the current cursor position
286
+ const value = textbox.value; // Get the current textbox value
287
+
288
+ // Split the text around the cursor
289
+ const beforeCursor = value.slice(0, cursorPosition);
290
+ const afterCursor = value.slice(cursorPosition);
291
+
292
+ // Find the position of the last comma before the cursor
293
+ const lastCommaIndex = beforeCursor.lastIndexOf(',');
294
+
295
+ // Determine if a comma is needed after the suggestion
296
+ const needsComma = afterCursor.trim().length === 0;
297
+
298
+ // Insert the suggestion, replacing the text after the last comma or at the start
299
+ const newValue = lastCommaIndex >= 0
300
+ ? beforeCursor.slice(0, lastCommaIndex + 1) + ` ${formattedText}${needsComma ? ',' : ''}` + afterCursor
301
+ : `${formattedText}${needsComma ? ',' : ''} ${afterCursor}`;
302
+
303
+ textbox.value = newValue.trim(); // Update the textbox with the new value
304
+
305
+ // Clear the current suggestions and hide the suggestion box
306
+ currentSuggestions = [];
307
+ suggestionBox.style.display = 'none';
308
+
309
+ // Trigger an input event to notify other listeners
310
+ textbox.dispatchEvent(new Event('input', { bubbles: true }));
311
+ textbox.focus(); // Refocus the textbox
312
+ }
313
+
314
+ // Update the position of the suggestion box dynamically
315
+ function updateSuggestionBoxPosition() {
316
+ const rect = textbox.getBoundingClientRect(); // Get the textbox's position and size
317
+ suggestionBox.style.top = `${rect.bottom + window.scrollY}px`; // Position below the textbox
318
+
319
+ const cursorPosition = textbox.selectionStart; // Get the cursor position
320
+ const textBeforeCursor = textbox.value.substring(0, cursorPosition); // Text before the cursor
321
+
322
+ // Create a temporary span to measure the cursor position
323
+ const tempSpan = document.createElement('span');
324
+ tempSpan.style.position = 'absolute';
325
+ tempSpan.style.visibility = 'hidden';
326
+ tempSpan.style.font = window.getComputedStyle(textbox).font; // Match the textbox font
327
+ tempSpan.textContent = textBeforeCursor;
328
+ document.body.appendChild(tempSpan);
329
+
330
+ // Calculate the offset of the cursor
331
+ const cursorOffset = tempSpan.offsetWidth;
332
+ document.body.removeChild(tempSpan); // Remove the temporary span
333
+
334
+ // Set the left position of the suggestion box based on cursor offset
335
+ let newLeft = rect.left + window.scrollX + cursorOffset;
336
+
337
+ // Prevent the suggestion box from overflowing the right edge of the viewport
338
+ const suggestionWidth = suggestionBox.offsetWidth;
339
+ const windowWidth = window.innerWidth;
340
+ if (newLeft + suggestionWidth > windowWidth) {
341
+ newLeft = windowWidth - suggestionWidth;
342
+ }
343
+ // Prevent the suggestion box from going beyond the left edge
344
+ if (newLeft < 0) {
345
+ newLeft = 0;
346
+ }
347
+
348
+ suggestionBox.style.left = `${newLeft}px`; // Apply the calculated left position
349
+
350
+ // Force a reflow to ensure the position updates
351
+ suggestionBox.style.transform = 'translateZ(0)';
352
+ }
353
+
354
+ // Update the suggestion box position on input
355
+ textbox.addEventListener('input', function () {
356
+ updateSuggestionBoxPosition();
357
+ });
358
+ // Update the suggestion box position on scroll
359
+ document.addEventListener('scroll', function () {
360
+ if (suggestionBox.style.display !== 'none') {
361
+ updateSuggestionBoxPosition();
362
+ }
363
+ }, true);
364
+
365
+ textbox.dataset.suggestionSetup = 'true'; // Mark the textbox as having the suggestion system set up
366
+ });
367
+ }
368
+
369
+ // Log that the script has loaded and attempt initial setup
370
+ console.log("Auto Tag JS: Script loaded, attempting initial setup");
371
+ setupSuggestionSystem(); // Initialize the suggestion system immediately
372
+ dark_theme(); // Apply the dark theme
373
+ }
scripts/lib.py CHANGED
@@ -20,27 +20,6 @@ LANG_EN = {
20
 
21
  LANG = LANG_EN
22
 
23
- # JavaScript
24
- JAVA_SCRIPT = """
25
- function refresh() {
26
- const url = new URL(window.location);
27
-
28
- if (url.searchParams.get('__theme') !== 'dark') {
29
- url.searchParams.set('__theme', 'dark');
30
- window.location.href = url.href;
31
- }
32
- }
33
- """
34
-
35
- # CSS
36
- CSS_SCRIPT = """
37
- #custom_prompt_text textarea {color: darkorange}
38
- #positive_prompt_text textarea {color: greenyellow}
39
- #negative_prompt_text textarea {color: red}
40
- #ai_prompt_text textarea {color: hotpink}
41
- #prompt_ban_text textarea {color: Khaki}
42
- """
43
-
44
  TITLE = "WAI Character Select Preview"
45
  CAT = "WAI_Character_Select"
46
  ENGLISH_CHARACTER_NAME = True
@@ -77,6 +56,17 @@ def download_file(url, file_path):
77
  print(f'[{CAT}]:Downloading... {url}')
78
  with open(file_path, 'wb') as file:
79
  file.write(response.content)
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  def load_jsons():
82
  global character_list
@@ -165,8 +155,13 @@ def refresh_character_thumb_image(character1, character2, character3):
165
  def get_prompt_manager():
166
  return PROMPT_MANAGER
167
 
168
- def init():
169
- load_jsons()
 
 
 
 
 
170
  print(f'[{CAT}]:Starting...')
171
 
172
- return character_list, character_list_cn, LANG
 
20
 
21
  LANG = LANG_EN
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  TITLE = "WAI Character Select Preview"
24
  CAT = "WAI_Character_Select"
25
  ENGLISH_CHARACTER_NAME = True
 
56
  print(f'[{CAT}]:Downloading... {url}')
57
  with open(file_path, 'wb') as file:
58
  file.write(response.content)
59
+
60
+ def load_text_file(file_path):
61
+ raw_text = ''
62
+ if os.path.exists(file_path):
63
+ print(f'[{CAT}]:Loading {file_path}')
64
+ with open(file_path, 'r', encoding='utf-8') as js_file:
65
+ raw_text = js_file.read()
66
+ else:
67
+ print(f"[{CAT}] ERROR: {file_path} file missing!!!")
68
+
69
+ return raw_text
70
 
71
  def load_jsons():
72
  global character_list
 
155
  def get_prompt_manager():
156
  return PROMPT_MANAGER
157
 
158
+ def init():
159
+ lib_js_path = os.path.join(current_dir, 'lib.js')
160
+ lib_css_path = os.path.join(current_dir, 'lib.css')
161
+
162
+ load_jsons()
163
+ js_script = load_text_file(lib_js_path)
164
+ css_script = load_text_file(lib_css_path)
165
  print(f'[{CAT}]:Starting...')
166
 
167
+ return character_list, character_list_cn, LANG, js_script, css_script
scripts/tag_autocomplete.py CHANGED
@@ -16,8 +16,6 @@ class PromptManager:
16
  # save user input to keep track of changes
17
  self.last_custom_prompt = ""
18
  self.previous_custom_prompt = ""
19
- # flag for just applied suggestion
20
- self.just_applied_suggestion = False
21
  # flag for data loaded
22
  self.data_loaded = False
23
 
@@ -118,84 +116,25 @@ class PromptManager:
118
  if prompt not in matches or prompt_info['heat'] > matches[prompt]['heat']:
119
  matches[prompt] = {'prompt': prompt_info['prompt'], 'heat': prompt_info['heat']}
120
 
121
- # we only need 12 items
122
- if len(matches) == 12:
123
  break
124
 
125
  sorted_matches = sorted(matches.values(), key=lambda x: x['heat'], reverse=True)
126
  return sorted_matches
127
 
128
- def process_parts(self, parts):
129
- """
130
- Process each part of the prompt, handling ':' and '_'.
131
- """
132
- processed_parts = []
133
- for part in parts:
134
- # Remove extra spaces
135
- part = part.strip()
136
-
137
- # If the part starts with ':', keep it as is
138
- if part.startswith(':'):
139
- processed_parts.append(part)
140
- continue
141
-
142
- # If the part is enclosed in parentheses, process the content inside
143
- if part.startswith('(') and part.endswith(')'):
144
- # Remove parentheses and split the content by commas
145
- inner_content = part[1:-1]
146
- inner_parts = inner_content.split(',')
147
- # Recursively process the parts inside the parentheses
148
- processed_inner_parts = self.process_parts(inner_parts)
149
- # Rejoin the processed parts and re-enclose them in parentheses
150
- processed_parts.append(f"({', '.join(processed_inner_parts)})")
151
- continue
152
-
153
- # If the part contains ':', handle it based on the content after ':'
154
- if ':' in part:
155
- # Match the content before and after ':'
156
- match = re.match(r'^(.*?):([-+]?\d*\.?\d+)$', part)
157
- if match:
158
- prefix, number = match.groups()
159
- try:
160
- # Try to convert the number to a float
161
- number = float(number)
162
- # If the number is an integer, convert it to an integer
163
- if number.is_integer():
164
- number = int(number)
165
- # If the absolute value of the number is greater than 10, remove ':'
166
- if abs(number) > 10:
167
- processed_parts.append(f"{prefix} {number}")
168
- else:
169
- # If the absolute value is less than or equal to 10, keep ':'
170
- processed_parts.append(part)
171
- except ValueError:
172
- # If conversion fails, replace ':' with a space
173
- processed_parts.append(part.replace(':', ' '))
174
- else:
175
- # If the content after ':' does not match the pattern, replace ':' with a space
176
- processed_parts.append(part.replace(':', ' '))
177
- else:
178
- # If the part does not contain ':', add it directly
179
- processed_parts.append(part)
180
- return processed_parts
181
-
182
- def update_suggestions(self, text):
183
  """
184
  Update suggestions based on the current input and update global variables.
185
  """
 
 
186
  # If data is not loaded, return an empty dataset
187
  if not self.data_loaded:
188
  print(f"[{CAT}] No data loaded. Returning empty dataset.")
189
- return gr.Dataset(samples=[])
190
 
191
- # If apply_suggestion was just executed, return an empty dataset
192
- if self.just_applied_suggestion:
193
- # Reset the flag
194
- self.just_applied_suggestion = False
195
- return gr.Dataset(samples=[])
196
-
197
- matches = []
198
- items = []
199
 
200
  # Split the text by commas
201
  current_parts = text.split(',') if text else []
@@ -237,60 +176,5 @@ class PromptManager:
237
  if target_word is not None:
238
  print(f"Suggestions for '{target_word}': {items}")
239
  """
240
- return gr.Dataset(samples=items)
241
-
242
- def apply_suggestion(self, evt: gr.SelectData, text, custom_prompt):
243
- """
244
- Apply the suggestion selected by the user to the prompt and clear the prompt box.
245
- """
246
- # If data is not loaded, return the original prompt and an empty dataset
247
- if not self.data_loaded:
248
- print(f"[{CAT}] No data loaded. Cannot apply suggestion.")
249
- return custom_prompt, gr.Dataset(samples=[])
250
-
251
- # Check the type of evt.value
252
- if isinstance(evt.value, list):
253
- suggestion = evt.value[0]
254
- elif isinstance(evt.value, str):
255
- suggestion = evt.value
256
- else:
257
- # Should not reach here
258
- raise ValueError(f"Unexpected value type: {type(evt.value)}. Expected a string or list.")
259
-
260
- # Get the suggestion content
261
- suggestion = suggestion.split(' ')[0]
262
- if not suggestion:
263
- return custom_prompt, gr.Dataset(samples=[])
264
-
265
- # Split custom_prompt by commas
266
- parts = custom_prompt.split(',') if custom_prompt else []
267
- previous_parts = self.previous_custom_prompt.split(',') if self.previous_custom_prompt else []
268
-
269
- # Locate the position of the word modified by the user
270
- modified_index = -1
271
- for i, (current, previous) in enumerate(zip(parts, previous_parts)):
272
- if current.strip() != previous.strip():
273
- modified_index = i
274
- break
275
-
276
- # If no modified word is found and the current input is longer than the previous input, set the modified index to the last index
277
- if modified_index == -1 and len(parts) > len(previous_parts):
278
- modified_index = len(parts) - 1
279
-
280
- # If the modified word is not found, set the modified index to the last index
281
- if modified_index < 0 or modified_index >= len(parts):
282
- modified_index = len(parts) - 1
283
-
284
- # Replace the modified word with the suggestion
285
- parts[modified_index] = suggestion
286
-
287
- # Update the global variables
288
- self.previous_custom_prompt = self.last_custom_prompt
289
- self.last_custom_prompt = ', '.join(self.process_parts(parts)).replace('_', ' ')
290
-
291
- # Set just_applied_suggestion to True
292
- self.just_applied_suggestion = True
293
-
294
- # Return the updated prompt and an empty dataset
295
- return self.last_custom_prompt, gr.Dataset(samples=[])
296
 
 
16
  # save user input to keep track of changes
17
  self.last_custom_prompt = ""
18
  self.previous_custom_prompt = ""
 
 
19
  # flag for data loaded
20
  self.data_loaded = False
21
 
 
116
  if prompt not in matches or prompt_info['heat'] > matches[prompt]['heat']:
117
  matches[prompt] = {'prompt': prompt_info['prompt'], 'heat': prompt_info['heat']}
118
 
119
+ # we only need 50 items
120
+ if len(matches) == 50:
121
  break
122
 
123
  sorted_matches = sorted(matches.values(), key=lambda x: x['heat'], reverse=True)
124
  return sorted_matches
125
 
126
+ def update_suggestions_js(self, text):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  """
128
  Update suggestions based on the current input and update global variables.
129
  """
130
+ items = []
131
+
132
  # If data is not loaded, return an empty dataset
133
  if not self.data_loaded:
134
  print(f"[{CAT}] No data loaded. Returning empty dataset.")
135
+ return items
136
 
137
+ matches = []
 
 
 
 
 
 
 
138
 
139
  # Split the text by commas
140
  current_parts = text.split(',') if text else []
 
176
  if target_word is not None:
177
  print(f"Suggestions for '{target_word}': {items}")
178
  """
179
+ return items
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180