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
@@ -0,0 +1,1929 @@
|
|
1
|
+
{% extends "base.html" %}
|
2
|
+
|
3
|
+
{% set active_page = 'metrics' %}
|
4
|
+
|
5
|
+
{% block title %}Metrics Dashboard - Deep Research System{% endblock %}
|
6
|
+
|
7
|
+
{% block extra_head %}
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
9
|
+
<style>
|
10
|
+
.metrics-grid {
|
11
|
+
display: grid;
|
12
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
13
|
+
gap: 1.5rem;
|
14
|
+
margin-bottom: 2rem;
|
15
|
+
}
|
16
|
+
|
17
|
+
.metric-card {
|
18
|
+
padding: 1.5rem;
|
19
|
+
background: var(--card-bg);
|
20
|
+
border: 1px solid var(--border-color);
|
21
|
+
border-radius: 0.5rem;
|
22
|
+
transition: transform 0.2s;
|
23
|
+
}
|
24
|
+
|
25
|
+
.metric-card:hover {
|
26
|
+
transform: translateY(-2px);
|
27
|
+
}
|
28
|
+
|
29
|
+
.metric-value {
|
30
|
+
font-size: 2rem;
|
31
|
+
font-weight: bold;
|
32
|
+
color: var(--primary-color);
|
33
|
+
margin: 0.5rem 0;
|
34
|
+
}
|
35
|
+
|
36
|
+
.metric-label {
|
37
|
+
color: var(--text-secondary);
|
38
|
+
font-size: 0.875rem;
|
39
|
+
text-transform: uppercase;
|
40
|
+
letter-spacing: 0.05em;
|
41
|
+
}
|
42
|
+
|
43
|
+
.metric-icon {
|
44
|
+
width: 40px;
|
45
|
+
height: 40px;
|
46
|
+
display: flex;
|
47
|
+
align-items: center;
|
48
|
+
justify-content: center;
|
49
|
+
background: var(--primary-color);
|
50
|
+
color: white;
|
51
|
+
border-radius: 0.375rem;
|
52
|
+
margin-bottom: 1rem;
|
53
|
+
}
|
54
|
+
|
55
|
+
.expandable-card {
|
56
|
+
overflow: hidden;
|
57
|
+
}
|
58
|
+
|
59
|
+
.metric-main {
|
60
|
+
position: relative;
|
61
|
+
cursor: pointer;
|
62
|
+
user-select: none;
|
63
|
+
}
|
64
|
+
|
65
|
+
.expand-icon {
|
66
|
+
position: absolute;
|
67
|
+
top: 1rem;
|
68
|
+
right: 1rem;
|
69
|
+
color: var(--text-secondary);
|
70
|
+
transition: transform 0.3s ease;
|
71
|
+
}
|
72
|
+
|
73
|
+
.expand-icon.expanded {
|
74
|
+
transform: rotate(180deg);
|
75
|
+
}
|
76
|
+
|
77
|
+
.metric-details {
|
78
|
+
border-top: 1px solid var(--border-color);
|
79
|
+
margin-top: 1rem;
|
80
|
+
padding-top: 1rem;
|
81
|
+
animation: slideDown 0.3s ease;
|
82
|
+
}
|
83
|
+
|
84
|
+
@keyframes slideDown {
|
85
|
+
from {
|
86
|
+
opacity: 0;
|
87
|
+
max-height: 0;
|
88
|
+
}
|
89
|
+
to {
|
90
|
+
opacity: 1;
|
91
|
+
max-height: 300px;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
.token-breakdown {
|
96
|
+
display: grid;
|
97
|
+
grid-template-columns: 1fr 1fr;
|
98
|
+
gap: 1rem;
|
99
|
+
}
|
100
|
+
|
101
|
+
.breakdown-section h4 {
|
102
|
+
margin: 0 0 0.75rem 0;
|
103
|
+
color: var(--text-primary);
|
104
|
+
font-size: 0.875rem;
|
105
|
+
font-weight: 600;
|
106
|
+
text-transform: uppercase;
|
107
|
+
letter-spacing: 0.05em;
|
108
|
+
}
|
109
|
+
|
110
|
+
.breakdown-grid {
|
111
|
+
display: flex;
|
112
|
+
flex-direction: column;
|
113
|
+
gap: 0.5rem;
|
114
|
+
}
|
115
|
+
|
116
|
+
.breakdown-item {
|
117
|
+
display: flex;
|
118
|
+
justify-content: space-between;
|
119
|
+
align-items: center;
|
120
|
+
padding: 0.5rem 0;
|
121
|
+
}
|
122
|
+
|
123
|
+
.breakdown-item.total {
|
124
|
+
border-top: 1px solid var(--border-color);
|
125
|
+
font-weight: 600;
|
126
|
+
color: var(--primary-color);
|
127
|
+
}
|
128
|
+
|
129
|
+
.breakdown-label {
|
130
|
+
font-size: 0.875rem;
|
131
|
+
color: var(--text-secondary);
|
132
|
+
}
|
133
|
+
|
134
|
+
.breakdown-value {
|
135
|
+
font-size: 0.875rem;
|
136
|
+
font-weight: 500;
|
137
|
+
color: var(--text-primary);
|
138
|
+
}
|
139
|
+
|
140
|
+
.breakdown-item.total .breakdown-value {
|
141
|
+
color: var(--primary-color);
|
142
|
+
font-weight: 600;
|
143
|
+
}
|
144
|
+
|
145
|
+
.chart-container {
|
146
|
+
position: relative;
|
147
|
+
height: 300px;
|
148
|
+
margin-top: 2rem;
|
149
|
+
}
|
150
|
+
|
151
|
+
.model-usage-list {
|
152
|
+
margin-top: 1rem;
|
153
|
+
}
|
154
|
+
|
155
|
+
.model-usage-item {
|
156
|
+
display: flex;
|
157
|
+
justify-content: space-between;
|
158
|
+
align-items: center;
|
159
|
+
padding: 0.75rem;
|
160
|
+
margin-bottom: 0.5rem;
|
161
|
+
background: var(--card-bg);
|
162
|
+
border: 1px solid var(--border-color);
|
163
|
+
border-radius: 0.375rem;
|
164
|
+
}
|
165
|
+
|
166
|
+
.model-info {
|
167
|
+
flex: 1;
|
168
|
+
}
|
169
|
+
|
170
|
+
.model-name {
|
171
|
+
font-weight: 600;
|
172
|
+
color: var(--text-primary);
|
173
|
+
}
|
174
|
+
|
175
|
+
.model-provider {
|
176
|
+
font-size: 0.875rem;
|
177
|
+
color: var(--text-secondary);
|
178
|
+
}
|
179
|
+
|
180
|
+
.model-stats {
|
181
|
+
text-align: right;
|
182
|
+
}
|
183
|
+
|
184
|
+
.token-count {
|
185
|
+
font-size: 1.125rem;
|
186
|
+
font-weight: 600;
|
187
|
+
color: var(--primary-color);
|
188
|
+
}
|
189
|
+
|
190
|
+
.call-count {
|
191
|
+
font-size: 0.875rem;
|
192
|
+
color: var(--text-secondary);
|
193
|
+
}
|
194
|
+
|
195
|
+
.recent-research-item {
|
196
|
+
display: flex;
|
197
|
+
justify-content: space-between;
|
198
|
+
align-items: center;
|
199
|
+
padding: 0.75rem;
|
200
|
+
margin-bottom: 0.5rem;
|
201
|
+
background: var(--card-bg);
|
202
|
+
border: 1px solid var(--border-color);
|
203
|
+
border-radius: 0.375rem;
|
204
|
+
transition: background-color 0.2s;
|
205
|
+
}
|
206
|
+
|
207
|
+
.recent-research-item:hover {
|
208
|
+
background-color: var(--border-color);
|
209
|
+
}
|
210
|
+
|
211
|
+
.research-query {
|
212
|
+
flex: 1;
|
213
|
+
margin-right: 1rem;
|
214
|
+
overflow: hidden;
|
215
|
+
text-overflow: ellipsis;
|
216
|
+
white-space: nowrap;
|
217
|
+
}
|
218
|
+
|
219
|
+
.research-tokens {
|
220
|
+
font-weight: 600;
|
221
|
+
color: var(--primary-color);
|
222
|
+
}
|
223
|
+
|
224
|
+
.loading-spinner {
|
225
|
+
text-align: center;
|
226
|
+
padding: 3rem;
|
227
|
+
}
|
228
|
+
|
229
|
+
.error-message {
|
230
|
+
text-align: center;
|
231
|
+
padding: 2rem;
|
232
|
+
color: var(--error-color, #dc3545);
|
233
|
+
}
|
234
|
+
|
235
|
+
/* Compact section headings */
|
236
|
+
h4 {
|
237
|
+
font-size: 1rem;
|
238
|
+
font-weight: 600;
|
239
|
+
color: var(--text-primary);
|
240
|
+
margin: 0 0 0.75rem 0;
|
241
|
+
padding-bottom: 0.5rem;
|
242
|
+
border-bottom: 1px solid var(--border-color);
|
243
|
+
}
|
244
|
+
|
245
|
+
/* Expandable details styling */
|
246
|
+
details summary {
|
247
|
+
list-style: none;
|
248
|
+
outline: none;
|
249
|
+
transition: all 0.2s ease;
|
250
|
+
}
|
251
|
+
|
252
|
+
details summary::-webkit-details-marker {
|
253
|
+
display: none;
|
254
|
+
}
|
255
|
+
|
256
|
+
details summary:hover {
|
257
|
+
background: var(--bg-color) !important;
|
258
|
+
}
|
259
|
+
|
260
|
+
details[open] summary {
|
261
|
+
margin-bottom: 1rem;
|
262
|
+
border-bottom: 1px solid var(--border-color);
|
263
|
+
}
|
264
|
+
|
265
|
+
/* Time range selector */
|
266
|
+
.time-range-selector {
|
267
|
+
display: flex;
|
268
|
+
gap: 0.5rem;
|
269
|
+
align-items: center;
|
270
|
+
margin-left: auto;
|
271
|
+
}
|
272
|
+
|
273
|
+
.time-range-btn {
|
274
|
+
padding: 0.5rem 1rem;
|
275
|
+
border: 1px solid var(--border-color);
|
276
|
+
background: var(--card-bg);
|
277
|
+
color: var(--text-secondary);
|
278
|
+
border-radius: 0.375rem;
|
279
|
+
cursor: pointer;
|
280
|
+
transition: all 0.2s;
|
281
|
+
font-size: 0.875rem;
|
282
|
+
font-weight: 500;
|
283
|
+
}
|
284
|
+
|
285
|
+
.time-range-btn:hover {
|
286
|
+
background: var(--bg-color);
|
287
|
+
color: var(--text-primary);
|
288
|
+
}
|
289
|
+
|
290
|
+
.time-range-btn.active {
|
291
|
+
background: var(--primary-color);
|
292
|
+
color: white;
|
293
|
+
border-color: var(--primary-color);
|
294
|
+
}
|
295
|
+
|
296
|
+
/* Tooltip styles */
|
297
|
+
.tooltip {
|
298
|
+
position: relative;
|
299
|
+
cursor: help;
|
300
|
+
}
|
301
|
+
|
302
|
+
.tooltip::after {
|
303
|
+
content: attr(data-tooltip);
|
304
|
+
position: absolute;
|
305
|
+
bottom: 150%;
|
306
|
+
left: 50%;
|
307
|
+
transform: translateX(-50%);
|
308
|
+
background: rgba(0, 0, 0, 0.95);
|
309
|
+
color: white;
|
310
|
+
padding: 0.75rem;
|
311
|
+
border-radius: 0.5rem;
|
312
|
+
font-size: 0.75rem;
|
313
|
+
line-height: 1.4;
|
314
|
+
max-width: 280px;
|
315
|
+
min-width: 200px;
|
316
|
+
width: max-content;
|
317
|
+
white-space: normal;
|
318
|
+
word-wrap: break-word;
|
319
|
+
text-align: center;
|
320
|
+
opacity: 0;
|
321
|
+
pointer-events: none;
|
322
|
+
transition: opacity 0.2s, transform 0.2s;
|
323
|
+
z-index: 1001;
|
324
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
|
325
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
326
|
+
}
|
327
|
+
|
328
|
+
/* Smart positioning for edge detection */
|
329
|
+
.tooltip.tooltip-left::after {
|
330
|
+
left: 0;
|
331
|
+
transform: translateX(0);
|
332
|
+
}
|
333
|
+
|
334
|
+
.tooltip.tooltip-right::after {
|
335
|
+
left: auto;
|
336
|
+
right: 0;
|
337
|
+
transform: translateX(0);
|
338
|
+
}
|
339
|
+
|
340
|
+
.tooltip::before {
|
341
|
+
content: '';
|
342
|
+
position: absolute;
|
343
|
+
bottom: 135%;
|
344
|
+
left: 50%;
|
345
|
+
transform: translateX(-50%);
|
346
|
+
border: 6px solid transparent;
|
347
|
+
border-top-color: rgba(0, 0, 0, 0.95);
|
348
|
+
opacity: 0;
|
349
|
+
pointer-events: none;
|
350
|
+
transition: opacity 0.2s;
|
351
|
+
z-index: 1001;
|
352
|
+
}
|
353
|
+
|
354
|
+
/* Arrow positioning for edge tooltips */
|
355
|
+
.tooltip.tooltip-left::before {
|
356
|
+
left: 1rem;
|
357
|
+
transform: translateX(0);
|
358
|
+
}
|
359
|
+
|
360
|
+
.tooltip.tooltip-right::before {
|
361
|
+
left: auto;
|
362
|
+
right: 1rem;
|
363
|
+
transform: translateX(0);
|
364
|
+
}
|
365
|
+
|
366
|
+
.tooltip:hover::after,
|
367
|
+
.tooltip:hover::before {
|
368
|
+
opacity: 1;
|
369
|
+
transform: translateX(-50%) translateY(-2px);
|
370
|
+
}
|
371
|
+
|
372
|
+
/* Info icon for additional help */
|
373
|
+
.info-icon {
|
374
|
+
color: var(--text-secondary);
|
375
|
+
margin-left: 0.25rem;
|
376
|
+
font-size: 0.875rem;
|
377
|
+
cursor: help;
|
378
|
+
}
|
379
|
+
|
380
|
+
.info-icon:hover {
|
381
|
+
color: var(--primary-color);
|
382
|
+
}
|
383
|
+
</style>
|
384
|
+
{% endblock %}
|
385
|
+
|
386
|
+
{% block content %}
|
387
|
+
<div class="page active" id="metrics">
|
388
|
+
<div class="page-header" style="display: flex; align-items: center; justify-content: space-between;">
|
389
|
+
<div style="display: flex; align-items: center; gap: 1rem;">
|
390
|
+
<h1>Metrics Dashboard</h1>
|
391
|
+
<a href="/metrics/star-reviews" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 0.875rem; transition: transform 0.2s;">
|
392
|
+
⭐ Star Reviews
|
393
|
+
</a>
|
394
|
+
<a href="/metrics/costs" style="background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 0.875rem; transition: transform 0.2s;">
|
395
|
+
💰 Cost Analytics
|
396
|
+
</a>
|
397
|
+
</div>
|
398
|
+
<div style="display: flex; gap: 2rem; align-items: center;">
|
399
|
+
<!-- Research Mode Filter -->
|
400
|
+
<div class="time-range-selector">
|
401
|
+
<span style="color: var(--text-secondary); margin-right: 0.5rem; font-size: 0.875rem;">Research Mode:</span>
|
402
|
+
<button class="time-range-btn" data-mode="quick">Quick Summary</button>
|
403
|
+
<button class="time-range-btn" data-mode="detailed">Detailed</button>
|
404
|
+
<button class="time-range-btn active" data-mode="all">All</button>
|
405
|
+
</div>
|
406
|
+
<!-- Time Range Filter -->
|
407
|
+
<div class="time-range-selector">
|
408
|
+
<span style="color: var(--text-secondary); margin-right: 0.5rem; font-size: 0.875rem;">Time Range:</span>
|
409
|
+
<button class="time-range-btn active" data-period="30d">30D</button>
|
410
|
+
<button class="time-range-btn" data-period="7d">7D</button>
|
411
|
+
<button class="time-range-btn" data-period="3m">3M</button>
|
412
|
+
<button class="time-range-btn" data-period="1y">1Y</button>
|
413
|
+
<button class="time-range-btn" data-period="all">All</button>
|
414
|
+
</div>
|
415
|
+
</div>
|
416
|
+
</div>
|
417
|
+
|
418
|
+
<div id="loading" class="loading-spinner">
|
419
|
+
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
420
|
+
<p>Loading metrics...</p>
|
421
|
+
</div>
|
422
|
+
|
423
|
+
<div id="error" class="error-message" style="display: none;">
|
424
|
+
<i class="fas fa-exclamation-circle fa-2x"></i>
|
425
|
+
<p>Error loading metrics</p>
|
426
|
+
</div>
|
427
|
+
|
428
|
+
<div id="metrics-content" style="display: none;">
|
429
|
+
<!-- Key Metrics Overview -->
|
430
|
+
<div class="card">
|
431
|
+
<div class="card-header">
|
432
|
+
<h2><i class="fas fa-tachometer-alt"></i> System Overview</h2>
|
433
|
+
</div>
|
434
|
+
<div class="card-content">
|
435
|
+
<!-- Primary Metrics -->
|
436
|
+
<div class="metrics-grid">
|
437
|
+
<div class="metric-card expandable-card">
|
438
|
+
<div class="metric-main" onclick="toggleTokenDetails()">
|
439
|
+
<div class="metric-icon">
|
440
|
+
<i class="fas fa-coins"></i>
|
441
|
+
</div>
|
442
|
+
<div class="metric-label tooltip" data-tooltip="Total input + output tokens consumed by all LLM calls in the selected time period">
|
443
|
+
Total Tokens Used
|
444
|
+
<i class="fas fa-info-circle info-icon"></i>
|
445
|
+
</div>
|
446
|
+
<div class="metric-value" id="total-tokens">0</div>
|
447
|
+
<div class="expand-icon">
|
448
|
+
<i class="fas fa-chevron-down" id="token-expand-icon"></i>
|
449
|
+
</div>
|
450
|
+
</div>
|
451
|
+
<div class="metric-details" id="token-details" style="display: none;">
|
452
|
+
<div class="token-breakdown">
|
453
|
+
<div class="breakdown-section">
|
454
|
+
<h4 class="tooltip" data-tooltip="Average tokens consumed per research session">Average</h4>
|
455
|
+
<div class="breakdown-grid">
|
456
|
+
<div class="breakdown-item">
|
457
|
+
<span class="breakdown-label">Input:</span>
|
458
|
+
<span class="breakdown-value" id="avg-input-tokens">0</span>
|
459
|
+
</div>
|
460
|
+
<div class="breakdown-item">
|
461
|
+
<span class="breakdown-label">Output:</span>
|
462
|
+
<span class="breakdown-value" id="avg-output-tokens">0</span>
|
463
|
+
</div>
|
464
|
+
<div class="breakdown-item total">
|
465
|
+
<span class="breakdown-label">Total:</span>
|
466
|
+
<span class="breakdown-value" id="avg-total-tokens">0</span>
|
467
|
+
</div>
|
468
|
+
</div>
|
469
|
+
</div>
|
470
|
+
<div class="breakdown-section">
|
471
|
+
<h4 class="tooltip" data-tooltip="Total tokens consumed across all research sessions">Total Usage</h4>
|
472
|
+
<div class="breakdown-grid">
|
473
|
+
<div class="breakdown-item">
|
474
|
+
<span class="breakdown-label">Input:</span>
|
475
|
+
<span class="breakdown-value" id="total-input-tokens">0</span>
|
476
|
+
</div>
|
477
|
+
<div class="breakdown-item">
|
478
|
+
<span class="breakdown-label">Output:</span>
|
479
|
+
<span class="breakdown-value" id="total-output-tokens">0</span>
|
480
|
+
</div>
|
481
|
+
<div class="breakdown-item total">
|
482
|
+
<span class="breakdown-label">Total:</span>
|
483
|
+
<span class="breakdown-value" id="total-all-tokens">0</span>
|
484
|
+
</div>
|
485
|
+
</div>
|
486
|
+
</div>
|
487
|
+
</div>
|
488
|
+
</div>
|
489
|
+
</div>
|
490
|
+
|
491
|
+
<div class="metric-card">
|
492
|
+
<div class="metric-icon">
|
493
|
+
<i class="fas fa-search"></i>
|
494
|
+
</div>
|
495
|
+
<div class="metric-label tooltip" data-tooltip="Number of research sessions that used LLM tokens in the selected time period">
|
496
|
+
Total Researches
|
497
|
+
<i class="fas fa-info-circle info-icon"></i>
|
498
|
+
</div>
|
499
|
+
<div class="metric-value" id="total-researches">0</div>
|
500
|
+
</div>
|
501
|
+
|
502
|
+
<div class="metric-card">
|
503
|
+
<div class="metric-icon">
|
504
|
+
<i class="fas fa-tachometer-alt"></i>
|
505
|
+
</div>
|
506
|
+
<div class="metric-label tooltip" data-tooltip="Average time for LLM calls to complete, measured from request to response">
|
507
|
+
Avg Response Time (All)
|
508
|
+
<i class="fas fa-info-circle info-icon"></i>
|
509
|
+
</div>
|
510
|
+
<div class="metric-value" id="avg-response-time">0s</div>
|
511
|
+
</div>
|
512
|
+
|
513
|
+
<div class="metric-card">
|
514
|
+
<div class="metric-icon">
|
515
|
+
<i class="fas fa-check-circle"></i>
|
516
|
+
</div>
|
517
|
+
<div class="metric-label tooltip" data-tooltip="Percentage of LLM calls that completed successfully without errors">
|
518
|
+
Success Rate
|
519
|
+
<i class="fas fa-info-circle info-icon"></i>
|
520
|
+
</div>
|
521
|
+
<div class="metric-value" id="success-rate">0%</div>
|
522
|
+
</div>
|
523
|
+
|
524
|
+
<div class="metric-card">
|
525
|
+
<div class="metric-icon">
|
526
|
+
<i class="fas fa-star"></i>
|
527
|
+
</div>
|
528
|
+
<div class="metric-label tooltip" data-tooltip="Average user satisfaction rating (1-5 stars) for research sessions. All rating data is stored locally on your device only and never shared.">
|
529
|
+
User Satisfaction
|
530
|
+
<i class="fas fa-info-circle info-icon"></i>
|
531
|
+
</div>
|
532
|
+
<div class="metric-value" id="avg-user-rating">-</div>
|
533
|
+
</div>
|
534
|
+
|
535
|
+
<div class="metric-card expandable-card" onclick="toggleCostDetails()">
|
536
|
+
<div class="metric-icon">
|
537
|
+
<i class="fas fa-dollar-sign"></i>
|
538
|
+
</div>
|
539
|
+
<div class="metric-header">
|
540
|
+
<div class="metric-label tooltip" data-tooltip="Estimated cost based on token usage and current model pricing. Costs are calculated using static pricing data for major LLM providers.">
|
541
|
+
Estimated Cost
|
542
|
+
<i class="fas fa-info-circle info-icon"></i>
|
543
|
+
</div>
|
544
|
+
<div class="metric-value" id="total-cost">-</div>
|
545
|
+
<div class="expand-icon">
|
546
|
+
<i class="fas fa-chevron-down" id="cost-expand-icon"></i>
|
547
|
+
</div>
|
548
|
+
</div>
|
549
|
+
<div class="metric-details" id="cost-details" style="display: none;">
|
550
|
+
<div class="cost-breakdown">
|
551
|
+
<div class="breakdown-section">
|
552
|
+
<h4 class="tooltip" data-tooltip="Average cost per research session">Average Cost</h4>
|
553
|
+
<div class="breakdown-grid">
|
554
|
+
<div class="breakdown-item">
|
555
|
+
<span class="breakdown-label">Per Research:</span>
|
556
|
+
<span class="breakdown-value" id="avg-cost-per-research">$0.00</span>
|
557
|
+
</div>
|
558
|
+
<div class="breakdown-item">
|
559
|
+
<span class="breakdown-label">Per Token:</span>
|
560
|
+
<span class="breakdown-value" id="avg-cost-per-token">$0.000000</span>
|
561
|
+
</div>
|
562
|
+
</div>
|
563
|
+
</div>
|
564
|
+
<div class="breakdown-section">
|
565
|
+
<h4 class="tooltip" data-tooltip="Cost breakdown by token type">Cost Breakdown</h4>
|
566
|
+
<div class="breakdown-grid">
|
567
|
+
<div class="breakdown-item">
|
568
|
+
<span class="breakdown-label">Input Tokens:</span>
|
569
|
+
<span class="breakdown-value" id="total-input-cost">$0.00</span>
|
570
|
+
</div>
|
571
|
+
<div class="breakdown-item">
|
572
|
+
<span class="breakdown-label">Output Tokens:</span>
|
573
|
+
<span class="breakdown-value" id="total-output-cost">$0.00</span>
|
574
|
+
</div>
|
575
|
+
<div class="breakdown-item total">
|
576
|
+
<span class="breakdown-label">Total:</span>
|
577
|
+
<span class="breakdown-value" id="total-cost-breakdown">$0.00</span>
|
578
|
+
</div>
|
579
|
+
</div>
|
580
|
+
</div>
|
581
|
+
</div>
|
582
|
+
</div>
|
583
|
+
</div>
|
584
|
+
</div>
|
585
|
+
|
586
|
+
<!-- Charts Section -->
|
587
|
+
<div style="margin-top: 2rem; display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
588
|
+
<!-- Token Consumption Chart -->
|
589
|
+
<div>
|
590
|
+
<h3><i class="fas fa-chart-line"></i> Token Consumption Over Time</h3>
|
591
|
+
<div class="chart-container" style="height: 400px;">
|
592
|
+
<canvas id="time-series-chart"></canvas>
|
593
|
+
</div>
|
594
|
+
</div>
|
595
|
+
|
596
|
+
<!-- Search Activity Chart -->
|
597
|
+
<div>
|
598
|
+
<h3><i class="fas fa-search"></i> Search Activity Over Time</h3>
|
599
|
+
<div class="chart-container" style="height: 400px;">
|
600
|
+
<canvas id="search-activity-chart"></canvas>
|
601
|
+
</div>
|
602
|
+
</div>
|
603
|
+
</div>
|
604
|
+
</div>
|
605
|
+
</div>
|
606
|
+
|
607
|
+
<!-- Models & Performance -->
|
608
|
+
<div class="card" style="margin-top: 2rem;">
|
609
|
+
<div class="card-header">
|
610
|
+
<h2><i class="fas fa-robot"></i> Models & Performance</h2>
|
611
|
+
</div>
|
612
|
+
<div class="card-content">
|
613
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
614
|
+
<!-- Model Usage List -->
|
615
|
+
<div>
|
616
|
+
<h3><i class="fas fa-list"></i> Model Usage</h3>
|
617
|
+
<div class="model-usage-list" id="model-usage-list">
|
618
|
+
<!-- Populated dynamically -->
|
619
|
+
</div>
|
620
|
+
</div>
|
621
|
+
|
622
|
+
<!-- Model Usage Chart -->
|
623
|
+
<div>
|
624
|
+
<h3><i class="fas fa-chart-bar"></i> Token Distribution</h3>
|
625
|
+
<div class="chart-container">
|
626
|
+
<canvas id="token-chart"></canvas>
|
627
|
+
</div>
|
628
|
+
</div>
|
629
|
+
</div>
|
630
|
+
|
631
|
+
<!-- Research Analytics in Compact Grid -->
|
632
|
+
<div style="margin-top: 2rem;">
|
633
|
+
<h3><i class="fas fa-analytics"></i> Research Analytics</h3>
|
634
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1rem;">
|
635
|
+
<!-- Research Mode Breakdown -->
|
636
|
+
<div>
|
637
|
+
<h4><i class="fas fa-layer-group"></i> Research Modes</h4>
|
638
|
+
<div id="mode-breakdown">
|
639
|
+
<!-- Populated dynamically -->
|
640
|
+
</div>
|
641
|
+
</div>
|
642
|
+
|
643
|
+
<!-- Search Engine Usage -->
|
644
|
+
<div>
|
645
|
+
<h4><i class="fas fa-globe"></i> Search Engines</h4>
|
646
|
+
<div id="search-engine-breakdown">
|
647
|
+
<!-- Populated dynamically -->
|
648
|
+
</div>
|
649
|
+
</div>
|
650
|
+
|
651
|
+
<!-- Strategy Usage -->
|
652
|
+
<div>
|
653
|
+
<h4 class="tooltip" data-tooltip="Shows which research strategies were used most frequently. Strategies determine how the system approaches and structures research queries.">
|
654
|
+
<i class="fas fa-chess"></i> Research Strategies
|
655
|
+
<i class="fas fa-info-circle info-icon"></i>
|
656
|
+
</h4>
|
657
|
+
<div id="strategy-breakdown">
|
658
|
+
<!-- Populated dynamically -->
|
659
|
+
</div>
|
660
|
+
</div>
|
661
|
+
|
662
|
+
<!-- Research Phase Analysis -->
|
663
|
+
<div>
|
664
|
+
<h4><i class="fas fa-project-diagram"></i> Research Phases</h4>
|
665
|
+
<div id="phase-breakdown">
|
666
|
+
<!-- Populated dynamically -->
|
667
|
+
</div>
|
668
|
+
</div>
|
669
|
+
|
670
|
+
<!-- User Satisfaction -->
|
671
|
+
<div>
|
672
|
+
<h4 class="tooltip" data-tooltip="Your personal research satisfaction ratings. All data stays private on your local device.">
|
673
|
+
<i class="fas fa-star"></i> User Satisfaction
|
674
|
+
<i class="fas fa-info-circle info-icon"></i>
|
675
|
+
</h4>
|
676
|
+
<div id="rating-breakdown">
|
677
|
+
<!-- Populated dynamically -->
|
678
|
+
</div>
|
679
|
+
</div>
|
680
|
+
</div>
|
681
|
+
</div>
|
682
|
+
</div>
|
683
|
+
</div>
|
684
|
+
|
685
|
+
<!-- Developer Insights -->
|
686
|
+
<div class="card" style="margin-top: 2rem;">
|
687
|
+
<div class="card-header">
|
688
|
+
<h2><i class="fas fa-code"></i> Developer Insights</h2>
|
689
|
+
</div>
|
690
|
+
<div class="card-content">
|
691
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-bottom: 2rem;">
|
692
|
+
<!-- Most Active Files -->
|
693
|
+
<div>
|
694
|
+
<h3><i class="fas fa-file-code"></i> Most Active Files</h3>
|
695
|
+
<div id="call-stack-files" style="margin-top: 1rem;">
|
696
|
+
<!-- Populated dynamically -->
|
697
|
+
</div>
|
698
|
+
</div>
|
699
|
+
|
700
|
+
<!-- Most Active Functions -->
|
701
|
+
<div>
|
702
|
+
<h3><i class="fas fa-function"></i> Most Active Functions</h3>
|
703
|
+
<div id="call-stack-functions" style="margin-top: 1rem;">
|
704
|
+
<!-- Populated dynamically -->
|
705
|
+
</div>
|
706
|
+
</div>
|
707
|
+
</div>
|
708
|
+
|
709
|
+
<!-- Expandable Recent Call Stack Traces -->
|
710
|
+
<details style="margin-top: 1rem;">
|
711
|
+
<summary style="cursor: pointer; font-weight: 600; color: var(--text-primary); padding: 0.5rem; background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 0.375rem;">
|
712
|
+
<i class="fas fa-sitemap"></i> Recent Call Stack Traces (Click to expand)
|
713
|
+
</summary>
|
714
|
+
<div id="recent-call-stacks" style="margin-top: 1rem; max-height: 400px; overflow-y: auto; padding: 1rem; background: var(--bg-color); border-radius: 0.375rem;">
|
715
|
+
<!-- Populated dynamically -->
|
716
|
+
</div>
|
717
|
+
</details>
|
718
|
+
</div>
|
719
|
+
</div>
|
720
|
+
|
721
|
+
<!-- Research History & Activity -->
|
722
|
+
<div class="card" style="margin-top: 2rem;">
|
723
|
+
<div class="card-header">
|
724
|
+
<h2><i class="fas fa-history"></i> Research History & Activity</h2>
|
725
|
+
</div>
|
726
|
+
<div class="card-content">
|
727
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
728
|
+
<!-- Recent Researches -->
|
729
|
+
<div>
|
730
|
+
<h3><i class="fas fa-search"></i> Recent Researches</h3>
|
731
|
+
<div id="recent-researches" style="margin-top: 1rem;">
|
732
|
+
<!-- Populated dynamically -->
|
733
|
+
</div>
|
734
|
+
</div>
|
735
|
+
|
736
|
+
<!-- Recent Enhanced Tracking Data -->
|
737
|
+
<div>
|
738
|
+
<h3><i class="fas fa-list"></i> Recent LLM Calls</h3>
|
739
|
+
<div id="recent-enhanced-data" style="margin-top: 1rem; max-height: 400px; overflow-y: auto;">
|
740
|
+
<!-- Populated dynamically -->
|
741
|
+
</div>
|
742
|
+
</div>
|
743
|
+
</div>
|
744
|
+
</div>
|
745
|
+
</div>
|
746
|
+
|
747
|
+
<!-- Information Section -->
|
748
|
+
<div class="card" style="margin-top: 2rem; border-left: 4px solid var(--primary-color);">
|
749
|
+
<div class="card-header">
|
750
|
+
<h2><i class="fas fa-info-circle"></i> Understanding Your Metrics</h2>
|
751
|
+
</div>
|
752
|
+
<div class="card-content">
|
753
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem;">
|
754
|
+
<!-- Token Definitions -->
|
755
|
+
<div>
|
756
|
+
<h4><i class="fas fa-coins"></i> Token Definitions</h4>
|
757
|
+
<ul style="list-style: none; padding: 0; color: var(--text-secondary); line-height: 1.6;">
|
758
|
+
<li style="margin-bottom: 0.5rem;">
|
759
|
+
<strong style="color: var(--text-primary);">Input Tokens:</strong> Text sent to the LLM (your prompts, context, etc.)
|
760
|
+
</li>
|
761
|
+
<li style="margin-bottom: 0.5rem;">
|
762
|
+
<strong style="color: var(--text-primary);">Output Tokens:</strong> Text generated by the LLM (responses, completions)
|
763
|
+
</li>
|
764
|
+
<li style="margin-bottom: 0.5rem;">
|
765
|
+
<strong style="color: var(--text-primary);">Total Tokens:</strong> Input tokens + Output tokens for each API call
|
766
|
+
</li>
|
767
|
+
</ul>
|
768
|
+
</div>
|
769
|
+
|
770
|
+
<!-- How We Calculate -->
|
771
|
+
<div>
|
772
|
+
<h4><i class="fas fa-calculator"></i> How We Calculate</h4>
|
773
|
+
<ul style="list-style: none; padding: 0; color: var(--text-secondary); line-height: 1.6;">
|
774
|
+
<li style="margin-bottom: 0.5rem;">
|
775
|
+
<strong style="color: var(--text-primary);">Response Time:</strong> Measured from API request start to completion (detailed research takes longer)
|
776
|
+
</li>
|
777
|
+
<li style="margin-bottom: 0.5rem;">
|
778
|
+
<strong style="color: var(--text-primary);">Success Rate:</strong> (Successful calls / Total calls) × 100%
|
779
|
+
</li>
|
780
|
+
<li style="margin-bottom: 0.5rem;">
|
781
|
+
<strong style="color: var(--text-primary);">Cumulative Charts:</strong> Running total of all tokens used over time
|
782
|
+
</li>
|
783
|
+
</ul>
|
784
|
+
</div>
|
785
|
+
|
786
|
+
<!-- Time Periods -->
|
787
|
+
<div>
|
788
|
+
<h4><i class="fas fa-clock"></i> Time Period Filtering</h4>
|
789
|
+
<ul style="list-style: none; padding: 0; color: var(--text-secondary); line-height: 1.6;">
|
790
|
+
<li style="margin-bottom: 0.5rem;">
|
791
|
+
<strong style="color: var(--text-primary);">7D/30D/3M/1Y:</strong> Shows data for the last N days/months/year
|
792
|
+
</li>
|
793
|
+
<li style="margin-bottom: 0.5rem;">
|
794
|
+
<strong style="color: var(--text-primary);">All:</strong> Complete history (no time limits)
|
795
|
+
</li>
|
796
|
+
<li style="margin-bottom: 0.5rem;">
|
797
|
+
<strong style="color: var(--text-primary);">Real-time:</strong> All metrics update instantly when you change time periods
|
798
|
+
</li>
|
799
|
+
</ul>
|
800
|
+
</div>
|
801
|
+
</div>
|
802
|
+
|
803
|
+
<!-- Cost Note -->
|
804
|
+
<div style="margin-top: 1.5rem; padding: 1rem; background: var(--bg-color); border-radius: 0.375rem; border-left: 3px solid var(--primary-color);">
|
805
|
+
<p style="margin: 0; color: var(--text-secondary); font-size: 0.875rem;">
|
806
|
+
<i class="fas fa-lightbulb" style="color: var(--primary-color); margin-right: 0.5rem;"></i>
|
807
|
+
<strong style="color: var(--text-primary);">Tip:</strong> Token usage directly impacts API costs. Input tokens are typically cheaper than output tokens. Quick research mode uses fewer tokens but detailed mode provides more comprehensive results.
|
808
|
+
</p>
|
809
|
+
</div>
|
810
|
+
</div>
|
811
|
+
</div>
|
812
|
+
</div>
|
813
|
+
</div>
|
814
|
+
{% endblock %}
|
815
|
+
|
816
|
+
{% block component_scripts %}
|
817
|
+
<script>
|
818
|
+
// Metrics Dashboard JavaScript
|
819
|
+
(function() {
|
820
|
+
console.log("=== METRICS SCRIPT STARTED ===");
|
821
|
+
|
822
|
+
let metricsData = null;
|
823
|
+
let tokenChart = null;
|
824
|
+
let timeSeriesChart = null;
|
825
|
+
let currentPeriod = '30d'; // Default period
|
826
|
+
let currentMode = 'all'; // Default research mode
|
827
|
+
|
828
|
+
// Format large numbers with commas
|
829
|
+
function formatNumber(num) {
|
830
|
+
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
831
|
+
}
|
832
|
+
|
833
|
+
// Toggle token details breakdown (make globally accessible)
|
834
|
+
window.toggleTokenDetails = function() {
|
835
|
+
const details = document.getElementById('token-details');
|
836
|
+
const icon = document.getElementById('token-expand-icon');
|
837
|
+
|
838
|
+
if (details.style.display === 'none') {
|
839
|
+
details.style.display = 'block';
|
840
|
+
icon.classList.add('expanded');
|
841
|
+
} else {
|
842
|
+
details.style.display = 'none';
|
843
|
+
icon.classList.remove('expanded');
|
844
|
+
}
|
845
|
+
}
|
846
|
+
|
847
|
+
// Toggle cost details breakdown (make globally accessible)
|
848
|
+
window.toggleCostDetails = function() {
|
849
|
+
const details = document.getElementById('cost-details');
|
850
|
+
const icon = document.getElementById('cost-expand-icon');
|
851
|
+
|
852
|
+
if (details.style.display === 'none') {
|
853
|
+
details.style.display = 'block';
|
854
|
+
icon.classList.add('expanded');
|
855
|
+
} else {
|
856
|
+
details.style.display = 'none';
|
857
|
+
icon.classList.remove('expanded');
|
858
|
+
}
|
859
|
+
}
|
860
|
+
|
861
|
+
// Format currency
|
862
|
+
function formatCurrency(amount) {
|
863
|
+
if (amount < 0.01) {
|
864
|
+
return `$${amount.toFixed(6)}`;
|
865
|
+
} else if (amount < 1) {
|
866
|
+
return `$${amount.toFixed(4)}`;
|
867
|
+
} else {
|
868
|
+
return `$${amount.toFixed(2)}`;
|
869
|
+
}
|
870
|
+
}
|
871
|
+
|
872
|
+
// Load metrics data for current time period
|
873
|
+
async function loadMetrics(period = currentPeriod) {
|
874
|
+
console.log('=== STARTING LOADMETRICS ===');
|
875
|
+
console.log('Period:', period, 'Mode:', currentMode);
|
876
|
+
|
877
|
+
try {
|
878
|
+
// Show loading state
|
879
|
+
console.log('Setting loading state...');
|
880
|
+
document.getElementById('loading').style.display = 'block';
|
881
|
+
document.getElementById('metrics-content').style.display = 'none';
|
882
|
+
document.getElementById('error').style.display = 'none';
|
883
|
+
|
884
|
+
console.log('Making basic API call...');
|
885
|
+
const basicResponse = await fetch(`/metrics/api/metrics?period=${period}&mode=${currentMode}`);
|
886
|
+
console.log('Basic response status:', basicResponse.status);
|
887
|
+
|
888
|
+
if (!basicResponse.ok) {
|
889
|
+
throw new Error(`Basic API failed: ${basicResponse.status}`);
|
890
|
+
}
|
891
|
+
|
892
|
+
const basicData = await basicResponse.json();
|
893
|
+
console.log('Basic data received:', basicData);
|
894
|
+
|
895
|
+
console.log('Making enhanced API call...');
|
896
|
+
const enhancedResponse = await fetch(`/metrics/api/metrics/enhanced?period=${period}&mode=${currentMode}`);
|
897
|
+
console.log('Enhanced response status:', enhancedResponse.status);
|
898
|
+
|
899
|
+
if (!enhancedResponse.ok) {
|
900
|
+
throw new Error(`Enhanced API failed: ${enhancedResponse.status}`);
|
901
|
+
}
|
902
|
+
|
903
|
+
const enhancedData = await enhancedResponse.json();
|
904
|
+
console.log('Enhanced data received:', enhancedData);
|
905
|
+
|
906
|
+
if (basicData.status === 'success') {
|
907
|
+
console.log('Basic data success, setting metricsData');
|
908
|
+
metricsData = basicData.metrics;
|
909
|
+
|
910
|
+
// Check if we have any data at all
|
911
|
+
const hasData = metricsData.total_tokens > 0 ||
|
912
|
+
metricsData.total_researches > 0 ||
|
913
|
+
(metricsData.by_model && metricsData.by_model.length > 0) ||
|
914
|
+
(metricsData.recent_researches && metricsData.recent_researches.length > 0);
|
915
|
+
|
916
|
+
console.log('Has data check:', hasData, 'tokens:', metricsData.total_tokens, 'researches:', metricsData.total_researches);
|
917
|
+
|
918
|
+
if (hasData) {
|
919
|
+
console.log('Displaying metrics...');
|
920
|
+
// We have data, display normally
|
921
|
+
displayMetrics();
|
922
|
+
|
923
|
+
if (enhancedData.status === 'success') {
|
924
|
+
console.log('Displaying enhanced metrics...');
|
925
|
+
displayEnhancedMetrics(enhancedData.metrics);
|
926
|
+
createTimeSeriesChart(enhancedData.metrics.time_series_data);
|
927
|
+
createSearchActivityChart(enhancedData.metrics.search_time_series);
|
928
|
+
// Re-setup tooltips for any new content
|
929
|
+
setTimeout(setupTooltipPositioning, 100);
|
930
|
+
}
|
931
|
+
|
932
|
+
// Load cost analytics
|
933
|
+
loadCostAnalytics(period);
|
934
|
+
|
935
|
+
document.getElementById('loading').style.display = 'none';
|
936
|
+
document.getElementById('metrics-content').style.display = 'block';
|
937
|
+
console.log('Metrics display completed');
|
938
|
+
} else {
|
939
|
+
console.log('No data, showing empty state');
|
940
|
+
// No data yet, show empty state with helpful messages
|
941
|
+
showEmptyState();
|
942
|
+
|
943
|
+
// Still try to display enhanced metrics in case there's some enhanced data
|
944
|
+
if (enhancedData.status === 'success') {
|
945
|
+
console.log('Displaying enhanced metrics in empty state...');
|
946
|
+
displayEnhancedMetrics(enhancedData.metrics);
|
947
|
+
// Re-setup tooltips for any new content
|
948
|
+
setTimeout(setupTooltipPositioning, 100);
|
949
|
+
}
|
950
|
+
}
|
951
|
+
} else {
|
952
|
+
console.log('Basic data failed, showing error');
|
953
|
+
showError();
|
954
|
+
}
|
955
|
+
} catch (error) {
|
956
|
+
console.error('Error loading metrics:', error);
|
957
|
+
showError();
|
958
|
+
}
|
959
|
+
}
|
960
|
+
|
961
|
+
// Display metrics on the page
|
962
|
+
function displayMetrics() {
|
963
|
+
console.log('displayMetrics called with metricsData:', metricsData);
|
964
|
+
|
965
|
+
// Update summary cards
|
966
|
+
console.log('Updating total tokens:', metricsData.total_tokens);
|
967
|
+
document.getElementById('total-tokens').textContent = formatNumber(metricsData.total_tokens);
|
968
|
+
console.log('Updating total researches:', metricsData.total_researches);
|
969
|
+
document.getElementById('total-researches').textContent = formatNumber(metricsData.total_researches);
|
970
|
+
|
971
|
+
// Update token breakdown details
|
972
|
+
if (metricsData.token_breakdown) {
|
973
|
+
const breakdown = metricsData.token_breakdown;
|
974
|
+
document.getElementById('avg-input-tokens').textContent = formatNumber(breakdown.avg_input_tokens);
|
975
|
+
document.getElementById('avg-output-tokens').textContent = formatNumber(breakdown.avg_output_tokens);
|
976
|
+
document.getElementById('avg-total-tokens').textContent = formatNumber(breakdown.avg_total_tokens);
|
977
|
+
document.getElementById('total-input-tokens').textContent = formatNumber(breakdown.total_input_tokens);
|
978
|
+
document.getElementById('total-output-tokens').textContent = formatNumber(breakdown.total_output_tokens);
|
979
|
+
document.getElementById('total-all-tokens').textContent = formatNumber(metricsData.total_tokens);
|
980
|
+
}
|
981
|
+
|
982
|
+
// Set default values for avg-response-time and success-rate (will be updated by enhanced metrics)
|
983
|
+
document.getElementById('avg-response-time').textContent = '0s';
|
984
|
+
document.getElementById('success-rate').textContent = '0%';
|
985
|
+
|
986
|
+
// Update user satisfaction rating
|
987
|
+
console.log('User satisfaction data:', metricsData.user_satisfaction);
|
988
|
+
if (metricsData.user_satisfaction && metricsData.user_satisfaction.avg_rating !== null) {
|
989
|
+
const rating = metricsData.user_satisfaction.avg_rating;
|
990
|
+
const stars = '⭐'.repeat(Math.floor(rating));
|
991
|
+
const displayText = `${rating} ${stars}`;
|
992
|
+
console.log('Setting user rating to:', displayText);
|
993
|
+
document.getElementById('avg-user-rating').textContent = displayText;
|
994
|
+
} else {
|
995
|
+
console.log('No user satisfaction data available');
|
996
|
+
document.getElementById('avg-user-rating').textContent = '-';
|
997
|
+
}
|
998
|
+
|
999
|
+
// Display model usage list
|
1000
|
+
displayModelUsage();
|
1001
|
+
|
1002
|
+
// Create token usage chart
|
1003
|
+
createTokenChart();
|
1004
|
+
|
1005
|
+
// Display recent researches
|
1006
|
+
displayRecentResearches();
|
1007
|
+
}
|
1008
|
+
|
1009
|
+
// Display model usage list
|
1010
|
+
function displayModelUsage() {
|
1011
|
+
const container = document.getElementById('model-usage-list');
|
1012
|
+
container.innerHTML = '';
|
1013
|
+
|
1014
|
+
metricsData.by_model.forEach(model => {
|
1015
|
+
const item = document.createElement('div');
|
1016
|
+
item.className = 'model-usage-item';
|
1017
|
+
item.innerHTML = `
|
1018
|
+
<div class="model-info">
|
1019
|
+
<div class="model-name">${model.model}</div>
|
1020
|
+
<div class="model-provider">${model.provider}</div>
|
1021
|
+
</div>
|
1022
|
+
<div class="model-stats">
|
1023
|
+
<div class="token-count">${formatNumber(model.tokens)} tokens</div>
|
1024
|
+
<div class="call-count">${formatNumber(model.calls)} calls</div>
|
1025
|
+
</div>
|
1026
|
+
`;
|
1027
|
+
container.appendChild(item);
|
1028
|
+
});
|
1029
|
+
}
|
1030
|
+
|
1031
|
+
// Create token usage chart
|
1032
|
+
function createTokenChart() {
|
1033
|
+
try {
|
1034
|
+
const chartElement = document.getElementById('token-chart');
|
1035
|
+
if (!chartElement) {
|
1036
|
+
console.warn('Token chart element not found');
|
1037
|
+
return;
|
1038
|
+
}
|
1039
|
+
const ctx = chartElement.getContext('2d');
|
1040
|
+
|
1041
|
+
// Prepare data for chart
|
1042
|
+
const labels = metricsData.by_model.map(m => m.model);
|
1043
|
+
const promptTokens = metricsData.by_model.map(m => m.prompt_tokens);
|
1044
|
+
const completionTokens = metricsData.by_model.map(m => m.completion_tokens);
|
1045
|
+
|
1046
|
+
// Destroy existing chart if it exists
|
1047
|
+
if (tokenChart) {
|
1048
|
+
tokenChart.destroy();
|
1049
|
+
}
|
1050
|
+
|
1051
|
+
tokenChart = new Chart(ctx, {
|
1052
|
+
type: 'bar',
|
1053
|
+
data: {
|
1054
|
+
labels: labels,
|
1055
|
+
datasets: [{
|
1056
|
+
label: 'Input Tokens',
|
1057
|
+
data: promptTokens,
|
1058
|
+
backgroundColor: 'rgba(54, 162, 235, 0.8)',
|
1059
|
+
borderColor: 'rgba(54, 162, 235, 1)',
|
1060
|
+
borderWidth: 1
|
1061
|
+
}, {
|
1062
|
+
label: 'Output Tokens',
|
1063
|
+
data: completionTokens,
|
1064
|
+
backgroundColor: 'rgba(255, 99, 132, 0.8)',
|
1065
|
+
borderColor: 'rgba(255, 99, 132, 1)',
|
1066
|
+
borderWidth: 1
|
1067
|
+
}]
|
1068
|
+
},
|
1069
|
+
options: {
|
1070
|
+
responsive: true,
|
1071
|
+
maintainAspectRatio: false,
|
1072
|
+
scales: {
|
1073
|
+
x: {
|
1074
|
+
stacked: true,
|
1075
|
+
},
|
1076
|
+
y: {
|
1077
|
+
stacked: true,
|
1078
|
+
beginAtZero: true
|
1079
|
+
}
|
1080
|
+
},
|
1081
|
+
plugins: {
|
1082
|
+
legend: {
|
1083
|
+
position: 'bottom'
|
1084
|
+
},
|
1085
|
+
tooltip: {
|
1086
|
+
callbacks: {
|
1087
|
+
label: function(context) {
|
1088
|
+
let label = context.dataset.label || '';
|
1089
|
+
if (label) {
|
1090
|
+
label += ': ';
|
1091
|
+
}
|
1092
|
+
label += formatNumber(context.parsed.y);
|
1093
|
+
return label;
|
1094
|
+
}
|
1095
|
+
}
|
1096
|
+
}
|
1097
|
+
}
|
1098
|
+
}
|
1099
|
+
});
|
1100
|
+
} catch (error) {
|
1101
|
+
console.error('Error creating token chart:', error);
|
1102
|
+
}
|
1103
|
+
}
|
1104
|
+
|
1105
|
+
// Display recent researches
|
1106
|
+
function displayRecentResearches() {
|
1107
|
+
const container = document.getElementById('recent-researches');
|
1108
|
+
container.innerHTML = '';
|
1109
|
+
|
1110
|
+
if (metricsData.recent_researches.length === 0) {
|
1111
|
+
container.innerHTML = '<p style="text-align: center; color: var(--text-secondary);">No recent researches</p>';
|
1112
|
+
return;
|
1113
|
+
}
|
1114
|
+
|
1115
|
+
metricsData.recent_researches.forEach(research => {
|
1116
|
+
const item = document.createElement('div');
|
1117
|
+
item.className = 'recent-research-item';
|
1118
|
+
item.innerHTML = `
|
1119
|
+
<div class="research-query">${research.query}</div>
|
1120
|
+
<div class="research-tokens">${formatNumber(research.tokens)} tokens</div>
|
1121
|
+
`;
|
1122
|
+
item.onclick = () => {
|
1123
|
+
window.location.href = `/research/details/${research.id}`;
|
1124
|
+
};
|
1125
|
+
container.appendChild(item);
|
1126
|
+
});
|
1127
|
+
}
|
1128
|
+
|
1129
|
+
// Display enhanced Phase 1 metrics
|
1130
|
+
function displayEnhancedMetrics(enhancedData) {
|
1131
|
+
// Update performance stats
|
1132
|
+
if (enhancedData.performance_stats) {
|
1133
|
+
const stats = enhancedData.performance_stats;
|
1134
|
+
document.getElementById('avg-response-time').textContent = `${(stats.avg_response_time / 1000).toFixed(1)}s`;
|
1135
|
+
document.getElementById('success-rate').textContent = `${stats.success_rate}%`;
|
1136
|
+
}
|
1137
|
+
|
1138
|
+
// Display mode breakdown
|
1139
|
+
const modeContainer = document.getElementById('mode-breakdown');
|
1140
|
+
modeContainer.innerHTML = '';
|
1141
|
+
enhancedData.mode_breakdown.forEach(mode => {
|
1142
|
+
const item = document.createElement('div');
|
1143
|
+
item.className = 'model-usage-item';
|
1144
|
+
item.innerHTML = `
|
1145
|
+
<div class="model-info">
|
1146
|
+
<div class="model-name">${mode.mode || 'Unknown'} Mode</div>
|
1147
|
+
<div class="model-provider">Avg Response: ${(mode.avg_response_time / 1000).toFixed(1)}s</div>
|
1148
|
+
</div>
|
1149
|
+
<div class="model-stats">
|
1150
|
+
<div class="token-count">${formatNumber(mode.avg_tokens)} avg tokens</div>
|
1151
|
+
<div class="call-count">${formatNumber(mode.count)} calls</div>
|
1152
|
+
</div>
|
1153
|
+
`;
|
1154
|
+
modeContainer.appendChild(item);
|
1155
|
+
});
|
1156
|
+
|
1157
|
+
// Display search engine stats (prefer search call data over enhanced data)
|
1158
|
+
const searchContainer = document.getElementById('search-engine-breakdown');
|
1159
|
+
searchContainer.innerHTML = '';
|
1160
|
+
|
1161
|
+
// Use search call data if available, otherwise fall back to enhanced data
|
1162
|
+
const searchStats = metricsData.search_engine_stats || enhancedData.search_engine_stats || [];
|
1163
|
+
|
1164
|
+
searchStats.forEach(engine => {
|
1165
|
+
const item = document.createElement('div');
|
1166
|
+
item.className = 'model-usage-item';
|
1167
|
+
|
1168
|
+
// Handle both search call format and enhanced data format
|
1169
|
+
const engineName = engine.engine || engine.search_engine || 'Unknown';
|
1170
|
+
const avgResponseTime = engine.avg_response_time || 0;
|
1171
|
+
const usageCount = engine.call_count || engine.usage_count || 0;
|
1172
|
+
const successRate = engine.success_rate || null;
|
1173
|
+
|
1174
|
+
item.innerHTML = `
|
1175
|
+
<div class="model-info">
|
1176
|
+
<div class="model-name">${engineName}</div>
|
1177
|
+
<div class="model-provider">
|
1178
|
+
Avg Response: ${(avgResponseTime / 1000).toFixed(1)}s
|
1179
|
+
${successRate !== null ? ` | Success: ${successRate.toFixed(1)}%` : ''}
|
1180
|
+
</div>
|
1181
|
+
</div>
|
1182
|
+
<div class="model-stats">
|
1183
|
+
<div class="token-count">${formatNumber(usageCount)} calls</div>
|
1184
|
+
${engine.total_results ? `<div class="call-count">${formatNumber(engine.total_results)} results</div>` : ''}
|
1185
|
+
</div>
|
1186
|
+
`;
|
1187
|
+
searchContainer.appendChild(item);
|
1188
|
+
});
|
1189
|
+
|
1190
|
+
// Display strategy breakdown
|
1191
|
+
const strategyContainer = document.getElementById('strategy-breakdown');
|
1192
|
+
strategyContainer.innerHTML = '';
|
1193
|
+
|
1194
|
+
// Get strategy data from metricsData (added by our API)
|
1195
|
+
const strategyData = metricsData.strategy_analytics;
|
1196
|
+
|
1197
|
+
// Log strategy data for debugging
|
1198
|
+
console.log('Strategy data received:', strategyData);
|
1199
|
+
|
1200
|
+
if (strategyData && strategyData.strategy_usage && strategyData.strategy_usage.length > 0) {
|
1201
|
+
strategyData.strategy_usage.forEach(strategy => {
|
1202
|
+
const item = document.createElement('div');
|
1203
|
+
item.className = 'model-usage-item';
|
1204
|
+
item.innerHTML = `
|
1205
|
+
<div class="model-info">
|
1206
|
+
<div class="model-name">${strategy.strategy}</div>
|
1207
|
+
<div class="model-provider">${strategy.percentage}% of research sessions</div>
|
1208
|
+
</div>
|
1209
|
+
<div class="model-stats">
|
1210
|
+
<div class="token-count">${formatNumber(strategy.count)} uses</div>
|
1211
|
+
<div class="call-count">${strategyData.most_popular_strategy === strategy.strategy ? '👑 Most Popular' : ''}</div>
|
1212
|
+
</div>
|
1213
|
+
`;
|
1214
|
+
strategyContainer.appendChild(item);
|
1215
|
+
});
|
1216
|
+
} else {
|
1217
|
+
const noDataMsg = document.createElement('div');
|
1218
|
+
noDataMsg.style.textAlign = 'center';
|
1219
|
+
noDataMsg.style.color = 'var(--text-secondary)';
|
1220
|
+
noDataMsg.style.padding = '1rem';
|
1221
|
+
|
1222
|
+
let message = 'No strategy data yet. <a href="/research/" style="color: var(--accent-tertiary);">Start a research</a> to track strategies!';
|
1223
|
+
|
1224
|
+
// Show different message based on what data we have
|
1225
|
+
if (strategyData) {
|
1226
|
+
if (strategyData.message) {
|
1227
|
+
message = strategyData.message;
|
1228
|
+
} else if (strategyData.total_research > 0 && strategyData.total_research_with_strategy === 0) {
|
1229
|
+
message = `Found ${strategyData.total_research} research sessions but no strategy data. Strategy tracking was recently added - new research will be tracked!`;
|
1230
|
+
} else if (strategyData.error) {
|
1231
|
+
message = `Error loading strategy data: ${strategyData.error}`;
|
1232
|
+
}
|
1233
|
+
}
|
1234
|
+
|
1235
|
+
noDataMsg.innerHTML = message;
|
1236
|
+
strategyContainer.appendChild(noDataMsg);
|
1237
|
+
}
|
1238
|
+
|
1239
|
+
// Display phase breakdown
|
1240
|
+
const phaseContainer = document.getElementById('phase-breakdown');
|
1241
|
+
phaseContainer.innerHTML = '';
|
1242
|
+
enhancedData.phase_breakdown.forEach(phase => {
|
1243
|
+
const item = document.createElement('div');
|
1244
|
+
item.className = 'model-usage-item';
|
1245
|
+
item.innerHTML = `
|
1246
|
+
<div class="model-info">
|
1247
|
+
<div class="model-name">${phase.phase || 'Unknown'} Phase</div>
|
1248
|
+
<div class="model-provider">Avg Response: ${((phase.avg_response_time || 0) / 1000).toFixed(1)}s</div>
|
1249
|
+
</div>
|
1250
|
+
<div class="model-stats">
|
1251
|
+
<div class="token-count">${formatNumber(phase.avg_tokens)} avg tokens</div>
|
1252
|
+
<div class="call-count">${formatNumber(phase.count)} calls</div>
|
1253
|
+
</div>
|
1254
|
+
`;
|
1255
|
+
phaseContainer.appendChild(item);
|
1256
|
+
});
|
1257
|
+
|
1258
|
+
// Display user satisfaction ratings
|
1259
|
+
const ratingContainer = document.getElementById('rating-breakdown');
|
1260
|
+
ratingContainer.innerHTML = '';
|
1261
|
+
|
1262
|
+
if (metricsData.user_satisfaction && metricsData.user_satisfaction.total_ratings > 0) {
|
1263
|
+
const satisfaction = metricsData.user_satisfaction;
|
1264
|
+
const avgRating = satisfaction.avg_rating;
|
1265
|
+
const totalRatings = satisfaction.total_ratings;
|
1266
|
+
const stars = '⭐'.repeat(Math.floor(avgRating));
|
1267
|
+
|
1268
|
+
const item = document.createElement('div');
|
1269
|
+
item.className = 'model-usage-item';
|
1270
|
+
item.innerHTML = `
|
1271
|
+
<div class="model-info">
|
1272
|
+
<div class="model-name">${avgRating} ${stars}</div>
|
1273
|
+
<div class="model-provider">Average User Rating</div>
|
1274
|
+
</div>
|
1275
|
+
<div class="model-stats">
|
1276
|
+
<div class="token-count">${totalRatings} ratings</div>
|
1277
|
+
<div class="call-count">
|
1278
|
+
<a href="/metrics/star-reviews" style="color: var(--accent-tertiary); text-decoration: none;">View Details →</a>
|
1279
|
+
</div>
|
1280
|
+
</div>
|
1281
|
+
`;
|
1282
|
+
ratingContainer.appendChild(item);
|
1283
|
+
} else {
|
1284
|
+
const noDataMsg = document.createElement('div');
|
1285
|
+
noDataMsg.style.textAlign = 'center';
|
1286
|
+
noDataMsg.style.color = 'var(--text-secondary)';
|
1287
|
+
noDataMsg.style.padding = '1rem';
|
1288
|
+
noDataMsg.innerHTML = 'No ratings yet. <a href="/research/" style="color: var(--accent-tertiary);">Start a research</a> and rate it!';
|
1289
|
+
ratingContainer.appendChild(noDataMsg);
|
1290
|
+
}
|
1291
|
+
|
1292
|
+
// Display recent enhanced data
|
1293
|
+
const enhancedContainer = document.getElementById('recent-enhanced-data');
|
1294
|
+
enhancedContainer.innerHTML = '';
|
1295
|
+
|
1296
|
+
if (enhancedData.recent_enhanced_data.length === 0) {
|
1297
|
+
enhancedContainer.innerHTML = '<p style="text-align: center; color: var(--text-secondary);">No enhanced tracking data yet. Run a research to see enhanced metrics!</p>';
|
1298
|
+
return;
|
1299
|
+
}
|
1300
|
+
|
1301
|
+
enhancedData.recent_enhanced_data.slice(0, 10).forEach(item => {
|
1302
|
+
const row = document.createElement('div');
|
1303
|
+
row.className = 'model-usage-item';
|
1304
|
+
row.style.marginBottom = '0.5rem';
|
1305
|
+
row.style.cursor = 'pointer';
|
1306
|
+
row.innerHTML = `
|
1307
|
+
<div class="model-info">
|
1308
|
+
<div class="model-name" style="margin-bottom: 0.25rem;">${item.research_query || 'Unknown Query'}</div>
|
1309
|
+
<div class="model-provider">
|
1310
|
+
Mode: ${item.research_mode || 'N/A'} |
|
1311
|
+
Phase: ${item.research_phase || 'N/A'} |
|
1312
|
+
Engine: ${item.search_engine_selected || 'N/A'}
|
1313
|
+
</div>
|
1314
|
+
</div>
|
1315
|
+
<div class="model-stats">
|
1316
|
+
<div class="token-count">${((item.response_time_ms || 0) / 1000).toFixed(1)}s</div>
|
1317
|
+
<div class="call-count" style="color: ${item.success_status === 'success' ? 'green' : 'red'}">
|
1318
|
+
${item.success_status || 'unknown'}
|
1319
|
+
</div>
|
1320
|
+
</div>
|
1321
|
+
`;
|
1322
|
+
// Add click handler to navigate to research details
|
1323
|
+
if (item.research_id) {
|
1324
|
+
row.onclick = () => {
|
1325
|
+
window.location.href = `/research/details/${item.research_id}`;
|
1326
|
+
};
|
1327
|
+
row.title = `Click to view research details`;
|
1328
|
+
}
|
1329
|
+
enhancedContainer.appendChild(row);
|
1330
|
+
});
|
1331
|
+
}
|
1332
|
+
|
1333
|
+
// Create time-series chart for token consumption over time
|
1334
|
+
function createTimeSeriesChart(timeSeriesData) {
|
1335
|
+
try {
|
1336
|
+
const chartElement = document.getElementById('time-series-chart');
|
1337
|
+
if (!chartElement) {
|
1338
|
+
console.warn('Time series chart element not found');
|
1339
|
+
return;
|
1340
|
+
}
|
1341
|
+
if (!timeSeriesData || timeSeriesData.length === 0) {
|
1342
|
+
console.warn('No time series data provided');
|
1343
|
+
return;
|
1344
|
+
}
|
1345
|
+
const ctx = chartElement.getContext('2d');
|
1346
|
+
|
1347
|
+
// Prepare data for time-series chart
|
1348
|
+
const labels = timeSeriesData.map(item => {
|
1349
|
+
// Format timestamp for display (show time only if same day, otherwise show date)
|
1350
|
+
const date = new Date(item.timestamp);
|
1351
|
+
return date.toLocaleString('en-US', {
|
1352
|
+
month: 'short',
|
1353
|
+
day: 'numeric',
|
1354
|
+
hour: '2-digit',
|
1355
|
+
minute: '2-digit'
|
1356
|
+
});
|
1357
|
+
});
|
1358
|
+
|
1359
|
+
const cumulativeTokens = timeSeriesData.map(item => item.cumulative_tokens);
|
1360
|
+
const cumulativePromptTokens = timeSeriesData.map(item => item.cumulative_prompt_tokens);
|
1361
|
+
const cumulativeCompletionTokens = timeSeriesData.map(item => item.cumulative_completion_tokens);
|
1362
|
+
const promptTokens = timeSeriesData.map(item => item.prompt_tokens);
|
1363
|
+
const completionTokens = timeSeriesData.map(item => item.completion_tokens);
|
1364
|
+
|
1365
|
+
// Destroy existing chart if it exists
|
1366
|
+
if (timeSeriesChart) {
|
1367
|
+
timeSeriesChart.destroy();
|
1368
|
+
}
|
1369
|
+
|
1370
|
+
timeSeriesChart = new Chart(ctx, {
|
1371
|
+
type: 'line',
|
1372
|
+
data: {
|
1373
|
+
labels: labels,
|
1374
|
+
datasets: [{
|
1375
|
+
label: 'Cumulative Total Tokens',
|
1376
|
+
data: cumulativeTokens,
|
1377
|
+
borderColor: 'rgba(75, 192, 192, 1)',
|
1378
|
+
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
1379
|
+
borderWidth: 3,
|
1380
|
+
fill: true,
|
1381
|
+
tension: 0.4,
|
1382
|
+
pointRadius: 4,
|
1383
|
+
pointHoverRadius: 6,
|
1384
|
+
}, {
|
1385
|
+
label: 'Cumulative Input Tokens',
|
1386
|
+
data: cumulativePromptTokens,
|
1387
|
+
borderColor: 'rgba(54, 162, 235, 1)',
|
1388
|
+
backgroundColor: 'rgba(54, 162, 235, 0.05)',
|
1389
|
+
borderWidth: 2,
|
1390
|
+
fill: true,
|
1391
|
+
tension: 0.4,
|
1392
|
+
pointRadius: 2,
|
1393
|
+
pointHoverRadius: 4,
|
1394
|
+
}, {
|
1395
|
+
label: 'Cumulative Output Tokens',
|
1396
|
+
data: cumulativeCompletionTokens,
|
1397
|
+
borderColor: 'rgba(255, 99, 132, 1)',
|
1398
|
+
backgroundColor: 'rgba(255, 99, 132, 0.05)',
|
1399
|
+
borderWidth: 2,
|
1400
|
+
fill: true,
|
1401
|
+
tension: 0.4,
|
1402
|
+
pointRadius: 2,
|
1403
|
+
pointHoverRadius: 4,
|
1404
|
+
}, {
|
1405
|
+
label: 'Input Tokens per Call',
|
1406
|
+
data: promptTokens,
|
1407
|
+
borderColor: 'rgba(54, 162, 235, 0.7)',
|
1408
|
+
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
1409
|
+
borderWidth: 1,
|
1410
|
+
fill: false,
|
1411
|
+
tension: 0.2,
|
1412
|
+
pointRadius: 2,
|
1413
|
+
pointHoverRadius: 4,
|
1414
|
+
yAxisID: 'y1',
|
1415
|
+
borderDash: [5, 5],
|
1416
|
+
}, {
|
1417
|
+
label: 'Output Tokens per Call',
|
1418
|
+
data: completionTokens,
|
1419
|
+
borderColor: 'rgba(255, 99, 132, 0.7)',
|
1420
|
+
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
1421
|
+
borderWidth: 1,
|
1422
|
+
fill: false,
|
1423
|
+
tension: 0.2,
|
1424
|
+
pointRadius: 2,
|
1425
|
+
pointHoverRadius: 4,
|
1426
|
+
yAxisID: 'y1',
|
1427
|
+
borderDash: [5, 5],
|
1428
|
+
}]
|
1429
|
+
},
|
1430
|
+
options: {
|
1431
|
+
responsive: true,
|
1432
|
+
maintainAspectRatio: false,
|
1433
|
+
interaction: {
|
1434
|
+
intersect: false,
|
1435
|
+
mode: 'index'
|
1436
|
+
},
|
1437
|
+
scales: {
|
1438
|
+
x: {
|
1439
|
+
display: true,
|
1440
|
+
title: {
|
1441
|
+
display: true,
|
1442
|
+
text: 'Time'
|
1443
|
+
},
|
1444
|
+
ticks: {
|
1445
|
+
maxRotation: 45
|
1446
|
+
}
|
1447
|
+
},
|
1448
|
+
y: {
|
1449
|
+
type: 'linear',
|
1450
|
+
display: true,
|
1451
|
+
position: 'left',
|
1452
|
+
title: {
|
1453
|
+
display: true,
|
1454
|
+
text: 'Cumulative Tokens'
|
1455
|
+
},
|
1456
|
+
beginAtZero: true
|
1457
|
+
},
|
1458
|
+
y1: {
|
1459
|
+
type: 'linear',
|
1460
|
+
display: true,
|
1461
|
+
position: 'right',
|
1462
|
+
title: {
|
1463
|
+
display: true,
|
1464
|
+
text: 'Tokens per Call'
|
1465
|
+
},
|
1466
|
+
beginAtZero: true,
|
1467
|
+
grid: {
|
1468
|
+
drawOnChartArea: false,
|
1469
|
+
},
|
1470
|
+
}
|
1471
|
+
},
|
1472
|
+
plugins: {
|
1473
|
+
legend: {
|
1474
|
+
position: 'top'
|
1475
|
+
},
|
1476
|
+
tooltip: {
|
1477
|
+
callbacks: {
|
1478
|
+
title: function(context) {
|
1479
|
+
const index = context[0].dataIndex;
|
1480
|
+
const item = timeSeriesData[index];
|
1481
|
+
return `${item.research_query || 'Unknown Query'}`;
|
1482
|
+
},
|
1483
|
+
label: function(context) {
|
1484
|
+
let label = context.dataset.label || '';
|
1485
|
+
if (label) {
|
1486
|
+
label += ': ';
|
1487
|
+
}
|
1488
|
+
label += formatNumber(context.parsed.y);
|
1489
|
+
return label;
|
1490
|
+
},
|
1491
|
+
afterBody: function(context) {
|
1492
|
+
const index = context[0].dataIndex;
|
1493
|
+
const item = timeSeriesData[index];
|
1494
|
+
return [
|
1495
|
+
`Research ID: ${item.research_id}`,
|
1496
|
+
`Time: ${new Date(item.timestamp).toLocaleString()}`
|
1497
|
+
];
|
1498
|
+
}
|
1499
|
+
}
|
1500
|
+
}
|
1501
|
+
}
|
1502
|
+
}
|
1503
|
+
});
|
1504
|
+
} catch (error) {
|
1505
|
+
console.error('Error creating time series chart:', error);
|
1506
|
+
}
|
1507
|
+
}
|
1508
|
+
|
1509
|
+
// Create search activity chart showing search engine usage over time
|
1510
|
+
function createSearchActivityChart(searchTimeSeriesData) {
|
1511
|
+
try {
|
1512
|
+
const chartElement = document.getElementById('search-activity-chart');
|
1513
|
+
if (!chartElement) {
|
1514
|
+
console.warn('Search activity chart element not found');
|
1515
|
+
return;
|
1516
|
+
}
|
1517
|
+
if (!searchTimeSeriesData || searchTimeSeriesData.length === 0) {
|
1518
|
+
console.warn('No search time series data provided');
|
1519
|
+
return;
|
1520
|
+
}
|
1521
|
+
|
1522
|
+
const ctx = chartElement.getContext('2d');
|
1523
|
+
|
1524
|
+
// Get unique search engines and assign colors
|
1525
|
+
const searchEngines = [...new Set(searchTimeSeriesData.map(item => item.search_engine))];
|
1526
|
+
const colors = [
|
1527
|
+
'rgba(255, 99, 132, 0.8)', // Red
|
1528
|
+
'rgba(54, 162, 235, 0.8)', // Blue
|
1529
|
+
'rgba(255, 205, 86, 0.8)', // Yellow
|
1530
|
+
'rgba(75, 192, 192, 0.8)', // Teal
|
1531
|
+
'rgba(153, 102, 255, 0.8)', // Purple
|
1532
|
+
'rgba(255, 159, 64, 0.8)', // Orange
|
1533
|
+
'rgba(199, 199, 199, 0.8)', // Gray
|
1534
|
+
'rgba(83, 102, 255, 0.8)', // Indigo
|
1535
|
+
'rgba(255, 99, 255, 0.8)', // Pink
|
1536
|
+
'rgba(99, 255, 132, 0.8)' // Green
|
1537
|
+
];
|
1538
|
+
|
1539
|
+
// Group data by time periods (e.g., by hour or day depending on range)
|
1540
|
+
const timeGroups = {};
|
1541
|
+
searchTimeSeriesData.forEach(item => {
|
1542
|
+
const timestamp = new Date(item.timestamp);
|
1543
|
+
// Group by hour for better visualization
|
1544
|
+
const timeKey = new Date(timestamp.getFullYear(), timestamp.getMonth(),
|
1545
|
+
timestamp.getDate(), timestamp.getHours()).toISOString();
|
1546
|
+
|
1547
|
+
if (!timeGroups[timeKey]) {
|
1548
|
+
timeGroups[timeKey] = {};
|
1549
|
+
searchEngines.forEach(engine => {
|
1550
|
+
timeGroups[timeKey][engine] = { count: 0, totalResults: 0 };
|
1551
|
+
});
|
1552
|
+
}
|
1553
|
+
|
1554
|
+
if (timeGroups[timeKey][item.search_engine]) {
|
1555
|
+
timeGroups[timeKey][item.search_engine].count += 1;
|
1556
|
+
timeGroups[timeKey][item.search_engine].totalResults += item.results_count || 0;
|
1557
|
+
}
|
1558
|
+
});
|
1559
|
+
|
1560
|
+
// Prepare chart data
|
1561
|
+
const labels = Object.keys(timeGroups).sort().map(timeKey => {
|
1562
|
+
const date = new Date(timeKey);
|
1563
|
+
return date.toLocaleString('en-US', {
|
1564
|
+
month: 'short',
|
1565
|
+
day: 'numeric',
|
1566
|
+
hour: '2-digit',
|
1567
|
+
minute: '2-digit'
|
1568
|
+
});
|
1569
|
+
});
|
1570
|
+
|
1571
|
+
const datasets = searchEngines.map((engine, index) => ({
|
1572
|
+
label: engine.charAt(0).toUpperCase() + engine.slice(1),
|
1573
|
+
data: Object.keys(timeGroups).sort().map(timeKey =>
|
1574
|
+
timeGroups[timeKey][engine].totalResults
|
1575
|
+
),
|
1576
|
+
borderColor: colors[index % colors.length],
|
1577
|
+
backgroundColor: colors[index % colors.length].replace('0.8', '0.2'),
|
1578
|
+
borderWidth: 2,
|
1579
|
+
fill: false,
|
1580
|
+
tension: 0.4
|
1581
|
+
}));
|
1582
|
+
|
1583
|
+
// Destroy existing chart if it exists
|
1584
|
+
if (window.searchActivityChart) {
|
1585
|
+
window.searchActivityChart.destroy();
|
1586
|
+
}
|
1587
|
+
|
1588
|
+
window.searchActivityChart = new Chart(ctx, {
|
1589
|
+
type: 'line',
|
1590
|
+
data: {
|
1591
|
+
labels: labels,
|
1592
|
+
datasets: datasets
|
1593
|
+
},
|
1594
|
+
options: {
|
1595
|
+
responsive: true,
|
1596
|
+
maintainAspectRatio: false,
|
1597
|
+
interaction: {
|
1598
|
+
mode: 'index',
|
1599
|
+
intersect: false,
|
1600
|
+
},
|
1601
|
+
scales: {
|
1602
|
+
x: {
|
1603
|
+
title: {
|
1604
|
+
display: true,
|
1605
|
+
text: 'Time'
|
1606
|
+
},
|
1607
|
+
ticks: {
|
1608
|
+
maxRotation: 45
|
1609
|
+
}
|
1610
|
+
},
|
1611
|
+
y: {
|
1612
|
+
title: {
|
1613
|
+
display: true,
|
1614
|
+
text: 'Search Results Count'
|
1615
|
+
},
|
1616
|
+
beginAtZero: true
|
1617
|
+
}
|
1618
|
+
},
|
1619
|
+
plugins: {
|
1620
|
+
legend: {
|
1621
|
+
position: 'top'
|
1622
|
+
},
|
1623
|
+
tooltip: {
|
1624
|
+
callbacks: {
|
1625
|
+
title: function(context) {
|
1626
|
+
return `Search Activity at ${context[0].label}`;
|
1627
|
+
},
|
1628
|
+
label: function(context) {
|
1629
|
+
const engineName = context.dataset.label;
|
1630
|
+
const resultsCount = context.parsed.y;
|
1631
|
+
return `${engineName}: ${formatNumber(resultsCount)} results`;
|
1632
|
+
},
|
1633
|
+
afterBody: function(context) {
|
1634
|
+
const timeIndex = context[0].dataIndex;
|
1635
|
+
const timeKey = Object.keys(timeGroups).sort()[timeIndex];
|
1636
|
+
const totalSearches = Object.values(timeGroups[timeKey])
|
1637
|
+
.reduce((sum, engine) => sum + engine.count, 0);
|
1638
|
+
return `Total searches in this period: ${totalSearches}`;
|
1639
|
+
}
|
1640
|
+
}
|
1641
|
+
}
|
1642
|
+
}
|
1643
|
+
}
|
1644
|
+
});
|
1645
|
+
|
1646
|
+
} catch (error) {
|
1647
|
+
console.error('Error creating search activity chart:', error);
|
1648
|
+
}
|
1649
|
+
}
|
1650
|
+
|
1651
|
+
// Load cost analytics data
|
1652
|
+
async function loadCostAnalytics(period = currentPeriod) {
|
1653
|
+
try {
|
1654
|
+
console.log('Loading cost analytics for period:', period);
|
1655
|
+
|
1656
|
+
const response = await fetch(`/metrics/api/cost-analytics?period=${period}`);
|
1657
|
+
if (!response.ok) {
|
1658
|
+
console.warn('Cost analytics API failed:', response.status);
|
1659
|
+
displayCostData(null);
|
1660
|
+
return;
|
1661
|
+
}
|
1662
|
+
|
1663
|
+
const data = await response.json();
|
1664
|
+
console.log('Cost analytics data received:', data);
|
1665
|
+
|
1666
|
+
if (data.status === 'success') {
|
1667
|
+
displayCostData(data);
|
1668
|
+
} else {
|
1669
|
+
console.warn('Cost analytics returned error:', data.message);
|
1670
|
+
displayCostData(null);
|
1671
|
+
}
|
1672
|
+
} catch (error) {
|
1673
|
+
console.error('Error loading cost analytics:', error);
|
1674
|
+
displayCostData(null);
|
1675
|
+
}
|
1676
|
+
}
|
1677
|
+
|
1678
|
+
// Display cost data in the dashboard
|
1679
|
+
function displayCostData(costData) {
|
1680
|
+
if (!costData || !costData.overview) {
|
1681
|
+
// No cost data available, show dashes
|
1682
|
+
document.getElementById('total-cost').textContent = '-';
|
1683
|
+
document.getElementById('avg-cost-per-research').textContent = '-';
|
1684
|
+
document.getElementById('avg-cost-per-token').textContent = '-';
|
1685
|
+
document.getElementById('total-input-cost').textContent = '-';
|
1686
|
+
document.getElementById('total-output-cost').textContent = '-';
|
1687
|
+
document.getElementById('total-cost-breakdown').textContent = '-';
|
1688
|
+
return;
|
1689
|
+
}
|
1690
|
+
|
1691
|
+
const overview = costData.overview;
|
1692
|
+
|
1693
|
+
// Main cost display
|
1694
|
+
document.getElementById('total-cost').textContent = formatCurrency(overview.total_cost);
|
1695
|
+
|
1696
|
+
// Cost breakdown details
|
1697
|
+
document.getElementById('avg-cost-per-research').textContent = formatCurrency(overview.avg_cost_per_call);
|
1698
|
+
document.getElementById('avg-cost-per-token').textContent = formatCurrency(overview.cost_per_token);
|
1699
|
+
document.getElementById('total-input-cost').textContent = formatCurrency(overview.prompt_cost);
|
1700
|
+
document.getElementById('total-output-cost').textContent = formatCurrency(overview.completion_cost);
|
1701
|
+
document.getElementById('total-cost-breakdown').textContent = formatCurrency(overview.total_cost);
|
1702
|
+
|
1703
|
+
console.log('Cost data displayed successfully');
|
1704
|
+
}
|
1705
|
+
|
1706
|
+
// Show error message
|
1707
|
+
function showError() {
|
1708
|
+
document.getElementById('loading').style.display = 'none';
|
1709
|
+
document.getElementById('error').style.display = 'block';
|
1710
|
+
}
|
1711
|
+
|
1712
|
+
// Show empty state with helpful message
|
1713
|
+
function showEmptyState() {
|
1714
|
+
document.getElementById('loading').style.display = 'none';
|
1715
|
+
document.getElementById('metrics-content').style.display = 'block';
|
1716
|
+
|
1717
|
+
// Update summary cards with zeros
|
1718
|
+
document.getElementById('total-tokens').textContent = '0';
|
1719
|
+
document.getElementById('total-researches').textContent = '0';
|
1720
|
+
document.getElementById('avg-response-time').textContent = '0s';
|
1721
|
+
document.getElementById('success-rate').textContent = '0%';
|
1722
|
+
|
1723
|
+
// Reset token breakdown details
|
1724
|
+
document.getElementById('avg-input-tokens').textContent = '0';
|
1725
|
+
document.getElementById('avg-output-tokens').textContent = '0';
|
1726
|
+
document.getElementById('avg-total-tokens').textContent = '0';
|
1727
|
+
document.getElementById('total-input-tokens').textContent = '0';
|
1728
|
+
document.getElementById('total-output-tokens').textContent = '0';
|
1729
|
+
document.getElementById('total-all-tokens').textContent = '0';
|
1730
|
+
|
1731
|
+
// Reset cost breakdown details
|
1732
|
+
document.getElementById('total-cost').textContent = '-';
|
1733
|
+
document.getElementById('avg-cost-per-research').textContent = '-';
|
1734
|
+
document.getElementById('avg-cost-per-token').textContent = '-';
|
1735
|
+
document.getElementById('total-input-cost').textContent = '-';
|
1736
|
+
document.getElementById('total-output-cost').textContent = '-';
|
1737
|
+
document.getElementById('total-cost-breakdown').textContent = '-';
|
1738
|
+
|
1739
|
+
// Show helpful message in charts
|
1740
|
+
showEmptyChartsAndTables();
|
1741
|
+
}
|
1742
|
+
|
1743
|
+
// Show empty charts and tables with helpful messages
|
1744
|
+
function showEmptyChartsAndTables() {
|
1745
|
+
// Model usage list
|
1746
|
+
const modelContainer = document.getElementById('model-usage-list');
|
1747
|
+
modelContainer.innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--text-secondary);"><i class="fas fa-robot fa-2x" style="margin-bottom: 1rem;"></i><p>No research data yet</p><p style="font-size: 0.875rem;">Run a research to see model usage metrics</p></div>';
|
1748
|
+
|
1749
|
+
// Recent researches
|
1750
|
+
const recentContainer = document.getElementById('recent-researches');
|
1751
|
+
recentContainer.innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--text-secondary);"><i class="fas fa-search fa-2x" style="margin-bottom: 1rem;"></i><p>No research history</p><p style="font-size: 0.875rem;">Complete a research to see history</p></div>';
|
1752
|
+
|
1753
|
+
// Enhanced metrics sections
|
1754
|
+
const modeContainer = document.getElementById('mode-breakdown');
|
1755
|
+
modeContainer.innerHTML = '<p style="text-align: center; color: var(--text-secondary); padding: 1rem;">No mode data available</p>';
|
1756
|
+
|
1757
|
+
const engineContainer = document.getElementById('search-engine-breakdown');
|
1758
|
+
engineContainer.innerHTML = '<p style="text-align: center; color: var(--text-secondary); padding: 1rem;">No search engine data available</p>';
|
1759
|
+
|
1760
|
+
const phaseContainer = document.getElementById('phase-breakdown');
|
1761
|
+
phaseContainer.innerHTML = '<p style="text-align: center; color: var(--text-secondary); padding: 1rem;">No phase data available</p>';
|
1762
|
+
|
1763
|
+
const filesContainer = document.getElementById('call-stack-files');
|
1764
|
+
filesContainer.innerHTML = '<p style="text-align: center; color: var(--text-secondary); padding: 1rem;">No file activity data</p>';
|
1765
|
+
|
1766
|
+
const functionsContainer = document.getElementById('call-stack-functions');
|
1767
|
+
functionsContainer.innerHTML = '<p style="text-align: center; color: var(--text-secondary); padding: 1rem;">No function activity data</p>';
|
1768
|
+
|
1769
|
+
const enhancedContainer = document.getElementById('recent-enhanced-data');
|
1770
|
+
enhancedContainer.innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--text-secondary);"><i class="fas fa-chart-line fa-2x" style="margin-bottom: 1rem;"></i><p>No enhanced tracking data</p><p style="font-size: 0.875rem;">Enhanced metrics will appear here after running research</p></div>';
|
1771
|
+
|
1772
|
+
const callStacksContainer = document.getElementById('recent-call-stacks');
|
1773
|
+
callStacksContainer.innerHTML = '<p style="text-align: center; color: var(--text-secondary); padding: 1rem;">No call stack traces available</p>';
|
1774
|
+
|
1775
|
+
// Create empty chart
|
1776
|
+
createEmptyTimeSeriesChart();
|
1777
|
+
}
|
1778
|
+
|
1779
|
+
// Create empty time-series chart
|
1780
|
+
function createEmptyTimeSeriesChart() {
|
1781
|
+
const ctx = document.getElementById('time-series-chart').getContext('2d');
|
1782
|
+
|
1783
|
+
// Destroy existing chart if it exists
|
1784
|
+
if (timeSeriesChart) {
|
1785
|
+
timeSeriesChart.destroy();
|
1786
|
+
}
|
1787
|
+
|
1788
|
+
timeSeriesChart = new Chart(ctx, {
|
1789
|
+
type: 'line',
|
1790
|
+
data: {
|
1791
|
+
labels: [],
|
1792
|
+
datasets: [{
|
1793
|
+
label: 'No data available',
|
1794
|
+
data: [],
|
1795
|
+
borderColor: 'rgba(200, 200, 200, 0.5)',
|
1796
|
+
backgroundColor: 'rgba(200, 200, 200, 0.1)',
|
1797
|
+
}]
|
1798
|
+
},
|
1799
|
+
options: {
|
1800
|
+
responsive: true,
|
1801
|
+
maintainAspectRatio: false,
|
1802
|
+
plugins: {
|
1803
|
+
legend: {
|
1804
|
+
display: false
|
1805
|
+
}
|
1806
|
+
},
|
1807
|
+
elements: {
|
1808
|
+
point: {
|
1809
|
+
radius: 0
|
1810
|
+
}
|
1811
|
+
},
|
1812
|
+
scales: {
|
1813
|
+
x: {
|
1814
|
+
display: true,
|
1815
|
+
title: {
|
1816
|
+
display: true,
|
1817
|
+
text: 'Time'
|
1818
|
+
}
|
1819
|
+
},
|
1820
|
+
y: {
|
1821
|
+
display: true,
|
1822
|
+
title: {
|
1823
|
+
display: true,
|
1824
|
+
text: 'Tokens'
|
1825
|
+
},
|
1826
|
+
beginAtZero: true,
|
1827
|
+
max: 100
|
1828
|
+
}
|
1829
|
+
}
|
1830
|
+
},
|
1831
|
+
plugins: [{
|
1832
|
+
id: 'emptyChart',
|
1833
|
+
beforeDraw: function(chart) {
|
1834
|
+
const ctx = chart.ctx;
|
1835
|
+
const width = chart.width;
|
1836
|
+
const height = chart.height;
|
1837
|
+
|
1838
|
+
ctx.save();
|
1839
|
+
ctx.textAlign = 'center';
|
1840
|
+
ctx.textBaseline = 'middle';
|
1841
|
+
ctx.font = '16px Arial';
|
1842
|
+
ctx.fillStyle = 'rgba(150, 150, 150, 0.8)';
|
1843
|
+
ctx.fillText('No research data yet - run a research to see token usage over time', width / 2, height / 2);
|
1844
|
+
ctx.restore();
|
1845
|
+
}
|
1846
|
+
}]
|
1847
|
+
});
|
1848
|
+
}
|
1849
|
+
|
1850
|
+
// Handle time range button clicks
|
1851
|
+
function handleTimeRangeChange(period) {
|
1852
|
+
// Update current period
|
1853
|
+
currentPeriod = period;
|
1854
|
+
|
1855
|
+
// Update button states for time range
|
1856
|
+
document.querySelectorAll('[data-period]').forEach(btn => {
|
1857
|
+
btn.classList.remove('active');
|
1858
|
+
});
|
1859
|
+
document.querySelector(`[data-period="${period}"]`).classList.add('active');
|
1860
|
+
|
1861
|
+
// Reload metrics with current filters
|
1862
|
+
loadMetrics(currentPeriod);
|
1863
|
+
}
|
1864
|
+
|
1865
|
+
// Handle research mode button clicks
|
1866
|
+
function handleResearchModeChange(mode) {
|
1867
|
+
// Update current mode
|
1868
|
+
currentMode = mode;
|
1869
|
+
|
1870
|
+
// Update button states for research mode
|
1871
|
+
document.querySelectorAll('[data-mode]').forEach(btn => {
|
1872
|
+
btn.classList.remove('active');
|
1873
|
+
});
|
1874
|
+
document.querySelector(`[data-mode="${mode}"]`).classList.add('active');
|
1875
|
+
|
1876
|
+
// Reload metrics with current filters
|
1877
|
+
loadMetrics(currentPeriod);
|
1878
|
+
}
|
1879
|
+
|
1880
|
+
// Initialize when DOM is loaded
|
1881
|
+
document.addEventListener('DOMContentLoaded', function() {
|
1882
|
+
// Set up time range button event listeners
|
1883
|
+
document.querySelectorAll('[data-period]').forEach(btn => {
|
1884
|
+
btn.addEventListener('click', function() {
|
1885
|
+
const period = this.getAttribute('data-period');
|
1886
|
+
handleTimeRangeChange(period);
|
1887
|
+
});
|
1888
|
+
});
|
1889
|
+
|
1890
|
+
// Set up research mode button event listeners
|
1891
|
+
document.querySelectorAll('[data-mode]').forEach(btn => {
|
1892
|
+
btn.addEventListener('click', function() {
|
1893
|
+
const mode = this.getAttribute('data-mode');
|
1894
|
+
handleResearchModeChange(mode);
|
1895
|
+
});
|
1896
|
+
});
|
1897
|
+
|
1898
|
+
// Load initial metrics
|
1899
|
+
loadMetrics();
|
1900
|
+
|
1901
|
+
// Set up smart tooltip positioning
|
1902
|
+
setupTooltipPositioning();
|
1903
|
+
});
|
1904
|
+
|
1905
|
+
// Smart tooltip positioning to prevent cutoff
|
1906
|
+
function setupTooltipPositioning() {
|
1907
|
+
document.querySelectorAll('.tooltip').forEach(tooltip => {
|
1908
|
+
tooltip.addEventListener('mouseenter', function() {
|
1909
|
+
// Remove existing positioning classes
|
1910
|
+
this.classList.remove('tooltip-left', 'tooltip-right');
|
1911
|
+
|
1912
|
+
// Get element position
|
1913
|
+
const rect = this.getBoundingClientRect();
|
1914
|
+
const windowWidth = window.innerWidth;
|
1915
|
+
|
1916
|
+
// Check if tooltip would go off left edge
|
1917
|
+
if (rect.left < 150) {
|
1918
|
+
this.classList.add('tooltip-left');
|
1919
|
+
}
|
1920
|
+
// Check if tooltip would go off right edge
|
1921
|
+
else if (rect.right > windowWidth - 150) {
|
1922
|
+
this.classList.add('tooltip-right');
|
1923
|
+
}
|
1924
|
+
});
|
1925
|
+
});
|
1926
|
+
}
|
1927
|
+
})();
|
1928
|
+
</script>
|
1929
|
+
{% endblock %}
|