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,546 @@
1
+ /**
2
+ * UI utility functions
3
+ */
4
+
5
+ /**
6
+ * Update a progress bar UI element
7
+ * @param {string|Element} fillElementId - The ID or element to fill
8
+ * @param {string|Element} percentageElementId - The ID or element to show percentage
9
+ * @param {number} percentage - The percentage to set
10
+ */
11
+ function updateProgressBar(fillElementId, percentageElementId, percentage) {
12
+ const progressFill = typeof fillElementId === 'string' ? document.getElementById(fillElementId) : fillElementId;
13
+ const progressPercentage = typeof percentageElementId === 'string' ? document.getElementById(percentageElementId) : percentageElementId;
14
+
15
+ if (progressFill && progressPercentage) {
16
+ // Convert any value to a percentage between 0-100
17
+ const safePercentage = Math.min(100, Math.max(0, percentage || 0));
18
+
19
+ // Update the width of the fill element
20
+ progressFill.style.width = `${safePercentage}%`;
21
+
22
+ // Update the percentage text
23
+ progressPercentage.textContent = `${Math.round(safePercentage)}%`;
24
+
25
+ // Add classes for visual feedback
26
+ if (safePercentage >= 100) {
27
+ progressFill.classList.add('complete');
28
+ } else {
29
+ progressFill.classList.remove('complete');
30
+ }
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Show a loading spinner
36
+ * @param {string|Element} container - The container ID or element to add the spinner to
37
+ * @param {string} message - Optional message to show with the spinner
38
+ */
39
+ function showSpinner(container, message = 'Loading...') {
40
+ const containerEl = typeof container === 'string' ? document.getElementById(container) : container;
41
+
42
+ if (containerEl) {
43
+ containerEl.innerHTML = '';
44
+
45
+ const spinnerHTML = `
46
+ <div class="loading-spinner centered">
47
+ <div class="spinner"></div>
48
+ ${message ? `<div class="spinner-message">${message}</div>` : ''}
49
+ </div>
50
+ `;
51
+
52
+ containerEl.innerHTML = spinnerHTML;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Hide a loading spinner
58
+ * @param {string|Element} container - The container ID or element with the spinner
59
+ */
60
+ function hideSpinner(container) {
61
+ const containerEl = typeof container === 'string' ? document.getElementById(container) : container;
62
+
63
+ if (containerEl) {
64
+ const spinner = containerEl.querySelector('.loading-spinner');
65
+ if (spinner) {
66
+ spinner.remove();
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Show an error message
73
+ * @param {string|Element} container - The container ID or element to add the error to
74
+ * @param {string} message - The error message
75
+ */
76
+ function showError(container, message) {
77
+ const containerEl = typeof container === 'string' ? document.getElementById(container) : container;
78
+
79
+ if (containerEl) {
80
+ const errorHTML = `
81
+ <div class="error-message">
82
+ <i class="fas fa-exclamation-circle"></i>
83
+ <span>${message}</span>
84
+ </div>
85
+ `;
86
+
87
+ containerEl.innerHTML = errorHTML;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Show a notification message
93
+ * @param {string} message - The message to display
94
+ * @param {string} type - Message type: 'success', 'error', 'info', 'warning'
95
+ * @param {number} duration - How long to show the message in ms
96
+ */
97
+ function showMessage(message, type = 'success', duration = 3000) {
98
+ // Check if the toast container exists
99
+ let toastContainer = document.getElementById('toast-container');
100
+
101
+ // Create it if it doesn't exist
102
+ if (!toastContainer) {
103
+ toastContainer = document.createElement('div');
104
+ toastContainer.id = 'toast-container';
105
+ document.body.appendChild(toastContainer);
106
+ }
107
+
108
+ // Create a new toast
109
+ const toast = document.createElement('div');
110
+ toast.className = `toast toast-${type}`;
111
+
112
+ // Add icon based on type
113
+ let icon = '';
114
+ switch (type) {
115
+ case 'success':
116
+ icon = '<i class="fas fa-check-circle"></i>';
117
+ break;
118
+ case 'error':
119
+ icon = '<i class="fas fa-exclamation-circle"></i>';
120
+ break;
121
+ case 'info':
122
+ icon = '<i class="fas fa-info-circle"></i>';
123
+ break;
124
+ case 'warning':
125
+ icon = '<i class="fas fa-exclamation-triangle"></i>';
126
+ break;
127
+ }
128
+
129
+ // Set the content
130
+ toast.innerHTML = `
131
+ ${icon}
132
+ <div class="toast-message">${message}</div>
133
+ `;
134
+
135
+ // Add to container
136
+ toastContainer.appendChild(toast);
137
+
138
+ // Show with animation
139
+ setTimeout(() => {
140
+ toast.classList.add('visible');
141
+ }, 10);
142
+
143
+ // Remove after duration
144
+ setTimeout(() => {
145
+ toast.classList.remove('visible');
146
+
147
+ // Remove from DOM after animation
148
+ setTimeout(() => {
149
+ toast.remove();
150
+ }, 300);
151
+ }, duration);
152
+ }
153
+
154
+ /**
155
+ * Format and render Markdown content
156
+ * @param {string} markdown - The markdown content
157
+ * @returns {string} The rendered HTML
158
+ */
159
+ function renderMarkdown(markdown) {
160
+ if (!markdown) {
161
+ return '<div class="alert alert-warning">No content available</div>';
162
+ }
163
+
164
+ try {
165
+ // Use marked library if available
166
+ if (typeof marked !== 'undefined') {
167
+ // Configure marked options
168
+ marked.setOptions({
169
+ breaks: true,
170
+ gfm: true,
171
+ headerIds: true,
172
+ smartLists: true,
173
+ smartypants: true,
174
+ highlight: function(code, language) {
175
+ // Use Prism for syntax highlighting if available
176
+ if (typeof Prism !== 'undefined' && Prism.languages[language]) {
177
+ return Prism.highlight(code, Prism.languages[language], language);
178
+ }
179
+ return code;
180
+ }
181
+ });
182
+
183
+ // Parse markdown and return HTML
184
+ const html = marked.parse(markdown);
185
+
186
+ // Process any special elements like image references
187
+ const processedHtml = processSpecialMarkdown(html);
188
+
189
+ return `<div class="markdown-content">${processedHtml}</div>`;
190
+ } else {
191
+ // Basic fallback if marked is not available
192
+ console.warn('Marked library not available. Using basic formatting.');
193
+ const basic = markdown
194
+ .replace(/\n\n/g, '<br><br>')
195
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
196
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
197
+ .replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank">$1</a>');
198
+
199
+ return `<div class="markdown-content">${basic}</div>`;
200
+ }
201
+ } catch (error) {
202
+ console.error('Error rendering markdown:', error);
203
+ return `<div class="alert alert-danger">Error rendering content: ${error.message}</div>`;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Process special markdown elements
209
+ * @param {string} html - HTML content to process
210
+ * @returns {string} - Processed HTML
211
+ */
212
+ function processSpecialMarkdown(html) {
213
+ // Process image references
214
+ return html.replace(/\!\[ref:([^\]]+)\]/g, (match, ref) => {
215
+ // Check if this is a reference to a generated image
216
+ if (ref.startsWith('image-')) {
217
+ return `<div class="generated-image" data-image-id="${ref}">
218
+ <img src="/research/static/img/generated/${ref}.png"
219
+ alt="Generated image ${ref}"
220
+ class="img-fluid"
221
+ loading="lazy" />
222
+ <div class="image-caption">Generated image (${ref})</div>
223
+ </div>`;
224
+ }
225
+ return match;
226
+ });
227
+ }
228
+
229
+ /**
230
+ * Create a dynamic favicon
231
+ * @param {string} emoji - The emoji to use for the favicon
232
+ */
233
+ function createDynamicFavicon(emoji = '⚡') {
234
+ // Create a canvas element
235
+ const canvas = document.createElement('canvas');
236
+ canvas.width = 64;
237
+ canvas.height = 64;
238
+
239
+ // Get the 2D drawing context
240
+ const ctx = canvas.getContext('2d');
241
+
242
+ // Clear the canvas
243
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
244
+
245
+ // Set font
246
+ ctx.font = '48px Arial';
247
+ ctx.textAlign = 'center';
248
+ ctx.textBaseline = 'middle';
249
+
250
+ // Draw the emoji
251
+ ctx.fillText(emoji, 32, 32);
252
+
253
+ // Convert to data URL
254
+ const dataUrl = canvas.toDataURL('image/png');
255
+
256
+ // Create or update the favicon link
257
+ let link = document.querySelector('link[rel="icon"]');
258
+ if (!link) {
259
+ link = document.createElement('link');
260
+ link.rel = 'icon';
261
+ document.head.appendChild(link);
262
+ }
263
+
264
+ // Set the new favicon
265
+ link.href = dataUrl;
266
+ }
267
+
268
+ /**
269
+ * Update favicon based on status
270
+ * @param {string} status - The research status
271
+ */
272
+ function updateFavicon(status) {
273
+ try {
274
+ // Find favicon link or create it if it doesn't exist
275
+ let link = document.querySelector("link[rel='icon']") ||
276
+ document.querySelector("link[rel='shortcut icon']");
277
+
278
+ if (!link) {
279
+ console.log('Favicon link not found, creating a new one');
280
+ link = document.createElement('link');
281
+ link.rel = 'icon';
282
+ link.type = 'image/x-icon';
283
+ document.head.appendChild(link);
284
+ }
285
+
286
+ // Create dynamic favicon using canvas
287
+ const canvas = document.createElement('canvas');
288
+ canvas.width = 32;
289
+ canvas.height = 32;
290
+ const ctx = canvas.getContext('2d');
291
+
292
+ // Background color based on status
293
+ let bgColor = '#007bff'; // Default blue
294
+
295
+ if (status === 'completed') {
296
+ bgColor = '#28a745'; // Success green
297
+ } else if (status === 'failed' || status === 'error') {
298
+ bgColor = '#dc3545'; // Error red
299
+ } else if (status === 'cancelled') {
300
+ bgColor = '#6c757d'; // Gray
301
+ }
302
+
303
+ // Draw circle background
304
+ ctx.fillStyle = bgColor;
305
+ ctx.beginPath();
306
+ ctx.arc(16, 16, 16, 0, 2 * Math.PI);
307
+ ctx.fill();
308
+
309
+ // Draw inner circle
310
+ ctx.fillStyle = '#ffffff';
311
+ ctx.beginPath();
312
+ ctx.arc(16, 16, 10, 0, 2 * Math.PI);
313
+ ctx.fill();
314
+
315
+ // Draw letter R
316
+ ctx.fillStyle = bgColor;
317
+ ctx.font = 'bold 16px Arial';
318
+ ctx.textAlign = 'center';
319
+ ctx.textBaseline = 'middle';
320
+ ctx.fillText('R', 16, 16);
321
+
322
+ // Set the favicon to the canvas data URL
323
+ link.href = canvas.toDataURL('image/png');
324
+
325
+ console.log('Updated favicon to:', status);
326
+ } catch (error) {
327
+ console.error('Error updating favicon:', error);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Show an alert message in a container on the page
333
+ * @param {string} message - The message to display
334
+ * @param {string} type - The alert type: success, error, warning, info
335
+ * @param {boolean} skipIfToastShown - Whether to skip showing this alert if a toast was already shown
336
+ */
337
+ function showAlert(message, type = 'info', skipIfToastShown = true) {
338
+ // If we're showing a toast and we want to skip the regular alert, just return
339
+ if (skipIfToastShown && window.ui && window.ui.showMessage) {
340
+ return;
341
+ }
342
+
343
+ // Find the alert container - look for different possible alert containers
344
+ let alertContainer = document.getElementById('filtered-settings-alert');
345
+
346
+ // If not found, try other common alert containers
347
+ if (!alertContainer) {
348
+ alertContainer = document.getElementById('settings-alert');
349
+ }
350
+
351
+ if (!alertContainer) {
352
+ alertContainer = document.getElementById('research-alert');
353
+ }
354
+
355
+ if (!alertContainer) return;
356
+
357
+ // Clear any existing alerts
358
+ alertContainer.innerHTML = '';
359
+
360
+ // Create alert element
361
+ const alert = document.createElement('div');
362
+ alert.className = `alert alert-${type}`;
363
+ alert.innerHTML = `<i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'}"></i> ${message}`;
364
+
365
+ // Add a close button
366
+ const closeBtn = document.createElement('span');
367
+ closeBtn.className = 'alert-close';
368
+ closeBtn.innerHTML = '&times;';
369
+ closeBtn.addEventListener('click', () => {
370
+ alert.remove();
371
+ alertContainer.style.display = 'none';
372
+ });
373
+
374
+ alert.appendChild(closeBtn);
375
+
376
+ // Add to container
377
+ alertContainer.appendChild(alert);
378
+ alertContainer.style.display = 'block';
379
+
380
+ // Auto-hide after 5 seconds
381
+ setTimeout(() => {
382
+ alert.remove();
383
+ if (alertContainer.children.length === 0) {
384
+ alertContainer.style.display = 'none';
385
+ }
386
+ }, 5000);
387
+ }
388
+
389
+ // Add CSS for toast messages
390
+ function addToastStyles() {
391
+ if (document.getElementById('toast-styles')) return;
392
+
393
+ const styleEl = document.createElement('style');
394
+ styleEl.id = 'toast-styles';
395
+ styleEl.textContent = `
396
+ #toast-container {
397
+ position: fixed;
398
+ top: 20px;
399
+ right: 20px;
400
+ z-index: 9999;
401
+ display: flex;
402
+ flex-direction: column;
403
+ gap: 10px;
404
+ }
405
+
406
+ .toast {
407
+ display: flex;
408
+ align-items: center;
409
+ gap: 10px;
410
+ background-color: var(--card-bg, #2a2a2a);
411
+ color: var(--text-color, #fff);
412
+ padding: 12px 16px;
413
+ border-radius: 8px;
414
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
415
+ max-width: 350px;
416
+ transform: translateX(120%);
417
+ opacity: 0;
418
+ transition: transform 0.3s ease, opacity 0.3s ease;
419
+ border-left: 4px solid transparent;
420
+ }
421
+
422
+ .toast.visible {
423
+ transform: translateX(0);
424
+ opacity: 1;
425
+ }
426
+
427
+ .toast i {
428
+ font-size: 1.2rem;
429
+ }
430
+
431
+ .toast-success {
432
+ border-left-color: var(--success-color, #28a745);
433
+ }
434
+
435
+ .toast-success i {
436
+ color: var(--success-color, #28a745);
437
+ }
438
+
439
+ .toast-error {
440
+ border-left-color: var(--danger-color, #dc3545);
441
+ }
442
+
443
+ .toast-error i {
444
+ color: var(--danger-color, #dc3545);
445
+ }
446
+
447
+ .toast-info {
448
+ border-left-color: var(--info-color, #17a2b8);
449
+ }
450
+
451
+ .toast-info i {
452
+ color: var(--info-color, #17a2b8);
453
+ }
454
+
455
+ .toast-warning {
456
+ border-left-color: var(--warning-color, #ffc107);
457
+ }
458
+
459
+ .toast-warning i {
460
+ color: var(--warning-color, #ffc107);
461
+ }
462
+ `;
463
+
464
+ document.head.appendChild(styleEl);
465
+ }
466
+
467
+ // Add CSS for alert styles
468
+ function addAlertStyles() {
469
+ if (document.getElementById('alert-styles')) return;
470
+
471
+ const styleEl = document.createElement('style');
472
+ styleEl.id = 'alert-styles';
473
+ styleEl.textContent = `
474
+ .alert {
475
+ padding: 12px 16px;
476
+ margin-bottom: 1rem;
477
+ border-radius: 8px;
478
+ display: flex;
479
+ align-items: center;
480
+ position: relative;
481
+ }
482
+
483
+ .alert i {
484
+ margin-right: 12px;
485
+ font-size: 1.2rem;
486
+ }
487
+
488
+ .alert-success {
489
+ background-color: rgba(40, 167, 69, 0.15);
490
+ color: var(--success-color, #28a745);
491
+ border-left: 4px solid var(--success-color, #28a745);
492
+ }
493
+
494
+ .alert-error, .alert-danger {
495
+ background-color: rgba(220, 53, 69, 0.15);
496
+ color: var(--danger-color, #dc3545);
497
+ border-left: 4px solid var(--danger-color, #dc3545);
498
+ }
499
+
500
+ .alert-info {
501
+ background-color: rgba(23, 162, 184, 0.15);
502
+ color: var(--info-color, #17a2b8);
503
+ border-left: 4px solid var(--info-color, #17a2b8);
504
+ }
505
+
506
+ .alert-warning {
507
+ background-color: rgba(255, 193, 7, 0.15);
508
+ color: var(--warning-color, #ffc107);
509
+ border-left: 4px solid var(--warning-color, #ffc107);
510
+ }
511
+
512
+ .alert-close {
513
+ position: absolute;
514
+ right: 10px;
515
+ top: 8px;
516
+ font-size: 1.2rem;
517
+ font-weight: bold;
518
+ cursor: pointer;
519
+ opacity: 0.7;
520
+ }
521
+
522
+ .alert-close:hover {
523
+ opacity: 1;
524
+ }
525
+ `;
526
+
527
+ document.head.appendChild(styleEl);
528
+ }
529
+
530
+ // Add toast styles when the script loads
531
+ addToastStyles();
532
+ // Add alert styles when the script loads
533
+ addAlertStyles();
534
+
535
+ // Export the UI functions
536
+ window.ui = {
537
+ updateProgressBar,
538
+ showSpinner,
539
+ hideSpinner,
540
+ showError,
541
+ showMessage,
542
+ renderMarkdown,
543
+ createDynamicFavicon,
544
+ updateFavicon,
545
+ showAlert
546
+ };
@@ -0,0 +1,72 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="csrf-token" content="{{ csrf_token() }}">
7
+ <title>{% block title %}Deep Research System{% endblock %}</title>
8
+ <link rel="stylesheet" href="{{ url_for('research.serve_static', path='css/styles.css') }}">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github-dark.min.css">
11
+ {% block extra_head %}{% endblock %}
12
+ </head>
13
+ <body>
14
+ <div class="app-container">
15
+ <!-- Sidebar -->
16
+ {% include 'components/sidebar.html' %}
17
+
18
+ <!-- Main Content -->
19
+ <main class="main-content">
20
+ {% block content %}{% endblock %}
21
+
22
+ <!-- Collapsible Log Panel is included in specific pages -->
23
+ </main>
24
+ </div>
25
+
26
+ <!-- Mobile Tab Bar -->
27
+ {% include 'components/mobile_nav.html' %}
28
+
29
+ <!-- Common Templates -->
30
+ {% block templates %}{% endblock %}
31
+
32
+ <!-- Scripts -->
33
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.min.js"></script>
34
+ <script src="https://cdn.jsdelivr.net/npm/marked@4.3.0/lib/marked.umd.min.js"></script>
35
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
36
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
37
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js" integrity="sha512-BNaRQnYJYiPSqHHDb58B0yaPfCu+Wgds8Gp/gU33kqBtgNS4tSPHuGibyoeqMV/TJlSKda6FXzoEyYGjTe+vXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
38
+
39
+ <!-- Core JS -->
40
+ <script src="{{ url_for('research.serve_static', path='js/services/formatting.js') }}"></script>
41
+ <script src="{{ url_for('research.serve_static', path='js/services/ui.js') }}"></script>
42
+ <script src="{{ url_for('research.serve_static', path='js/services/api.js') }}"></script>
43
+ <script src="{{ url_for('research.serve_static', path='js/services/socket.js') }}"></script>
44
+
45
+ <!-- Shared Components -->
46
+ <script src="{{ url_for('research.serve_static', path='js/components/logpanel.js') }}"></script>
47
+
48
+ <!-- Page-specific Components -->
49
+ {% block component_scripts %}{% endblock %}
50
+
51
+ <!-- Page-specific JS -->
52
+ {% block page_scripts %}{% endblock %}
53
+
54
+ <script>
55
+ // Configure marked to not use eval
56
+ if (typeof marked !== 'undefined') {
57
+ marked.setOptions({
58
+ headerIds: false,
59
+ mangle: false,
60
+ smartypants: false
61
+ });
62
+ }
63
+
64
+ // Configure html2canvas to avoid using eval if possible
65
+ if (typeof html2canvas !== 'undefined') {
66
+ window.html2canvas_noSandbox = true;
67
+ }
68
+ </script>
69
+
70
+ <script src="/research/static/js/components/settings_sync.js"></script>
71
+ </body>
72
+ </html>
@@ -0,0 +1,47 @@
1
+ {% macro render_dropdown(input_id, dropdown_id, placeholder, label=None, help_text=None, allow_custom=False, show_refresh=False, refresh_aria_label="Refresh options", data_setting_key=None) %}
2
+ <div class="form-group">
3
+ {% if label %}
4
+ <label for="{{ input_id }}">{{ label }}</label>
5
+ {% endif %}
6
+
7
+ {% if show_refresh %}
8
+ <div class="custom-dropdown-with-refresh">
9
+ <div class="custom-dropdown" id="{{ dropdown_id }}">
10
+ <input type="text"
11
+ id="{{ input_id }}"
12
+ data-key="{{ data_setting_key or input_id }}"
13
+ class="custom-dropdown-input"
14
+ placeholder="{{ placeholder }}"
15
+ autocomplete="off"
16
+ aria-haspopup="listbox">
17
+ <!-- Hidden input that will be included in form submission -->
18
+ <input type="hidden" name="{{ input_id }}" id="{{ input_id }}_hidden" value="">
19
+ <div class="custom-dropdown-list" id="{{ dropdown_id }}-list"></div>
20
+ </div>
21
+ <button type="button"
22
+ class="custom-dropdown-refresh-btn dropdown-refresh-button"
23
+ id="{{ input_id }}-refresh"
24
+ aria-label="{{ refresh_aria_label }}">
25
+ <i class="fas fa-sync-alt"></i>
26
+ </button>
27
+ </div>
28
+ {% else %}
29
+ <div class="custom-dropdown" id="{{ dropdown_id }}">
30
+ <input type="text"
31
+ id="{{ input_id }}"
32
+ data-key="{{ data_setting_key or input_id }}"
33
+ class="custom-dropdown-input"
34
+ placeholder="{{ placeholder }}"
35
+ autocomplete="off"
36
+ aria-haspopup="listbox">
37
+ <!-- Hidden input that will be included in form submission -->
38
+ <input type="hidden" name="{{ input_id }}" id="{{ input_id }}_hidden" value="">
39
+ <div class="custom-dropdown-list" id="{{ dropdown_id }}-list"></div>
40
+ </div>
41
+ {% endif %}
42
+
43
+ {% if help_text %}
44
+ <span class="input-help">{{ help_text }}</span>
45
+ {% endif %}
46
+ </div>
47
+ {% endmacro %}
@@ -0,0 +1,32 @@
1
+ <div class="collapsible-log-panel">
2
+ <div class="log-panel-header" id="log-panel-toggle">
3
+ <i class="fas fa-chevron-right toggle-icon"></i>
4
+ <span>Research Logs</span>
5
+ <span class="log-indicator" id="log-indicator">0</span>
6
+ </div>
7
+ <div class="log-panel-content collapsed" id="log-panel-content">
8
+ <div class="log-controls">
9
+ <div class="log-filter">
10
+ <div class="filter-buttons">
11
+ <button class="small-btn selected" onclick="window.filterLogsByType('all')">All</button>
12
+ <button class="small-btn" onclick="window.filterLogsByType('milestone')">Milestones</button>
13
+ <button class="small-btn" onclick="window.filterLogsByType('info')">Info</button>
14
+ <button class="small-btn" onclick="window.filterLogsByType('error')">Errors</button>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ <div class="console-log" id="console-log-container">
19
+ <!-- Logs will be added here dynamically -->
20
+ <div class="empty-log-message">No logs yet. Research logs will appear here as they occur.</div>
21
+ </div>
22
+ </div>
23
+ </div>
24
+
25
+ <!-- Template for console log entries -->
26
+ <template id="console-log-entry-template">
27
+ <div class="console-log-entry">
28
+ <span class="log-timestamp"></span>
29
+ <span class="log-badge"></span>
30
+ <span class="log-message"></span>
31
+ </div>
32
+ </template>