local-deep-research 0.1.26__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. local_deep_research/__init__.py +23 -22
  2. local_deep_research/__main__.py +16 -0
  3. local_deep_research/advanced_search_system/__init__.py +7 -0
  4. local_deep_research/advanced_search_system/filters/__init__.py +8 -0
  5. local_deep_research/advanced_search_system/filters/base_filter.py +38 -0
  6. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +200 -0
  7. local_deep_research/advanced_search_system/findings/base_findings.py +81 -0
  8. local_deep_research/advanced_search_system/findings/repository.py +452 -0
  9. local_deep_research/advanced_search_system/knowledge/__init__.py +1 -0
  10. local_deep_research/advanced_search_system/knowledge/base_knowledge.py +151 -0
  11. local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +159 -0
  12. local_deep_research/advanced_search_system/questions/__init__.py +1 -0
  13. local_deep_research/advanced_search_system/questions/base_question.py +64 -0
  14. local_deep_research/advanced_search_system/questions/decomposition_question.py +445 -0
  15. local_deep_research/advanced_search_system/questions/standard_question.py +119 -0
  16. local_deep_research/advanced_search_system/repositories/__init__.py +7 -0
  17. local_deep_research/advanced_search_system/strategies/__init__.py +1 -0
  18. local_deep_research/advanced_search_system/strategies/base_strategy.py +118 -0
  19. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +450 -0
  20. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +312 -0
  21. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +270 -0
  22. local_deep_research/advanced_search_system/strategies/standard_strategy.py +300 -0
  23. local_deep_research/advanced_search_system/tools/__init__.py +1 -0
  24. local_deep_research/advanced_search_system/tools/base_tool.py +100 -0
  25. local_deep_research/advanced_search_system/tools/knowledge_tools/__init__.py +1 -0
  26. local_deep_research/advanced_search_system/tools/question_tools/__init__.py +1 -0
  27. local_deep_research/advanced_search_system/tools/search_tools/__init__.py +1 -0
  28. local_deep_research/api/__init__.py +5 -5
  29. local_deep_research/api/research_functions.py +96 -84
  30. local_deep_research/app.py +8 -0
  31. local_deep_research/citation_handler.py +25 -16
  32. local_deep_research/{config.py → config/config_files.py} +102 -110
  33. local_deep_research/config/llm_config.py +472 -0
  34. local_deep_research/config/search_config.py +77 -0
  35. local_deep_research/defaults/__init__.py +10 -5
  36. local_deep_research/defaults/main.toml +2 -2
  37. local_deep_research/defaults/search_engines.toml +60 -34
  38. local_deep_research/main.py +121 -19
  39. local_deep_research/migrate_db.py +147 -0
  40. local_deep_research/report_generator.py +72 -44
  41. local_deep_research/search_system.py +147 -283
  42. local_deep_research/setup_data_dir.py +35 -0
  43. local_deep_research/test_migration.py +178 -0
  44. local_deep_research/utilities/__init__.py +0 -0
  45. local_deep_research/utilities/db_utils.py +49 -0
  46. local_deep_research/{utilties → utilities}/enums.py +2 -2
  47. local_deep_research/{utilties → utilities}/llm_utils.py +63 -29
  48. local_deep_research/utilities/search_utilities.py +242 -0
  49. local_deep_research/{utilties → utilities}/setup_utils.py +4 -2
  50. local_deep_research/web/__init__.py +0 -1
  51. local_deep_research/web/app.py +86 -1709
  52. local_deep_research/web/app_factory.py +289 -0
  53. local_deep_research/web/database/README.md +70 -0
  54. local_deep_research/web/database/migrate_to_ldr_db.py +289 -0
  55. local_deep_research/web/database/migrations.py +447 -0
  56. local_deep_research/web/database/models.py +117 -0
  57. local_deep_research/web/database/schema_upgrade.py +107 -0
  58. local_deep_research/web/models/database.py +294 -0
  59. local_deep_research/web/models/settings.py +94 -0
  60. local_deep_research/web/routes/api_routes.py +559 -0
  61. local_deep_research/web/routes/history_routes.py +354 -0
  62. local_deep_research/web/routes/research_routes.py +715 -0
  63. local_deep_research/web/routes/settings_routes.py +1592 -0
  64. local_deep_research/web/services/research_service.py +947 -0
  65. local_deep_research/web/services/resource_service.py +149 -0
  66. local_deep_research/web/services/settings_manager.py +669 -0
  67. local_deep_research/web/services/settings_service.py +187 -0
  68. local_deep_research/web/services/socket_service.py +210 -0
  69. local_deep_research/web/static/css/custom_dropdown.css +277 -0
  70. local_deep_research/web/static/css/settings.css +1223 -0
  71. local_deep_research/web/static/css/styles.css +525 -48
  72. local_deep_research/web/static/js/components/custom_dropdown.js +428 -0
  73. local_deep_research/web/static/js/components/detail.js +348 -0
  74. local_deep_research/web/static/js/components/fallback/formatting.js +122 -0
  75. local_deep_research/web/static/js/components/fallback/ui.js +215 -0
  76. local_deep_research/web/static/js/components/history.js +487 -0
  77. local_deep_research/web/static/js/components/logpanel.js +949 -0
  78. local_deep_research/web/static/js/components/progress.js +1107 -0
  79. local_deep_research/web/static/js/components/research.js +1865 -0
  80. local_deep_research/web/static/js/components/results.js +766 -0
  81. local_deep_research/web/static/js/components/settings.js +3981 -0
  82. local_deep_research/web/static/js/components/settings_sync.js +106 -0
  83. local_deep_research/web/static/js/main.js +226 -0
  84. local_deep_research/web/static/js/services/api.js +253 -0
  85. local_deep_research/web/static/js/services/audio.js +31 -0
  86. local_deep_research/web/static/js/services/formatting.js +119 -0
  87. local_deep_research/web/static/js/services/pdf.js +622 -0
  88. local_deep_research/web/static/js/services/socket.js +882 -0
  89. local_deep_research/web/static/js/services/ui.js +546 -0
  90. local_deep_research/web/templates/base.html +72 -0
  91. local_deep_research/web/templates/components/custom_dropdown.html +47 -0
  92. local_deep_research/web/templates/components/log_panel.html +32 -0
  93. local_deep_research/web/templates/components/mobile_nav.html +22 -0
  94. local_deep_research/web/templates/components/settings_form.html +299 -0
  95. local_deep_research/web/templates/components/sidebar.html +21 -0
  96. local_deep_research/web/templates/pages/details.html +73 -0
  97. local_deep_research/web/templates/pages/history.html +51 -0
  98. local_deep_research/web/templates/pages/progress.html +57 -0
  99. local_deep_research/web/templates/pages/research.html +139 -0
  100. local_deep_research/web/templates/pages/results.html +59 -0
  101. local_deep_research/web/templates/settings_dashboard.html +78 -192
  102. local_deep_research/web/utils/__init__.py +0 -0
  103. local_deep_research/web/utils/formatters.py +76 -0
  104. local_deep_research/web_search_engines/engines/full_search.py +18 -16
  105. local_deep_research/web_search_engines/engines/meta_search_engine.py +182 -131
  106. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +224 -139
  107. local_deep_research/web_search_engines/engines/search_engine_brave.py +88 -71
  108. local_deep_research/web_search_engines/engines/search_engine_ddg.py +48 -39
  109. local_deep_research/web_search_engines/engines/search_engine_github.py +415 -204
  110. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +123 -90
  111. local_deep_research/web_search_engines/engines/search_engine_guardian.py +210 -157
  112. local_deep_research/web_search_engines/engines/search_engine_local.py +532 -369
  113. local_deep_research/web_search_engines/engines/search_engine_local_all.py +42 -36
  114. local_deep_research/web_search_engines/engines/search_engine_pubmed.py +358 -266
  115. local_deep_research/web_search_engines/engines/search_engine_searxng.py +211 -159
  116. local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +213 -170
  117. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +84 -68
  118. local_deep_research/web_search_engines/engines/search_engine_wayback.py +186 -154
  119. local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +115 -77
  120. local_deep_research/web_search_engines/search_engine_base.py +174 -99
  121. local_deep_research/web_search_engines/search_engine_factory.py +192 -102
  122. local_deep_research/web_search_engines/search_engines_config.py +22 -15
  123. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.0.dist-info}/METADATA +177 -97
  124. local_deep_research-0.2.0.dist-info/RECORD +135 -0
  125. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.0.dist-info}/WHEEL +1 -2
  126. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.0.dist-info}/entry_points.txt +3 -0
  127. local_deep_research/defaults/llm_config.py +0 -338
  128. local_deep_research/utilties/search_utilities.py +0 -114
  129. local_deep_research/web/static/js/app.js +0 -3763
  130. local_deep_research/web/templates/api_keys_config.html +0 -82
  131. local_deep_research/web/templates/collections_config.html +0 -90
  132. local_deep_research/web/templates/index.html +0 -348
  133. local_deep_research/web/templates/llm_config.html +0 -120
  134. local_deep_research/web/templates/main_config.html +0 -89
  135. local_deep_research/web/templates/search_engines_config.html +0 -154
  136. local_deep_research/web/templates/settings.html +0 -519
  137. local_deep_research-0.1.26.dist-info/RECORD +0 -61
  138. local_deep_research-0.1.26.dist-info/top_level.txt +0 -1
  139. /local_deep_research/{utilties → config}/__init__.py +0 -0
  140. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Custom Dropdown Component
