local-deep-research 0.4.4__py3-none-any.whl → 0.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- local_deep_research/__init__.py +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 +20 -3
- local_deep_research/web/database/models.py +74 -25
- local_deep_research/web/database/schema_upgrade.py +49 -29
- local_deep_research/web/models/database.py +63 -83
- 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 +192 -54
- local_deep_research/web/routes/settings_routes.py +156 -55
- local_deep_research/web/services/research_service.py +412 -251
- 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.2.dist-info}/METADATA +7 -1
- local_deep_research-0.5.2.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.2.dist-info}/WHEEL +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,348 +1,714 @@
|
|
1
1
|
/**
|
2
|
-
*
|
3
|
-
* Manages the display of
|
2
|
+
* Research Details Component
|
3
|
+
* Manages the display of research details and token metrics for a specific research
|
4
4
|
*/
|
5
5
|
(function() {
|
6
6
|
// DOM Elements
|
7
|
-
let
|
8
|
-
let
|
9
|
-
let
|
10
|
-
|
7
|
+
let queryElement = null;
|
8
|
+
let statusElement = null;
|
9
|
+
let modeElement = null;
|
10
|
+
let progressBar = null;
|
11
|
+
let progressPercentage = null;
|
12
|
+
let pollInterval = null;
|
13
|
+
|
14
|
+
// Token metrics elements
|
15
|
+
let timelineChart = null;
|
16
|
+
|
11
17
|
// Component state
|
12
18
|
let currentResearchId = null;
|
13
|
-
let
|
14
|
-
|
15
|
-
|
19
|
+
let isCompleted = false;
|
20
|
+
|
16
21
|
/**
|
17
|
-
* Initialize the
|
22
|
+
* Initialize the research details component
|
18
23
|
*/
|
19
|
-
function
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
if (!currentResearchId
|
26
|
-
console.error('No research
|
24
|
+
async function initializeResearchDetails() {
|
25
|
+
console.log('Initializing research details component...');
|
26
|
+
|
27
|
+
// Get research ID from URL
|
28
|
+
currentResearchId = getResearchIdFromUrl();
|
29
|
+
|
30
|
+
if (!currentResearchId) {
|
31
|
+
console.error('No research ID found in URL');
|
27
32
|
if (window.ui && window.ui.showError) {
|
28
|
-
window.ui.showError('
|
33
|
+
window.ui.showError('No research ID found. Please return to the history page.');
|
29
34
|
}
|
30
35
|
return;
|
31
36
|
}
|
32
|
-
|
37
|
+
|
38
|
+
console.log('Research ID:', currentResearchId);
|
39
|
+
|
33
40
|
// Get DOM elements
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
+
queryElement = document.getElementById('detail-query');
|
42
|
+
statusElement = document.getElementById('detail-status');
|
43
|
+
modeElement = document.getElementById('detail-mode');
|
44
|
+
progressBar = document.getElementById('detail-progress-fill');
|
45
|
+
progressPercentage = document.getElementById('detail-progress-percentage');
|
46
|
+
|
47
|
+
console.log('DOM elements found, setting up event listeners...');
|
48
|
+
|
41
49
|
// Set up event listeners
|
42
50
|
setupEventListeners();
|
43
|
-
|
44
|
-
// Load
|
45
|
-
|
46
|
-
|
47
|
-
|
51
|
+
|
52
|
+
// Load initial research details
|
53
|
+
await loadResearchDetails();
|
54
|
+
|
55
|
+
// Start polling for updates (only if needed)
|
56
|
+
startPolling();
|
57
|
+
|
58
|
+
console.log('Research details component initialized');
|
48
59
|
}
|
49
|
-
|
60
|
+
|
50
61
|
/**
|
51
|
-
* Extract research
|
52
|
-
* @returns {
|
62
|
+
* Extract research ID from URL
|
63
|
+
* @returns {string|null} Research ID or null if not found
|
53
64
|
*/
|
54
|
-
function
|
65
|
+
function getResearchIdFromUrl() {
|
55
66
|
const pathParts = window.location.pathname.split('/');
|
56
|
-
const
|
57
|
-
|
58
|
-
if (
|
59
|
-
return
|
60
|
-
researchId: pathParts[detailIndex + 1],
|
61
|
-
topicId: pathParts[detailIndex + 2]
|
62
|
-
};
|
67
|
+
const detailsIndex = pathParts.indexOf('details');
|
68
|
+
|
69
|
+
if (detailsIndex > 0 && detailsIndex + 1 < pathParts.length) {
|
70
|
+
return pathParts[detailsIndex + 1];
|
63
71
|
}
|
64
|
-
|
65
|
-
return
|
72
|
+
|
73
|
+
return null;
|
66
74
|
}
|
67
|
-
|
75
|
+
|
68
76
|
/**
|
69
77
|
* Set up event listeners
|
70
78
|
*/
|
71
79
|
function setupEventListeners() {
|
72
|
-
|
80
|
+
const viewResultsButton = document.getElementById('view-results-btn');
|
81
|
+
if (viewResultsButton) {
|
82
|
+
viewResultsButton.addEventListener('click', function() {
|
83
|
+
window.location.href = `/research/results/${currentResearchId}`;
|
84
|
+
});
|
85
|
+
}
|
86
|
+
|
73
87
|
const backButton = document.getElementById('back-to-history-from-details');
|
74
88
|
if (backButton) {
|
75
89
|
backButton.addEventListener('click', function() {
|
76
90
|
window.location.href = '/research/history';
|
77
91
|
});
|
78
92
|
}
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
}
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* Load basic research details (for polling)
|
97
|
+
*/
|
98
|
+
async function loadBasicResearchDetails() {
|
99
|
+
console.log('Loading basic research details...');
|
100
|
+
|
101
|
+
try {
|
102
|
+
const response = await fetch(`/research/api/research/${currentResearchId}`, {
|
103
|
+
headers: {
|
104
|
+
'Accept': 'application/json'
|
109
105
|
}
|
110
106
|
});
|
107
|
+
|
108
|
+
if (!response.ok) {
|
109
|
+
throw new Error(`Failed to load research details: ${response.statusText}`);
|
110
|
+
}
|
111
|
+
|
112
|
+
const data = await response.json();
|
113
|
+
|
114
|
+
// Update UI with research details
|
115
|
+
updateResearchInfo(data);
|
116
|
+
|
117
|
+
// Check if research is completed
|
118
|
+
isCompleted = data.status === 'Completed' || data.status === 'Failed';
|
119
|
+
|
120
|
+
} catch (error) {
|
121
|
+
console.error('Error loading basic research details:', error);
|
111
122
|
}
|
112
123
|
}
|
113
|
-
|
124
|
+
|
114
125
|
/**
|
115
|
-
* Load
|
126
|
+
* Load research details from API (full load including token metrics)
|
116
127
|
*/
|
117
|
-
async function
|
118
|
-
|
119
|
-
|
120
|
-
|
128
|
+
async function loadResearchDetails() {
|
129
|
+
console.log('Loading research details...');
|
130
|
+
|
121
131
|
try {
|
122
|
-
//
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
132
|
+
// Fetch research details
|
133
|
+
const response = await fetch(`/research/api/research/${currentResearchId}`, {
|
134
|
+
headers: {
|
135
|
+
'Accept': 'application/json'
|
136
|
+
}
|
137
|
+
});
|
138
|
+
|
139
|
+
if (!response.ok) {
|
140
|
+
throw new Error(`Failed to load research details: ${response.statusText}`);
|
141
|
+
}
|
142
|
+
|
143
|
+
const data = await response.json();
|
144
|
+
console.log('Research details loaded:', data);
|
145
|
+
|
146
|
+
// Update UI with research details
|
147
|
+
updateResearchInfo(data);
|
148
|
+
|
149
|
+
// Check if research is completed
|
150
|
+
isCompleted = data.status === 'Completed' || data.status === 'Failed';
|
151
|
+
if (isCompleted && pollInterval) {
|
152
|
+
clearInterval(pollInterval);
|
153
|
+
pollInterval = null;
|
127
154
|
}
|
128
|
-
|
129
|
-
//
|
130
|
-
|
155
|
+
|
156
|
+
// Load token metrics
|
157
|
+
await loadTokenMetrics();
|
158
|
+
|
159
|
+
// Load search metrics
|
160
|
+
await loadSearchMetrics();
|
161
|
+
|
131
162
|
} catch (error) {
|
132
|
-
console.error('Error loading
|
133
|
-
window.ui.
|
134
|
-
|
163
|
+
console.error('Error loading research details:', error);
|
164
|
+
if (window.ui && window.ui.showError) {
|
165
|
+
window.ui.showError('Error loading research details: ' + error.message);
|
166
|
+
}
|
135
167
|
}
|
136
168
|
}
|
137
|
-
|
169
|
+
|
138
170
|
/**
|
139
|
-
*
|
140
|
-
* @param {Object} data -
|
171
|
+
* Update research info display
|
172
|
+
* @param {Object} data - Research data
|
141
173
|
*/
|
142
|
-
function
|
143
|
-
|
144
|
-
|
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;
|
174
|
+
function updateResearchInfo(data) {
|
175
|
+
if (queryElement) {
|
176
|
+
queryElement.textContent = data.query || 'N/A';
|
153
177
|
}
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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>';
|
178
|
+
|
179
|
+
if (statusElement) {
|
180
|
+
statusElement.textContent = data.status || 'N/A';
|
181
|
+
statusElement.className = 'metadata-value status-' + (data.status || 'unknown').toLowerCase();
|
165
182
|
}
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
renderSources(data.sources);
|
183
|
+
|
184
|
+
if (modeElement) {
|
185
|
+
modeElement.textContent = data.mode || 'standard';
|
170
186
|
}
|
171
|
-
|
172
|
-
//
|
173
|
-
const
|
174
|
-
if (
|
175
|
-
|
187
|
+
|
188
|
+
// Update progress
|
189
|
+
const progress = data.progress_percentage || 0;
|
190
|
+
if (progressBar) {
|
191
|
+
progressBar.style.width = progress + '%';
|
176
192
|
}
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
193
|
+
if (progressPercentage) {
|
194
|
+
progressPercentage.textContent = progress + '%';
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
/**
|
199
|
+
* Load search metrics for this research
|
200
|
+
*/
|
201
|
+
async function loadSearchMetrics() {
|
202
|
+
console.log('Loading search metrics...');
|
203
|
+
|
204
|
+
try {
|
205
|
+
const response = await fetch(`/metrics/api/metrics/research/${currentResearchId}/search`, {
|
206
|
+
headers: {
|
207
|
+
'Accept': 'application/json'
|
208
|
+
}
|
209
|
+
});
|
210
|
+
|
211
|
+
if (!response.ok) {
|
212
|
+
console.log('No search metrics available for this research');
|
213
|
+
return;
|
214
|
+
}
|
215
|
+
|
216
|
+
const data = await response.json();
|
217
|
+
|
218
|
+
if (data.status === 'success' && data.metrics) {
|
219
|
+
console.log('Search metrics loaded:', data.metrics);
|
220
|
+
displaySearchMetrics(data.metrics);
|
221
|
+
displaySearchEnginePerformance(data.metrics.engine_stats || []);
|
222
|
+
displaySearchTimeline(data.metrics.search_calls || []);
|
183
223
|
}
|
224
|
+
|
225
|
+
} catch (error) {
|
226
|
+
console.error('Error loading search metrics:', error);
|
184
227
|
}
|
185
228
|
}
|
186
|
-
|
229
|
+
|
187
230
|
/**
|
188
|
-
*
|
189
|
-
* @param {
|
231
|
+
* Display search metrics summary
|
232
|
+
* @param {Object} searchData - Search metrics data
|
190
233
|
*/
|
191
|
-
function
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
234
|
+
function displaySearchMetrics(searchData) {
|
235
|
+
const searchSummary = document.getElementById('search-summary');
|
236
|
+
if (!searchSummary || !searchData) return;
|
237
|
+
|
238
|
+
// Show the search analytics section
|
239
|
+
const searchSection = document.getElementById('search-metrics-section');
|
240
|
+
if (searchSection) {
|
241
|
+
searchSection.style.display = 'block';
|
242
|
+
}
|
243
|
+
|
244
|
+
const totalSearches = searchData.total_searches || 0;
|
245
|
+
const successRate = searchData.success_rate || 0;
|
246
|
+
const avgResponseTime = (searchData.avg_response_time || 0) / 1000; // Convert ms to seconds
|
247
|
+
const totalResults = searchData.total_results || 0;
|
248
|
+
|
249
|
+
// Update individual metric elements
|
250
|
+
const totalSearchesEl = document.getElementById('total-searches');
|
251
|
+
const totalResultsEl = document.getElementById('total-search-results');
|
252
|
+
const avgResponseTimeEl = document.getElementById('avg-search-response-time');
|
253
|
+
const successRateEl = document.getElementById('search-success-rate');
|
254
|
+
|
255
|
+
if (totalSearchesEl) totalSearchesEl.textContent = totalSearches;
|
256
|
+
if (totalResultsEl) totalResultsEl.textContent = formatNumber(totalResults);
|
257
|
+
if (avgResponseTimeEl) avgResponseTimeEl.textContent = avgResponseTime.toFixed(2) + 's';
|
258
|
+
if (successRateEl) successRateEl.textContent = successRate.toFixed(1) + '%';
|
259
|
+
}
|
260
|
+
|
261
|
+
/**
|
262
|
+
* Display search engine performance breakdown
|
263
|
+
* @param {Array} enginePerformance - Search engine performance data
|
264
|
+
*/
|
265
|
+
function displaySearchEnginePerformance(enginePerformance) {
|
266
|
+
const engineContainer = document.getElementById('search-engine-performance');
|
267
|
+
if (!engineContainer) return;
|
268
|
+
|
269
|
+
if (!enginePerformance || enginePerformance.length === 0) {
|
270
|
+
engineContainer.innerHTML = '<p style="text-align: center; color: var(--text-secondary);">No search engine data available</p>';
|
271
|
+
return;
|
272
|
+
}
|
273
|
+
|
274
|
+
const engineHtml = enginePerformance.map(engine => `
|
275
|
+
<div class="search-engine-item">
|
276
|
+
<div class="engine-name">${escapeHtml(engine.engine)}</div>
|
277
|
+
<div class="engine-stats">
|
278
|
+
<span class="stat">${engine.call_count} searches</span>
|
279
|
+
<span class="stat">${engine.success_rate.toFixed(1)}% success</span>
|
280
|
+
<span class="stat">${(engine.avg_response_time / 1000).toFixed(2)}s avg</span>
|
281
|
+
<span class="stat">${formatNumber(engine.total_results)} results</span>
|
215
282
|
</div>
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
});
|
283
|
+
</div>
|
284
|
+
`).join('');
|
285
|
+
|
286
|
+
engineContainer.innerHTML = engineHtml;
|
221
287
|
}
|
222
|
-
|
288
|
+
|
223
289
|
/**
|
224
|
-
*
|
225
|
-
* @param {
|
290
|
+
* Display search timeline
|
291
|
+
* @param {Array} searchTimeline - Search timeline data
|
226
292
|
*/
|
227
|
-
function
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
293
|
+
function displaySearchTimeline(searchTimeline) {
|
294
|
+
const timelineContainer = document.getElementById('search-timeline');
|
295
|
+
if (!timelineContainer) return;
|
296
|
+
|
297
|
+
if (!searchTimeline || searchTimeline.length === 0) {
|
298
|
+
timelineContainer.innerHTML = '<p style="text-align: center; color: var(--text-secondary);">No search timeline data available</p>';
|
299
|
+
return;
|
300
|
+
}
|
301
|
+
|
302
|
+
const timelineHtml = searchTimeline.map(search => {
|
303
|
+
const timestamp = new Date(search.timestamp).toLocaleTimeString();
|
304
|
+
const statusClass = search.success_status === 'success' ? 'success' : 'error';
|
305
|
+
const statusText = search.success_status === 'success' ? 'Success' : 'Failed';
|
306
|
+
|
307
|
+
return `
|
308
|
+
<div class="timeline-item">
|
309
|
+
<div class="timeline-time">${timestamp}</div>
|
310
|
+
<div class="timeline-engine">${escapeHtml(search.engine)}</div>
|
311
|
+
<div class="timeline-query">${escapeHtml(search.query)}</div>
|
312
|
+
<div class="timeline-results">${formatNumber(search.results_count)} results</div>
|
313
|
+
<div class="timeline-response">${(search.response_time_ms / 1000).toFixed(2)}s</div>
|
314
|
+
<div class="timeline-status ${statusClass}">${statusText}</div>
|
315
|
+
</div>
|
316
|
+
`;
|
317
|
+
}).join('');
|
318
|
+
|
319
|
+
timelineContainer.innerHTML = timelineHtml;
|
239
320
|
}
|
240
|
-
|
321
|
+
|
322
|
+
|
241
323
|
/**
|
242
|
-
*
|
243
|
-
* @param {HTMLElement} sourceEl - The source element to highlight
|
324
|
+
* Start polling for updates (only if research is not completed)
|
244
325
|
*/
|
245
|
-
function
|
246
|
-
//
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
326
|
+
function startPolling() {
|
327
|
+
// Only poll if research is not completed
|
328
|
+
if (!isCompleted) {
|
329
|
+
console.log('Starting polling for updates...');
|
330
|
+
|
331
|
+
// Poll every 10 seconds (much less frequent)
|
332
|
+
pollInterval = setInterval(async () => {
|
333
|
+
if (!isCompleted) {
|
334
|
+
// Only reload basic research info, not token metrics
|
335
|
+
await loadBasicResearchDetails();
|
336
|
+
} else {
|
337
|
+
// Stop polling once completed
|
338
|
+
if (pollInterval) {
|
339
|
+
clearInterval(pollInterval);
|
340
|
+
pollInterval = null;
|
341
|
+
console.log('Research completed, stopped polling');
|
342
|
+
}
|
343
|
+
}
|
344
|
+
}, 10000);
|
345
|
+
} else {
|
346
|
+
console.log('Research already completed, no polling needed');
|
347
|
+
}
|
258
348
|
}
|
259
|
-
|
349
|
+
|
260
350
|
/**
|
261
|
-
*
|
262
|
-
* @param {number} sourceIndex - The source index to highlight
|
351
|
+
* Load token metrics for this research
|
263
352
|
*/
|
264
|
-
function
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
if (citations.length > 0) {
|
278
|
-
citations[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
353
|
+
async function loadTokenMetrics() {
|
354
|
+
console.log('Loading token metrics...');
|
355
|
+
|
356
|
+
try {
|
357
|
+
const response = await fetch(`/metrics/api/metrics/research/${currentResearchId}/timeline`, {
|
358
|
+
headers: {
|
359
|
+
'Accept': 'application/json'
|
360
|
+
}
|
361
|
+
});
|
362
|
+
|
363
|
+
if (!response.ok) {
|
364
|
+
console.log('No token metrics available for this research');
|
365
|
+
return;
|
279
366
|
}
|
280
|
-
|
367
|
+
|
368
|
+
const data = await response.json();
|
369
|
+
|
370
|
+
if (data.status === 'success') {
|
371
|
+
console.log('Token metrics loaded:', data.metrics);
|
372
|
+
displayTokenMetrics(data.metrics);
|
373
|
+
}
|
374
|
+
|
375
|
+
} catch (error) {
|
376
|
+
console.error('Error loading token metrics:', error);
|
377
|
+
}
|
281
378
|
}
|
282
|
-
|
379
|
+
|
283
380
|
/**
|
284
|
-
*
|
285
|
-
* @param {
|
381
|
+
* Display token metrics on the page
|
382
|
+
* @param {Object} metrics - Token metrics data
|
286
383
|
*/
|
287
|
-
|
288
|
-
|
289
|
-
|
384
|
+
function displayTokenMetrics(metrics) {
|
385
|
+
console.log('Displaying token metrics with data:', metrics);
|
386
|
+
|
387
|
+
// Show the metrics section
|
388
|
+
const metricsSection = document.getElementById('token-metrics-section');
|
389
|
+
if (metricsSection) {
|
390
|
+
metricsSection.style.display = 'block';
|
290
391
|
}
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
392
|
+
|
393
|
+
// Update summary cards with safe data access
|
394
|
+
const summary = metrics.summary || {};
|
395
|
+
const totalTokensEl = document.getElementById('total-tokens');
|
396
|
+
const totalPromptTokensEl = document.getElementById('total-prompt-tokens');
|
397
|
+
const totalCompletionTokensEl = document.getElementById('total-completion-tokens');
|
398
|
+
const totalCallsEl = document.getElementById('total-calls');
|
399
|
+
const avgResponseTimeEl = document.getElementById('avg-response-time');
|
400
|
+
const successRateEl = document.getElementById('success-rate');
|
401
|
+
|
402
|
+
if (totalTokensEl) totalTokensEl.textContent = formatNumber(summary.total_tokens || 0);
|
403
|
+
if (totalPromptTokensEl) totalPromptTokensEl.textContent = formatNumber(summary.total_prompt_tokens || 0);
|
404
|
+
if (totalCompletionTokensEl) totalCompletionTokensEl.textContent = formatNumber(summary.total_completion_tokens || 0);
|
405
|
+
if (totalCallsEl) totalCallsEl.textContent = formatNumber(summary.total_calls || 0);
|
406
|
+
if (avgResponseTimeEl) avgResponseTimeEl.textContent = (summary.avg_response_time || 0) + 'ms';
|
407
|
+
if (successRateEl) successRateEl.textContent = (summary.success_rate || 0) + '%';
|
408
|
+
|
409
|
+
// Create timeline chart
|
410
|
+
createTimelineChart(metrics.timeline);
|
411
|
+
|
412
|
+
// Display phase breakdown
|
413
|
+
displayPhaseBreakdown(metrics.phase_stats);
|
414
|
+
|
415
|
+
// Display call stack traces
|
416
|
+
displayCallStackTraces(metrics.timeline);
|
417
|
+
}
|
418
|
+
|
419
|
+
/**
|
420
|
+
* Create timeline chart
|
421
|
+
* @param {Array} timeline - Timeline data
|
422
|
+
*/
|
423
|
+
function createTimelineChart(timeline) {
|
424
|
+
const ctx = document.getElementById('timeline-chart');
|
425
|
+
if (!ctx) return;
|
426
|
+
|
427
|
+
// Handle empty timeline
|
428
|
+
if (!timeline || timeline.length === 0) {
|
429
|
+
console.log('No timeline data available');
|
430
|
+
ctx.getContext('2d').clearRect(0, 0, ctx.width, ctx.height);
|
431
|
+
|
432
|
+
// Show a message in the chart area
|
433
|
+
const chartContainer = ctx.parentElement;
|
434
|
+
if (chartContainer) {
|
435
|
+
chartContainer.innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--text-secondary);">No token usage timeline available for this research.<br>This research may have used search-only mode or meta search engine.</div>';
|
301
436
|
}
|
302
|
-
|
303
|
-
}
|
304
|
-
|
305
|
-
if (source.title) {
|
306
|
-
citation += `"${source.title}". `;
|
437
|
+
return;
|
307
438
|
}
|
308
|
-
|
309
|
-
|
310
|
-
|
439
|
+
|
440
|
+
// Prepare data
|
441
|
+
const labels = timeline.map(item => {
|
442
|
+
const date = new Date(item.timestamp);
|
443
|
+
return date.toLocaleTimeString('en-US', {
|
444
|
+
hour: '2-digit',
|
445
|
+
minute: '2-digit',
|
446
|
+
second: '2-digit'
|
447
|
+
});
|
448
|
+
});
|
449
|
+
|
450
|
+
const cumulativeTokens = timeline.map(item => item.cumulative_tokens);
|
451
|
+
const cumulativePromptTokens = timeline.map(item => item.cumulative_prompt_tokens);
|
452
|
+
const cumulativeCompletionTokens = timeline.map(item => item.cumulative_completion_tokens);
|
453
|
+
const promptTokens = timeline.map(item => item.prompt_tokens);
|
454
|
+
const completionTokens = timeline.map(item => item.completion_tokens);
|
455
|
+
|
456
|
+
// Debug logging
|
457
|
+
console.log('Timeline data sample:', timeline.slice(0, 2));
|
458
|
+
console.log('Prompt tokens sample:', promptTokens.slice(0, 5));
|
459
|
+
console.log('Completion tokens sample:', completionTokens.slice(0, 5));
|
460
|
+
|
461
|
+
// Destroy existing chart
|
462
|
+
if (timelineChart) {
|
463
|
+
timelineChart.destroy();
|
311
464
|
}
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
465
|
+
|
466
|
+
timelineChart = new Chart(ctx, {
|
467
|
+
type: 'line',
|
468
|
+
data: {
|
469
|
+
labels: labels,
|
470
|
+
datasets: [{
|
471
|
+
label: 'Cumulative Total Tokens',
|
472
|
+
data: cumulativeTokens,
|
473
|
+
borderColor: 'rgba(75, 192, 192, 1)',
|
474
|
+
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
475
|
+
borderWidth: 3,
|
476
|
+
fill: true,
|
477
|
+
tension: 0.1,
|
478
|
+
pointRadius: 4,
|
479
|
+
pointHoverRadius: 6,
|
480
|
+
}, {
|
481
|
+
label: 'Cumulative Input Tokens',
|
482
|
+
data: cumulativePromptTokens,
|
483
|
+
borderColor: 'rgba(54, 162, 235, 1)',
|
484
|
+
backgroundColor: 'rgba(54, 162, 235, 0.05)',
|
485
|
+
borderWidth: 2,
|
486
|
+
fill: true,
|
487
|
+
tension: 0.1,
|
488
|
+
pointRadius: 2,
|
489
|
+
pointHoverRadius: 4,
|
490
|
+
}, {
|
491
|
+
label: 'Cumulative Output Tokens',
|
492
|
+
data: cumulativeCompletionTokens,
|
493
|
+
borderColor: 'rgba(255, 99, 132, 1)',
|
494
|
+
backgroundColor: 'rgba(255, 99, 132, 0.05)',
|
495
|
+
borderWidth: 2,
|
496
|
+
fill: true,
|
497
|
+
tension: 0.1,
|
498
|
+
pointRadius: 2,
|
499
|
+
pointHoverRadius: 4,
|
500
|
+
}, {
|
501
|
+
label: 'Input Tokens per Call',
|
502
|
+
data: promptTokens,
|
503
|
+
borderColor: 'rgba(54, 162, 235, 0.8)',
|
504
|
+
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
505
|
+
borderWidth: 1,
|
506
|
+
fill: false,
|
507
|
+
tension: 0.1,
|
508
|
+
pointRadius: 2,
|
509
|
+
pointHoverRadius: 4,
|
510
|
+
yAxisID: 'y1',
|
511
|
+
borderDash: [5, 5],
|
512
|
+
}, {
|
513
|
+
label: 'Output Tokens per Call',
|
514
|
+
data: completionTokens,
|
515
|
+
borderColor: 'rgba(255, 99, 132, 0.8)',
|
516
|
+
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
517
|
+
borderWidth: 1,
|
518
|
+
fill: false,
|
519
|
+
tension: 0.1,
|
520
|
+
pointRadius: 2,
|
521
|
+
pointHoverRadius: 4,
|
522
|
+
yAxisID: 'y1',
|
523
|
+
borderDash: [5, 5],
|
524
|
+
}]
|
525
|
+
},
|
526
|
+
options: {
|
527
|
+
responsive: true,
|
528
|
+
maintainAspectRatio: false,
|
529
|
+
interaction: {
|
530
|
+
intersect: false,
|
531
|
+
mode: 'index'
|
532
|
+
},
|
533
|
+
scales: {
|
534
|
+
x: {
|
535
|
+
display: true,
|
536
|
+
title: {
|
537
|
+
display: true,
|
538
|
+
text: 'Time'
|
539
|
+
}
|
540
|
+
},
|
541
|
+
y: {
|
542
|
+
type: 'linear',
|
543
|
+
display: true,
|
544
|
+
position: 'left',
|
545
|
+
title: {
|
546
|
+
display: true,
|
547
|
+
text: 'Cumulative Tokens'
|
548
|
+
},
|
549
|
+
beginAtZero: true
|
550
|
+
},
|
551
|
+
y1: {
|
552
|
+
type: 'linear',
|
553
|
+
display: true,
|
554
|
+
position: 'right',
|
555
|
+
title: {
|
556
|
+
display: true,
|
557
|
+
text: 'Tokens per Call'
|
558
|
+
},
|
559
|
+
beginAtZero: true,
|
560
|
+
grid: {
|
561
|
+
drawOnChartArea: false,
|
562
|
+
},
|
563
|
+
}
|
564
|
+
},
|
565
|
+
plugins: {
|
566
|
+
legend: {
|
567
|
+
position: 'top'
|
568
|
+
},
|
569
|
+
tooltip: {
|
570
|
+
callbacks: {
|
571
|
+
title: function(context) {
|
572
|
+
const index = context[0].dataIndex;
|
573
|
+
const item = timeline[index];
|
574
|
+
return `${item.research_phase || 'Unknown Phase'} - ${item.model_name || 'Unknown Model'}`;
|
575
|
+
},
|
576
|
+
label: function(context) {
|
577
|
+
let label = context.dataset.label || '';
|
578
|
+
if (label) {
|
579
|
+
label += ': ';
|
580
|
+
}
|
581
|
+
label += formatNumber(context.parsed.y);
|
582
|
+
return label;
|
583
|
+
},
|
584
|
+
afterBody: function(context) {
|
585
|
+
const index = context[0].dataIndex;
|
586
|
+
const item = timeline[index];
|
587
|
+
return [
|
588
|
+
`Response Time: ${item.response_time_ms || 0}ms`,
|
589
|
+
`Status: ${item.success_status || 'unknown'}`,
|
590
|
+
`Engine: ${item.search_engine_selected || 'N/A'}`
|
591
|
+
];
|
592
|
+
}
|
593
|
+
}
|
594
|
+
}
|
595
|
+
}
|
326
596
|
}
|
327
|
-
}
|
328
|
-
console.error('Failed to copy citation:', error);
|
329
|
-
}
|
597
|
+
});
|
330
598
|
}
|
331
|
-
|
599
|
+
|
332
600
|
/**
|
333
|
-
*
|
601
|
+
* Display phase breakdown
|
602
|
+
* @param {Object} phaseStats - Phase statistics
|
334
603
|
*/
|
335
|
-
function
|
336
|
-
|
337
|
-
if (
|
338
|
-
|
604
|
+
function displayPhaseBreakdown(phaseStats) {
|
605
|
+
const container = document.getElementById('phase-breakdown');
|
606
|
+
if (!container) return;
|
607
|
+
|
608
|
+
container.innerHTML = '';
|
609
|
+
|
610
|
+
Object.keys(phaseStats).forEach(phase => {
|
611
|
+
const stats = phaseStats[phase];
|
612
|
+
const item = document.createElement('div');
|
613
|
+
item.className = 'phase-stat-item';
|
614
|
+
item.innerHTML = `
|
615
|
+
<div class="phase-name">${phase}</div>
|
616
|
+
<div class="phase-tokens">${formatNumber(stats.tokens)} tokens</div>
|
617
|
+
<div class="phase-calls">${stats.count} calls | ${stats.avg_response_time}ms avg</div>
|
618
|
+
`;
|
619
|
+
container.appendChild(item);
|
620
|
+
});
|
621
|
+
}
|
622
|
+
|
623
|
+
/**
|
624
|
+
* Display call stack traces for LLM calls
|
625
|
+
* @param {Array} timeline - Timeline data with call stack info
|
626
|
+
*/
|
627
|
+
function displayCallStackTraces(timeline) {
|
628
|
+
const container = document.getElementById('call-stack-traces');
|
629
|
+
if (!container) return;
|
630
|
+
|
631
|
+
container.innerHTML = '';
|
632
|
+
|
633
|
+
// Filter timeline items that have call stack information
|
634
|
+
const itemsWithCallStack = timeline.filter(item => item.call_stack || item.calling_function);
|
635
|
+
|
636
|
+
if (itemsWithCallStack.length === 0) {
|
637
|
+
container.innerHTML = '<p style="text-align: center; color: var(--text-secondary);">No call stack traces available for this research</p>';
|
638
|
+
return;
|
339
639
|
}
|
640
|
+
|
641
|
+
itemsWithCallStack.forEach((item, index) => {
|
642
|
+
const trace = document.createElement('div');
|
643
|
+
trace.style.marginBottom = '1rem';
|
644
|
+
trace.style.padding = '0.75rem';
|
645
|
+
trace.style.backgroundColor = 'var(--card-bg)';
|
646
|
+
trace.style.borderRadius = '6px';
|
647
|
+
trace.style.border = '1px solid var(--border-color)';
|
648
|
+
|
649
|
+
// Format timestamp
|
650
|
+
const timestamp = item.timestamp ? new Date(item.timestamp).toLocaleTimeString() : 'Unknown time';
|
651
|
+
|
652
|
+
trace.innerHTML = `
|
653
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
|
654
|
+
<div style="font-weight: 500; color: var(--text-primary);">
|
655
|
+
${escapeHtml(item.calling_function || 'Unknown Function')}
|
656
|
+
</div>
|
657
|
+
<div style="font-size: 0.875rem; color: var(--text-secondary);">
|
658
|
+
${timestamp} - ${item.prompt_tokens || 0} in + ${item.completion_tokens || 0} out = ${item.tokens || 0} tokens, ${item.response_time_ms || 0}ms
|
659
|
+
</div>
|
660
|
+
</div>
|
661
|
+
<div style="font-family: 'Courier New', monospace; font-size: 0.75rem; background: var(--bg-color); padding: 0.5rem; border-radius: 4px; color: var(--text-secondary); overflow-x: auto; margin-bottom: 0.5rem;">
|
662
|
+
${escapeHtml(item.call_stack || 'No stack trace available')}
|
663
|
+
</div>
|
664
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 0.5rem; font-size: 0.875rem;">
|
665
|
+
<div><strong>File:</strong> ${escapeHtml((item.calling_file || 'Unknown').split('/').pop())}</div>
|
666
|
+
<div><strong>Phase:</strong> ${escapeHtml(item.research_phase || 'N/A')}</div>
|
667
|
+
<div><strong>Model:</strong> ${escapeHtml(item.model_name || 'N/A')}</div>
|
668
|
+
<div><strong>Status:</strong>
|
669
|
+
<span style="color: ${item.success_status === 'success' ? 'green' : 'red'}">
|
670
|
+
${escapeHtml(item.success_status || 'Unknown')}
|
671
|
+
</span>
|
672
|
+
</div>
|
673
|
+
</div>
|
674
|
+
`;
|
675
|
+
container.appendChild(trace);
|
676
|
+
});
|
677
|
+
}
|
678
|
+
|
679
|
+
/**
|
680
|
+
* Format large numbers with commas
|
681
|
+
* @param {number} num - Number to format
|
682
|
+
* @returns {string} Formatted number
|
683
|
+
*/
|
684
|
+
function formatNumber(num) {
|
685
|
+
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
340
686
|
}
|
341
|
-
|
687
|
+
|
688
|
+
/**
|
689
|
+
* Escape HTML to prevent XSS
|
690
|
+
* @param {string} text - Text to escape
|
691
|
+
* @returns {string} Escaped text
|
692
|
+
*/
|
693
|
+
function escapeHtml(text) {
|
694
|
+
const div = document.createElement('div');
|
695
|
+
div.textContent = text;
|
696
|
+
return div.innerHTML;
|
697
|
+
}
|
698
|
+
|
699
|
+
// Clean up on page unload
|
700
|
+
window.addEventListener('beforeunload', function() {
|
701
|
+
if (pollInterval) {
|
702
|
+
clearInterval(pollInterval);
|
703
|
+
}
|
704
|
+
});
|
705
|
+
|
342
706
|
// Initialize on DOM content loaded
|
343
707
|
if (document.readyState === 'loading') {
|
344
|
-
document.addEventListener('DOMContentLoaded',
|
708
|
+
document.addEventListener('DOMContentLoaded', () => {
|
709
|
+
initializeResearchDetails().catch(console.error);
|
710
|
+
});
|
345
711
|
} else {
|
346
|
-
|
712
|
+
initializeResearchDetails().catch(console.error);
|
347
713
|
}
|
348
|
-
})();
|
714
|
+
})();
|