local-deep-research 0.1.26__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +96 -84
- 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 +72 -44
- local_deep_research/search_system.py +147 -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 +1592 -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 +211 -159
- 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.0.dist-info}/METADATA +177 -97
- local_deep_research-0.2.0.dist-info/RECORD +135 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.0.dist-info}/WHEEL +1 -2
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.0.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.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,348 @@
|
|
1
|
+
/**
|
2
|
+
* Detail Component
|
3
|
+
* Manages the display of detailed information for a specific research topic
|
4
|
+
*/
|
5
|
+
(function() {
|
6
|
+
// DOM Elements
|
7
|
+
let detailContainer = null;
|
8
|
+
let sourcesList = null;
|
9
|
+
let tabsContainer = null;
|
10
|
+
|
11
|
+
// Component state
|
12
|
+
let currentResearchId = null;
|
13
|
+
let currentTopicId = null;
|
14
|
+
let detailData = null;
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Initialize the detail component
|
18
|
+
*/
|
19
|
+
function initializeDetail() {
|
20
|
+
// Get IDs from URL
|
21
|
+
const ids = getIdsFromUrl();
|
22
|
+
currentResearchId = ids.researchId;
|
23
|
+
currentTopicId = ids.topicId;
|
24
|
+
|
25
|
+
if (!currentResearchId || !currentTopicId) {
|
26
|
+
console.error('No research or topic ID found');
|
27
|
+
if (window.ui && window.ui.showError) {
|
28
|
+
window.ui.showError('Invalid research or topic. Please return to the results page.');
|
29
|
+
}
|
30
|
+
return;
|
31
|
+
}
|
32
|
+
|
33
|
+
// Get DOM elements
|
34
|
+
detailContainer = document.getElementById('research-log');
|
35
|
+
|
36
|
+
if (!detailContainer) {
|
37
|
+
console.error('Required DOM elements not found for detail component');
|
38
|
+
return;
|
39
|
+
}
|
40
|
+
|
41
|
+
// Set up event listeners
|
42
|
+
setupEventListeners();
|
43
|
+
|
44
|
+
// Load topic detail
|
45
|
+
loadTopicDetail();
|
46
|
+
|
47
|
+
console.log('Detail component initialized for research:', currentResearchId, 'topic:', currentTopicId);
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Extract research and topic IDs from URL
|
52
|
+
* @returns {Object} Object with researchId and topicId
|
53
|
+
*/
|
54
|
+
function getIdsFromUrl() {
|
55
|
+
const pathParts = window.location.pathname.split('/');
|
56
|
+
const detailIndex = pathParts.indexOf('detail');
|
57
|
+
|
58
|
+
if (detailIndex > 0 && detailIndex + 2 < pathParts.length) {
|
59
|
+
return {
|
60
|
+
researchId: pathParts[detailIndex + 1],
|
61
|
+
topicId: pathParts[detailIndex + 2]
|
62
|
+
};
|
63
|
+
}
|
64
|
+
|
65
|
+
return { researchId: null, topicId: null };
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Set up event listeners
|
70
|
+
*/
|
71
|
+
function setupEventListeners() {
|
72
|
+
// Back button
|
73
|
+
const backButton = document.getElementById('back-to-history-from-details');
|
74
|
+
if (backButton) {
|
75
|
+
backButton.addEventListener('click', function() {
|
76
|
+
window.location.href = '/research/history';
|
77
|
+
});
|
78
|
+
}
|
79
|
+
|
80
|
+
// Progress elements
|
81
|
+
const progressBar = document.getElementById('detail-progress-fill');
|
82
|
+
const progressPercentage = document.getElementById('detail-progress-percentage');
|
83
|
+
|
84
|
+
// Tab click events
|
85
|
+
if (tabsContainer) {
|
86
|
+
tabsContainer.addEventListener('click', function(e) {
|
87
|
+
if (e.target && e.target.matches('.tab-item')) {
|
88
|
+
const tabId = e.target.dataset.tab;
|
89
|
+
switchTab(tabId);
|
90
|
+
}
|
91
|
+
});
|
92
|
+
}
|
93
|
+
|
94
|
+
// Source highlight and citation copy
|
95
|
+
if (sourcesList) {
|
96
|
+
sourcesList.addEventListener('click', function(e) {
|
97
|
+
// Handle citation copy
|
98
|
+
if (e.target && e.target.matches('.copy-citation-btn')) {
|
99
|
+
const sourceId = e.target.closest('.source-item').dataset.id;
|
100
|
+
handleCopyCitation(sourceId);
|
101
|
+
}
|
102
|
+
|
103
|
+
// Handle source highlighting
|
104
|
+
if (e.target && e.target.closest('.source-item')) {
|
105
|
+
const sourceItem = e.target.closest('.source-item');
|
106
|
+
if (!e.target.matches('.copy-citation-btn')) {
|
107
|
+
toggleSourceHighlight(sourceItem);
|
108
|
+
}
|
109
|
+
}
|
110
|
+
});
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Load topic detail from API
|
116
|
+
*/
|
117
|
+
async function loadTopicDetail() {
|
118
|
+
// Show loading state
|
119
|
+
window.ui.showSpinner(detailContainer, 'Loading topic details...');
|
120
|
+
|
121
|
+
try {
|
122
|
+
// Get topic detail
|
123
|
+
detailData = await window.api.getTopicDetail(currentResearchId, currentTopicId);
|
124
|
+
|
125
|
+
if (!detailData) {
|
126
|
+
throw new Error('No topic details found');
|
127
|
+
}
|
128
|
+
|
129
|
+
// Render detail
|
130
|
+
renderDetail(detailData);
|
131
|
+
} catch (error) {
|
132
|
+
console.error('Error loading topic detail:', error);
|
133
|
+
window.ui.hideSpinner(detailContainer);
|
134
|
+
window.ui.showError('Error loading topic detail: ' + error.message);
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
/**
|
139
|
+
* Render topic detail
|
140
|
+
* @param {Object} data - The topic detail data
|
141
|
+
*/
|
142
|
+
function renderDetail(data) {
|
143
|
+
// Hide spinner
|
144
|
+
window.ui.hideSpinner(detailContainer);
|
145
|
+
|
146
|
+
// Set page title
|
147
|
+
document.title = `${data.title || 'Topic Detail'} - Local Deep Research`;
|
148
|
+
|
149
|
+
// Update page header
|
150
|
+
const pageTitle = document.querySelector('h1.page-title');
|
151
|
+
if (pageTitle && data.title) {
|
152
|
+
pageTitle.textContent = data.title;
|
153
|
+
}
|
154
|
+
|
155
|
+
// Render content
|
156
|
+
if (data.content) {
|
157
|
+
// Render markdown
|
158
|
+
const renderedHtml = window.ui.renderMarkdown(data.content);
|
159
|
+
detailContainer.innerHTML = renderedHtml;
|
160
|
+
|
161
|
+
// Add syntax highlighting
|
162
|
+
highlightCodeBlocks();
|
163
|
+
} else {
|
164
|
+
detailContainer.innerHTML = '<p class="text-error">No content available for this topic.</p>';
|
165
|
+
}
|
166
|
+
|
167
|
+
// Render sources
|
168
|
+
if (sourcesList && data.sources && data.sources.length > 0) {
|
169
|
+
renderSources(data.sources);
|
170
|
+
}
|
171
|
+
|
172
|
+
// Set sources count
|
173
|
+
const sourcesCountEl = document.getElementById('sources-count');
|
174
|
+
if (sourcesCountEl && data.sources) {
|
175
|
+
sourcesCountEl.textContent = `${data.sources.length} source${data.sources.length !== 1 ? 's' : ''}`;
|
176
|
+
}
|
177
|
+
|
178
|
+
// Show first tab
|
179
|
+
if (tabsContainer) {
|
180
|
+
const firstTab = tabsContainer.querySelector('.tab-item');
|
181
|
+
if (firstTab) {
|
182
|
+
switchTab(firstTab.dataset.tab);
|
183
|
+
}
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
/**
|
188
|
+
* Render sources list
|
189
|
+
* @param {Array} sources - Array of source objects
|
190
|
+
*/
|
191
|
+
function renderSources(sources) {
|
192
|
+
sourcesList.innerHTML = '';
|
193
|
+
|
194
|
+
sources.forEach((source, index) => {
|
195
|
+
const sourceEl = document.createElement('div');
|
196
|
+
sourceEl.className = 'source-item';
|
197
|
+
sourceEl.dataset.id = index;
|
198
|
+
|
199
|
+
const title = source.title || source.url || `Source ${index + 1}`;
|
200
|
+
const url = source.url || '';
|
201
|
+
const author = source.author || '';
|
202
|
+
const date = source.date || '';
|
203
|
+
|
204
|
+
sourceEl.innerHTML = `
|
205
|
+
<div class="source-header">
|
206
|
+
<h4 class="source-title">${title}</h4>
|
207
|
+
<button class="copy-citation-btn" title="Copy citation">
|
208
|
+
<i class="fas fa-clipboard"></i>
|
209
|
+
</button>
|
210
|
+
</div>
|
211
|
+
${url ? `<div class="source-url"><a href="${url}" target="_blank">${url}</a></div>` : ''}
|
212
|
+
<div class="source-metadata">
|
213
|
+
${author ? `<span><i class="fas fa-user"></i> ${author}</span>` : ''}
|
214
|
+
${date ? `<span><i class="far fa-calendar"></i> ${date}</span>` : ''}
|
215
|
+
</div>
|
216
|
+
${source.relevance ? `<div class="source-relevance">Relevance: ${source.relevance}</div>` : ''}
|
217
|
+
`;
|
218
|
+
|
219
|
+
sourcesList.appendChild(sourceEl);
|
220
|
+
});
|
221
|
+
}
|
222
|
+
|
223
|
+
/**
|
224
|
+
* Switch between tabs
|
225
|
+
* @param {string} tabId - The tab ID to switch to
|
226
|
+
*/
|
227
|
+
function switchTab(tabId) {
|
228
|
+
// Update active tab
|
229
|
+
const tabs = document.querySelectorAll('.tab-item');
|
230
|
+
tabs.forEach(tab => {
|
231
|
+
tab.classList.toggle('active', tab.dataset.tab === tabId);
|
232
|
+
});
|
233
|
+
|
234
|
+
// Update visible content
|
235
|
+
const tabContents = document.querySelectorAll('.tab-content');
|
236
|
+
tabContents.forEach(content => {
|
237
|
+
content.style.display = content.dataset.tab === tabId ? 'block' : 'none';
|
238
|
+
});
|
239
|
+
}
|
240
|
+
|
241
|
+
/**
|
242
|
+
* Toggle source highlight
|
243
|
+
* @param {HTMLElement} sourceEl - The source element to highlight
|
244
|
+
*/
|
245
|
+
function toggleSourceHighlight(sourceEl) {
|
246
|
+
// Remove highlight from all sources
|
247
|
+
const allSources = document.querySelectorAll('.source-item');
|
248
|
+
allSources.forEach(s => s.classList.remove('highlighted'));
|
249
|
+
|
250
|
+
// Add highlight to clicked source
|
251
|
+
sourceEl.classList.add('highlighted');
|
252
|
+
|
253
|
+
// Get source index
|
254
|
+
const sourceIndex = parseInt(sourceEl.dataset.id);
|
255
|
+
|
256
|
+
// Highlight content with this source
|
257
|
+
highlightContentFromSource(sourceIndex);
|
258
|
+
}
|
259
|
+
|
260
|
+
/**
|
261
|
+
* Highlight content sections from a specific source
|
262
|
+
* @param {number} sourceIndex - The source index to highlight
|
263
|
+
*/
|
264
|
+
function highlightContentFromSource(sourceIndex) {
|
265
|
+
// Remove all existing highlights
|
266
|
+
const allCitations = document.querySelectorAll('.citation-highlight');
|
267
|
+
allCitations.forEach(el => {
|
268
|
+
el.classList.remove('citation-highlight');
|
269
|
+
});
|
270
|
+
|
271
|
+
// Add highlight to citations from this source
|
272
|
+
const citations = document.querySelectorAll(`.citation[data-source="${sourceIndex}"]`);
|
273
|
+
citations.forEach(citation => {
|
274
|
+
citation.classList.add('citation-highlight');
|
275
|
+
|
276
|
+
// Scroll to first citation
|
277
|
+
if (citations.length > 0) {
|
278
|
+
citations[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
279
|
+
}
|
280
|
+
});
|
281
|
+
}
|
282
|
+
|
283
|
+
/**
|
284
|
+
* Handle copying citation
|
285
|
+
* @param {number} sourceIndex - The source index to copy
|
286
|
+
*/
|
287
|
+
async function handleCopyCitation(sourceIndex) {
|
288
|
+
if (!detailData || !detailData.sources || !detailData.sources[sourceIndex]) {
|
289
|
+
return;
|
290
|
+
}
|
291
|
+
|
292
|
+
const source = detailData.sources[sourceIndex];
|
293
|
+
|
294
|
+
// Format citation
|
295
|
+
let citation = '';
|
296
|
+
|
297
|
+
if (source.author) {
|
298
|
+
citation += source.author;
|
299
|
+
if (source.date) {
|
300
|
+
citation += ` (${source.date})`;
|
301
|
+
}
|
302
|
+
citation += '. ';
|
303
|
+
}
|
304
|
+
|
305
|
+
if (source.title) {
|
306
|
+
citation += `"${source.title}". `;
|
307
|
+
}
|
308
|
+
|
309
|
+
if (source.url) {
|
310
|
+
citation += `Retrieved from ${source.url}`;
|
311
|
+
}
|
312
|
+
|
313
|
+
// Copy to clipboard
|
314
|
+
try {
|
315
|
+
await navigator.clipboard.writeText(citation);
|
316
|
+
|
317
|
+
// Show tooltip
|
318
|
+
const btn = document.querySelector(`.source-item[data-id="${sourceIndex}"] .copy-citation-btn`);
|
319
|
+
if (btn) {
|
320
|
+
const originalHTML = btn.innerHTML;
|
321
|
+
btn.innerHTML = '<i class="fas fa-check"></i>';
|
322
|
+
|
323
|
+
setTimeout(() => {
|
324
|
+
btn.innerHTML = originalHTML;
|
325
|
+
}, 2000);
|
326
|
+
}
|
327
|
+
} catch (error) {
|
328
|
+
console.error('Failed to copy citation:', error);
|
329
|
+
}
|
330
|
+
}
|
331
|
+
|
332
|
+
/**
|
333
|
+
* Apply syntax highlighting to code blocks
|
334
|
+
*/
|
335
|
+
function highlightCodeBlocks() {
|
336
|
+
// Check if Prism is available
|
337
|
+
if (typeof Prism !== 'undefined') {
|
338
|
+
Prism.highlightAllUnder(detailContainer);
|
339
|
+
}
|
340
|
+
}
|
341
|
+
|
342
|
+
// Initialize on DOM content loaded
|
343
|
+
if (document.readyState === 'loading') {
|
344
|
+
document.addEventListener('DOMContentLoaded', initializeDetail);
|
345
|
+
} else {
|
346
|
+
initializeDetail();
|
347
|
+
}
|
348
|
+
})();
|
@@ -0,0 +1,122 @@
|
|
1
|
+
/**
|
2
|
+
* Formatting Fallback Utilities
|
3
|
+
* Basic implementations of formatting utilities that can be used if the main formatting module is not available
|
4
|
+
*/
|
5
|
+
(function() {
|
6
|
+
// Only initialize if window.formatting is not already defined
|
7
|
+
if (window.formatting) {
|
8
|
+
console.log('Main formatting utilities already available, skipping fallback');
|
9
|
+
return;
|
10
|
+
}
|
11
|
+
|
12
|
+
console.log('Initializing fallback formatting utilities');
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Format a date
|
16
|
+
* @param {string} dateStr - ISO date string
|
17
|
+
* @returns {string} Formatted date
|
18
|
+
*/
|
19
|
+
function formatDate(dateStr) {
|
20
|
+
if (!dateStr) return 'N/A';
|
21
|
+
|
22
|
+
try {
|
23
|
+
const date = new Date(dateStr);
|
24
|
+
return date.toLocaleString(undefined, {
|
25
|
+
year: 'numeric',
|
26
|
+
month: 'short',
|
27
|
+
day: 'numeric',
|
28
|
+
hour: '2-digit',
|
29
|
+
minute: '2-digit',
|
30
|
+
second: '2-digit'
|
31
|
+
});
|
32
|
+
} catch (e) {
|
33
|
+
console.error('Error formatting date:', e);
|
34
|
+
return dateStr;
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Format a status string
|
40
|
+
* @param {string} status - Status code
|
41
|
+
* @returns {string} Formatted status
|
42
|
+
*/
|
43
|
+
function formatStatus(status) {
|
44
|
+
if (!status) return 'Unknown';
|
45
|
+
|
46
|
+
const statusMap = {
|
47
|
+
'in_progress': 'In Progress',
|
48
|
+
'completed': 'Completed',
|
49
|
+
'failed': 'Failed',
|
50
|
+
'suspended': 'Suspended',
|
51
|
+
'pending': 'Pending',
|
52
|
+
'error': 'Error'
|
53
|
+
};
|
54
|
+
|
55
|
+
return statusMap[status] || status;
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Format a mode string
|
60
|
+
* @param {string} mode - Mode code
|
61
|
+
* @returns {string} Formatted mode
|
62
|
+
*/
|
63
|
+
function formatMode(mode) {
|
64
|
+
if (!mode) return 'Unknown';
|
65
|
+
|
66
|
+
const modeMap = {
|
67
|
+
'quick': 'Quick Summary',
|
68
|
+
'detailed': 'Detailed Report',
|
69
|
+
'standard': 'Standard Research',
|
70
|
+
'advanced': 'Advanced Research'
|
71
|
+
};
|
72
|
+
|
73
|
+
return modeMap[mode] || mode;
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Format duration in seconds to readable text
|
78
|
+
* @param {number} seconds - Duration in seconds
|
79
|
+
* @returns {string} Formatted duration
|
80
|
+
*/
|
81
|
+
function formatDuration(seconds) {
|
82
|
+
if (!seconds && seconds !== 0) return 'N/A';
|
83
|
+
|
84
|
+
const minutes = Math.floor(seconds / 60);
|
85
|
+
const remainingSeconds = seconds % 60;
|
86
|
+
|
87
|
+
if (minutes === 0) {
|
88
|
+
return `${remainingSeconds}s`;
|
89
|
+
} else {
|
90
|
+
return `${minutes}m ${remainingSeconds}s`;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Format file size in bytes to human readable format
|
96
|
+
* @param {number} bytes - Size in bytes
|
97
|
+
* @returns {string} Formatted size
|
98
|
+
*/
|
99
|
+
function formatFileSize(bytes) {
|
100
|
+
if (!bytes && bytes !== 0) return 'N/A';
|
101
|
+
|
102
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
103
|
+
let size = bytes;
|
104
|
+
let unitIndex = 0;
|
105
|
+
|
106
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
107
|
+
size /= 1024;
|
108
|
+
unitIndex++;
|
109
|
+
}
|
110
|
+
|
111
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
112
|
+
}
|
113
|
+
|
114
|
+
// Export utilities to window.formatting
|
115
|
+
window.formatting = {
|
116
|
+
formatDate,
|
117
|
+
formatStatus,
|
118
|
+
formatMode,
|
119
|
+
formatDuration,
|
120
|
+
formatFileSize
|
121
|
+
};
|
122
|
+
})();
|
@@ -0,0 +1,215 @@
|
|
1
|
+
/**
|
2
|
+
* UI Fallback Utilities
|
3
|
+
* Basic implementations of UI utilities that can be used if the main UI module is not available
|
4
|
+
*/
|
5
|
+
(function() {
|
6
|
+
// Only initialize if window.ui is not already defined
|
7
|
+
if (window.ui) {
|
8
|
+
console.log('Main UI utilities already available, skipping fallback');
|
9
|
+
return;
|
10
|
+
}
|
11
|
+
|
12
|
+
console.log('Initializing fallback UI utilities');
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Show a loading spinner
|
16
|
+
* @param {HTMLElement} container - Container element for spinner
|
17
|
+
* @param {string} message - Optional loading message
|
18
|
+
*/
|
19
|
+
function showSpinner(container, message) {
|
20
|
+
if (!container) container = document.body;
|
21
|
+
const spinnerHtml = `
|
22
|
+
<div class="loading-spinner centered">
|
23
|
+
<div class="spinner"></div>
|
24
|
+
${message ? `<div class="spinner-message">${message}</div>` : ''}
|
25
|
+
</div>
|
26
|
+
`;
|
27
|
+
container.innerHTML = spinnerHtml;
|
28
|
+
}
|
29
|
+
|
30
|
+
/**
|
31
|
+
* Hide loading spinner
|
32
|
+
* @param {HTMLElement} container - Container with spinner
|
33
|
+
*/
|
34
|
+
function hideSpinner(container) {
|
35
|
+
if (!container) container = document.body;
|
36
|
+
const spinner = container.querySelector('.loading-spinner');
|
37
|
+
if (spinner) {
|
38
|
+
spinner.remove();
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Show an error message
|
44
|
+
* @param {string} message - Error message to display
|
45
|
+
*/
|
46
|
+
function showError(message) {
|
47
|
+
console.error(message);
|
48
|
+
|
49
|
+
// Create a notification element
|
50
|
+
const notification = document.createElement('div');
|
51
|
+
notification.className = 'notification error';
|
52
|
+
notification.innerHTML = `
|
53
|
+
<i class="fas fa-exclamation-circle"></i>
|
54
|
+
<span>${message}</span>
|
55
|
+
<button class="close-notification"><i class="fas fa-times"></i></button>
|
56
|
+
`;
|
57
|
+
|
58
|
+
// Add to the page if a notification container exists, otherwise use alert
|
59
|
+
const container = document.querySelector('.notifications-container');
|
60
|
+
if (container) {
|
61
|
+
container.appendChild(notification);
|
62
|
+
|
63
|
+
// Remove after a delay
|
64
|
+
setTimeout(() => {
|
65
|
+
notification.classList.add('removing');
|
66
|
+
setTimeout(() => notification.remove(), 500);
|
67
|
+
}, 5000);
|
68
|
+
|
69
|
+
// Set up close button
|
70
|
+
const closeBtn = notification.querySelector('.close-notification');
|
71
|
+
if (closeBtn) {
|
72
|
+
closeBtn.addEventListener('click', () => {
|
73
|
+
notification.classList.add('removing');
|
74
|
+
setTimeout(() => notification.remove(), 500);
|
75
|
+
});
|
76
|
+
}
|
77
|
+
} else {
|
78
|
+
// Fallback to alert
|
79
|
+
alert(message);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
/**
|
84
|
+
* Show a success/info message
|
85
|
+
* @param {string} message - Message to display
|
86
|
+
*/
|
87
|
+
function showMessage(message) {
|
88
|
+
console.log(message);
|
89
|
+
|
90
|
+
// Create a notification element
|
91
|
+
const notification = document.createElement('div');
|
92
|
+
notification.className = 'notification success';
|
93
|
+
notification.innerHTML = `
|
94
|
+
<i class="fas fa-check-circle"></i>
|
95
|
+
<span>${message}</span>
|
96
|
+
<button class="close-notification"><i class="fas fa-times"></i></button>
|
97
|
+
`;
|
98
|
+
|
99
|
+
// Add to the page if a notification container exists, otherwise use alert
|
100
|
+
const container = document.querySelector('.notifications-container');
|
101
|
+
if (container) {
|
102
|
+
container.appendChild(notification);
|
103
|
+
|
104
|
+
// Remove after a delay
|
105
|
+
setTimeout(() => {
|
106
|
+
notification.classList.add('removing');
|
107
|
+
setTimeout(() => notification.remove(), 500);
|
108
|
+
}, 5000);
|
109
|
+
|
110
|
+
// Set up close button
|
111
|
+
const closeBtn = notification.querySelector('.close-notification');
|
112
|
+
if (closeBtn) {
|
113
|
+
closeBtn.addEventListener('click', () => {
|
114
|
+
notification.classList.add('removing');
|
115
|
+
setTimeout(() => notification.remove(), 500);
|
116
|
+
});
|
117
|
+
}
|
118
|
+
} else {
|
119
|
+
// Fallback to alert
|
120
|
+
alert(message);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
/**
|
125
|
+
* Simple markdown renderer
|
126
|
+
* @param {string} markdown - Markdown content
|
127
|
+
* @returns {string} HTML content
|
128
|
+
*/
|
129
|
+
function renderMarkdown(markdown) {
|
130
|
+
if (!markdown) return '';
|
131
|
+
|
132
|
+
// This is a very basic markdown renderer for fallback purposes
|
133
|
+
let html = markdown;
|
134
|
+
|
135
|
+
// Convert headers
|
136
|
+
html = html.replace(/^# (.*$)/gm, '<h1>$1</h1>');
|
137
|
+
html = html.replace(/^## (.*$)/gm, '<h2>$1</h2>');
|
138
|
+
html = html.replace(/^### (.*$)/gm, '<h3>$1</h3>');
|
139
|
+
html = html.replace(/^#### (.*$)/gm, '<h4>$1</h4>');
|
140
|
+
html = html.replace(/^##### (.*$)/gm, '<h5>$1</h5>');
|
141
|
+
|
142
|
+
// Convert code blocks
|
143
|
+
html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
|
144
|
+
|
145
|
+
// Convert inline code
|
146
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
147
|
+
|
148
|
+
// Convert bold
|
149
|
+
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
150
|
+
|
151
|
+
// Convert italic
|
152
|
+
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
153
|
+
|
154
|
+
// Convert links
|
155
|
+
html = html.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>');
|
156
|
+
|
157
|
+
// Convert paragraphs - this is simplistic
|
158
|
+
html = html.replace(/\n\s*\n/g, '</p><p>');
|
159
|
+
html = '<p>' + html + '</p>';
|
160
|
+
|
161
|
+
// Fix potentially broken paragraph tags
|
162
|
+
html = html.replace(/<\/p><p><\/p><p>/g, '</p><p>');
|
163
|
+
html = html.replace(/<\/p><p><(h[1-5])/g, '</p><$1');
|
164
|
+
html = html.replace(/<\/(h[1-5])><p>/g, '</$1>');
|
165
|
+
|
166
|
+
return html;
|
167
|
+
}
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Update favicon to indicate status
|
171
|
+
* @param {string} status - Status to indicate (active, complete, error)
|
172
|
+
*/
|
173
|
+
function updateFavicon(status) {
|
174
|
+
try {
|
175
|
+
const faviconLink = document.querySelector('link[rel="icon"]') ||
|
176
|
+
document.querySelector('link[rel="shortcut icon"]');
|
177
|
+
|
178
|
+
if (!faviconLink) {
|
179
|
+
console.warn('Favicon link not found');
|
180
|
+
return;
|
181
|
+
}
|
182
|
+
|
183
|
+
let iconPath;
|
184
|
+
switch (status) {
|
185
|
+
case 'active':
|
186
|
+
iconPath = '/research/static/img/favicon-active.ico';
|
187
|
+
break;
|
188
|
+
case 'complete':
|
189
|
+
iconPath = '/research/static/img/favicon-complete.ico';
|
190
|
+
break;
|
191
|
+
case 'error':
|
192
|
+
iconPath = '/research/static/img/favicon-error.ico';
|
193
|
+
break;
|
194
|
+
default:
|
195
|
+
iconPath = '/research/static/img/favicon.ico';
|
196
|
+
}
|
197
|
+
|
198
|
+
// Add cache busting parameter to force reload
|
199
|
+
faviconLink.href = iconPath + '?v=' + new Date().getTime();
|
200
|
+
console.log('Updated favicon to:', status);
|
201
|
+
} catch (error) {
|
202
|
+
console.error('Failed to update favicon:', error);
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
// Export utilities to window.ui
|
207
|
+
window.ui = {
|
208
|
+
showSpinner,
|
209
|
+
hideSpinner,
|
210
|
+
showError,
|
211
|
+
showMessage,
|
212
|
+
renderMarkdown,
|
213
|
+
updateFavicon
|
214
|
+
};
|
215
|
+
})();
|