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.
- local_deep_research/__init__.py +23 -22
- local_deep_research/__main__.py +16 -0
- local_deep_research/advanced_search_system/__init__.py +7 -0
- local_deep_research/advanced_search_system/filters/__init__.py +8 -0
- local_deep_research/advanced_search_system/filters/base_filter.py +38 -0
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +200 -0
- local_deep_research/advanced_search_system/findings/base_findings.py +81 -0
- local_deep_research/advanced_search_system/findings/repository.py +452 -0
- local_deep_research/advanced_search_system/knowledge/__init__.py +1 -0
- local_deep_research/advanced_search_system/knowledge/base_knowledge.py +151 -0
- local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +159 -0
- local_deep_research/advanced_search_system/questions/__init__.py +1 -0
- local_deep_research/advanced_search_system/questions/base_question.py +64 -0
- local_deep_research/advanced_search_system/questions/decomposition_question.py +445 -0
- local_deep_research/advanced_search_system/questions/standard_question.py +119 -0
- local_deep_research/advanced_search_system/repositories/__init__.py +7 -0
- local_deep_research/advanced_search_system/strategies/__init__.py +1 -0
- local_deep_research/advanced_search_system/strategies/base_strategy.py +118 -0
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +450 -0
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +312 -0
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +270 -0
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +300 -0
- local_deep_research/advanced_search_system/tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/base_tool.py +100 -0
- local_deep_research/advanced_search_system/tools/knowledge_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/question_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/search_tools/__init__.py +1 -0
- local_deep_research/api/__init__.py +5 -5
- local_deep_research/api/research_functions.py +154 -160
- local_deep_research/app.py +8 -0
- local_deep_research/citation_handler.py +25 -16
- local_deep_research/{config.py → config/config_files.py} +102 -110
- local_deep_research/config/llm_config.py +472 -0
- local_deep_research/config/search_config.py +77 -0
- local_deep_research/defaults/__init__.py +10 -5
- local_deep_research/defaults/main.toml +2 -2
- local_deep_research/defaults/search_engines.toml +60 -34
- local_deep_research/main.py +121 -19
- local_deep_research/migrate_db.py +147 -0
- local_deep_research/report_generator.py +87 -45
- local_deep_research/search_system.py +153 -283
- local_deep_research/setup_data_dir.py +35 -0
- local_deep_research/test_migration.py +178 -0
- local_deep_research/utilities/__init__.py +0 -0
- local_deep_research/utilities/db_utils.py +49 -0
- local_deep_research/{utilties → utilities}/enums.py +2 -2
- local_deep_research/{utilties → utilities}/llm_utils.py +63 -29
- local_deep_research/utilities/search_utilities.py +242 -0
- local_deep_research/{utilties → utilities}/setup_utils.py +4 -2
- local_deep_research/web/__init__.py +0 -1
- local_deep_research/web/app.py +86 -1709
- local_deep_research/web/app_factory.py +289 -0
- local_deep_research/web/database/README.md +70 -0
- local_deep_research/web/database/migrate_to_ldr_db.py +289 -0
- local_deep_research/web/database/migrations.py +447 -0
- local_deep_research/web/database/models.py +117 -0
- local_deep_research/web/database/schema_upgrade.py +107 -0
- local_deep_research/web/models/database.py +294 -0
- local_deep_research/web/models/settings.py +94 -0
- local_deep_research/web/routes/api_routes.py +559 -0
- local_deep_research/web/routes/history_routes.py +354 -0
- local_deep_research/web/routes/research_routes.py +715 -0
- local_deep_research/web/routes/settings_routes.py +1583 -0
- local_deep_research/web/services/research_service.py +947 -0
- local_deep_research/web/services/resource_service.py +149 -0
- local_deep_research/web/services/settings_manager.py +669 -0
- local_deep_research/web/services/settings_service.py +187 -0
- local_deep_research/web/services/socket_service.py +210 -0
- local_deep_research/web/static/css/custom_dropdown.css +277 -0
- local_deep_research/web/static/css/settings.css +1223 -0
- local_deep_research/web/static/css/styles.css +525 -48
- local_deep_research/web/static/js/components/custom_dropdown.js +428 -0
- local_deep_research/web/static/js/components/detail.js +348 -0
- local_deep_research/web/static/js/components/fallback/formatting.js +122 -0
- local_deep_research/web/static/js/components/fallback/ui.js +215 -0
- local_deep_research/web/static/js/components/history.js +487 -0
- local_deep_research/web/static/js/components/logpanel.js +949 -0
- local_deep_research/web/static/js/components/progress.js +1107 -0
- local_deep_research/web/static/js/components/research.js +1865 -0
- local_deep_research/web/static/js/components/results.js +766 -0
- local_deep_research/web/static/js/components/settings.js +3981 -0
- local_deep_research/web/static/js/components/settings_sync.js +106 -0
- local_deep_research/web/static/js/main.js +226 -0
- local_deep_research/web/static/js/services/api.js +253 -0
- local_deep_research/web/static/js/services/audio.js +31 -0
- local_deep_research/web/static/js/services/formatting.js +119 -0
- local_deep_research/web/static/js/services/pdf.js +622 -0
- local_deep_research/web/static/js/services/socket.js +882 -0
- local_deep_research/web/static/js/services/ui.js +546 -0
- local_deep_research/web/templates/base.html +72 -0
- local_deep_research/web/templates/components/custom_dropdown.html +47 -0
- local_deep_research/web/templates/components/log_panel.html +32 -0
- local_deep_research/web/templates/components/mobile_nav.html +22 -0
- local_deep_research/web/templates/components/settings_form.html +299 -0
- local_deep_research/web/templates/components/sidebar.html +21 -0
- local_deep_research/web/templates/pages/details.html +73 -0
- local_deep_research/web/templates/pages/history.html +51 -0
- local_deep_research/web/templates/pages/progress.html +57 -0
- local_deep_research/web/templates/pages/research.html +139 -0
- local_deep_research/web/templates/pages/results.html +59 -0
- local_deep_research/web/templates/settings_dashboard.html +78 -192
- local_deep_research/web/utils/__init__.py +0 -0
- local_deep_research/web/utils/formatters.py +76 -0
- local_deep_research/web_search_engines/engines/full_search.py +18 -16
- local_deep_research/web_search_engines/engines/meta_search_engine.py +182 -131
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +224 -139
- local_deep_research/web_search_engines/engines/search_engine_brave.py +88 -71
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +48 -39
- local_deep_research/web_search_engines/engines/search_engine_github.py +415 -204
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +123 -90
- local_deep_research/web_search_engines/engines/search_engine_guardian.py +210 -157
- local_deep_research/web_search_engines/engines/search_engine_local.py +532 -369
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +42 -36
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +358 -266
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +212 -160
- local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +213 -170
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +84 -68
- local_deep_research/web_search_engines/engines/search_engine_wayback.py +186 -154
- local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +115 -77
- local_deep_research/web_search_engines/search_engine_base.py +174 -99
- local_deep_research/web_search_engines/search_engine_factory.py +192 -102
- local_deep_research/web_search_engines/search_engines_config.py +22 -15
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/METADATA +177 -97
- local_deep_research-0.2.2.dist-info/RECORD +135 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/WHEEL +1 -2
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/entry_points.txt +3 -0
- local_deep_research/defaults/llm_config.py +0 -338
- local_deep_research/utilties/search_utilities.py +0 -114
- local_deep_research/web/static/js/app.js +0 -3763
- local_deep_research/web/templates/api_keys_config.html +0 -82
- local_deep_research/web/templates/collections_config.html +0 -90
- local_deep_research/web/templates/index.html +0 -348
- local_deep_research/web/templates/llm_config.html +0 -120
- local_deep_research/web/templates/main_config.html +0 -89
- local_deep_research/web/templates/search_engines_config.html +0 -154
- local_deep_research/web/templates/settings.html +0 -519
- local_deep_research-0.1.26.dist-info/RECORD +0 -61
- local_deep_research-0.1.26.dist-info/top_level.txt +0 -1
- /local_deep_research/{utilties → config}/__init__.py +0 -0
- {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 = '×';
|
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>
|