local-deep-research 0.1.26__py3-none-any.whl → 0.2.2__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 +154 -160
  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 +87 -45
  41. local_deep_research/search_system.py +153 -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 +1583 -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 +212 -160
  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.2.dist-info}/METADATA +177 -97
  124. local_deep_research-0.2.2.dist-info/RECORD +135 -0
  125. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/WHEEL +1 -2
  126. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.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.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,487 @@
1
+ /**
2
+ * History Component
3
+ * Manages the display and interaction with research history
4
+ */
5
+ (function() {
6
+ // DOM Elements
7
+ let historyContainer = null;
8
+ let searchInput = null;
9
+ let clearHistoryBtn = null;
10
+ let historyEmptyMessage = null;
11
+
12
+ // Component state
13
+ let historyItems = [];
14
+ let filteredItems = [];
15
+
16
+ // Fallback UI utilities in case main UI utils aren't loaded
17
+ const uiUtils = {
18
+ showSpinner: function(container, message) {
19
+ if (window.ui && window.ui.showSpinner) {
20
+ return window.ui.showSpinner(container, message);
21
+ }
22
+
23
+ // Fallback implementation
24
+ if (!container) container = document.body;
25
+ const spinnerHtml = `
26
+ <div class="loading-spinner centered">
27
+ <div class="spinner"></div>
28
+ ${message ? `<div class="spinner-message">${message}</div>` : ''}
29
+ </div>
30
+ `;
31
+ container.innerHTML = spinnerHtml;
32
+ },
33
+
34
+ hideSpinner: function(container) {
35
+ if (window.ui && window.ui.hideSpinner) {
36
+ return window.ui.hideSpinner(container);
37
+ }
38
+
39
+ // Fallback implementation
40
+ if (!container) container = document.body;
41
+ const spinner = container.querySelector('.loading-spinner');
42
+ if (spinner) {
43
+ spinner.remove();
44
+ }
45
+ },
46
+
47
+ showError: function(message) {
48
+ if (window.ui && window.ui.showError) {
49
+ return window.ui.showError(message);
50
+ }
51
+
52
+ // Fallback implementation
53
+ console.error(message);
54
+ alert(message);
55
+ },
56
+
57
+ showMessage: function(message) {
58
+ if (window.ui && window.ui.showMessage) {
59
+ return window.ui.showMessage(message);
60
+ }
61
+
62
+ // Fallback implementation
63
+ console.log(message);
64
+ alert(message);
65
+ }
66
+ };
67
+
68
+ // Fallback API utilities
69
+ const apiUtils = {
70
+ getResearchHistory: async function() {
71
+ if (window.api && window.api.getResearchHistory) {
72
+ return window.api.getResearchHistory();
73
+ }
74
+
75
+ // Fallback implementation
76
+ try {
77
+ const response = await fetch('/research/api/history');
78
+ if (!response.ok) {
79
+ throw new Error(`API Error: ${response.status} ${response.statusText}`);
80
+ }
81
+ return await response.json();
82
+ } catch (error) {
83
+ console.error('API Error:', error);
84
+ throw error;
85
+ }
86
+ },
87
+
88
+ deleteResearch: async function(researchId) {
89
+ if (window.api && window.api.deleteResearch) {
90
+ return window.api.deleteResearch(researchId);
91
+ }
92
+
93
+ // Fallback implementation
94
+ try {
95
+ const response = await fetch(`/research/api/delete/${researchId}`, {
96
+ method: 'DELETE'
97
+ });
98
+ if (!response.ok) {
99
+ throw new Error(`API Error: ${response.status} ${response.statusText}`);
100
+ }
101
+ return await response.json();
102
+ } catch (error) {
103
+ console.error('API Error:', error);
104
+ throw error;
105
+ }
106
+ },
107
+
108
+ clearResearchHistory: async function() {
109
+ if (window.api && window.api.clearResearchHistory) {
110
+ return window.api.clearResearchHistory();
111
+ }
112
+
113
+ // Fallback implementation
114
+ try {
115
+ const response = await fetch('/research/api/clear_history', {
116
+ method: 'POST',
117
+ headers: {
118
+ 'Content-Type': 'application/json'
119
+ },
120
+ body: JSON.stringify({})
121
+ });
122
+ if (!response.ok) {
123
+ throw new Error(`API Error: ${response.status} ${response.statusText}`);
124
+ }
125
+ return await response.json();
126
+ } catch (error) {
127
+ console.error('API Error:', error);
128
+ throw error;
129
+ }
130
+ }
131
+ };
132
+
133
+ /**
134
+ * Initialize the history component
135
+ */
136
+ function initializeHistory() {
137
+ // Get DOM elements
138
+ historyContainer = document.getElementById('history-items');
139
+ searchInput = document.getElementById('history-search');
140
+ clearHistoryBtn = document.getElementById('clear-history-btn');
141
+ historyEmptyMessage = document.getElementById('history-empty-message');
142
+
143
+ if (!historyContainer) {
144
+ console.error('Required DOM elements not found for history component');
145
+ return;
146
+ }
147
+
148
+ // Set up event listeners
149
+ setupEventListeners();
150
+
151
+ // Load history data
152
+ loadHistoryData();
153
+
154
+ console.log('History component initialized');
155
+ }
156
+
157
+ /**
158
+ * Set up event listeners
159
+ */
160
+ function setupEventListeners() {
161
+ // Search input
162
+ if (searchInput) {
163
+ searchInput.addEventListener('input', handleSearchInput);
164
+ }
165
+
166
+ // Clear history button
167
+ if (clearHistoryBtn) {
168
+ clearHistoryBtn.addEventListener('click', handleClearHistory);
169
+ }
170
+
171
+ // Delegation for history item clicks
172
+ if (historyContainer) {
173
+ historyContainer.addEventListener('click', function(e) {
174
+ // Handle delete button click
175
+ if (e.target && e.target.closest('.delete-item-btn')) {
176
+ const itemId = e.target.closest('.history-item').dataset.id;
177
+ handleDeleteItem(itemId);
178
+ }
179
+ });
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Load history data from API
185
+ */
186
+ async function loadHistoryData() {
187
+ // Show loading state
188
+ uiUtils.showSpinner(historyContainer, 'Loading research history...');
189
+
190
+ try {
191
+ // Get history items
192
+ const response = await apiUtils.getResearchHistory();
193
+
194
+ if (response && Array.isArray(response.items)) {
195
+ historyItems = response.items;
196
+ filteredItems = [...historyItems];
197
+
198
+ // Render history items
199
+ renderHistoryItems();
200
+ } else {
201
+ throw new Error('Invalid response format');
202
+ }
203
+ } catch (error) {
204
+ console.error('Error loading history:', error);
205
+ uiUtils.hideSpinner(historyContainer);
206
+ uiUtils.showError('Error loading history: ' + error.message);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Render history items
212
+ */
213
+ function renderHistoryItems() {
214
+ // Hide spinner
215
+ uiUtils.hideSpinner(historyContainer);
216
+
217
+ // Clear container
218
+ historyContainer.innerHTML = '';
219
+
220
+ // Show empty message if no items
221
+ if (filteredItems.length === 0) {
222
+ if (historyEmptyMessage) {
223
+ historyEmptyMessage.style.display = 'block';
224
+ } else {
225
+ historyContainer.innerHTML = `
226
+ <div class="empty-state">
227
+ <i class="fas fa-history empty-icon"></i>
228
+ <p>No research history found.</p>
229
+ ${searchInput && searchInput.value ? '<p>Try adjusting your search query.</p>' : ''}
230
+ </div>
231
+ `;
232
+ }
233
+
234
+ if (clearHistoryBtn) {
235
+ clearHistoryBtn.style.display = 'none';
236
+ }
237
+ return;
238
+ }
239
+
240
+ // Hide empty message
241
+ if (historyEmptyMessage) {
242
+ historyEmptyMessage.style.display = 'none';
243
+ }
244
+
245
+ // Show clear button
246
+ if (clearHistoryBtn) {
247
+ clearHistoryBtn.style.display = 'inline-block';
248
+ }
249
+
250
+ // Create items
251
+ filteredItems.forEach(item => {
252
+ const itemElement = createHistoryItemElement(item);
253
+ historyContainer.appendChild(itemElement);
254
+ });
255
+ }
256
+
257
+ /**
258
+ * Format date safely using the formatter if available
259
+ */
260
+ function formatDate(dateStr) {
261
+ if (window.formatting && window.formatting.formatDate) {
262
+ return window.formatting.formatDate(dateStr);
263
+ }
264
+
265
+ // Simple fallback date formatting
266
+ try {
267
+ const date = new Date(dateStr);
268
+ return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
269
+ } catch (e) {
270
+ return dateStr;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Format status safely using the formatter if available
276
+ */
277
+ function formatStatus(status) {
278
+ if (window.formatting && window.formatting.formatStatus) {
279
+ return window.formatting.formatStatus(status);
280
+ }
281
+
282
+ // Simple fallback formatting
283
+ const statusMap = {
284
+ 'in_progress': 'In Progress',
285
+ 'completed': 'Completed',
286
+ 'failed': 'Failed',
287
+ 'suspended': 'Suspended'
288
+ };
289
+
290
+ return statusMap[status] || status;
291
+ }
292
+
293
+ /**
294
+ * Format mode safely using the formatter if available
295
+ */
296
+ function formatMode(mode) {
297
+ if (window.formatting && window.formatting.formatMode) {
298
+ return window.formatting.formatMode(mode);
299
+ }
300
+
301
+ // Simple fallback formatting
302
+ const modeMap = {
303
+ 'quick': 'Quick Summary',
304
+ 'detailed': 'Detailed Report'
305
+ };
306
+
307
+ return modeMap[mode] || mode;
308
+ }
309
+
310
+ /**
311
+ * Create a history item element
312
+ * @param {Object} item - The history item data
313
+ * @returns {HTMLElement} The history item element
314
+ */
315
+ function createHistoryItemElement(item) {
316
+ const itemEl = document.createElement('div');
317
+ itemEl.className = 'history-item';
318
+ itemEl.dataset.id = item.id;
319
+
320
+ // Format date
321
+ const formattedDate = formatDate(item.created_at);
322
+
323
+ // Get a display title (use query if title is not available)
324
+ const displayTitle = item.title || formatTitleFromQuery(item.query);
325
+
326
+ // Status class - convert in_progress to in-progress for CSS
327
+ const statusClass = item.status ? item.status.replace('_', '-') : '';
328
+
329
+ // Create the HTML content
330
+ itemEl.innerHTML = `
331
+ <div class="history-item-header">
332
+ <div class="history-item-title">${displayTitle}</div>
333
+ <div class="history-item-status status-${statusClass}">${formatStatus(item.status)}</div>
334
+ </div>
335
+ <div class="history-item-meta">
336
+ <div class="history-item-date">${formattedDate}</div>
337
+ <div class="history-item-mode">${formatMode(item.mode)}</div>
338
+ </div>
339
+ <div class="history-item-actions">
340
+ ${item.status === 'completed' ?
341
+ `<button class="btn btn-sm btn-outline view-btn">
342
+ <i class="fas fa-eye"></i> View
343
+ </button>` : ''}
344
+ <button class="btn btn-sm btn-outline delete-item-btn">
345
+ <i class="fas fa-trash-alt"></i>
346
+ </button>
347
+ </div>
348
+ `;
349
+
350
+ // Add event listeners
351
+ const viewBtn = itemEl.querySelector('.view-btn');
352
+ if (viewBtn) {
353
+ viewBtn.addEventListener('click', (e) => {
354
+ e.stopPropagation(); // Prevent item click
355
+ window.location.href = `/research/results/${item.id}`;
356
+ });
357
+ }
358
+
359
+ const deleteBtn = itemEl.querySelector('.delete-item-btn');
360
+ if (deleteBtn) {
361
+ deleteBtn.addEventListener('click', (e) => {
362
+ e.stopPropagation(); // Prevent item click
363
+ handleDeleteItem(item.id);
364
+ });
365
+ }
366
+
367
+ // Add click event to the whole item
368
+ itemEl.addEventListener('click', () => {
369
+ if (item.status === 'completed') {
370
+ window.location.href = `/research/results/${item.id}`;
371
+ } else {
372
+ window.location.href = `/research/progress/${item.id}`;
373
+ }
374
+ });
375
+
376
+ return itemEl;
377
+ }
378
+
379
+ /**
380
+ * Format a title from a query string
381
+ * Truncates long queries and adds ellipsis
382
+ * @param {string} query - The query string
383
+ * @returns {string} Formatted title
384
+ */
385
+ function formatTitleFromQuery(query) {
386
+ if (!query) return 'Untitled Research';
387
+
388
+ // Truncate long queries
389
+ if (query.length > 60) {
390
+ return query.substring(0, 57) + '...';
391
+ }
392
+
393
+ return query;
394
+ }
395
+
396
+ /**
397
+ * Handle search input
398
+ */
399
+ function handleSearchInput() {
400
+ const searchTerm = searchInput.value.trim().toLowerCase();
401
+
402
+ if (!searchTerm) {
403
+ // Reset to show all items
404
+ filteredItems = [...historyItems];
405
+ } else {
406
+ // Filter items based on search term
407
+ filteredItems = historyItems.filter(item => {
408
+ // Search in title if available, otherwise in query
409
+ const titleMatch = item.title ?
410
+ item.title.toLowerCase().includes(searchTerm) :
411
+ false;
412
+
413
+ // Always search in query
414
+ const queryMatch = item.query ?
415
+ item.query.toLowerCase().includes(searchTerm) :
416
+ false;
417
+
418
+ return titleMatch || queryMatch;
419
+ });
420
+ }
421
+
422
+ // Render filtered items
423
+ renderHistoryItems();
424
+ }
425
+
426
+ /**
427
+ * Handle delete item
428
+ * @param {string} itemId - The item ID to delete
429
+ */
430
+ async function handleDeleteItem(itemId) {
431
+ if (!confirm('Are you sure you want to delete this research? This action cannot be undone.')) {
432
+ return;
433
+ }
434
+
435
+ try {
436
+ // Delete item via API
437
+ await apiUtils.deleteResearch(itemId);
438
+
439
+ // Remove from arrays
440
+ historyItems = historyItems.filter(item => item.id != itemId);
441
+ filteredItems = filteredItems.filter(item => item.id != itemId);
442
+
443
+ // Show success message
444
+ uiUtils.showMessage('Research deleted successfully');
445
+
446
+ // Re-render history items
447
+ renderHistoryItems();
448
+ } catch (error) {
449
+ console.error('Error deleting research:', error);
450
+ uiUtils.showError('Error deleting research: ' + error.message);
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Handle clear history
456
+ */
457
+ async function handleClearHistory() {
458
+ if (!confirm('Are you sure you want to clear all research history? This action cannot be undone.')) {
459
+ return;
460
+ }
461
+
462
+ try {
463
+ // Clear history via API
464
+ await apiUtils.clearResearchHistory();
465
+
466
+ // Clear arrays
467
+ historyItems = [];
468
+ filteredItems = [];
469
+
470
+ // Show success message
471
+ uiUtils.showMessage('Research history cleared successfully');
472
+
473
+ // Re-render history items
474
+ renderHistoryItems();
475
+ } catch (error) {
476
+ console.error('Error clearing history:', error);
477
+ uiUtils.showError('Error clearing history: ' + error.message);
478
+ }
479
+ }
480
+
481
+ // Initialize on DOM content loaded
482
+ if (document.readyState === 'loading') {
483
+ document.addEventListener('DOMContentLoaded', initializeHistory);
484
+ } else {
485
+ initializeHistory();
486
+ }
487
+ })();