3
+ *
4
+ * This module provides functionality for custom dropdown menus with filtering and keyboard navigation.
5
+ * It can be used across the application for consistent dropdown behavior.
6
+ */
7
+ (function() {
8
+ 'use strict';
9
+
10
+ // Make the setupCustomDropdown function available globally
11
+ window.setupCustomDropdown = setupCustomDropdown;
12
+
13
+ // Also export the updateDropdownOptions function
14
+ window.updateDropdownOptions = updateDropdownOptions;
15
+
16
+ // Keep a registry of inputs and their associated options functions
17
+ const dropdownRegistry = {};
18
+
19
+ /**
20
+ * Update the options for an existing dropdown without destroying it
21
+ * @param {HTMLElement} input - The input element
22
+ * @param {Array} newOptions - New options array to use [{value: string, label: string}]
23
+ */
24
+ function updateDropdownOptions(input, newOptions) {
25
+ if (!input || !input.id) {
26
+ console.warn('Cannot update dropdown: Invalid input element');
27
+ return;
28
+ }
29
+
30
+ // Check if dropdown is registered
31
+ if (!dropdownRegistry[input.id]) {
32
+ console.warn(`Dropdown ${input.id} not found in registry, unable to update options`);
33
+ return;
34
+ }
35
+
36
+ const dropdownInfo = dropdownRegistry[input.id];
37
+
38
+ // Update the options getter function to return new options
39
+ dropdownInfo.getOptions = () => newOptions;
40
+
41
+ // If dropdown is currently open, update its content
42
+ const dropdownList = document.getElementById(`${dropdownInfo.dropdownId}-list`);
43
+ if (dropdownList && window.getComputedStyle(dropdownList).display !== 'none') {
44
+ console.log(`Dropdown ${input.id} is open, updating content in place`);
45
+
46
+ // Save scroll position
47
+ const scrollPos = dropdownList.scrollTop;
48
+
49
+ // Update dropdown content
50
+ const filteredData = dropdownInfo.getOptions();
51
+ dropdownList.innerHTML = '';
52
+
53
+ if (filteredData.length === 0) {
54
+ dropdownList.innerHTML = `<div class="custom-dropdown-no-results">${dropdownInfo.noResultsText}</div>`;
55
+ return;
56
+ }
57
+
58
+ filteredData.forEach((item, index) => {
59
+ const div = document.createElement('div');
60
+ div.className = 'custom-dropdown-item';
61
+ div.innerHTML = item.label;
62
+ div.setAttribute('data-value', item.value);
63
+ div.addEventListener('click', () => {
64
+ // Set display value
65
+ input.value = item.label;
66
+ // Update hidden input if exists
67
+ const hiddenInput = document.getElementById(`${input.id}_hidden`);
68
+ if (hiddenInput) {
69
+ hiddenInput.value = item.value;
70
+ hiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
71
+ }
72
+ // Call original onSelect callback
73
+ if (dropdownInfo.onSelect) {
74
+ dropdownInfo.onSelect(item.value, item);
75
+ }
76
+ // Hide dropdown
77
+ dropdownList.style.display = 'none';
78
+ });
79
+
80
+ dropdownList.appendChild(div);
81
+ });
82
+
83
+ // Restore scroll position
84
+ dropdownList.scrollTop = scrollPos;
85
+ } else {
86
+ console.log(`Dropdown ${input.id} is closed, options will update when opened`);
87
+ }
88
+
89
+ return true;
90
+ }
91
+
92
+ /**
93
+ * Setup a custom dropdown component
94
+ * @param {HTMLElement} input - The input element
95
+ * @param {HTMLElement} dropdownList - The dropdown list element
96
+ * @param {Function} getOptions - Function that returns the current options array [{value: string, label: string}]
97
+ * @param {Function} onSelect - Callback when an item is selected (value, item) => {}
98
+ * @param {boolean} allowCustomValues - Whether to allow values not in the options list
99
+ * @param {string} noResultsText - Text to show when no results are found
100
+ */
101
+ function setupCustomDropdown(input, dropdownList, getOptions, onSelect, allowCustomValues = false, noResultsText = 'No results found.') {
102
+ let selectedIndex = -1;
103
+ let isOpen = false;
104
+ let showAllOptions = false; // Flag to track if we should show all options
105
+
106
+ // Find the associated hidden input field
107
+ const hiddenInput = document.getElementById(`${input.id}_hidden`);
108
+
109
+ // Function to update hidden field
110
+ function updateHiddenField(value) {
111
+ if (hiddenInput) {
112
+ hiddenInput.value = value;
113
+ // Also dispatch a change event on the hidden input to trigger form handling
114
+ hiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
115
+ }
116
+ }
117
+
118
+ // Function to filter options
119
+ function filterOptions(searchText, showAll = false) {
120
+ const options = getOptions();
121
+ if (showAll || !searchText.trim()) return options;
122
+
123
+ return options.filter(item =>
124
+ item.label.toLowerCase().includes(searchText.toLowerCase()) ||
125
+ item.value.toLowerCase().includes(searchText.toLowerCase())
126
+ );
127
+ }
128
+
129
+ // Function to highlight matched text
130
+ function highlightText(text, search) {
131
+ if (!search.trim() || showAllOptions) return text;
132
+ const regex = new RegExp(`(${search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
133
+ return text.replace(regex, '<span class="highlight">$1</span>');
134
+ }
135
+
136
+ // Function to show the dropdown
137
+ function showDropdown() {
138
+ const inputRect = input.getBoundingClientRect();
139
+
140
+ // Debug logging
141
+ console.log('Dropdown positioning:', {
142
+ inputLeft: inputRect.left,
143
+ inputBottom: inputRect.bottom,
144
+ scrollY: window.scrollY,
145
+ inputWidth: inputRect.width,
146
+ windowWidth: window.innerWidth,
147
+ windowHeight: window.innerHeight
148
+ });
149
+
150
+ // Store original parent for when we close
151
+ if (!dropdownList._originalParent) {
152
+ dropdownList._originalParent = dropdownList.parentNode;
153
+ }
154
+
155
+ // Remove from current parent
156
+ if (dropdownList.parentNode) {
157
+ dropdownList.parentNode.removeChild(dropdownList);
158
+ }
159
+
160
+ // Append directly to body
161
+ document.body.appendChild(dropdownList);
162
+
163
+ // Make dropdown visible
164
+ dropdownList.style.display = 'block';
165
+
166
+ // Add active class for dropdown.
167
+ dropdownList.classList.add('dropdown-active');
168
+
169
+ // Add dropdown-active class to body
170
+ document.body.classList.add('dropdown-active');
171
+
172
+ // Add a small offset (6px) to ensure it's visibly separated from the input
173
+ const verticalOffset = 6;
174
+
175
+ // Calculate position relative to viewport
176
+ const left = Math.min(inputRect.left, window.innerWidth - inputRect.width - 10);
177
+ const top = inputRect.bottom + window.scrollY + verticalOffset;
178
+
179
+ // Apply the calculated position
180
+ dropdownList.style.left = `${left}px`;
181
+ dropdownList.style.top = `${top}px`;
182
+ dropdownList.style.width = `${inputRect.width}px`;
183
+
184
+ // Force DOM reflow to ensure styles are applied
185
+ dropdownList.getBoundingClientRect();
186
+
187
+ // Additional styling to ensure visibility
188
+ dropdownList.style.visibility = 'visible';
189
+ dropdownList.style.opacity = '1';
190
+
191
+ // Ensure dropdown is visible by bringing it to front
192
+ dropdownList.style.zIndex = '999999';
193
+
194
+ input.setAttribute('aria-expanded', 'true');
195
+ isOpen = true;
196
+ }
197
+
198
+ // Function to hide the dropdown
199
+ function hideDropdown() {
200
+ // Get current parent
201
+ const currentParent = dropdownList.parentNode;
202
+
203
+ // Hide first
204
+ dropdownList.style.display = 'none';
205
+ // Remove active class
206
+ dropdownList.classList.remove('dropdown-active');
207
+
208
+ // Remove dropdown-active class from body
209
+ document.body.classList.remove('dropdown-active');
210
+
211
+ // Reset position styles
212
+ dropdownList.style.left = '';
213
+ dropdownList.style.top = '';
214
+ dropdownList.style.width = '';
215
+
216
+ // Move back to original parent if it exists and we're not already there
217
+ if (dropdownList._originalParent && currentParent !== dropdownList._originalParent) {
218
+ currentParent.removeChild(dropdownList);
219
+ dropdownList._originalParent.appendChild(dropdownList);
220
+ }
221
+
222
+ input.setAttribute('aria-expanded', 'false');
223
+ selectedIndex = -1;
224
+ isOpen = false;
225
+ showAllOptions = false; // Reset the flag when closing dropdown
226
+ }
227
+
228
+ // Function to update the dropdown
229
+ function updateDropdown() {
230
+ const searchText = input.value;
231
+ const filteredData = filterOptions(searchText, showAllOptions);
232
+
233
+ dropdownList.innerHTML = '';
234
+
235
+ if (filteredData.length === 0) {
236
+ dropdownList.innerHTML = `<div class="custom-dropdown-no-results">${noResultsText}</div>`;
237
+
238
+ if (allowCustomValues && searchText.trim()) {
239
+ const customOption = document.createElement('div');
240
+ customOption.className = 'custom-dropdown-footer';
241
+ customOption.textContent = `Press Enter to use "${searchText}"`;
242
+ dropdownList.appendChild(customOption);
243
+ }
244
+
245
+ return;
246
+ }
247
+
248
+ filteredData.forEach((item, index) => {
249
+ const div = document.createElement('div');
250
+ div.className = 'custom-dropdown-item';
251
+ div.innerHTML = highlightText(item.label, searchText);
252
+ div.setAttribute('data-value', item.value);
253
+ div.addEventListener('click', () => {
254
+ // Set display value
255
+ input.value = item.label;
256
+ // Update hidden input value
257
+ updateHiddenField(item.value);
258
+ // Call onSelect callback
259
+ onSelect(item.value, item);
260
+ hideDropdown();
261
+ });
262
+
263
+ if (index === selectedIndex) {
264
+ div.classList.add('active');
265
+ }
266
+
267
+ dropdownList.appendChild(div);
268
+ });
269
+ }
270
+
271
+ // Input event - filter as user types
272
+ input.addEventListener('input', () => {
273
+ showAllOptions = false; // Reset when typing
274
+ showDropdown();
275
+ updateDropdown();
276
+ selectedIndex = -1;
277
+ });
278
+
279
+ // Click event - show all options when clicking in the input
280
+ input.addEventListener('click', (e) => {
281
+ e.stopPropagation();
282
+ showAllOptions = true;
283
+ showDropdown();
284
+ updateDropdown();
285
+ });
286
+
287
+ // Focus event - show dropdown when input is focused
288
+ input.addEventListener('focus', () => {
289
+ if (!isOpen) {
290
+ showAllOptions = true; // Show all options on focus
291
+ showDropdown();
292
+ updateDropdown();
293
+ }
294
+ });
295
+
296
+ // Keyboard navigation for dropdown
297
+ input.addEventListener('keydown', (e) => {
298
+ const items = dropdownList.querySelectorAll('.custom-dropdown-item');
299
+
300
+ if (e.key === 'ArrowDown') {
301
+ e.preventDefault();
302
+ if (!isOpen) {
303
+ showAllOptions = true;
304
+ showDropdown();
305
+ updateDropdown();
306
+ selectedIndex = 0;
307
+ } else {
308
+ selectedIndex = (selectedIndex + 1) % items.length;
309
+ }
310
+ } else if (e.key === 'ArrowUp') {
311
+ e.preventDefault();
312
+ if (!isOpen) {
313
+ showAllOptions = true;
314
+ showDropdown();
315
+ updateDropdown();
316
+ selectedIndex = items.length - 1;
317
+ } else {
318
+ selectedIndex = (selectedIndex - 1 + items.length) % items.length;
319
+ }
320
+ } else if (e.key === 'Enter') {
321
+ e.preventDefault();
322
+
323
+ if (selectedIndex >= 0 && selectedIndex < items.length) {
324
+ // Select the highlighted item
325
+ const selectedItem = items[selectedIndex];
326
+ const value = selectedItem.getAttribute('data-value');
327
+ const item = getOptions().find(o => o.value === value);
328
+ // Update display value
329
+ input.value = item.label;
330
+ // Update hidden input
331
+ updateHiddenField(value);
332
+ // Call callback
333
+ onSelect(value, item);
334
+ } else if (allowCustomValues && input.value.trim()) {
335
+ // Use the custom value
336
+ const customValue = input.value.trim();
337
+ // Update hidden input with custom value
338
+ updateHiddenField(customValue);
339
+ onSelect(customValue, null);
340
+ }
341
+ hideDropdown();
342
+ } else if (e.key === 'Escape') {
343
+ e.preventDefault();
344
+ hideDropdown();
345
+ }
346
+
347
+ // Update selected item styling
348
+ items.forEach((item, index) => {
349
+ if (index === selectedIndex) {
350
+ item.classList.add('active');
351
+ // Scroll into view if necessary
352
+ if (item.offsetTop < dropdownList.scrollTop) {
353
+ dropdownList.scrollTop = item.offsetTop;
354
+ } else if (item.offsetTop + item.offsetHeight > dropdownList.scrollTop + dropdownList.offsetHeight) {
355
+ dropdownList.scrollTop = item.offsetTop + item.offsetHeight - dropdownList.offsetHeight;
356
+ }
357
+ } else {
358
+ item.classList.remove('active');
359
+ }
360
+ });
361
+ });
362
+
363
+ // Close dropdown when clicking outside
364
+ document.addEventListener('click', () => {
365
+ if (isOpen) {
366
+ hideDropdown();
367
+ }
368
+ });
369
+
370
+ // Prevent clicks in the dropdown from closing it
371
+ dropdownList.addEventListener('click', (e) => {
372
+ e.stopPropagation();
373
+ });
374
+
375
+ // Register this dropdown for future updates
376
+ if (input && input.id && dropdownList && dropdownList.id) {
377
+ const dropdownId = dropdownList.id.replace('-list', '');
378
+ console.log(`Registering dropdown: ${input.id} with list ${dropdownId}`);
379
+
380
+ dropdownRegistry[input.id] = {
381
+ getOptions: getOptions,
382
+ onSelect: onSelect,
383
+ dropdownId: dropdownId,
384
+ allowCustomValues: allowCustomValues,
385
+ noResultsText: noResultsText
386
+ };
387
+ } else {
388
+ console.warn('Cannot register dropdown: Missing input ID or dropdown list ID');
389
+ }
390
+
391
+ // Initial state
392
+ hideDropdown();
393
+
394
+ // Return functions that might be needed externally
395
+ return {
396
+ updateDropdown,
397
+ showDropdown,
398
+ hideDropdown,
399
+ setValue: (value, triggerChange = true) => {
400
+ const options = getOptions();
401
+ const matchedOption = options.find(opt => opt.value === value);
402
+
403
+ if (matchedOption) {
404
+ input.value = matchedOption.label;
405
+ } else if (allowCustomValues && value) {
406
+ input.value = value;
407
+ } else {
408
+ input.value = '';
409
+ }
410
+
411
+ if (triggerChange) {
412
+ updateHiddenField(value);
413
+ // Also call onSelect if triggerChange is true
414
+ if (matchedOption) {
415
+ onSelect(value, matchedOption);
416
+ } else {
417
+ onSelect(value, { value, label: value });
418
+ }
419
+ } else {
420
+ // Even if we don't trigger events, we should update the hidden field
421
+ if (hiddenInput) {
422
+ hiddenInput.value = value;
423
+ }
424
+ }
425
+ }
426
+ };
427
+ }
428
+ })();