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.
Files changed (220) hide show
  1. local_deep_research/__init__.py +7 -0
  2. local_deep_research/__version__.py +1 -1
  3. local_deep_research/advanced_search_system/answer_decoding/__init__.py +5 -0
  4. local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py +421 -0
  5. local_deep_research/advanced_search_system/candidate_exploration/README.md +219 -0
  6. local_deep_research/advanced_search_system/candidate_exploration/__init__.py +25 -0
  7. local_deep_research/advanced_search_system/candidate_exploration/adaptive_explorer.py +329 -0
  8. local_deep_research/advanced_search_system/candidate_exploration/base_explorer.py +341 -0
  9. local_deep_research/advanced_search_system/candidate_exploration/constraint_guided_explorer.py +436 -0
  10. local_deep_research/advanced_search_system/candidate_exploration/diversity_explorer.py +457 -0
  11. local_deep_research/advanced_search_system/candidate_exploration/parallel_explorer.py +250 -0
  12. local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +255 -0
  13. local_deep_research/advanced_search_system/candidates/__init__.py +5 -0
  14. local_deep_research/advanced_search_system/candidates/base_candidate.py +59 -0
  15. local_deep_research/advanced_search_system/constraint_checking/README.md +150 -0
  16. local_deep_research/advanced_search_system/constraint_checking/__init__.py +35 -0
  17. local_deep_research/advanced_search_system/constraint_checking/base_constraint_checker.py +122 -0
  18. local_deep_research/advanced_search_system/constraint_checking/constraint_checker.py +223 -0
  19. local_deep_research/advanced_search_system/constraint_checking/constraint_satisfaction_tracker.py +387 -0
  20. local_deep_research/advanced_search_system/constraint_checking/dual_confidence_checker.py +424 -0
  21. local_deep_research/advanced_search_system/constraint_checking/evidence_analyzer.py +174 -0
  22. local_deep_research/advanced_search_system/constraint_checking/intelligent_constraint_relaxer.py +503 -0
  23. local_deep_research/advanced_search_system/constraint_checking/rejection_engine.py +143 -0
  24. local_deep_research/advanced_search_system/constraint_checking/strict_checker.py +259 -0
  25. local_deep_research/advanced_search_system/constraint_checking/threshold_checker.py +213 -0
  26. local_deep_research/advanced_search_system/constraints/__init__.py +6 -0
  27. local_deep_research/advanced_search_system/constraints/base_constraint.py +58 -0
  28. local_deep_research/advanced_search_system/constraints/constraint_analyzer.py +143 -0
  29. local_deep_research/advanced_search_system/evidence/__init__.py +12 -0
  30. local_deep_research/advanced_search_system/evidence/base_evidence.py +57 -0
  31. local_deep_research/advanced_search_system/evidence/evaluator.py +159 -0
  32. local_deep_research/advanced_search_system/evidence/requirements.py +122 -0
  33. local_deep_research/advanced_search_system/filters/base_filter.py +3 -1
  34. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +8 -2
  35. local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +43 -29
  36. local_deep_research/advanced_search_system/findings/repository.py +54 -17
  37. local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +3 -1
  38. local_deep_research/advanced_search_system/query_generation/adaptive_query_generator.py +405 -0
  39. local_deep_research/advanced_search_system/questions/__init__.py +16 -0
  40. local_deep_research/advanced_search_system/questions/atomic_fact_question.py +171 -0
  41. local_deep_research/advanced_search_system/questions/browsecomp_question.py +287 -0
  42. local_deep_research/advanced_search_system/questions/decomposition_question.py +13 -4
  43. local_deep_research/advanced_search_system/questions/entity_aware_question.py +184 -0
  44. local_deep_research/advanced_search_system/questions/standard_question.py +9 -3
  45. local_deep_research/advanced_search_system/search_optimization/cross_constraint_manager.py +624 -0
  46. local_deep_research/advanced_search_system/source_management/diversity_manager.py +613 -0
  47. local_deep_research/advanced_search_system/strategies/__init__.py +42 -0
  48. local_deep_research/advanced_search_system/strategies/adaptive_decomposition_strategy.py +564 -0
  49. local_deep_research/advanced_search_system/strategies/base_strategy.py +4 -4
  50. local_deep_research/advanced_search_system/strategies/browsecomp_entity_strategy.py +1031 -0
  51. local_deep_research/advanced_search_system/strategies/browsecomp_optimized_strategy.py +778 -0
  52. local_deep_research/advanced_search_system/strategies/concurrent_dual_confidence_strategy.py +446 -0
  53. local_deep_research/advanced_search_system/strategies/constrained_search_strategy.py +1348 -0
  54. local_deep_research/advanced_search_system/strategies/constraint_parallel_strategy.py +522 -0
  55. local_deep_research/advanced_search_system/strategies/direct_search_strategy.py +217 -0
  56. local_deep_research/advanced_search_system/strategies/dual_confidence_strategy.py +320 -0
  57. local_deep_research/advanced_search_system/strategies/dual_confidence_with_rejection.py +219 -0
  58. local_deep_research/advanced_search_system/strategies/early_stop_constrained_strategy.py +369 -0
  59. local_deep_research/advanced_search_system/strategies/entity_aware_source_strategy.py +140 -0
  60. local_deep_research/advanced_search_system/strategies/evidence_based_strategy.py +1248 -0
  61. local_deep_research/advanced_search_system/strategies/evidence_based_strategy_v2.py +1337 -0
  62. local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +537 -0
  63. local_deep_research/advanced_search_system/strategies/improved_evidence_based_strategy.py +782 -0
  64. local_deep_research/advanced_search_system/strategies/iterative_reasoning_strategy.py +760 -0
  65. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +55 -21
  66. local_deep_research/advanced_search_system/strategies/llm_driven_modular_strategy.py +865 -0
  67. local_deep_research/advanced_search_system/strategies/modular_strategy.py +1142 -0
  68. local_deep_research/advanced_search_system/strategies/parallel_constrained_strategy.py +506 -0
  69. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +34 -16
  70. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +29 -9
  71. local_deep_research/advanced_search_system/strategies/recursive_decomposition_strategy.py +492 -0
  72. local_deep_research/advanced_search_system/strategies/smart_decomposition_strategy.py +284 -0
  73. local_deep_research/advanced_search_system/strategies/smart_query_strategy.py +515 -0
  74. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +48 -24
  75. local_deep_research/advanced_search_system/strategies/standard_strategy.py +34 -14
  76. local_deep_research/advanced_search_system/tools/base_tool.py +7 -2
  77. local_deep_research/api/benchmark_functions.py +6 -2
  78. local_deep_research/api/research_functions.py +10 -4
  79. local_deep_research/benchmarks/__init__.py +9 -7
  80. local_deep_research/benchmarks/benchmark_functions.py +6 -2
  81. local_deep_research/benchmarks/cli/benchmark_commands.py +27 -10
  82. local_deep_research/benchmarks/cli.py +38 -13
  83. local_deep_research/benchmarks/comparison/__init__.py +4 -2
  84. local_deep_research/benchmarks/comparison/evaluator.py +316 -239
  85. local_deep_research/benchmarks/datasets/__init__.py +1 -1
  86. local_deep_research/benchmarks/datasets/base.py +91 -72
  87. local_deep_research/benchmarks/datasets/browsecomp.py +54 -33
  88. local_deep_research/benchmarks/datasets/custom_dataset_template.py +19 -19
  89. local_deep_research/benchmarks/datasets/simpleqa.py +14 -14
  90. local_deep_research/benchmarks/datasets/utils.py +48 -29
  91. local_deep_research/benchmarks/datasets.py +4 -11
  92. local_deep_research/benchmarks/efficiency/__init__.py +8 -4
  93. local_deep_research/benchmarks/efficiency/resource_monitor.py +223 -171
  94. local_deep_research/benchmarks/efficiency/speed_profiler.py +62 -48
  95. local_deep_research/benchmarks/evaluators/browsecomp.py +3 -1
  96. local_deep_research/benchmarks/evaluators/composite.py +6 -2
  97. local_deep_research/benchmarks/evaluators/simpleqa.py +36 -13
  98. local_deep_research/benchmarks/graders.py +32 -10
  99. local_deep_research/benchmarks/metrics/README.md +1 -1
  100. local_deep_research/benchmarks/metrics/calculation.py +25 -10
  101. local_deep_research/benchmarks/metrics/reporting.py +7 -3
  102. local_deep_research/benchmarks/metrics/visualization.py +42 -23
  103. local_deep_research/benchmarks/metrics.py +1 -1
  104. local_deep_research/benchmarks/optimization/__init__.py +3 -1
  105. local_deep_research/benchmarks/optimization/api.py +7 -1
  106. local_deep_research/benchmarks/optimization/optuna_optimizer.py +75 -26
  107. local_deep_research/benchmarks/runners.py +48 -15
  108. local_deep_research/citation_handler.py +65 -92
  109. local_deep_research/citation_handlers/__init__.py +15 -0
  110. local_deep_research/citation_handlers/base_citation_handler.py +70 -0
  111. local_deep_research/citation_handlers/forced_answer_citation_handler.py +179 -0
  112. local_deep_research/citation_handlers/precision_extraction_handler.py +550 -0
  113. local_deep_research/citation_handlers/standard_citation_handler.py +80 -0
  114. local_deep_research/config/llm_config.py +271 -169
  115. local_deep_research/config/search_config.py +14 -5
  116. local_deep_research/defaults/__init__.py +0 -1
  117. local_deep_research/metrics/__init__.py +13 -0
  118. local_deep_research/metrics/database.py +58 -0
  119. local_deep_research/metrics/db_models.py +115 -0
  120. local_deep_research/metrics/migrate_add_provider_to_token_usage.py +148 -0
  121. local_deep_research/metrics/migrate_call_stack_tracking.py +105 -0
  122. local_deep_research/metrics/migrate_enhanced_tracking.py +75 -0
  123. local_deep_research/metrics/migrate_research_ratings.py +31 -0
  124. local_deep_research/metrics/models.py +61 -0
  125. local_deep_research/metrics/pricing/__init__.py +12 -0
  126. local_deep_research/metrics/pricing/cost_calculator.py +237 -0
  127. local_deep_research/metrics/pricing/pricing_cache.py +143 -0
  128. local_deep_research/metrics/pricing/pricing_fetcher.py +240 -0
  129. local_deep_research/metrics/query_utils.py +51 -0
  130. local_deep_research/metrics/search_tracker.py +380 -0
  131. local_deep_research/metrics/token_counter.py +1078 -0
  132. local_deep_research/migrate_db.py +3 -1
  133. local_deep_research/report_generator.py +22 -8
  134. local_deep_research/search_system.py +390 -9
  135. local_deep_research/test_migration.py +15 -5
  136. local_deep_research/utilities/db_utils.py +7 -4
  137. local_deep_research/utilities/es_utils.py +115 -104
  138. local_deep_research/utilities/llm_utils.py +15 -5
  139. local_deep_research/utilities/log_utils.py +151 -0
  140. local_deep_research/utilities/search_cache.py +387 -0
  141. local_deep_research/utilities/search_utilities.py +14 -6
  142. local_deep_research/utilities/threading_utils.py +92 -0
  143. local_deep_research/utilities/url_utils.py +6 -0
  144. local_deep_research/web/api.py +347 -0
  145. local_deep_research/web/app.py +13 -17
  146. local_deep_research/web/app_factory.py +71 -66
  147. local_deep_research/web/database/migrate_to_ldr_db.py +12 -4
  148. local_deep_research/web/database/migrations.py +20 -3
  149. local_deep_research/web/database/models.py +74 -25
  150. local_deep_research/web/database/schema_upgrade.py +49 -29
  151. local_deep_research/web/models/database.py +63 -83
  152. local_deep_research/web/routes/api_routes.py +56 -22
  153. local_deep_research/web/routes/benchmark_routes.py +4 -1
  154. local_deep_research/web/routes/globals.py +22 -0
  155. local_deep_research/web/routes/history_routes.py +71 -46
  156. local_deep_research/web/routes/metrics_routes.py +1155 -0
  157. local_deep_research/web/routes/research_routes.py +192 -54
  158. local_deep_research/web/routes/settings_routes.py +156 -55
  159. local_deep_research/web/services/research_service.py +412 -251
  160. local_deep_research/web/services/resource_service.py +36 -11
  161. local_deep_research/web/services/settings_manager.py +55 -17
  162. local_deep_research/web/services/settings_service.py +12 -4
  163. local_deep_research/web/services/socket_service.py +295 -188
  164. local_deep_research/web/static/css/custom_dropdown.css +180 -0
  165. local_deep_research/web/static/css/styles.css +39 -1
  166. local_deep_research/web/static/js/components/detail.js +633 -267
  167. local_deep_research/web/static/js/components/details.js +751 -0
  168. local_deep_research/web/static/js/components/fallback/formatting.js +11 -11
  169. local_deep_research/web/static/js/components/fallback/ui.js +23 -23
  170. local_deep_research/web/static/js/components/history.js +76 -76
  171. local_deep_research/web/static/js/components/logpanel.js +61 -13
  172. local_deep_research/web/static/js/components/progress.js +13 -2
  173. local_deep_research/web/static/js/components/research.js +99 -12
  174. local_deep_research/web/static/js/components/results.js +239 -106
  175. local_deep_research/web/static/js/main.js +40 -40
  176. local_deep_research/web/static/js/services/audio.js +1 -1
  177. local_deep_research/web/static/js/services/formatting.js +11 -11
  178. local_deep_research/web/static/js/services/keyboard.js +157 -0
  179. local_deep_research/web/static/js/services/pdf.js +80 -80
  180. local_deep_research/web/static/sounds/README.md +1 -1
  181. local_deep_research/web/templates/base.html +1 -0
  182. local_deep_research/web/templates/components/log_panel.html +7 -1
  183. local_deep_research/web/templates/components/mobile_nav.html +1 -1
  184. local_deep_research/web/templates/components/sidebar.html +3 -0
  185. local_deep_research/web/templates/pages/cost_analytics.html +1245 -0
  186. local_deep_research/web/templates/pages/details.html +325 -24
  187. local_deep_research/web/templates/pages/history.html +1 -1
  188. local_deep_research/web/templates/pages/metrics.html +1929 -0
  189. local_deep_research/web/templates/pages/progress.html +2 -2
  190. local_deep_research/web/templates/pages/research.html +53 -17
  191. local_deep_research/web/templates/pages/results.html +12 -1
  192. local_deep_research/web/templates/pages/star_reviews.html +803 -0
  193. local_deep_research/web/utils/formatters.py +9 -3
  194. local_deep_research/web_search_engines/default_search_engines.py +5 -3
  195. local_deep_research/web_search_engines/engines/full_search.py +8 -2
  196. local_deep_research/web_search_engines/engines/meta_search_engine.py +59 -20
  197. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +19 -6
  198. local_deep_research/web_search_engines/engines/search_engine_brave.py +6 -2
  199. local_deep_research/web_search_engines/engines/search_engine_ddg.py +3 -1
  200. local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +81 -58
  201. local_deep_research/web_search_engines/engines/search_engine_github.py +46 -15
  202. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +16 -6
  203. local_deep_research/web_search_engines/engines/search_engine_guardian.py +39 -15
  204. local_deep_research/web_search_engines/engines/search_engine_local.py +58 -25
  205. local_deep_research/web_search_engines/engines/search_engine_local_all.py +15 -5
  206. local_deep_research/web_search_engines/engines/search_engine_pubmed.py +63 -21
  207. local_deep_research/web_search_engines/engines/search_engine_searxng.py +37 -11
  208. local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +27 -9
  209. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +12 -4
  210. local_deep_research/web_search_engines/engines/search_engine_wayback.py +31 -10
  211. local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +12 -3
  212. local_deep_research/web_search_engines/search_engine_base.py +83 -35
  213. local_deep_research/web_search_engines/search_engine_factory.py +25 -8
  214. local_deep_research/web_search_engines/search_engines_config.py +9 -3
  215. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/METADATA +7 -1
  216. local_deep_research-0.5.2.dist-info/RECORD +265 -0
  217. local_deep_research-0.4.4.dist-info/RECORD +0 -177
  218. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/WHEEL +0 -0
  219. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/entry_points.txt +0 -0
  220. {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 %}