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,766 @@
|
|
1
|
+
/**
|
2
|
+
* Results Component
|
3
|
+
* Handles the display of research results
|
4
|
+
*/
|
5
|
+
(function() {
|
6
|
+
// DOM Elements
|
7
|
+
let resultsContainer = null;
|
8
|
+
let exportBtn = null;
|
9
|
+
let pdfBtn = null;
|
10
|
+
let researchId = null;
|
11
|
+
let researchData = null;
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Initialize the results component
|
15
|
+
*/
|
16
|
+
function initializeResults() {
|
17
|
+
// Get DOM elements
|
18
|
+
resultsContainer = document.getElementById('results-content');
|
19
|
+
exportBtn = document.getElementById('export-markdown-btn');
|
20
|
+
pdfBtn = document.getElementById('download-pdf-btn');
|
21
|
+
|
22
|
+
if (!resultsContainer) {
|
23
|
+
console.error('Results container not found');
|
24
|
+
return;
|
25
|
+
}
|
26
|
+
|
27
|
+
console.log('Results component initialized');
|
28
|
+
|
29
|
+
// Get research ID from URL
|
30
|
+
researchId = getResearchIdFromUrl();
|
31
|
+
|
32
|
+
if (!researchId) {
|
33
|
+
showError('Research ID not found in URL');
|
34
|
+
return;
|
35
|
+
}
|
36
|
+
|
37
|
+
// Set up event listeners
|
38
|
+
setupEventListeners();
|
39
|
+
|
40
|
+
// Load research results
|
41
|
+
loadResearchResults();
|
42
|
+
|
43
|
+
// Note: Log panel is now automatically initialized by logpanel.js
|
44
|
+
// No need to manually initialize it here
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Set up event listeners
|
49
|
+
*/
|
50
|
+
function setupEventListeners() {
|
51
|
+
// Export button
|
52
|
+
if (exportBtn) {
|
53
|
+
exportBtn.addEventListener('click', handleExport);
|
54
|
+
}
|
55
|
+
|
56
|
+
// PDF button
|
57
|
+
if (pdfBtn) {
|
58
|
+
pdfBtn.addEventListener('click', handlePdfExport);
|
59
|
+
}
|
60
|
+
|
61
|
+
// Back to history button
|
62
|
+
const backBtn = document.getElementById('back-to-history');
|
63
|
+
if (backBtn) {
|
64
|
+
backBtn.addEventListener('click', () => {
|
65
|
+
window.location.href = '/research/history';
|
66
|
+
});
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
/**
|
71
|
+
* Get research ID from URL
|
72
|
+
* @returns {string|null} Research ID
|
73
|
+
*/
|
74
|
+
function getResearchIdFromUrl() {
|
75
|
+
const path = window.location.pathname;
|
76
|
+
const match = path.match(/\/research\/results\/(\d+)/);
|
77
|
+
return match ? match[1] : null;
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Load research results from API
|
82
|
+
*/
|
83
|
+
async function loadResearchResults() {
|
84
|
+
try {
|
85
|
+
// Show loading state
|
86
|
+
resultsContainer.innerHTML = '<div class="text-center my-5"><i class="fas fa-spinner fa-pulse"></i><p class="mt-3">Loading research results...</p></div>';
|
87
|
+
|
88
|
+
// Fetch result from API
|
89
|
+
const response = await fetch(`/research/api/report/${researchId}`);
|
90
|
+
|
91
|
+
if (!response.ok) {
|
92
|
+
throw new Error(`HTTP error ${response.status}`);
|
93
|
+
}
|
94
|
+
|
95
|
+
const responseData = await response.json();
|
96
|
+
console.log('Original API response:', responseData);
|
97
|
+
|
98
|
+
// Store data for export
|
99
|
+
researchData = responseData;
|
100
|
+
|
101
|
+
// Check if we have data to display
|
102
|
+
if (!responseData) {
|
103
|
+
throw new Error('No data received from server');
|
104
|
+
}
|
105
|
+
|
106
|
+
// Use the API metadata directly
|
107
|
+
if (responseData.metadata && typeof responseData.metadata === 'object') {
|
108
|
+
console.log('Using metadata directly from API response:', responseData.metadata);
|
109
|
+
populateMetadataFromApiResponse(responseData);
|
110
|
+
} else {
|
111
|
+
// Fallback to content extraction if no metadata in response
|
112
|
+
populateMetadata(responseData);
|
113
|
+
}
|
114
|
+
|
115
|
+
// Render the content
|
116
|
+
if (responseData.content && typeof responseData.content === 'string') {
|
117
|
+
console.log('Rendering content from API response');
|
118
|
+
renderResults(responseData.content);
|
119
|
+
} else {
|
120
|
+
// Try to find content in other response formats
|
121
|
+
console.log('No direct content found, trying to find content in response');
|
122
|
+
findAndRenderContent(responseData);
|
123
|
+
}
|
124
|
+
|
125
|
+
// Enable export buttons
|
126
|
+
if (exportBtn) exportBtn.disabled = false;
|
127
|
+
if (pdfBtn) pdfBtn.disabled = false;
|
128
|
+
|
129
|
+
} catch (error) {
|
130
|
+
console.error('Error loading research results:', error);
|
131
|
+
showError(`Error loading research results: ${error.message}`);
|
132
|
+
|
133
|
+
// Disable export buttons
|
134
|
+
if (exportBtn) exportBtn.disabled = true;
|
135
|
+
if (pdfBtn) pdfBtn.disabled = true;
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
/**
|
140
|
+
* Populate metadata directly from API response metadata
|
141
|
+
* @param {Object} data - API response with metadata
|
142
|
+
*/
|
143
|
+
function populateMetadataFromApiResponse(data) {
|
144
|
+
const metadata = data.metadata || {};
|
145
|
+
console.log('Using API response metadata:', metadata);
|
146
|
+
|
147
|
+
// Query field
|
148
|
+
const queryElement = document.getElementById('result-query');
|
149
|
+
if (queryElement) {
|
150
|
+
// Use query from metadata or content title
|
151
|
+
const query = metadata.query || metadata.title || data.query || 'Untitled Research';
|
152
|
+
console.log('Setting query to:', query);
|
153
|
+
queryElement.textContent = query;
|
154
|
+
}
|
155
|
+
|
156
|
+
// Generated date field
|
157
|
+
const dateElement = document.getElementById('result-date');
|
158
|
+
if (dateElement) {
|
159
|
+
let dateStr = 'Unknown date';
|
160
|
+
|
161
|
+
// Try multiple sources for the timestamp - first from the API response directly, then from metadata
|
162
|
+
const timestamp = data.created_at || data.timestamp || data.date ||
|
163
|
+
metadata.created_at || metadata.timestamp || metadata.date;
|
164
|
+
|
165
|
+
console.log('Found timestamp:', timestamp);
|
166
|
+
|
167
|
+
if (timestamp) {
|
168
|
+
if (window.formatting && typeof window.formatting.formatDate === 'function') {
|
169
|
+
dateStr = window.formatting.formatDate(timestamp);
|
170
|
+
console.log('Formatting timestamp with formatter:', timestamp, '→', dateStr);
|
171
|
+
} else {
|
172
|
+
try {
|
173
|
+
const date = new Date(timestamp);
|
174
|
+
dateStr = date.toLocaleString();
|
175
|
+
console.log('Formatting timestamp with toLocaleString:', timestamp, '→', dateStr);
|
176
|
+
} catch (e) {
|
177
|
+
console.error('Error parsing date:', e);
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
// Add duration if available - format as "Xm Ys" for values over 60 seconds
|
182
|
+
if (metadata.duration || metadata.duration_seconds || data.duration_seconds) {
|
183
|
+
const durationSeconds = parseInt(metadata.duration || metadata.duration_seconds || data.duration_seconds, 10);
|
184
|
+
|
185
|
+
if (!isNaN(durationSeconds)) {
|
186
|
+
let durationStr;
|
187
|
+
if (durationSeconds < 60) {
|
188
|
+
durationStr = `${durationSeconds}s`;
|
189
|
+
} else {
|
190
|
+
const minutes = Math.floor(durationSeconds / 60);
|
191
|
+
const seconds = durationSeconds % 60;
|
192
|
+
durationStr = `${minutes}m ${seconds}s`;
|
193
|
+
}
|
194
|
+
dateStr += ` (${durationStr})`;
|
195
|
+
}
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
199
|
+
console.log('Setting date to:', dateStr);
|
200
|
+
dateElement.textContent = dateStr;
|
201
|
+
}
|
202
|
+
|
203
|
+
// Mode field
|
204
|
+
const modeElement = document.getElementById('result-mode');
|
205
|
+
if (modeElement) {
|
206
|
+
// Get mode from metadata or main response
|
207
|
+
let mode = metadata.mode || metadata.research_mode || metadata.type ||
|
208
|
+
data.mode || data.research_mode || data.type;
|
209
|
+
|
210
|
+
// Detect if this is a detailed report based on content structure
|
211
|
+
if (!mode && data.content) {
|
212
|
+
if (data.content.toLowerCase().includes('table of contents') ||
|
213
|
+
data.content.match(/^#.*\n+##.*\n+###/m)) {
|
214
|
+
mode = 'detailed';
|
215
|
+
} else {
|
216
|
+
mode = 'quick';
|
217
|
+
}
|
218
|
+
}
|
219
|
+
|
220
|
+
// Format mode using available formatter
|
221
|
+
if (window.formatting && typeof window.formatting.formatMode === 'function') {
|
222
|
+
mode = window.formatting.formatMode(mode);
|
223
|
+
console.log('Formatted mode:', mode);
|
224
|
+
}
|
225
|
+
|
226
|
+
console.log('Setting mode to:', mode || 'Quick');
|
227
|
+
modeElement.textContent = mode || 'Quick';
|
228
|
+
}
|
229
|
+
}
|
230
|
+
|
231
|
+
/**
|
232
|
+
* Find and render content from various response formats
|
233
|
+
* @param {Object} data - Research data to extract content from
|
234
|
+
*/
|
235
|
+
function findAndRenderContent(data) {
|
236
|
+
if (data.content && typeof data.content === 'string') {
|
237
|
+
// Direct content property (newer format)
|
238
|
+
console.log('Rendering from data.content');
|
239
|
+
renderResults(data.content);
|
240
|
+
} else if (data.research && data.research.content) {
|
241
|
+
// Nested content in research object (older format)
|
242
|
+
console.log('Rendering from data.research.content');
|
243
|
+
renderResults(data.research.content);
|
244
|
+
} else if (data.report && typeof data.report === 'string') {
|
245
|
+
// Report format
|
246
|
+
console.log('Rendering from data.report');
|
247
|
+
renderResults(data.report);
|
248
|
+
} else if (data.results && data.results.content) {
|
249
|
+
// Results with content field
|
250
|
+
console.log('Rendering from data.results.content');
|
251
|
+
renderResults(data.results.content);
|
252
|
+
} else if (data.results && typeof data.results === 'string') {
|
253
|
+
// Results as direct string
|
254
|
+
console.log('Rendering from data.results string');
|
255
|
+
renderResults(data.results);
|
256
|
+
} else if (typeof data === 'string') {
|
257
|
+
// Plain string format
|
258
|
+
console.log('Rendering from string data');
|
259
|
+
renderResults(data);
|
260
|
+
} else {
|
261
|
+
// Look for any property that might contain the content
|
262
|
+
const contentProps = ['markdown', 'text', 'summary', 'output', 'research_output'];
|
263
|
+
let foundContent = false;
|
264
|
+
|
265
|
+
for (const prop of contentProps) {
|
266
|
+
if (data[prop] && typeof data[prop] === 'string') {
|
267
|
+
console.log(`Rendering from data.${prop}`);
|
268
|
+
renderResults(data[prop]);
|
269
|
+
foundContent = true;
|
270
|
+
break;
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|
274
|
+
if (!foundContent) {
|
275
|
+
// Last resort: try to render the entire data object
|
276
|
+
console.log('No clear content found, rendering entire data object');
|
277
|
+
renderResults(data);
|
278
|
+
}
|
279
|
+
}
|
280
|
+
}
|
281
|
+
|
282
|
+
/**
|
283
|
+
* Populate metadata fields with information from the research data
|
284
|
+
* @param {Object} data - Research data with metadata
|
285
|
+
*/
|
286
|
+
function populateMetadata(data) {
|
287
|
+
// Debug the data structure
|
288
|
+
console.log('API response data:', data);
|
289
|
+
console.log('Data type:', typeof data);
|
290
|
+
console.log('Available top-level keys:', Object.keys(data));
|
291
|
+
|
292
|
+
// Direct extraction from content
|
293
|
+
if (data.content && typeof data.content === 'string') {
|
294
|
+
console.log('Attempting to extract metadata from content');
|
295
|
+
|
296
|
+
// Extract the query from content first line or header
|
297
|
+
// Avoid matching "Table of Contents" as query
|
298
|
+
const queryMatch = data.content.match(/^#\s*([^\n]+)/m) || // First heading
|
299
|
+
data.content.match(/Query:\s*([^\n]+)/i) || // Explicit query label
|
300
|
+
data.content.match(/Question:\s*([^\n]+)/i) || // Question label
|
301
|
+
data.content.match(/^([^\n#]+)(?=\n)/); // First line if not starting with #
|
302
|
+
|
303
|
+
if (queryMatch && queryMatch[1] && !queryMatch[1].toLowerCase().includes('table of contents')) {
|
304
|
+
const queryElement = document.getElementById('result-query');
|
305
|
+
if (queryElement) {
|
306
|
+
const extractedQuery = queryMatch[1].trim();
|
307
|
+
console.log('Extracted query from content:', extractedQuery);
|
308
|
+
queryElement.textContent = extractedQuery;
|
309
|
+
}
|
310
|
+
} else {
|
311
|
+
// Try to find the second heading if first was "Table of Contents"
|
312
|
+
const secondHeadingMatch = data.content.match(/^#\s*([^\n]+)[\s\S]*?^##\s*([^\n]+)/m);
|
313
|
+
if (secondHeadingMatch && secondHeadingMatch[2]) {
|
314
|
+
const queryElement = document.getElementById('result-query');
|
315
|
+
if (queryElement) {
|
316
|
+
const extractedQuery = secondHeadingMatch[2].trim();
|
317
|
+
console.log('Extracted query from second heading:', extractedQuery);
|
318
|
+
queryElement.textContent = extractedQuery;
|
319
|
+
}
|
320
|
+
}
|
321
|
+
}
|
322
|
+
|
323
|
+
// Extract generated date/time - Try multiple formats
|
324
|
+
const dateMatch = data.content.match(/Generated at:\s*([^\n]+)/i) ||
|
325
|
+
data.content.match(/Date:\s*([^\n]+)/i) ||
|
326
|
+
data.content.match(/Generated:\s*([^\n]+)/i) ||
|
327
|
+
data.content.match(/Created:\s*([^\n]+)/i);
|
328
|
+
|
329
|
+
if (dateMatch && dateMatch[1]) {
|
330
|
+
const dateElement = document.getElementById('result-date');
|
331
|
+
if (dateElement) {
|
332
|
+
const extractedDate = dateMatch[1].trim();
|
333
|
+
console.log('Extracted date from content:', extractedDate);
|
334
|
+
|
335
|
+
// Format the date using the available formatter
|
336
|
+
let formattedDate = extractedDate;
|
337
|
+
if (window.formatting && typeof window.formatting.formatDate === 'function') {
|
338
|
+
formattedDate = window.formatting.formatDate(extractedDate);
|
339
|
+
console.log('Date formatted using formatter:', formattedDate);
|
340
|
+
}
|
341
|
+
|
342
|
+
dateElement.textContent = formattedDate || new Date().toLocaleString();
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
// Extract mode
|
347
|
+
const modeMatch = data.content.match(/Mode:\s*([^\n]+)/i) ||
|
348
|
+
data.content.match(/Research type:\s*([^\n]+)/i);
|
349
|
+
|
350
|
+
if (modeMatch && modeMatch[1]) {
|
351
|
+
const modeElement = document.getElementById('result-mode');
|
352
|
+
if (modeElement) {
|
353
|
+
const extractedMode = modeMatch[1].trim();
|
354
|
+
console.log('Extracted mode from content:', extractedMode);
|
355
|
+
|
356
|
+
// Format mode using available formatter
|
357
|
+
let formattedMode = extractedMode;
|
358
|
+
if (window.formatting && typeof window.formatting.formatMode === 'function') {
|
359
|
+
formattedMode = window.formatting.formatMode(extractedMode);
|
360
|
+
console.log('Mode formatted using formatter:', formattedMode);
|
361
|
+
}
|
362
|
+
|
363
|
+
modeElement.textContent = formattedMode || 'Standard';
|
364
|
+
}
|
365
|
+
} else {
|
366
|
+
// Detect mode based on content structure and keywords
|
367
|
+
const modeElement = document.getElementById('result-mode');
|
368
|
+
if (modeElement) {
|
369
|
+
if (data.content.toLowerCase().includes('table of contents') ||
|
370
|
+
data.content.toLowerCase().includes('detailed report') ||
|
371
|
+
data.content.match(/^#.*\n+##.*\n+###/m)) { // Has H1, H2, H3 structure
|
372
|
+
modeElement.textContent = 'Detailed';
|
373
|
+
} else if (data.content.toLowerCase().includes('quick research') ||
|
374
|
+
data.content.toLowerCase().includes('summary')) {
|
375
|
+
modeElement.textContent = 'Quick';
|
376
|
+
} else {
|
377
|
+
modeElement.textContent = 'Standard'; // Better default
|
378
|
+
}
|
379
|
+
}
|
380
|
+
}
|
381
|
+
|
382
|
+
return; // Exit early since we've handled extraction from content
|
383
|
+
}
|
384
|
+
|
385
|
+
// Also check the metadata field which likely contains the actual metadata
|
386
|
+
const metadata = data.metadata || {};
|
387
|
+
console.log('Metadata object:', metadata);
|
388
|
+
if (metadata) {
|
389
|
+
console.log('Metadata keys:', Object.keys(metadata));
|
390
|
+
}
|
391
|
+
|
392
|
+
// Extract research object if nested
|
393
|
+
const researchData = data.research || data;
|
394
|
+
|
395
|
+
// Debug nested structure if exists
|
396
|
+
if (data.research) {
|
397
|
+
console.log('Nested research data:', data.research);
|
398
|
+
console.log('Research keys:', Object.keys(data.research));
|
399
|
+
}
|
400
|
+
|
401
|
+
// Query field
|
402
|
+
const queryElement = document.getElementById('result-query');
|
403
|
+
if (queryElement) {
|
404
|
+
// Try different possible locations for query data
|
405
|
+
let query = 'Unknown query';
|
406
|
+
|
407
|
+
if (metadata.query) {
|
408
|
+
query = metadata.query;
|
409
|
+
} else if (metadata.title) {
|
410
|
+
query = metadata.title;
|
411
|
+
} else if (researchData.query) {
|
412
|
+
query = researchData.query;
|
413
|
+
} else if (researchData.prompt) {
|
414
|
+
query = researchData.prompt;
|
415
|
+
} else if (researchData.title) {
|
416
|
+
query = researchData.title;
|
417
|
+
} else if (researchData.question) {
|
418
|
+
query = researchData.question;
|
419
|
+
} else if (researchData.input) {
|
420
|
+
query = researchData.input;
|
421
|
+
}
|
422
|
+
|
423
|
+
console.log('Setting query to:', query);
|
424
|
+
queryElement.textContent = query;
|
425
|
+
}
|
426
|
+
|
427
|
+
// Generated date field
|
428
|
+
const dateElement = document.getElementById('result-date');
|
429
|
+
if (dateElement) {
|
430
|
+
let dateStr = 'Unknown date';
|
431
|
+
let timestampField = null;
|
432
|
+
|
433
|
+
// Try different possible date fields
|
434
|
+
if (metadata.created_at) {
|
435
|
+
timestampField = metadata.created_at;
|
436
|
+
} else if (metadata.timestamp) {
|
437
|
+
timestampField = metadata.timestamp;
|
438
|
+
} else if (metadata.date) {
|
439
|
+
timestampField = metadata.date;
|
440
|
+
} else if (researchData.timestamp) {
|
441
|
+
timestampField = researchData.timestamp;
|
442
|
+
} else if (researchData.created_at) {
|
443
|
+
timestampField = researchData.created_at;
|
444
|
+
} else if (researchData.date) {
|
445
|
+
timestampField = researchData.date;
|
446
|
+
} else if (researchData.time) {
|
447
|
+
timestampField = researchData.time;
|
448
|
+
}
|
449
|
+
|
450
|
+
// Format the date using the available formatter
|
451
|
+
if (timestampField) {
|
452
|
+
if (window.formatting && typeof window.formatting.formatDate === 'function') {
|
453
|
+
dateStr = window.formatting.formatDate(timestampField);
|
454
|
+
console.log('Using formatter for timestamp:', timestampField, '→', dateStr);
|
455
|
+
} else {
|
456
|
+
try {
|
457
|
+
const date = new Date(timestampField);
|
458
|
+
dateStr = date.toLocaleString();
|
459
|
+
console.log('Using timestamp:', timestampField, '→', dateStr);
|
460
|
+
} catch (e) {
|
461
|
+
console.error('Error parsing date:', timestampField, e);
|
462
|
+
}
|
463
|
+
}
|
464
|
+
}
|
465
|
+
|
466
|
+
// Add duration if available
|
467
|
+
if (metadata.duration) {
|
468
|
+
dateStr += ` (${metadata.duration} seconds)`;
|
469
|
+
} else if (metadata.duration_seconds) {
|
470
|
+
dateStr += ` (${metadata.duration_seconds} seconds)`;
|
471
|
+
} else if (researchData.duration) {
|
472
|
+
dateStr += ` (${researchData.duration} seconds)`;
|
473
|
+
}
|
474
|
+
|
475
|
+
console.log('Setting date to:', dateStr);
|
476
|
+
dateElement.textContent = dateStr;
|
477
|
+
}
|
478
|
+
|
479
|
+
// Mode field
|
480
|
+
const modeElement = document.getElementById('result-mode');
|
481
|
+
if (modeElement) {
|
482
|
+
let mode = 'Quick'; // Default to Quick
|
483
|
+
|
484
|
+
if (metadata.mode) {
|
485
|
+
mode = metadata.mode;
|
486
|
+
} else if (metadata.research_mode) {
|
487
|
+
mode = metadata.research_mode;
|
488
|
+
} else if (metadata.type) {
|
489
|
+
mode = metadata.type;
|
490
|
+
} else if (researchData.mode) {
|
491
|
+
mode = researchData.mode;
|
492
|
+
} else if (researchData.research_mode) {
|
493
|
+
mode = researchData.research_mode;
|
494
|
+
} else if (researchData.type) {
|
495
|
+
mode = researchData.type;
|
496
|
+
}
|
497
|
+
|
498
|
+
// Format mode using available formatter
|
499
|
+
if (window.formatting && typeof window.formatting.formatMode === 'function') {
|
500
|
+
mode = window.formatting.formatMode(mode);
|
501
|
+
}
|
502
|
+
|
503
|
+
console.log('Setting mode to:', mode);
|
504
|
+
modeElement.textContent = mode;
|
505
|
+
}
|
506
|
+
}
|
507
|
+
|
508
|
+
/**
|
509
|
+
* Render research results in the container
|
510
|
+
* @param {Object|string} data - Research data to render
|
511
|
+
*/
|
512
|
+
function renderResults(data) {
|
513
|
+
try {
|
514
|
+
// Clear container
|
515
|
+
resultsContainer.innerHTML = '';
|
516
|
+
|
517
|
+
// Determine the content to render
|
518
|
+
let content = '';
|
519
|
+
|
520
|
+
if (typeof data === 'string') {
|
521
|
+
// Direct string content
|
522
|
+
content = data;
|
523
|
+
} else if (data.markdown) {
|
524
|
+
// Markdown content
|
525
|
+
content = data.markdown;
|
526
|
+
} else if (data.html) {
|
527
|
+
// HTML content
|
528
|
+
resultsContainer.innerHTML = data.html;
|
529
|
+
return; // Return early since we've set HTML directly
|
530
|
+
} else if (data.text) {
|
531
|
+
// Text content
|
532
|
+
content = data.text;
|
533
|
+
} else if (data.summary) {
|
534
|
+
// Summary content
|
535
|
+
content = data.summary;
|
536
|
+
} else if (data.results) {
|
537
|
+
// Results array (old format)
|
538
|
+
if (Array.isArray(data.results)) {
|
539
|
+
content = data.results.join('\n\n');
|
540
|
+
} else {
|
541
|
+
content = JSON.stringify(data.results, null, 2);
|
542
|
+
}
|
543
|
+
} else {
|
544
|
+
// Last resort: stringify the entire object
|
545
|
+
content = JSON.stringify(data, null, 2);
|
546
|
+
}
|
547
|
+
|
548
|
+
// Render the content as Markdown if possible
|
549
|
+
if (window.ui && window.ui.renderMarkdown) {
|
550
|
+
const renderedHtml = window.ui.renderMarkdown(content);
|
551
|
+
resultsContainer.innerHTML = renderedHtml;
|
552
|
+
} else {
|
553
|
+
// Fall back to basic formatting
|
554
|
+
content = content.replace(/\n/g, '<br>');
|
555
|
+
resultsContainer.innerHTML = `<div class="markdown-content">${content}</div>`;
|
556
|
+
}
|
557
|
+
|
558
|
+
// Add syntax highlighting if Prism is available
|
559
|
+
if (window.Prism) {
|
560
|
+
window.Prism.highlightAllUnder(resultsContainer);
|
561
|
+
}
|
562
|
+
|
563
|
+
} catch (error) {
|
564
|
+
console.error('Error rendering results:', error);
|
565
|
+
showError(`Error rendering results: ${error.message}`);
|
566
|
+
}
|
567
|
+
}
|
568
|
+
|
569
|
+
/**
|
570
|
+
* Show error message in the results container
|
571
|
+
* @param {string} message - Error message
|
572
|
+
*/
|
573
|
+
function showError(message) {
|
574
|
+
resultsContainer.innerHTML = `
|
575
|
+
<div class="alert alert-danger" role="alert">
|
576
|
+
<i class="fas fa-exclamation-triangle"></i> ${message}
|
577
|
+
</div>
|
578
|
+
<p class="text-center mt-3">
|
579
|
+
<a href="/research" class="btn btn-primary">
|
580
|
+
<i class="fas fa-arrow-left"></i> Back to Research
|
581
|
+
</a>
|
582
|
+
</p>
|
583
|
+
`;
|
584
|
+
}
|
585
|
+
|
586
|
+
/**
|
587
|
+
* Handle export button click
|
588
|
+
*/
|
589
|
+
function handleExport() {
|
590
|
+
try {
|
591
|
+
if (!researchData) {
|
592
|
+
throw new Error('No research data available');
|
593
|
+
}
|
594
|
+
|
595
|
+
// Get metadata from DOM (which should be populated by now)
|
596
|
+
const query = document.getElementById('result-query')?.textContent || 'Unknown query';
|
597
|
+
const generated = document.getElementById('result-date')?.textContent || 'Unknown date';
|
598
|
+
const mode = document.getElementById('result-mode')?.textContent || 'Quick';
|
599
|
+
|
600
|
+
// Create markdown header with metadata
|
601
|
+
let markdownHeader = `# Research Results: ${query}\n\n`;
|
602
|
+
markdownHeader += `- **Generated:** ${generated}\n`;
|
603
|
+
markdownHeader += `- **Mode:** ${mode}\n\n`;
|
604
|
+
markdownHeader += `---\n\n`;
|
605
|
+
|
606
|
+
// Extract the content to export
|
607
|
+
let markdownContent = '';
|
608
|
+
|
609
|
+
// Try to extract the markdown content from various possible locations
|
610
|
+
if (typeof researchData === 'string') {
|
611
|
+
markdownContent = researchData;
|
612
|
+
} else {
|
613
|
+
// Check for content in standard locations
|
614
|
+
const contentProps = [
|
615
|
+
'content',
|
616
|
+
'report',
|
617
|
+
'markdown',
|
618
|
+
'text',
|
619
|
+
'summary',
|
620
|
+
'output',
|
621
|
+
'research_output'
|
622
|
+
];
|
623
|
+
|
624
|
+
let found = false;
|
625
|
+
|
626
|
+
// First check direct properties
|
627
|
+
for (const prop of contentProps) {
|
628
|
+
if (researchData[prop] && typeof researchData[prop] === 'string') {
|
629
|
+
markdownContent = researchData[prop];
|
630
|
+
console.log(`Using ${prop} for markdown content`);
|
631
|
+
found = true;
|
632
|
+
break;
|
633
|
+
}
|
634
|
+
}
|
635
|
+
|
636
|
+
// Then check nested properties
|
637
|
+
if (!found && researchData.research) {
|
638
|
+
for (const prop of contentProps) {
|
639
|
+
if (researchData.research[prop] && typeof researchData.research[prop] === 'string') {
|
640
|
+
markdownContent = researchData.research[prop];
|
641
|
+
console.log(`Using research.${prop} for markdown content`);
|
642
|
+
found = true;
|
643
|
+
break;
|
644
|
+
}
|
645
|
+
}
|
646
|
+
}
|
647
|
+
|
648
|
+
// Check results property
|
649
|
+
if (!found && researchData.results) {
|
650
|
+
if (typeof researchData.results === 'string') {
|
651
|
+
markdownContent = researchData.results;
|
652
|
+
console.log('Using results string for markdown content');
|
653
|
+
} else {
|
654
|
+
for (const prop of contentProps) {
|
655
|
+
if (researchData.results[prop] && typeof researchData.results[prop] === 'string') {
|
656
|
+
markdownContent = researchData.results[prop];
|
657
|
+
console.log(`Using results.${prop} for markdown content`);
|
658
|
+
found = true;
|
659
|
+
break;
|
660
|
+
}
|
661
|
+
}
|
662
|
+
}
|
663
|
+
}
|
664
|
+
|
665
|
+
// Last resort
|
666
|
+
if (!markdownContent) {
|
667
|
+
console.warn('Could not extract markdown content, using JSON');
|
668
|
+
markdownContent = "```json\n" + JSON.stringify(researchData, null, 2) + "\n```";
|
669
|
+
}
|
670
|
+
}
|
671
|
+
|
672
|
+
// Combine header and content
|
673
|
+
const fullMarkdown = markdownHeader + markdownContent;
|
674
|
+
|
675
|
+
// Create blob and trigger download
|
676
|
+
const blob = new Blob([fullMarkdown], { type: 'text/markdown' });
|
677
|
+
const link = document.createElement('a');
|
678
|
+
link.href = URL.createObjectURL(blob);
|
679
|
+
link.download = `research_${researchId}.md`;
|
680
|
+
|
681
|
+
// Trigger download
|
682
|
+
document.body.appendChild(link);
|
683
|
+
link.click();
|
684
|
+
document.body.removeChild(link);
|
685
|
+
|
686
|
+
} catch (error) {
|
687
|
+
console.error('Error exporting markdown:', error);
|
688
|
+
alert(`Error exporting markdown: ${error.message}`);
|
689
|
+
}
|
690
|
+
}
|
691
|
+
|
692
|
+
/**
|
693
|
+
* Handle PDF export button click
|
694
|
+
*/
|
695
|
+
function handlePdfExport() {
|
696
|
+
try {
|
697
|
+
if (!researchData) {
|
698
|
+
throw new Error('No research data available');
|
699
|
+
}
|
700
|
+
|
701
|
+
console.log('PDF export initiated for research ID:', researchId);
|
702
|
+
|
703
|
+
// Show loading indicator
|
704
|
+
pdfBtn.disabled = true;
|
705
|
+
pdfBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating PDF...';
|
706
|
+
|
707
|
+
// Get metadata from DOM (which should be populated correctly by now)
|
708
|
+
const title = document.getElementById('result-query')?.textContent || `Research ${researchId}`;
|
709
|
+
console.log('Using title for PDF:', title);
|
710
|
+
|
711
|
+
// Check if PDF service is available
|
712
|
+
if (window.pdfService && window.pdfService.downloadPdf) {
|
713
|
+
console.log('PDF service available, calling downloadPdf');
|
714
|
+
|
715
|
+
// Add the metadata to the researchData for PDF generation
|
716
|
+
const pdfData = {
|
717
|
+
...researchData,
|
718
|
+
title: title,
|
719
|
+
query: title,
|
720
|
+
metadata: {
|
721
|
+
title: title,
|
722
|
+
date: document.getElementById('result-date')?.textContent || 'Unknown date',
|
723
|
+
mode: document.getElementById('result-mode')?.textContent || 'Standard'
|
724
|
+
}
|
725
|
+
};
|
726
|
+
|
727
|
+
// Use the PDF service to generate and download the PDF
|
728
|
+
window.pdfService.downloadPdf(pdfData, researchId)
|
729
|
+
.then(() => {
|
730
|
+
console.log('PDF generated successfully');
|
731
|
+
// Reset button
|
732
|
+
pdfBtn.disabled = false;
|
733
|
+
pdfBtn.innerHTML = '<i class="fas fa-file-pdf"></i> Download PDF';
|
734
|
+
})
|
735
|
+
.catch(error => {
|
736
|
+
console.error('Error generating PDF:', error);
|
737
|
+
alert(`Error generating PDF: ${error.message || 'Unknown error'}`);
|
738
|
+
|
739
|
+
// Reset button
|
740
|
+
pdfBtn.disabled = false;
|
741
|
+
pdfBtn.innerHTML = '<i class="fas fa-file-pdf"></i> Download PDF';
|
742
|
+
});
|
743
|
+
} else {
|
744
|
+
console.error('PDF service not available');
|
745
|
+
throw new Error('PDF service not available');
|
746
|
+
}
|
747
|
+
|
748
|
+
} catch (error) {
|
749
|
+
console.error('Error exporting PDF:', error);
|
750
|
+
alert(`Error exporting PDF: ${error.message || 'Unknown error'}`);
|
751
|
+
|
752
|
+
// Reset button
|
753
|
+
if (pdfBtn) {
|
754
|
+
pdfBtn.disabled = false;
|
755
|
+
pdfBtn.innerHTML = '<i class="fas fa-file-pdf"></i> Download PDF';
|
756
|
+
}
|
757
|
+
}
|
758
|
+
}
|
759
|
+
|
760
|
+
// Initialize on DOM content loaded
|
761
|
+
if (document.readyState === 'loading') {
|
762
|
+
document.addEventListener('DOMContentLoaded', initializeResults);
|
763
|
+
} else {
|
764
|
+
initializeResults();
|
765
|
+
}
|
766
|
+
})();
|