local-deep-research 0.4.4__py3-none-any.whl → 0.5.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 +7 -0
- local_deep_research/__version__.py +1 -1
- local_deep_research/advanced_search_system/answer_decoding/__init__.py +5 -0
- local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py +421 -0
- local_deep_research/advanced_search_system/candidate_exploration/README.md +219 -0
- local_deep_research/advanced_search_system/candidate_exploration/__init__.py +25 -0
- local_deep_research/advanced_search_system/candidate_exploration/adaptive_explorer.py +329 -0
- local_deep_research/advanced_search_system/candidate_exploration/base_explorer.py +341 -0
- local_deep_research/advanced_search_system/candidate_exploration/constraint_guided_explorer.py +436 -0
- local_deep_research/advanced_search_system/candidate_exploration/diversity_explorer.py +457 -0
- local_deep_research/advanced_search_system/candidate_exploration/parallel_explorer.py +250 -0
- local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +255 -0
- local_deep_research/advanced_search_system/candidates/__init__.py +5 -0
- local_deep_research/advanced_search_system/candidates/base_candidate.py +59 -0
- local_deep_research/advanced_search_system/constraint_checking/README.md +150 -0
- local_deep_research/advanced_search_system/constraint_checking/__init__.py +35 -0
- local_deep_research/advanced_search_system/constraint_checking/base_constraint_checker.py +122 -0
- local_deep_research/advanced_search_system/constraint_checking/constraint_checker.py +223 -0
- local_deep_research/advanced_search_system/constraint_checking/constraint_satisfaction_tracker.py +387 -0
- local_deep_research/advanced_search_system/constraint_checking/dual_confidence_checker.py +424 -0
- local_deep_research/advanced_search_system/constraint_checking/evidence_analyzer.py +174 -0
- local_deep_research/advanced_search_system/constraint_checking/intelligent_constraint_relaxer.py +503 -0
- local_deep_research/advanced_search_system/constraint_checking/rejection_engine.py +143 -0
- local_deep_research/advanced_search_system/constraint_checking/strict_checker.py +259 -0
- local_deep_research/advanced_search_system/constraint_checking/threshold_checker.py +213 -0
- local_deep_research/advanced_search_system/constraints/__init__.py +6 -0
- local_deep_research/advanced_search_system/constraints/base_constraint.py +58 -0
- local_deep_research/advanced_search_system/constraints/constraint_analyzer.py +143 -0
- local_deep_research/advanced_search_system/evidence/__init__.py +12 -0
- local_deep_research/advanced_search_system/evidence/base_evidence.py +57 -0
- local_deep_research/advanced_search_system/evidence/evaluator.py +159 -0
- local_deep_research/advanced_search_system/evidence/requirements.py +122 -0
- local_deep_research/advanced_search_system/filters/base_filter.py +3 -1
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +8 -2
- local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +43 -29
- local_deep_research/advanced_search_system/findings/repository.py +54 -17
- local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +3 -1
- local_deep_research/advanced_search_system/query_generation/adaptive_query_generator.py +405 -0
- local_deep_research/advanced_search_system/questions/__init__.py +16 -0
- local_deep_research/advanced_search_system/questions/atomic_fact_question.py +171 -0
- local_deep_research/advanced_search_system/questions/browsecomp_question.py +287 -0
- local_deep_research/advanced_search_system/questions/decomposition_question.py +13 -4
- local_deep_research/advanced_search_system/questions/entity_aware_question.py +184 -0
- local_deep_research/advanced_search_system/questions/standard_question.py +9 -3
- local_deep_research/advanced_search_system/search_optimization/cross_constraint_manager.py +624 -0
- local_deep_research/advanced_search_system/source_management/diversity_manager.py +613 -0
- local_deep_research/advanced_search_system/strategies/__init__.py +42 -0
- local_deep_research/advanced_search_system/strategies/adaptive_decomposition_strategy.py +564 -0
- local_deep_research/advanced_search_system/strategies/base_strategy.py +4 -4
- local_deep_research/advanced_search_system/strategies/browsecomp_entity_strategy.py +1031 -0
- local_deep_research/advanced_search_system/strategies/browsecomp_optimized_strategy.py +778 -0
- local_deep_research/advanced_search_system/strategies/concurrent_dual_confidence_strategy.py +446 -0
- local_deep_research/advanced_search_system/strategies/constrained_search_strategy.py +1348 -0
- local_deep_research/advanced_search_system/strategies/constraint_parallel_strategy.py +522 -0
- local_deep_research/advanced_search_system/strategies/direct_search_strategy.py +217 -0
- local_deep_research/advanced_search_system/strategies/dual_confidence_strategy.py +320 -0
- local_deep_research/advanced_search_system/strategies/dual_confidence_with_rejection.py +219 -0
- local_deep_research/advanced_search_system/strategies/early_stop_constrained_strategy.py +369 -0
- local_deep_research/advanced_search_system/strategies/entity_aware_source_strategy.py +140 -0
- local_deep_research/advanced_search_system/strategies/evidence_based_strategy.py +1248 -0
- local_deep_research/advanced_search_system/strategies/evidence_based_strategy_v2.py +1337 -0
- local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +537 -0
- local_deep_research/advanced_search_system/strategies/improved_evidence_based_strategy.py +782 -0
- local_deep_research/advanced_search_system/strategies/iterative_reasoning_strategy.py +760 -0
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +55 -21
- local_deep_research/advanced_search_system/strategies/llm_driven_modular_strategy.py +865 -0
- local_deep_research/advanced_search_system/strategies/modular_strategy.py +1142 -0
- local_deep_research/advanced_search_system/strategies/parallel_constrained_strategy.py +506 -0
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +34 -16
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +29 -9
- local_deep_research/advanced_search_system/strategies/recursive_decomposition_strategy.py +492 -0
- local_deep_research/advanced_search_system/strategies/smart_decomposition_strategy.py +284 -0
- local_deep_research/advanced_search_system/strategies/smart_query_strategy.py +515 -0
- local_deep_research/advanced_search_system/strategies/source_based_strategy.py +48 -24
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +34 -14
- local_deep_research/advanced_search_system/tools/base_tool.py +7 -2
- local_deep_research/api/benchmark_functions.py +6 -2
- local_deep_research/api/research_functions.py +10 -4
- local_deep_research/benchmarks/__init__.py +9 -7
- local_deep_research/benchmarks/benchmark_functions.py +6 -2
- local_deep_research/benchmarks/cli/benchmark_commands.py +27 -10
- local_deep_research/benchmarks/cli.py +38 -13
- local_deep_research/benchmarks/comparison/__init__.py +4 -2
- local_deep_research/benchmarks/comparison/evaluator.py +316 -239
- local_deep_research/benchmarks/datasets/__init__.py +1 -1
- local_deep_research/benchmarks/datasets/base.py +91 -72
- local_deep_research/benchmarks/datasets/browsecomp.py +54 -33
- local_deep_research/benchmarks/datasets/custom_dataset_template.py +19 -19
- local_deep_research/benchmarks/datasets/simpleqa.py +14 -14
- local_deep_research/benchmarks/datasets/utils.py +48 -29
- local_deep_research/benchmarks/datasets.py +4 -11
- local_deep_research/benchmarks/efficiency/__init__.py +8 -4
- local_deep_research/benchmarks/efficiency/resource_monitor.py +223 -171
- local_deep_research/benchmarks/efficiency/speed_profiler.py +62 -48
- local_deep_research/benchmarks/evaluators/browsecomp.py +3 -1
- local_deep_research/benchmarks/evaluators/composite.py +6 -2
- local_deep_research/benchmarks/evaluators/simpleqa.py +36 -13
- local_deep_research/benchmarks/graders.py +32 -10
- local_deep_research/benchmarks/metrics/README.md +1 -1
- local_deep_research/benchmarks/metrics/calculation.py +25 -10
- local_deep_research/benchmarks/metrics/reporting.py +7 -3
- local_deep_research/benchmarks/metrics/visualization.py +42 -23
- local_deep_research/benchmarks/metrics.py +1 -1
- local_deep_research/benchmarks/optimization/__init__.py +3 -1
- local_deep_research/benchmarks/optimization/api.py +7 -1
- local_deep_research/benchmarks/optimization/optuna_optimizer.py +75 -26
- local_deep_research/benchmarks/runners.py +48 -15
- local_deep_research/citation_handler.py +65 -92
- local_deep_research/citation_handlers/__init__.py +15 -0
- local_deep_research/citation_handlers/base_citation_handler.py +70 -0
- local_deep_research/citation_handlers/forced_answer_citation_handler.py +179 -0
- local_deep_research/citation_handlers/precision_extraction_handler.py +550 -0
- local_deep_research/citation_handlers/standard_citation_handler.py +80 -0
- local_deep_research/config/llm_config.py +271 -169
- local_deep_research/config/search_config.py +14 -5
- local_deep_research/defaults/__init__.py +0 -1
- local_deep_research/metrics/__init__.py +13 -0
- local_deep_research/metrics/database.py +58 -0
- local_deep_research/metrics/db_models.py +115 -0
- local_deep_research/metrics/migrate_add_provider_to_token_usage.py +148 -0
- local_deep_research/metrics/migrate_call_stack_tracking.py +105 -0
- local_deep_research/metrics/migrate_enhanced_tracking.py +75 -0
- local_deep_research/metrics/migrate_research_ratings.py +31 -0
- local_deep_research/metrics/models.py +61 -0
- local_deep_research/metrics/pricing/__init__.py +12 -0
- local_deep_research/metrics/pricing/cost_calculator.py +237 -0
- local_deep_research/metrics/pricing/pricing_cache.py +143 -0
- local_deep_research/metrics/pricing/pricing_fetcher.py +240 -0
- local_deep_research/metrics/query_utils.py +51 -0
- local_deep_research/metrics/search_tracker.py +380 -0
- local_deep_research/metrics/token_counter.py +1078 -0
- local_deep_research/migrate_db.py +3 -1
- local_deep_research/report_generator.py +22 -8
- local_deep_research/search_system.py +390 -9
- local_deep_research/test_migration.py +15 -5
- local_deep_research/utilities/db_utils.py +7 -4
- local_deep_research/utilities/es_utils.py +115 -104
- local_deep_research/utilities/llm_utils.py +15 -5
- local_deep_research/utilities/log_utils.py +151 -0
- local_deep_research/utilities/search_cache.py +387 -0
- local_deep_research/utilities/search_utilities.py +14 -6
- local_deep_research/utilities/threading_utils.py +92 -0
- local_deep_research/utilities/url_utils.py +6 -0
- local_deep_research/web/api.py +347 -0
- local_deep_research/web/app.py +13 -17
- local_deep_research/web/app_factory.py +71 -66
- local_deep_research/web/database/migrate_to_ldr_db.py +12 -4
- local_deep_research/web/database/migrations.py +5 -3
- local_deep_research/web/database/models.py +51 -2
- local_deep_research/web/database/schema_upgrade.py +49 -29
- local_deep_research/web/models/database.py +51 -61
- local_deep_research/web/routes/api_routes.py +56 -22
- local_deep_research/web/routes/benchmark_routes.py +4 -1
- local_deep_research/web/routes/globals.py +22 -0
- local_deep_research/web/routes/history_routes.py +71 -46
- local_deep_research/web/routes/metrics_routes.py +1155 -0
- local_deep_research/web/routes/research_routes.py +227 -41
- local_deep_research/web/routes/settings_routes.py +156 -55
- local_deep_research/web/services/research_service.py +310 -103
- local_deep_research/web/services/resource_service.py +36 -11
- local_deep_research/web/services/settings_manager.py +55 -17
- local_deep_research/web/services/settings_service.py +12 -4
- local_deep_research/web/services/socket_service.py +295 -188
- local_deep_research/web/static/css/custom_dropdown.css +180 -0
- local_deep_research/web/static/css/styles.css +39 -1
- local_deep_research/web/static/js/components/detail.js +633 -267
- local_deep_research/web/static/js/components/details.js +751 -0
- local_deep_research/web/static/js/components/fallback/formatting.js +11 -11
- local_deep_research/web/static/js/components/fallback/ui.js +23 -23
- local_deep_research/web/static/js/components/history.js +76 -76
- local_deep_research/web/static/js/components/logpanel.js +61 -13
- local_deep_research/web/static/js/components/progress.js +13 -2
- local_deep_research/web/static/js/components/research.js +99 -12
- local_deep_research/web/static/js/components/results.js +239 -106
- local_deep_research/web/static/js/main.js +40 -40
- local_deep_research/web/static/js/services/audio.js +1 -1
- local_deep_research/web/static/js/services/formatting.js +11 -11
- local_deep_research/web/static/js/services/keyboard.js +157 -0
- local_deep_research/web/static/js/services/pdf.js +80 -80
- local_deep_research/web/static/sounds/README.md +1 -1
- local_deep_research/web/templates/base.html +1 -0
- local_deep_research/web/templates/components/log_panel.html +7 -1
- local_deep_research/web/templates/components/mobile_nav.html +1 -1
- local_deep_research/web/templates/components/sidebar.html +3 -0
- local_deep_research/web/templates/pages/cost_analytics.html +1245 -0
- local_deep_research/web/templates/pages/details.html +325 -24
- local_deep_research/web/templates/pages/history.html +1 -1
- local_deep_research/web/templates/pages/metrics.html +1929 -0
- local_deep_research/web/templates/pages/progress.html +2 -2
- local_deep_research/web/templates/pages/research.html +53 -17
- local_deep_research/web/templates/pages/results.html +12 -1
- local_deep_research/web/templates/pages/star_reviews.html +803 -0
- local_deep_research/web/utils/formatters.py +9 -3
- local_deep_research/web_search_engines/default_search_engines.py +5 -3
- local_deep_research/web_search_engines/engines/full_search.py +8 -2
- local_deep_research/web_search_engines/engines/meta_search_engine.py +59 -20
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +19 -6
- local_deep_research/web_search_engines/engines/search_engine_brave.py +6 -2
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +3 -1
- local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +81 -58
- local_deep_research/web_search_engines/engines/search_engine_github.py +46 -15
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +16 -6
- local_deep_research/web_search_engines/engines/search_engine_guardian.py +39 -15
- local_deep_research/web_search_engines/engines/search_engine_local.py +58 -25
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +15 -5
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +63 -21
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +37 -11
- local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +27 -9
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +12 -4
- local_deep_research/web_search_engines/engines/search_engine_wayback.py +31 -10
- local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +12 -3
- local_deep_research/web_search_engines/search_engine_base.py +83 -35
- local_deep_research/web_search_engines/search_engine_factory.py +25 -8
- local_deep_research/web_search_engines/search_engines_config.py +9 -3
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/METADATA +7 -1
- local_deep_research-0.5.0.dist-info/RECORD +265 -0
- local_deep_research-0.4.4.dist-info/RECORD +0 -177
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/WHEEL +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,751 @@
|
|
1
|
+
/**
|
2
|
+
* Research Details Page JavaScript
|
3
|
+
* Handles displaying detailed metrics for a specific research session
|
4
|
+
*/
|
5
|
+
(function() {
|
6
|
+
'use strict';
|
7
|
+
|
8
|
+
let researchId = null;
|
9
|
+
let metricsData = null;
|
10
|
+
let timelineChart = null;
|
11
|
+
let searchChart = null;
|
12
|
+
|
13
|
+
// Format large numbers with commas
|
14
|
+
function formatNumber(num) {
|
15
|
+
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
16
|
+
}
|
17
|
+
|
18
|
+
// Format currency
|
19
|
+
function formatCurrency(amount) {
|
20
|
+
if (amount < 0.01) {
|
21
|
+
return `$${amount.toFixed(6)}`;
|
22
|
+
} else if (amount < 1) {
|
23
|
+
return `$${amount.toFixed(4)}`;
|
24
|
+
} else {
|
25
|
+
return `$${amount.toFixed(2)}`;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
// Get research ID from URL
|
30
|
+
function getResearchIdFromUrl() {
|
31
|
+
const path = window.location.pathname;
|
32
|
+
const match = path.match(/\/research\/details\/(\d+)/);
|
33
|
+
return match ? parseInt(match[1]) : null;
|
34
|
+
}
|
35
|
+
|
36
|
+
// Load research metrics data
|
37
|
+
async function loadResearchMetrics() {
|
38
|
+
try {
|
39
|
+
console.log('Loading research metrics for ID:', researchId);
|
40
|
+
|
41
|
+
// Show loading state
|
42
|
+
document.getElementById('loading').style.display = 'block';
|
43
|
+
document.getElementById('details-content').style.display = 'none';
|
44
|
+
document.getElementById('error').style.display = 'none';
|
45
|
+
|
46
|
+
// Load research details (includes strategy)
|
47
|
+
console.log('Fetching research details...');
|
48
|
+
const detailsResponse = await fetch(`/history/details/${researchId}`);
|
49
|
+
console.log('Details response status:', detailsResponse.status);
|
50
|
+
|
51
|
+
let researchDetails = null;
|
52
|
+
if (detailsResponse.ok) {
|
53
|
+
researchDetails = await detailsResponse.json();
|
54
|
+
console.log('Research details loaded:', researchDetails);
|
55
|
+
}
|
56
|
+
|
57
|
+
// Load research metrics
|
58
|
+
console.log('Fetching research metrics...');
|
59
|
+
const metricsResponse = await fetch(`/metrics/api/metrics/research/${researchId}`);
|
60
|
+
console.log('Metrics response status:', metricsResponse.status);
|
61
|
+
|
62
|
+
if (!metricsResponse.ok) {
|
63
|
+
throw new Error(`Metrics API failed: ${metricsResponse.status}`);
|
64
|
+
}
|
65
|
+
|
66
|
+
const metricsResult = await metricsResponse.json();
|
67
|
+
console.log('Metrics result:', metricsResult);
|
68
|
+
|
69
|
+
if (metricsResult.status !== 'success') {
|
70
|
+
throw new Error('Failed to load research metrics');
|
71
|
+
}
|
72
|
+
|
73
|
+
metricsData = metricsResult.metrics;
|
74
|
+
console.log('Metrics data loaded:', metricsData);
|
75
|
+
|
76
|
+
// Display research details first
|
77
|
+
if (researchDetails) {
|
78
|
+
displayResearchDetails(researchDetails);
|
79
|
+
}
|
80
|
+
|
81
|
+
// Load timeline metrics
|
82
|
+
console.log('Fetching timeline metrics...');
|
83
|
+
const timelineResponse = await fetch(`/metrics/api/metrics/research/${researchId}/timeline`);
|
84
|
+
console.log('Timeline response status:', timelineResponse.status);
|
85
|
+
|
86
|
+
let timelineData = null;
|
87
|
+
if (timelineResponse.ok) {
|
88
|
+
const timelineResult = await timelineResponse.json();
|
89
|
+
console.log('Timeline result:', timelineResult);
|
90
|
+
if (timelineResult.status === 'success') {
|
91
|
+
timelineData = timelineResult.metrics;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
// Load search metrics
|
96
|
+
console.log('Fetching search metrics...');
|
97
|
+
const searchResponse = await fetch(`/metrics/api/metrics/research/${researchId}/search`);
|
98
|
+
console.log('Search response status:', searchResponse.status);
|
99
|
+
|
100
|
+
let searchData = null;
|
101
|
+
if (searchResponse.ok) {
|
102
|
+
const searchResult = await searchResponse.json();
|
103
|
+
console.log('Search result:', searchResult);
|
104
|
+
if (searchResult.status === 'success') {
|
105
|
+
searchData = searchResult.metrics;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
// Display all data
|
110
|
+
console.log('Displaying research metrics...');
|
111
|
+
displayResearchMetrics();
|
112
|
+
|
113
|
+
if (timelineData) {
|
114
|
+
console.log('Displaying timeline metrics...');
|
115
|
+
displayTimelineMetrics(timelineData);
|
116
|
+
|
117
|
+
console.log('Chart.js available:', typeof Chart !== 'undefined');
|
118
|
+
console.log('Timeline data for chart:', timelineData);
|
119
|
+
createTimelineChart(timelineData);
|
120
|
+
}
|
121
|
+
|
122
|
+
if (searchData) {
|
123
|
+
console.log('Displaying search metrics...');
|
124
|
+
displaySearchMetrics(searchData);
|
125
|
+
createSearchChart(searchData);
|
126
|
+
}
|
127
|
+
|
128
|
+
// Load cost data
|
129
|
+
console.log('Loading cost data...');
|
130
|
+
loadCostData();
|
131
|
+
|
132
|
+
console.log('Showing details content...');
|
133
|
+
const loadingEl = document.getElementById('loading');
|
134
|
+
const contentEl = document.getElementById('details-content');
|
135
|
+
const errorEl = document.getElementById('error');
|
136
|
+
|
137
|
+
loadingEl.style.display = 'none';
|
138
|
+
errorEl.style.display = 'none';
|
139
|
+
contentEl.style.display = 'block';
|
140
|
+
|
141
|
+
// Show metrics sections
|
142
|
+
console.log('Showing metrics sections...');
|
143
|
+
const tokenMetricsSection = document.getElementById('token-metrics-section');
|
144
|
+
const searchMetricsSection = document.getElementById('search-metrics-section');
|
145
|
+
|
146
|
+
if (tokenMetricsSection) {
|
147
|
+
tokenMetricsSection.style.display = 'block';
|
148
|
+
console.log('Token metrics section shown');
|
149
|
+
}
|
150
|
+
|
151
|
+
if (searchMetricsSection) {
|
152
|
+
searchMetricsSection.style.display = 'block';
|
153
|
+
console.log('Search metrics section shown');
|
154
|
+
}
|
155
|
+
|
156
|
+
// Force visibility with CSS overrides
|
157
|
+
contentEl.style.visibility = 'visible';
|
158
|
+
contentEl.style.opacity = '1';
|
159
|
+
contentEl.style.position = 'relative';
|
160
|
+
contentEl.style.zIndex = '1000';
|
161
|
+
|
162
|
+
console.log('Loading display:', loadingEl.style.display);
|
163
|
+
console.log('Content display:', contentEl.style.display);
|
164
|
+
console.log('Error display:', errorEl.style.display);
|
165
|
+
|
166
|
+
// Verify content is actually populated
|
167
|
+
const totalTokensEl = document.getElementById('total-tokens');
|
168
|
+
const researchQueryEl = document.getElementById('research-query');
|
169
|
+
console.log('Total tokens value:', totalTokensEl ? totalTokensEl.textContent : 'ELEMENT NOT FOUND');
|
170
|
+
console.log('Research query value:', researchQueryEl ? researchQueryEl.textContent : 'ELEMENT NOT FOUND');
|
171
|
+
console.log('Content element height:', contentEl.offsetHeight);
|
172
|
+
console.log('Content element children:', contentEl.children.length);
|
173
|
+
|
174
|
+
} catch (error) {
|
175
|
+
console.error('Error loading research metrics:', error);
|
176
|
+
console.error('Error details:', error.message, error.stack);
|
177
|
+
showError();
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
// Display research details from history endpoint
|
182
|
+
function displayResearchDetails(details) {
|
183
|
+
console.log('displayResearchDetails called with:', details);
|
184
|
+
|
185
|
+
// Update basic research info
|
186
|
+
if (details.query) {
|
187
|
+
document.getElementById('research-query').textContent = details.query;
|
188
|
+
}
|
189
|
+
if (details.mode) {
|
190
|
+
document.getElementById('research-mode').textContent = details.mode;
|
191
|
+
}
|
192
|
+
if (details.created_at) {
|
193
|
+
const date = new Date(details.created_at);
|
194
|
+
document.getElementById('research-date').textContent = date.toLocaleString();
|
195
|
+
}
|
196
|
+
|
197
|
+
// Update strategy information
|
198
|
+
if (details.strategy) {
|
199
|
+
document.getElementById('research-strategy').textContent = details.strategy;
|
200
|
+
} else {
|
201
|
+
document.getElementById('research-strategy').textContent = 'Not recorded';
|
202
|
+
}
|
203
|
+
|
204
|
+
// Update progress
|
205
|
+
if (details.progress !== undefined) {
|
206
|
+
const progressFill = document.getElementById('detail-progress-fill');
|
207
|
+
const progressText = document.getElementById('detail-progress-percentage');
|
208
|
+
if (progressFill && progressText) {
|
209
|
+
progressFill.style.width = `${details.progress}%`;
|
210
|
+
progressText.textContent = `${details.progress}%`;
|
211
|
+
}
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
// Display basic research metrics
|
216
|
+
function displayResearchMetrics() {
|
217
|
+
console.log('displayResearchMetrics called with:', metricsData);
|
218
|
+
if (!metricsData) {
|
219
|
+
console.error('No metrics data available');
|
220
|
+
return;
|
221
|
+
}
|
222
|
+
|
223
|
+
// Update summary cards
|
224
|
+
const totalTokensEl = document.getElementById('total-tokens');
|
225
|
+
const totalTokens = formatNumber(metricsData.total_tokens || 0);
|
226
|
+
console.log('Setting total tokens to:', totalTokens);
|
227
|
+
totalTokensEl.textContent = totalTokens;
|
228
|
+
|
229
|
+
// Calculate prompt/completion tokens from model usage
|
230
|
+
let totalPromptTokens = 0;
|
231
|
+
let totalCompletionTokens = 0;
|
232
|
+
let totalCalls = 0;
|
233
|
+
let model = 'Unknown';
|
234
|
+
|
235
|
+
if (metricsData.model_usage && metricsData.model_usage.length > 0) {
|
236
|
+
metricsData.model_usage.forEach(usage => {
|
237
|
+
totalPromptTokens += usage.prompt_tokens || 0;
|
238
|
+
totalCompletionTokens += usage.completion_tokens || 0;
|
239
|
+
totalCalls += usage.calls || 0;
|
240
|
+
if (model === 'Unknown') {
|
241
|
+
model = usage.model || 'Unknown';
|
242
|
+
}
|
243
|
+
});
|
244
|
+
}
|
245
|
+
|
246
|
+
document.getElementById('prompt-tokens').textContent = formatNumber(totalPromptTokens);
|
247
|
+
document.getElementById('completion-tokens').textContent = formatNumber(totalCompletionTokens);
|
248
|
+
document.getElementById('llm-calls').textContent = formatNumber(metricsData.total_calls || totalCalls);
|
249
|
+
|
250
|
+
// Update model info
|
251
|
+
document.getElementById('model-used').textContent = model;
|
252
|
+
|
253
|
+
// Response time will be updated by timeline data
|
254
|
+
document.getElementById('avg-response-time').textContent = '0s';
|
255
|
+
}
|
256
|
+
|
257
|
+
// Display timeline metrics
|
258
|
+
function displayTimelineMetrics(timelineData) {
|
259
|
+
if (!timelineData) return;
|
260
|
+
|
261
|
+
// Update research info from timeline data
|
262
|
+
if (timelineData.research_details) {
|
263
|
+
const details = timelineData.research_details;
|
264
|
+
document.getElementById('research-query').textContent = details.query || 'Unknown';
|
265
|
+
document.getElementById('research-mode').textContent = details.mode || 'Unknown';
|
266
|
+
if (details.created_at) {
|
267
|
+
const date = new Date(details.created_at);
|
268
|
+
document.getElementById('research-date').textContent = date.toLocaleString();
|
269
|
+
}
|
270
|
+
}
|
271
|
+
|
272
|
+
// Update summary info
|
273
|
+
if (timelineData.summary) {
|
274
|
+
const summary = timelineData.summary;
|
275
|
+
const avgResponseTime = (summary.avg_response_time || 0) / 1000;
|
276
|
+
document.getElementById('avg-response-time').textContent = `${avgResponseTime.toFixed(1)}s`;
|
277
|
+
}
|
278
|
+
|
279
|
+
// Display phase breakdown
|
280
|
+
if (timelineData.phase_stats) {
|
281
|
+
const container = document.getElementById('phase-breakdown');
|
282
|
+
container.innerHTML = '';
|
283
|
+
|
284
|
+
Object.entries(timelineData.phase_stats).forEach(([phaseName, stats]) => {
|
285
|
+
const item = document.createElement('div');
|
286
|
+
item.className = 'phase-stat-item';
|
287
|
+
item.innerHTML = `
|
288
|
+
<div class="phase-name">${phaseName}</div>
|
289
|
+
<div class="phase-tokens">${formatNumber(stats.tokens)} tokens</div>
|
290
|
+
<div class="phase-calls">${formatNumber(stats.count)} calls</div>
|
291
|
+
`;
|
292
|
+
container.appendChild(item);
|
293
|
+
});
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
// Display search metrics
|
298
|
+
function displaySearchMetrics(searchData) {
|
299
|
+
if (!searchData) return;
|
300
|
+
|
301
|
+
// Update search summary metrics
|
302
|
+
const totalSearches = searchData.total_searches || 0;
|
303
|
+
const totalResults = searchData.total_results || 0;
|
304
|
+
const avgResponseTime = searchData.avg_response_time || 0;
|
305
|
+
const successRate = searchData.success_rate || 0;
|
306
|
+
|
307
|
+
document.getElementById('total-searches').textContent = formatNumber(totalSearches);
|
308
|
+
document.getElementById('total-search-results').textContent = formatNumber(totalResults);
|
309
|
+
document.getElementById('avg-search-response-time').textContent = `${avgResponseTime.toFixed(0)}ms`;
|
310
|
+
document.getElementById('search-success-rate').textContent = `${successRate.toFixed(1)}%`;
|
311
|
+
|
312
|
+
// Display search engine breakdown
|
313
|
+
if (!searchData.search_calls) return;
|
314
|
+
|
315
|
+
const container = document.getElementById('search-engine-breakdown');
|
316
|
+
container.innerHTML = '';
|
317
|
+
|
318
|
+
searchData.search_calls.forEach(call => {
|
319
|
+
const item = document.createElement('div');
|
320
|
+
item.className = 'search-engine-item';
|
321
|
+
item.innerHTML = `
|
322
|
+
<div class="search-engine-info">
|
323
|
+
<div class="search-engine-name">${call.engine || 'Unknown'}</div>
|
324
|
+
<div class="search-engine-query">${call.query || 'No query'}</div>
|
325
|
+
</div>
|
326
|
+
<div class="search-engine-stats">
|
327
|
+
<div class="search-results">${formatNumber(call.results_count || 0)} results</div>
|
328
|
+
<div class="search-time">${((call.response_time_ms || 0) / 1000).toFixed(1)}s</div>
|
329
|
+
</div>
|
330
|
+
`;
|
331
|
+
container.appendChild(item);
|
332
|
+
});
|
333
|
+
}
|
334
|
+
|
335
|
+
// Create timeline chart
|
336
|
+
function createTimelineChart(timelineData) {
|
337
|
+
console.log('createTimelineChart called with:', timelineData);
|
338
|
+
|
339
|
+
if (!timelineData || !timelineData.timeline) {
|
340
|
+
console.log('No timeline data for chart, timelineData:', timelineData);
|
341
|
+
return;
|
342
|
+
}
|
343
|
+
|
344
|
+
if (typeof Chart === 'undefined') {
|
345
|
+
console.error('Chart.js not loaded!');
|
346
|
+
return;
|
347
|
+
}
|
348
|
+
|
349
|
+
const chartElement = document.getElementById('timeline-chart');
|
350
|
+
if (!chartElement) {
|
351
|
+
console.error('Timeline chart element not found');
|
352
|
+
return;
|
353
|
+
}
|
354
|
+
|
355
|
+
console.log('Creating timeline chart with', timelineData.timeline.length, 'data points');
|
356
|
+
|
357
|
+
try {
|
358
|
+
const ctx = chartElement.getContext('2d');
|
359
|
+
console.log('Canvas context obtained');
|
360
|
+
|
361
|
+
// Destroy existing chart
|
362
|
+
if (timelineChart) {
|
363
|
+
timelineChart.destroy();
|
364
|
+
}
|
365
|
+
|
366
|
+
// Prepare chart data with enhanced information
|
367
|
+
const chartData = timelineData.timeline.map((item, index) => ({
|
368
|
+
phase: item.research_phase || item.phase || `Step ${index + 1}`,
|
369
|
+
tokens: item.tokens || 0,
|
370
|
+
promptTokens: item.prompt_tokens || 0,
|
371
|
+
completionTokens: item.completion_tokens || 0,
|
372
|
+
timestamp: item.timestamp || item.created_at,
|
373
|
+
responseTime: item.response_time_ms || 0
|
374
|
+
}));
|
375
|
+
|
376
|
+
const labels = chartData.map(item => item.phase);
|
377
|
+
const totalTokens = chartData.map(item => item.tokens);
|
378
|
+
const promptTokens = chartData.map(item => item.promptTokens);
|
379
|
+
const completionTokens = chartData.map(item => item.completionTokens);
|
380
|
+
|
381
|
+
console.log('Enhanced chart data:', chartData);
|
382
|
+
|
383
|
+
timelineChart = new Chart(ctx, {
|
384
|
+
type: 'bar',
|
385
|
+
data: {
|
386
|
+
labels: labels,
|
387
|
+
datasets: [
|
388
|
+
{
|
389
|
+
label: 'Input Tokens',
|
390
|
+
data: promptTokens,
|
391
|
+
backgroundColor: 'rgba(99, 102, 241, 0.8)',
|
392
|
+
borderColor: 'rgba(99, 102, 241, 1)',
|
393
|
+
borderWidth: 1,
|
394
|
+
borderRadius: 4,
|
395
|
+
borderSkipped: false,
|
396
|
+
},
|
397
|
+
{
|
398
|
+
label: 'Output Tokens',
|
399
|
+
data: completionTokens,
|
400
|
+
backgroundColor: 'rgba(34, 197, 94, 0.8)',
|
401
|
+
borderColor: 'rgba(34, 197, 94, 1)',
|
402
|
+
borderWidth: 1,
|
403
|
+
borderRadius: 4,
|
404
|
+
borderSkipped: false,
|
405
|
+
}
|
406
|
+
]
|
407
|
+
},
|
408
|
+
options: {
|
409
|
+
responsive: true,
|
410
|
+
maintainAspectRatio: false,
|
411
|
+
animation: {
|
412
|
+
duration: 1000,
|
413
|
+
easing: 'easeInOutQuart'
|
414
|
+
},
|
415
|
+
interaction: {
|
416
|
+
intersect: false,
|
417
|
+
mode: 'index'
|
418
|
+
},
|
419
|
+
scales: {
|
420
|
+
x: {
|
421
|
+
stacked: true,
|
422
|
+
grid: {
|
423
|
+
display: false
|
424
|
+
},
|
425
|
+
ticks: {
|
426
|
+
font: {
|
427
|
+
size: 11
|
428
|
+
},
|
429
|
+
maxRotation: 45,
|
430
|
+
minRotation: 0
|
431
|
+
}
|
432
|
+
},
|
433
|
+
y: {
|
434
|
+
stacked: true,
|
435
|
+
beginAtZero: true,
|
436
|
+
grid: {
|
437
|
+
color: 'rgba(0, 0, 0, 0.1)',
|
438
|
+
drawBorder: false
|
439
|
+
},
|
440
|
+
ticks: {
|
441
|
+
font: {
|
442
|
+
size: 11
|
443
|
+
},
|
444
|
+
callback: function(value) {
|
445
|
+
return formatNumber(value);
|
446
|
+
}
|
447
|
+
},
|
448
|
+
title: {
|
449
|
+
display: true,
|
450
|
+
text: 'Tokens',
|
451
|
+
font: {
|
452
|
+
size: 12,
|
453
|
+
weight: 'bold'
|
454
|
+
}
|
455
|
+
}
|
456
|
+
}
|
457
|
+
},
|
458
|
+
plugins: {
|
459
|
+
legend: {
|
460
|
+
display: true,
|
461
|
+
position: 'top',
|
462
|
+
align: 'end',
|
463
|
+
labels: {
|
464
|
+
usePointStyle: true,
|
465
|
+
pointStyle: 'rect',
|
466
|
+
font: {
|
467
|
+
size: 11
|
468
|
+
},
|
469
|
+
padding: 15
|
470
|
+
}
|
471
|
+
},
|
472
|
+
tooltip: {
|
473
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
474
|
+
titleColor: 'white',
|
475
|
+
bodyColor: 'white',
|
476
|
+
borderColor: 'rgba(255, 255, 255, 0.1)',
|
477
|
+
borderWidth: 1,
|
478
|
+
cornerRadius: 8,
|
479
|
+
displayColors: true,
|
480
|
+
callbacks: {
|
481
|
+
title: function(tooltipItems) {
|
482
|
+
const index = tooltipItems[0].dataIndex;
|
483
|
+
const phase = chartData[index].phase;
|
484
|
+
return phase;
|
485
|
+
},
|
486
|
+
beforeBody: function(tooltipItems) {
|
487
|
+
const index = tooltipItems[0].dataIndex;
|
488
|
+
const data = chartData[index];
|
489
|
+
const total = data.tokens;
|
490
|
+
return [`Total: ${formatNumber(total)} tokens`];
|
491
|
+
},
|
492
|
+
afterBody: function(tooltipItems) {
|
493
|
+
const index = tooltipItems[0].dataIndex;
|
494
|
+
const data = chartData[index];
|
495
|
+
const lines = [];
|
496
|
+
|
497
|
+
if (data.responseTime > 0) {
|
498
|
+
lines.push(`Response time: ${(data.responseTime / 1000).toFixed(1)}s`);
|
499
|
+
}
|
500
|
+
|
501
|
+
if (data.timestamp) {
|
502
|
+
const time = new Date(data.timestamp).toLocaleTimeString();
|
503
|
+
lines.push(`Time: ${time}`);
|
504
|
+
}
|
505
|
+
|
506
|
+
return lines;
|
507
|
+
}
|
508
|
+
}
|
509
|
+
}
|
510
|
+
}
|
511
|
+
}
|
512
|
+
});
|
513
|
+
|
514
|
+
console.log('Timeline chart created successfully');
|
515
|
+
} catch (error) {
|
516
|
+
console.error('Error creating timeline chart:', error);
|
517
|
+
console.error('Chart error details:', error.message, error.stack);
|
518
|
+
}
|
519
|
+
}
|
520
|
+
|
521
|
+
// Create search chart
|
522
|
+
function createSearchChart(searchData) {
|
523
|
+
if (!searchData || !searchData.search_calls) {
|
524
|
+
console.log('No search data for chart');
|
525
|
+
return;
|
526
|
+
}
|
527
|
+
|
528
|
+
const chartElement = document.getElementById('search-chart');
|
529
|
+
if (!chartElement) {
|
530
|
+
console.error('Search chart element not found');
|
531
|
+
return;
|
532
|
+
}
|
533
|
+
|
534
|
+
const ctx = chartElement.getContext('2d');
|
535
|
+
|
536
|
+
// Destroy existing chart
|
537
|
+
if (searchChart) {
|
538
|
+
searchChart.destroy();
|
539
|
+
}
|
540
|
+
|
541
|
+
// Prepare enhanced search data
|
542
|
+
const searchCalls = searchData.search_calls.map((call, index) => ({
|
543
|
+
label: call.query ? call.query.substring(0, 20) + '...' : `Search ${index + 1}`,
|
544
|
+
results: call.results_count || 0,
|
545
|
+
engine: call.engine || 'Unknown',
|
546
|
+
responseTime: call.response_time_ms || 0,
|
547
|
+
timestamp: call.timestamp
|
548
|
+
}));
|
549
|
+
|
550
|
+
const labels = searchCalls.map(call => call.label);
|
551
|
+
const results = searchCalls.map(call => call.results);
|
552
|
+
|
553
|
+
searchChart = new Chart(ctx, {
|
554
|
+
type: 'line',
|
555
|
+
data: {
|
556
|
+
labels: labels,
|
557
|
+
datasets: [{
|
558
|
+
label: 'Results Found',
|
559
|
+
data: results,
|
560
|
+
borderColor: 'rgba(168, 85, 247, 1)',
|
561
|
+
backgroundColor: 'rgba(168, 85, 247, 0.1)',
|
562
|
+
borderWidth: 2.5,
|
563
|
+
fill: true,
|
564
|
+
tension: 0.3,
|
565
|
+
pointBackgroundColor: 'rgba(168, 85, 247, 1)',
|
566
|
+
pointBorderColor: 'rgba(255, 255, 255, 1)',
|
567
|
+
pointBorderWidth: 2,
|
568
|
+
pointRadius: 4,
|
569
|
+
pointHoverRadius: 6
|
570
|
+
}]
|
571
|
+
},
|
572
|
+
options: {
|
573
|
+
responsive: true,
|
574
|
+
maintainAspectRatio: false,
|
575
|
+
animation: {
|
576
|
+
duration: 800,
|
577
|
+
easing: 'easeInOutQuart'
|
578
|
+
},
|
579
|
+
interaction: {
|
580
|
+
intersect: false,
|
581
|
+
mode: 'index'
|
582
|
+
},
|
583
|
+
scales: {
|
584
|
+
x: {
|
585
|
+
grid: {
|
586
|
+
display: false
|
587
|
+
},
|
588
|
+
ticks: {
|
589
|
+
font: {
|
590
|
+
size: 10
|
591
|
+
},
|
592
|
+
maxRotation: 45
|
593
|
+
}
|
594
|
+
},
|
595
|
+
y: {
|
596
|
+
beginAtZero: true,
|
597
|
+
grid: {
|
598
|
+
color: 'rgba(0, 0, 0, 0.1)',
|
599
|
+
drawBorder: false
|
600
|
+
},
|
601
|
+
ticks: {
|
602
|
+
font: {
|
603
|
+
size: 10
|
604
|
+
},
|
605
|
+
callback: function(value) {
|
606
|
+
return formatNumber(value);
|
607
|
+
}
|
608
|
+
},
|
609
|
+
title: {
|
610
|
+
display: true,
|
611
|
+
text: 'Results',
|
612
|
+
font: {
|
613
|
+
size: 11,
|
614
|
+
weight: 'bold'
|
615
|
+
}
|
616
|
+
}
|
617
|
+
}
|
618
|
+
},
|
619
|
+
plugins: {
|
620
|
+
legend: {
|
621
|
+
display: false
|
622
|
+
},
|
623
|
+
tooltip: {
|
624
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
625
|
+
titleColor: 'white',
|
626
|
+
bodyColor: 'white',
|
627
|
+
borderColor: 'rgba(255, 255, 255, 0.1)',
|
628
|
+
borderWidth: 1,
|
629
|
+
cornerRadius: 6,
|
630
|
+
callbacks: {
|
631
|
+
title: function(tooltipItems) {
|
632
|
+
const index = tooltipItems[0].dataIndex;
|
633
|
+
return searchCalls[index].label;
|
634
|
+
},
|
635
|
+
beforeBody: function(tooltipItems) {
|
636
|
+
const index = tooltipItems[0].dataIndex;
|
637
|
+
const call = searchCalls[index];
|
638
|
+
return [`Engine: ${call.engine}`];
|
639
|
+
},
|
640
|
+
afterBody: function(tooltipItems) {
|
641
|
+
const index = tooltipItems[0].dataIndex;
|
642
|
+
const call = searchCalls[index];
|
643
|
+
const lines = [];
|
644
|
+
|
645
|
+
if (call.responseTime > 0) {
|
646
|
+
lines.push(`Response time: ${(call.responseTime / 1000).toFixed(1)}s`);
|
647
|
+
}
|
648
|
+
|
649
|
+
return lines;
|
650
|
+
}
|
651
|
+
}
|
652
|
+
}
|
653
|
+
}
|
654
|
+
}
|
655
|
+
});
|
656
|
+
}
|
657
|
+
|
658
|
+
// Load cost data
|
659
|
+
async function loadCostData() {
|
660
|
+
try {
|
661
|
+
// Temporarily disable cost calculation until pricing logic is optimized
|
662
|
+
document.getElementById('total-cost').textContent = '-';
|
663
|
+
return;
|
664
|
+
|
665
|
+
const response = await fetch(`/metrics/api/research-costs/${researchId}`);
|
666
|
+
if (response.ok) {
|
667
|
+
const data = await response.json();
|
668
|
+
if (data.status === 'success') {
|
669
|
+
document.getElementById('total-cost').textContent = formatCurrency(data.total_cost || 0);
|
670
|
+
}
|
671
|
+
}
|
672
|
+
} catch (error) {
|
673
|
+
console.error('Error loading cost data:', error);
|
674
|
+
document.getElementById('total-cost').textContent = '-';
|
675
|
+
}
|
676
|
+
}
|
677
|
+
|
678
|
+
// Show error message
|
679
|
+
function showError() {
|
680
|
+
document.getElementById('loading').style.display = 'none';
|
681
|
+
document.getElementById('error').style.display = 'block';
|
682
|
+
document.getElementById('details-content').style.display = 'none';
|
683
|
+
}
|
684
|
+
|
685
|
+
// Check if all required DOM elements exist
|
686
|
+
function checkRequiredElements() {
|
687
|
+
const requiredIds = [
|
688
|
+
'loading', 'error', 'details-content', 'total-tokens', 'prompt-tokens',
|
689
|
+
'completion-tokens', 'llm-calls', 'avg-response-time', 'model-used',
|
690
|
+
'research-query', 'research-mode', 'research-date', 'research-strategy', 'total-cost',
|
691
|
+
'phase-breakdown', 'search-engine-breakdown', 'timeline-chart', 'search-chart'
|
692
|
+
];
|
693
|
+
|
694
|
+
const missing = [];
|
695
|
+
requiredIds.forEach(id => {
|
696
|
+
if (!document.getElementById(id)) {
|
697
|
+
missing.push(id);
|
698
|
+
}
|
699
|
+
});
|
700
|
+
|
701
|
+
if (missing.length > 0) {
|
702
|
+
console.error('Missing required DOM elements:', missing);
|
703
|
+
return false;
|
704
|
+
}
|
705
|
+
return true;
|
706
|
+
}
|
707
|
+
|
708
|
+
// Initialize when DOM is ready
|
709
|
+
document.addEventListener('DOMContentLoaded', function() {
|
710
|
+
console.log('DOM loaded, initializing details page');
|
711
|
+
|
712
|
+
researchId = getResearchIdFromUrl();
|
713
|
+
console.log('Research ID from URL:', researchId);
|
714
|
+
|
715
|
+
if (!researchId) {
|
716
|
+
console.error('No research ID found in URL');
|
717
|
+
showError();
|
718
|
+
return;
|
719
|
+
}
|
720
|
+
|
721
|
+
// Check if all required elements exist
|
722
|
+
if (!checkRequiredElements()) {
|
723
|
+
console.error('Required DOM elements missing');
|
724
|
+
showError();
|
725
|
+
return;
|
726
|
+
}
|
727
|
+
|
728
|
+
// Update page title
|
729
|
+
document.title = `Research Details #${researchId} - Deep Research System`;
|
730
|
+
|
731
|
+
// Load research metrics
|
732
|
+
loadResearchMetrics();
|
733
|
+
|
734
|
+
// View Results button
|
735
|
+
const viewResultsBtn = document.getElementById('view-results-btn');
|
736
|
+
if (viewResultsBtn) {
|
737
|
+
viewResultsBtn.addEventListener('click', () => {
|
738
|
+
window.location.href = `/research/results/${researchId}`;
|
739
|
+
});
|
740
|
+
}
|
741
|
+
|
742
|
+
// Back button
|
743
|
+
const backBtn = document.getElementById('back-to-history');
|
744
|
+
if (backBtn) {
|
745
|
+
backBtn.addEventListener('click', () => {
|
746
|
+
window.location.href = '/research/history';
|
747
|
+
});
|
748
|
+
}
|
749
|
+
});
|
750
|
+
|
751
|
+
})();
|