@memberjunction/ng-dashboards 5.26.0 → 5.27.1

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 (98) hide show
  1. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.d.ts +96 -0
  2. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.d.ts.map +1 -0
  3. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.js +710 -0
  4. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.js.map +1 -0
  5. package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts +52 -0
  6. package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts.map +1 -0
  7. package/dist/AI/components/analytics/ai-analytics-resource.component.js +356 -0
  8. package/dist/AI/components/analytics/ai-analytics-resource.component.js.map +1 -0
  9. package/dist/AI/components/analytics/analytics-filter-bar.component.d.ts +52 -0
  10. package/dist/AI/components/analytics/analytics-filter-bar.component.d.ts.map +1 -0
  11. package/dist/AI/components/analytics/analytics-filter-bar.component.js +306 -0
  12. package/dist/AI/components/analytics/analytics-filter-bar.component.js.map +1 -0
  13. package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts +81 -0
  14. package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts.map +1 -0
  15. package/dist/AI/components/analytics/cost-budget/cost-budget.component.js +744 -0
  16. package/dist/AI/components/analytics/cost-budget/cost-budget.component.js.map +1 -0
  17. package/dist/AI/components/analytics/error-analysis/error-analysis.component.d.ts +61 -0
  18. package/dist/AI/components/analytics/error-analysis/error-analysis.component.d.ts.map +1 -0
  19. package/dist/AI/components/analytics/error-analysis/error-analysis.component.js +490 -0
  20. package/dist/AI/components/analytics/error-analysis/error-analysis.component.js.map +1 -0
  21. package/dist/AI/components/analytics/executive-summary/executive-summary.component.d.ts +77 -0
  22. package/dist/AI/components/analytics/executive-summary/executive-summary.component.d.ts.map +1 -0
  23. package/dist/AI/components/analytics/executive-summary/executive-summary.component.js +673 -0
  24. package/dist/AI/components/analytics/executive-summary/executive-summary.component.js.map +1 -0
  25. package/dist/AI/components/analytics/model-performance/model-performance.component.d.ts +65 -0
  26. package/dist/AI/components/analytics/model-performance/model-performance.component.d.ts.map +1 -0
  27. package/dist/AI/components/analytics/model-performance/model-performance.component.js +537 -0
  28. package/dist/AI/components/analytics/model-performance/model-performance.component.js.map +1 -0
  29. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.d.ts +131 -0
  30. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.d.ts.map +1 -0
  31. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.js +1030 -0
  32. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.js.map +1 -0
  33. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.d.ts +78 -0
  34. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.d.ts.map +1 -0
  35. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.js +569 -0
  36. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.js.map +1 -0
  37. package/dist/AI/components/execution-monitoring.component.d.ts.map +1 -1
  38. package/dist/AI/components/execution-monitoring.component.js +4 -14
  39. package/dist/AI/components/execution-monitoring.component.js.map +1 -1
  40. package/dist/AI/components/overview/ai-overview-hub.component.d.ts +58 -0
  41. package/dist/AI/components/overview/ai-overview-hub.component.d.ts.map +1 -0
  42. package/dist/AI/components/overview/ai-overview-hub.component.js +315 -0
  43. package/dist/AI/components/overview/ai-overview-hub.component.js.map +1 -0
  44. package/dist/AI/components/prompts/prompt-management.component.js +1 -1
  45. package/dist/AI/components/prompts/prompt-management.component.js.map +1 -1
  46. package/dist/AI/index.d.ts +11 -0
  47. package/dist/AI/index.d.ts.map +1 -1
  48. package/dist/AI/index.js +13 -0
  49. package/dist/AI/index.js.map +1 -1
  50. package/dist/AI/interfaces/analytics-preferences.interface.d.ts +50 -0
  51. package/dist/AI/interfaces/analytics-preferences.interface.d.ts.map +1 -0
  52. package/dist/AI/interfaces/analytics-preferences.interface.js +9 -0
  53. package/dist/AI/interfaces/analytics-preferences.interface.js.map +1 -0
  54. package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
  55. package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -1
  56. package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
  57. package/dist/Home/home-dashboard.component.js +2 -2
  58. package/dist/MCP/index.d.ts +1 -0
  59. package/dist/MCP/index.d.ts.map +1 -1
  60. package/dist/MCP/index.js +2 -0
  61. package/dist/MCP/index.js.map +1 -1
  62. package/dist/MCP/mcp-dashboard.component.d.ts +1 -0
  63. package/dist/MCP/mcp-dashboard.component.d.ts.map +1 -1
  64. package/dist/MCP/mcp-dashboard.component.js +5 -4
  65. package/dist/MCP/mcp-dashboard.component.js.map +1 -1
  66. package/dist/MCP/mcp-resource.component.d.ts +6 -5
  67. package/dist/MCP/mcp-resource.component.d.ts.map +1 -1
  68. package/dist/MCP/mcp-resource.component.js +7 -8
  69. package/dist/MCP/mcp-resource.component.js.map +1 -1
  70. package/dist/ai-dashboards.module.d.ts +27 -17
  71. package/dist/ai-dashboards.module.d.ts.map +1 -1
  72. package/dist/ai-dashboards.module.js +66 -3
  73. package/dist/ai-dashboards.module.js.map +1 -1
  74. package/dist/public-api.d.ts +1 -1
  75. package/dist/public-api.d.ts.map +1 -1
  76. package/dist/public-api.js +1 -1
  77. package/dist/public-api.js.map +1 -1
  78. package/package.json +48 -48
  79. package/dist/__tests__/analytics-resource.test.d.ts +0 -2
  80. package/dist/__tests__/analytics-resource.test.d.ts.map +0 -1
  81. package/dist/__tests__/analytics-resource.test.js +0 -181
  82. package/dist/__tests__/analytics-resource.test.js.map +0 -1
  83. package/dist/__tests__/dashboards.test.d.ts +0 -2
  84. package/dist/__tests__/dashboards.test.d.ts.map +0 -1
  85. package/dist/__tests__/dashboards.test.js +0 -40
  86. package/dist/__tests__/dashboards.test.js.map +0 -1
  87. package/dist/__tests__/integration-data-service.test.d.ts +0 -2
  88. package/dist/__tests__/integration-data-service.test.d.ts.map +0 -1
  89. package/dist/__tests__/integration-data-service.test.js +0 -132
  90. package/dist/__tests__/integration-data-service.test.js.map +0 -1
  91. package/dist/__tests__/mapping-validation.test.d.ts +0 -2
  92. package/dist/__tests__/mapping-validation.test.d.ts.map +0 -1
  93. package/dist/__tests__/mapping-validation.test.js +0 -170
  94. package/dist/__tests__/mapping-validation.test.js.map +0 -1
  95. package/dist/__tests__/scheduling.test.d.ts +0 -2
  96. package/dist/__tests__/scheduling.test.d.ts.map +0 -1
  97. package/dist/__tests__/scheduling.test.js +0 -205
  98. package/dist/__tests__/scheduling.test.js.map +0 -1
@@ -0,0 +1,1030 @@
1
+ /**
2
+ * @fileoverview Prompt Run Analysis — Charts-first analytics with a detail table.
3
+ *
4
+ * Displays stats summary, runs-over-time chart, model/prompt/status breakdowns,
5
+ * and a sortable, paginated detail table. All data is loaded via RunView from
6
+ * the "MJ: AI Prompt Runs" entity.
7
+ */
8
+ import { Component, Input, Output, EventEmitter, ChangeDetectorRef, inject } from '@angular/core';
9
+ import { Subject } from 'rxjs';
10
+ import { RunView } from '@memberjunction/core';
11
+ import * as i0 from "@angular/core";
12
+ import * as i1 from "@memberjunction/ng-shared-generic";
13
+ import * as i2 from "../analytics-filter-bar.component";
14
+ import * as i3 from "@angular/common";
15
+ const _forTrack0 = ($index, $item) => $item.key;
16
+ const _forTrack1 = ($index, $item) => $item.id;
17
+ const _forTrack2 = ($index, $item) => $item.name;
18
+ const _forTrack3 = ($index, $item) => $item.field;
19
+ const _forTrack4 = ($index, $item) => $item.ID;
20
+ const _forTrack5 = ($index, $item) => $item.label;
21
+ function AnalyticsPromptRunsComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
22
+ i0.ɵɵelementStart(0, "div", 1);
23
+ i0.ɵɵelement(1, "mj-loading", 2);
24
+ i0.ɵɵelementEnd();
25
+ } }
26
+ function AnalyticsPromptRunsComponent_Conditional_2_For_47_Template(rf, ctx) { if (rf & 1) {
27
+ const _r1 = i0.ɵɵgetCurrentView();
28
+ i0.ɵɵelementStart(0, "button", 32);
29
+ i0.ɵɵlistener("click", function AnalyticsPromptRunsComponent_Conditional_2_For_47_Template_button_click_0_listener() { const metric_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnChartMetricChange(metric_r2.key)); });
30
+ i0.ɵɵtext(1);
31
+ i0.ɵɵelementEnd();
32
+ } if (rf & 2) {
33
+ const metric_r2 = ctx.$implicit;
34
+ const ctx_r2 = i0.ɵɵnextContext(2);
35
+ i0.ɵɵclassProp("active", ctx_r2.ActiveChartMetric === metric_r2.key);
36
+ i0.ɵɵadvance();
37
+ i0.ɵɵtextInterpolate1(" ", metric_r2.label, " ");
38
+ } }
39
+ function AnalyticsPromptRunsComponent_Conditional_2_Conditional_49_Template(rf, ctx) { if (rf & 1) {
40
+ i0.ɵɵelementStart(0, "div", 16);
41
+ i0.ɵɵtext(1, "No data for selected time range");
42
+ i0.ɵɵelementEnd();
43
+ } }
44
+ function AnalyticsPromptRunsComponent_Conditional_2_Conditional_50_For_2_Template(rf, ctx) { if (rf & 1) {
45
+ const _r4 = i0.ɵɵgetCurrentView();
46
+ i0.ɵɵelementStart(0, "div", 34);
47
+ i0.ɵɵlistener("click", function AnalyticsPromptRunsComponent_Conditional_2_Conditional_50_For_2_Template_div_click_0_listener() { const bucket_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.OnChartBucketClick(bucket_r5)); });
48
+ i0.ɵɵelementStart(1, "div", 35);
49
+ i0.ɵɵtext(2);
50
+ i0.ɵɵelementEnd();
51
+ i0.ɵɵelement(3, "div", 36);
52
+ i0.ɵɵelementStart(4, "div", 37);
53
+ i0.ɵɵtext(5);
54
+ i0.ɵɵelementEnd()();
55
+ } if (rf & 2) {
56
+ const bucket_r5 = ctx.$implicit;
57
+ const ctx_r2 = i0.ɵɵnextContext(3);
58
+ i0.ɵɵproperty("title", bucket_r5.label + ": " + bucket_r5.value);
59
+ i0.ɵɵadvance(2);
60
+ i0.ɵɵtextInterpolate(ctx_r2.FormatChartValue(bucket_r5.value));
61
+ i0.ɵɵadvance();
62
+ i0.ɵɵstyleProp("height", bucket_r5.heightPercent, "%");
63
+ i0.ɵɵadvance(2);
64
+ i0.ɵɵtextInterpolate(bucket_r5.label);
65
+ } }
66
+ function AnalyticsPromptRunsComponent_Conditional_2_Conditional_50_Template(rf, ctx) { if (rf & 1) {
67
+ i0.ɵɵelementStart(0, "div", 17);
68
+ i0.ɵɵrepeaterCreate(1, AnalyticsPromptRunsComponent_Conditional_2_Conditional_50_For_2_Template, 6, 5, "div", 33, _forTrack5);
69
+ i0.ɵɵelementEnd();
70
+ } if (rf & 2) {
71
+ const ctx_r2 = i0.ɵɵnextContext(2);
72
+ i0.ɵɵadvance();
73
+ i0.ɵɵrepeater(ctx_r2.ChartBuckets);
74
+ } }
75
+ function AnalyticsPromptRunsComponent_Conditional_2_For_56_Template(rf, ctx) { if (rf & 1) {
76
+ const _r6 = i0.ɵɵgetCurrentView();
77
+ i0.ɵɵelementStart(0, "div", 38);
78
+ i0.ɵɵlistener("click", function AnalyticsPromptRunsComponent_Conditional_2_For_56_Template_div_click_0_listener() { const item_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.ApplyModelFilter(item_r7.id)); });
79
+ i0.ɵɵelementStart(1, "span", 39);
80
+ i0.ɵɵtext(2);
81
+ i0.ɵɵelementEnd();
82
+ i0.ɵɵelementStart(3, "span", 40);
83
+ i0.ɵɵtext(4);
84
+ i0.ɵɵelementEnd();
85
+ i0.ɵɵelementStart(5, "div", 41);
86
+ i0.ɵɵelement(6, "div", 42);
87
+ i0.ɵɵelementEnd()();
88
+ } if (rf & 2) {
89
+ const item_r7 = ctx.$implicit;
90
+ i0.ɵɵadvance(2);
91
+ i0.ɵɵtextInterpolate(item_r7.name);
92
+ i0.ɵɵadvance(2);
93
+ i0.ɵɵtextInterpolate(item_r7.count);
94
+ i0.ɵɵadvance(2);
95
+ i0.ɵɵstyleProp("width", item_r7.percentage, "%");
96
+ } }
97
+ function AnalyticsPromptRunsComponent_Conditional_2_Conditional_57_Template(rf, ctx) { if (rf & 1) {
98
+ i0.ɵɵelementStart(0, "div", 22);
99
+ i0.ɵɵtext(1, "No data");
100
+ i0.ɵɵelementEnd();
101
+ } }
102
+ function AnalyticsPromptRunsComponent_Conditional_2_For_62_Template(rf, ctx) { if (rf & 1) {
103
+ const _r8 = i0.ɵɵgetCurrentView();
104
+ i0.ɵɵelementStart(0, "div", 38);
105
+ i0.ɵɵlistener("click", function AnalyticsPromptRunsComponent_Conditional_2_For_62_Template_div_click_0_listener() { const item_r9 = i0.ɵɵrestoreView(_r8).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.ApplyPromptFilter(item_r9.id)); });
106
+ i0.ɵɵelementStart(1, "span", 39);
107
+ i0.ɵɵtext(2);
108
+ i0.ɵɵelementEnd();
109
+ i0.ɵɵelementStart(3, "span", 40);
110
+ i0.ɵɵtext(4);
111
+ i0.ɵɵelementEnd();
112
+ i0.ɵɵelementStart(5, "div", 41);
113
+ i0.ɵɵelement(6, "div", 42);
114
+ i0.ɵɵelementEnd()();
115
+ } if (rf & 2) {
116
+ const item_r9 = ctx.$implicit;
117
+ i0.ɵɵadvance(2);
118
+ i0.ɵɵtextInterpolate(item_r9.name);
119
+ i0.ɵɵadvance(2);
120
+ i0.ɵɵtextInterpolate(item_r9.count);
121
+ i0.ɵɵadvance(2);
122
+ i0.ɵɵstyleProp("width", item_r9.percentage, "%");
123
+ } }
124
+ function AnalyticsPromptRunsComponent_Conditional_2_Conditional_63_Template(rf, ctx) { if (rf & 1) {
125
+ i0.ɵɵelementStart(0, "div", 22);
126
+ i0.ɵɵtext(1, "No data");
127
+ i0.ɵɵelementEnd();
128
+ } }
129
+ function AnalyticsPromptRunsComponent_Conditional_2_For_68_Template(rf, ctx) { if (rf & 1) {
130
+ const _r10 = i0.ɵɵgetCurrentView();
131
+ i0.ɵɵelementStart(0, "div", 38);
132
+ i0.ɵɵlistener("click", function AnalyticsPromptRunsComponent_Conditional_2_For_68_Template_div_click_0_listener() { const item_r11 = i0.ɵɵrestoreView(_r10).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.ApplyStatusFilter(item_r11.name)); });
133
+ i0.ɵɵelement(1, "span", 43);
134
+ i0.ɵɵelementStart(2, "span", 39);
135
+ i0.ɵɵtext(3);
136
+ i0.ɵɵelementEnd();
137
+ i0.ɵɵelementStart(4, "span", 40);
138
+ i0.ɵɵtext(5);
139
+ i0.ɵɵpipe(6, "number");
140
+ i0.ɵɵelementEnd()();
141
+ } if (rf & 2) {
142
+ const item_r11 = ctx.$implicit;
143
+ i0.ɵɵadvance();
144
+ i0.ɵɵclassMap(item_r11.cssClass);
145
+ i0.ɵɵadvance(2);
146
+ i0.ɵɵtextInterpolate(item_r11.name);
147
+ i0.ɵɵadvance(2);
148
+ i0.ɵɵtextInterpolate2("", item_r11.count, " (", i0.ɵɵpipeBind2(6, 5, item_r11.percentage, "1.1-1"), "%)");
149
+ } }
150
+ function AnalyticsPromptRunsComponent_Conditional_2_Conditional_69_Template(rf, ctx) { if (rf & 1) {
151
+ i0.ɵɵelementStart(0, "div", 22);
152
+ i0.ɵɵtext(1, "No data");
153
+ i0.ɵɵelementEnd();
154
+ } }
155
+ function AnalyticsPromptRunsComponent_Conditional_2_For_82_Conditional_2_Template(rf, ctx) { if (rf & 1) {
156
+ i0.ɵɵelement(0, "i");
157
+ } if (rf & 2) {
158
+ const ctx_r2 = i0.ɵɵnextContext(3);
159
+ i0.ɵɵclassMap(ctx_r2.SortDirection === "asc" ? "fa-solid fa-caret-up" : "fa-solid fa-caret-down");
160
+ } }
161
+ function AnalyticsPromptRunsComponent_Conditional_2_For_82_Template(rf, ctx) { if (rf & 1) {
162
+ const _r12 = i0.ɵɵgetCurrentView();
163
+ i0.ɵɵelementStart(0, "th", 44);
164
+ i0.ɵɵlistener("click", function AnalyticsPromptRunsComponent_Conditional_2_For_82_Template_th_click_0_listener() { const col_r13 = i0.ɵɵrestoreView(_r12).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(col_r13.sortable ? ctx_r2.OnSortChange(col_r13.field) : null); });
165
+ i0.ɵɵtext(1);
166
+ i0.ɵɵconditionalCreate(2, AnalyticsPromptRunsComponent_Conditional_2_For_82_Conditional_2_Template, 1, 2, "i", 45);
167
+ i0.ɵɵelementEnd();
168
+ } if (rf & 2) {
169
+ const col_r13 = ctx.$implicit;
170
+ const ctx_r2 = i0.ɵɵnextContext(2);
171
+ i0.ɵɵclassProp("sortable", col_r13.sortable)("sorted", ctx_r2.SortField === col_r13.field);
172
+ i0.ɵɵadvance();
173
+ i0.ɵɵtextInterpolate1(" ", col_r13.label, " ");
174
+ i0.ɵɵadvance();
175
+ i0.ɵɵconditional(col_r13.sortable && ctx_r2.SortField === col_r13.field ? 2 : -1);
176
+ } }
177
+ function AnalyticsPromptRunsComponent_Conditional_2_For_85_Template(rf, ctx) { if (rf & 1) {
178
+ i0.ɵɵelementStart(0, "tr")(1, "td", 46);
179
+ i0.ɵɵtext(2);
180
+ i0.ɵɵelementEnd();
181
+ i0.ɵɵelementStart(3, "td", 47);
182
+ i0.ɵɵtext(4);
183
+ i0.ɵɵelementEnd();
184
+ i0.ɵɵelementStart(5, "td")(6, "span", 48);
185
+ i0.ɵɵtext(7);
186
+ i0.ɵɵelementEnd()();
187
+ i0.ɵɵelementStart(8, "td")(9, "span", 49);
188
+ i0.ɵɵtext(10);
189
+ i0.ɵɵelementEnd()();
190
+ i0.ɵɵelementStart(11, "td", 50);
191
+ i0.ɵɵtext(12);
192
+ i0.ɵɵelementEnd();
193
+ i0.ɵɵelementStart(13, "td", 50);
194
+ i0.ɵɵtext(14);
195
+ i0.ɵɵpipe(15, "number");
196
+ i0.ɵɵelementEnd();
197
+ i0.ɵɵelementStart(16, "td", 50);
198
+ i0.ɵɵtext(17);
199
+ i0.ɵɵelementEnd()();
200
+ } if (rf & 2) {
201
+ const run_r14 = ctx.$implicit;
202
+ const ɵ$index_200_r15 = ctx.$index;
203
+ const ctx_r2 = i0.ɵɵnextContext(2);
204
+ i0.ɵɵclassProp("row-even", ɵ$index_200_r15 % 2 === 0);
205
+ i0.ɵɵadvance(2);
206
+ i0.ɵɵtextInterpolate(ctx_r2.FormatTimestamp(run_r14.RunAt));
207
+ i0.ɵɵadvance(2);
208
+ i0.ɵɵtextInterpolate(run_r14.Prompt ?? "(unnamed)");
209
+ i0.ɵɵadvance(3);
210
+ i0.ɵɵtextInterpolate(run_r14.Model ?? "N/A");
211
+ i0.ɵɵadvance(2);
212
+ i0.ɵɵclassMap(ctx_r2.GetStatusClass(run_r14.Status));
213
+ i0.ɵɵadvance();
214
+ i0.ɵɵtextInterpolate(run_r14.Status);
215
+ i0.ɵɵadvance(2);
216
+ i0.ɵɵtextInterpolate(ctx_r2.FormatDuration(run_r14.ExecutionTimeMS));
217
+ i0.ɵɵadvance(2);
218
+ i0.ɵɵtextInterpolate(run_r14.TokensUsed != null ? i0.ɵɵpipeBind1(15, 11, run_r14.TokensUsed) : "-");
219
+ i0.ɵɵadvance(3);
220
+ i0.ɵɵtextInterpolate(ctx_r2.FormatCurrency(run_r14.Cost, 4));
221
+ } }
222
+ function AnalyticsPromptRunsComponent_Conditional_2_Conditional_86_Template(rf, ctx) { if (rf & 1) {
223
+ i0.ɵɵelementStart(0, "tr")(1, "td", 51);
224
+ i0.ɵɵtext(2, "No prompt runs found for the selected filters.");
225
+ i0.ɵɵelementEnd()();
226
+ } }
227
+ function AnalyticsPromptRunsComponent_Conditional_2_Conditional_87_Template(rf, ctx) { if (rf & 1) {
228
+ const _r16 = i0.ɵɵgetCurrentView();
229
+ i0.ɵɵelementStart(0, "div", 31)(1, "button", 52);
230
+ i0.ɵɵlistener("click", function AnalyticsPromptRunsComponent_Conditional_2_Conditional_87_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r16); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnPageChange(ctx_r2.CurrentPage - 1)); });
231
+ i0.ɵɵelement(2, "i", 53);
232
+ i0.ɵɵelementEnd();
233
+ i0.ɵɵelementStart(3, "span", 54);
234
+ i0.ɵɵtext(4);
235
+ i0.ɵɵelementEnd();
236
+ i0.ɵɵelementStart(5, "button", 52);
237
+ i0.ɵɵlistener("click", function AnalyticsPromptRunsComponent_Conditional_2_Conditional_87_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r16); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnPageChange(ctx_r2.CurrentPage + 1)); });
238
+ i0.ɵɵelement(6, "i", 55);
239
+ i0.ɵɵelementEnd()();
240
+ } if (rf & 2) {
241
+ const ctx_r2 = i0.ɵɵnextContext(2);
242
+ i0.ɵɵadvance();
243
+ i0.ɵɵproperty("disabled", ctx_r2.CurrentPage === 1);
244
+ i0.ɵɵadvance(3);
245
+ i0.ɵɵtextInterpolate2("Page ", ctx_r2.CurrentPage, " of ", ctx_r2.TotalPages);
246
+ i0.ɵɵadvance();
247
+ i0.ɵɵproperty("disabled", ctx_r2.CurrentPage === ctx_r2.TotalPages);
248
+ } }
249
+ function AnalyticsPromptRunsComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
250
+ i0.ɵɵelementStart(0, "div", 3)(1, "div", 4)(2, "div", 5);
251
+ i0.ɵɵtext(3, "Total Runs");
252
+ i0.ɵɵelementEnd();
253
+ i0.ɵɵelementStart(4, "div", 6);
254
+ i0.ɵɵtext(5);
255
+ i0.ɵɵpipe(6, "number");
256
+ i0.ɵɵelementEnd()();
257
+ i0.ɵɵelementStart(7, "div", 7)(8, "div", 5);
258
+ i0.ɵɵtext(9, "Avg Cost");
259
+ i0.ɵɵelementEnd();
260
+ i0.ɵɵelementStart(10, "div", 6);
261
+ i0.ɵɵtext(11);
262
+ i0.ɵɵelementEnd()();
263
+ i0.ɵɵelementStart(12, "div", 7)(13, "div", 5);
264
+ i0.ɵɵtext(14, "Avg Tokens");
265
+ i0.ɵɵelementEnd();
266
+ i0.ɵɵelementStart(15, "div", 6);
267
+ i0.ɵɵtext(16);
268
+ i0.ɵɵpipe(17, "number");
269
+ i0.ɵɵelementEnd()();
270
+ i0.ɵɵelementStart(18, "div", 7)(19, "div", 5);
271
+ i0.ɵɵtext(20, "Avg Latency");
272
+ i0.ɵɵelementEnd();
273
+ i0.ɵɵelementStart(21, "div", 6);
274
+ i0.ɵɵtext(22);
275
+ i0.ɵɵpipe(23, "number");
276
+ i0.ɵɵelementEnd()();
277
+ i0.ɵɵelementStart(24, "div", 8)(25, "div", 5);
278
+ i0.ɵɵtext(26, "Success Rate");
279
+ i0.ɵɵelementEnd();
280
+ i0.ɵɵelementStart(27, "div", 6);
281
+ i0.ɵɵtext(28);
282
+ i0.ɵɵpipe(29, "number");
283
+ i0.ɵɵelementEnd()();
284
+ i0.ɵɵelementStart(30, "div", 9)(31, "div", 5);
285
+ i0.ɵɵtext(32, "P95 Latency");
286
+ i0.ɵɵelementEnd();
287
+ i0.ɵɵelementStart(33, "div", 6);
288
+ i0.ɵɵtext(34);
289
+ i0.ɵɵpipe(35, "number");
290
+ i0.ɵɵelementEnd()();
291
+ i0.ɵɵelementStart(36, "div", 7)(37, "div", 5);
292
+ i0.ɵɵtext(38, "Total Cost");
293
+ i0.ɵɵelementEnd();
294
+ i0.ɵɵelementStart(39, "div", 6);
295
+ i0.ɵɵtext(40);
296
+ i0.ɵɵelementEnd()()();
297
+ i0.ɵɵelementStart(41, "div", 10)(42, "div", 11)(43, "h3", 12);
298
+ i0.ɵɵtext(44, "Runs Over Time");
299
+ i0.ɵɵelementEnd();
300
+ i0.ɵɵelementStart(45, "div", 13);
301
+ i0.ɵɵrepeaterCreate(46, AnalyticsPromptRunsComponent_Conditional_2_For_47_Template, 2, 3, "button", 14, _forTrack0);
302
+ i0.ɵɵelementEnd()();
303
+ i0.ɵɵelementStart(48, "div", 15);
304
+ i0.ɵɵconditionalCreate(49, AnalyticsPromptRunsComponent_Conditional_2_Conditional_49_Template, 2, 0, "div", 16)(50, AnalyticsPromptRunsComponent_Conditional_2_Conditional_50_Template, 3, 0, "div", 17);
305
+ i0.ɵɵelementEnd()();
306
+ i0.ɵɵelementStart(51, "div", 18)(52, "div", 19)(53, "h4", 20);
307
+ i0.ɵɵtext(54, "By Model");
308
+ i0.ɵɵelementEnd();
309
+ i0.ɵɵrepeaterCreate(55, AnalyticsPromptRunsComponent_Conditional_2_For_56_Template, 7, 4, "div", 21, _forTrack1);
310
+ i0.ɵɵconditionalCreate(57, AnalyticsPromptRunsComponent_Conditional_2_Conditional_57_Template, 2, 0, "div", 22);
311
+ i0.ɵɵelementEnd();
312
+ i0.ɵɵelementStart(58, "div", 19)(59, "h4", 20);
313
+ i0.ɵɵtext(60, "By Prompt");
314
+ i0.ɵɵelementEnd();
315
+ i0.ɵɵrepeaterCreate(61, AnalyticsPromptRunsComponent_Conditional_2_For_62_Template, 7, 4, "div", 21, _forTrack1);
316
+ i0.ɵɵconditionalCreate(63, AnalyticsPromptRunsComponent_Conditional_2_Conditional_63_Template, 2, 0, "div", 22);
317
+ i0.ɵɵelementEnd();
318
+ i0.ɵɵelementStart(64, "div", 19)(65, "h4", 20);
319
+ i0.ɵɵtext(66, "By Status");
320
+ i0.ɵɵelementEnd();
321
+ i0.ɵɵrepeaterCreate(67, AnalyticsPromptRunsComponent_Conditional_2_For_68_Template, 7, 8, "div", 21, _forTrack2);
322
+ i0.ɵɵconditionalCreate(69, AnalyticsPromptRunsComponent_Conditional_2_Conditional_69_Template, 2, 0, "div", 22);
323
+ i0.ɵɵelementEnd()();
324
+ i0.ɵɵelementStart(70, "div", 23)(71, "div", 24)(72, "h3", 25);
325
+ i0.ɵɵtext(73, "Run Details");
326
+ i0.ɵɵelementEnd();
327
+ i0.ɵɵelementStart(74, "span", 26);
328
+ i0.ɵɵtext(75);
329
+ i0.ɵɵpipe(76, "number");
330
+ i0.ɵɵelementEnd()();
331
+ i0.ɵɵelementStart(77, "div", 27)(78, "table", 28)(79, "thead")(80, "tr");
332
+ i0.ɵɵrepeaterCreate(81, AnalyticsPromptRunsComponent_Conditional_2_For_82_Template, 3, 6, "th", 29, _forTrack3);
333
+ i0.ɵɵelementEnd()();
334
+ i0.ɵɵelementStart(83, "tbody");
335
+ i0.ɵɵrepeaterCreate(84, AnalyticsPromptRunsComponent_Conditional_2_For_85_Template, 18, 13, "tr", 30, _forTrack4);
336
+ i0.ɵɵconditionalCreate(86, AnalyticsPromptRunsComponent_Conditional_2_Conditional_86_Template, 3, 0, "tr");
337
+ i0.ɵɵelementEnd()()();
338
+ i0.ɵɵconditionalCreate(87, AnalyticsPromptRunsComponent_Conditional_2_Conditional_87_Template, 7, 4, "div", 31);
339
+ i0.ɵɵelementEnd();
340
+ } if (rf & 2) {
341
+ const ctx_r2 = i0.ɵɵnextContext();
342
+ i0.ɵɵadvance(5);
343
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(6, 14, ctx_r2.Stats.TotalRuns));
344
+ i0.ɵɵadvance(6);
345
+ i0.ɵɵtextInterpolate(ctx_r2.FormatCurrency(ctx_r2.Stats.AvgCost, 4));
346
+ i0.ɵɵadvance(5);
347
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(17, 16, ctx_r2.Stats.AvgTokens, "1.0-0"));
348
+ i0.ɵɵadvance(6);
349
+ i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind2(23, 19, ctx_r2.Stats.AvgLatencySeconds, "1.2-2"), "s");
350
+ i0.ɵɵadvance(6);
351
+ i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind2(29, 22, ctx_r2.Stats.SuccessRate, "1.1-1"), "%");
352
+ i0.ɵɵadvance(6);
353
+ i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind2(35, 25, ctx_r2.Stats.P95LatencySeconds, "1.2-2"), "s");
354
+ i0.ɵɵadvance(6);
355
+ i0.ɵɵtextInterpolate(ctx_r2.FormatCurrency(ctx_r2.Stats.TotalCost, 2));
356
+ i0.ɵɵadvance(6);
357
+ i0.ɵɵrepeater(ctx_r2.ChartMetricOptions);
358
+ i0.ɵɵadvance(3);
359
+ i0.ɵɵconditional(ctx_r2.ChartBuckets.length === 0 ? 49 : 50);
360
+ i0.ɵɵadvance(6);
361
+ i0.ɵɵrepeater(ctx_r2.ModelBreakdown);
362
+ i0.ɵɵadvance(2);
363
+ i0.ɵɵconditional(ctx_r2.ModelBreakdown.length === 0 ? 57 : -1);
364
+ i0.ɵɵadvance(4);
365
+ i0.ɵɵrepeater(ctx_r2.PromptBreakdown);
366
+ i0.ɵɵadvance(2);
367
+ i0.ɵɵconditional(ctx_r2.PromptBreakdown.length === 0 ? 63 : -1);
368
+ i0.ɵɵadvance(4);
369
+ i0.ɵɵrepeater(ctx_r2.StatusBreakdown);
370
+ i0.ɵɵadvance(2);
371
+ i0.ɵɵconditional(ctx_r2.StatusBreakdown.length === 0 ? 69 : -1);
372
+ i0.ɵɵadvance(6);
373
+ i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind1(76, 28, ctx_r2.FilteredRuns.length), " runs");
374
+ i0.ɵɵadvance(6);
375
+ i0.ɵɵrepeater(ctx_r2.TableColumns);
376
+ i0.ɵɵadvance(3);
377
+ i0.ɵɵrepeater(ctx_r2.PagedRuns);
378
+ i0.ɵɵadvance(2);
379
+ i0.ɵɵconditional(ctx_r2.PagedRuns.length === 0 ? 86 : -1);
380
+ i0.ɵɵadvance();
381
+ i0.ɵɵconditional(ctx_r2.TotalPages > 1 ? 87 : -1);
382
+ } }
383
+ const FIELDS = [
384
+ 'ID', 'RunAt', 'CompletedAt', 'Status', 'Success', 'Cost', 'TotalCost',
385
+ 'TokensUsed', 'TokensPrompt', 'TokensCompletion', 'ExecutionTimeMS',
386
+ 'ModelID', 'Model', 'AgentID', 'Agent', 'PromptID', 'Prompt', 'ErrorMessage'
387
+ ];
388
+ const PAGE_SIZE = 25;
389
+ export class AnalyticsPromptRunsComponent {
390
+ cdr = inject(ChangeDetectorRef);
391
+ destroy$ = new Subject();
392
+ isInitialized = false;
393
+ // ── Inputs with setter pattern ──
394
+ _timeRange = '24h';
395
+ set TimeRange(value) {
396
+ if (value !== this._timeRange) {
397
+ this._timeRange = value;
398
+ if (this.isInitialized) {
399
+ this.LoadData();
400
+ }
401
+ }
402
+ }
403
+ get TimeRange() {
404
+ return this._timeRange;
405
+ }
406
+ _filters = { Models: [], Agents: [], Prompts: [], Statuses: [] };
407
+ set Filters(value) {
408
+ this._filters = value;
409
+ if (this.isInitialized) {
410
+ this.resetPagination();
411
+ this.cdr.detectChanges();
412
+ }
413
+ }
414
+ get Filters() {
415
+ return this._filters;
416
+ }
417
+ TimeRangeChange = new EventEmitter();
418
+ FiltersChange = new EventEmitter();
419
+ // ── State ──
420
+ IsLoading = false;
421
+ ActiveChartMetric = 'volume';
422
+ SortField = 'RunAt';
423
+ SortDirection = 'desc';
424
+ CurrentPage = 1;
425
+ allRuns = [];
426
+ ChartMetricOptions = [
427
+ { key: 'volume', label: 'By Volume' },
428
+ { key: 'cost', label: 'By Cost' },
429
+ { key: 'tokens', label: 'By Tokens' },
430
+ ];
431
+ TableColumns = [
432
+ { field: 'RunAt', label: 'Timestamp', sortable: true },
433
+ { field: 'Prompt', label: 'Prompt', sortable: true },
434
+ { field: 'Model', label: 'Model', sortable: true },
435
+ { field: 'Status', label: 'Status', sortable: true },
436
+ { field: 'ExecutionTimeMS', label: 'Duration', sortable: true },
437
+ { field: 'TokensUsed', label: 'Tokens', sortable: true },
438
+ { field: 'Cost', label: 'Cost', sortable: true },
439
+ ];
440
+ // ── Lifecycle ──
441
+ async ngOnInit() {
442
+ this.isInitialized = true;
443
+ await this.LoadData();
444
+ }
445
+ ngOnDestroy() {
446
+ this.destroy$.next();
447
+ this.destroy$.complete();
448
+ }
449
+ // ── Computed Properties ──
450
+ get FilteredRuns() {
451
+ return this.applyFilters(this.allRuns);
452
+ }
453
+ get Stats() {
454
+ return this.computeStats(this.FilteredRuns);
455
+ }
456
+ get ChartBuckets() {
457
+ return this.computeChartBuckets(this.FilteredRuns);
458
+ }
459
+ get ModelBreakdown() {
460
+ return this.computeBreakdown(this.FilteredRuns, 'Model', 'ModelID');
461
+ }
462
+ get PromptBreakdown() {
463
+ return this.computeBreakdown(this.FilteredRuns, 'Prompt', 'PromptID');
464
+ }
465
+ get StatusBreakdown() {
466
+ return this.computeStatusBreakdown(this.FilteredRuns);
467
+ }
468
+ get PagedRuns() {
469
+ const sorted = this.sortRuns(this.FilteredRuns);
470
+ const start = (this.CurrentPage - 1) * PAGE_SIZE;
471
+ return sorted.slice(start, start + PAGE_SIZE);
472
+ }
473
+ get TotalPages() {
474
+ return Math.max(1, Math.ceil(this.FilteredRuns.length / PAGE_SIZE));
475
+ }
476
+ // ── Event Handlers ──
477
+ OnTimeRangeChange(range) {
478
+ this.TimeRange = range;
479
+ this.TimeRangeChange.emit(range);
480
+ }
481
+ OnFiltersChange(filters) {
482
+ this.Filters = filters;
483
+ this.FiltersChange.emit(filters);
484
+ }
485
+ OnChartMetricChange(metric) {
486
+ this.ActiveChartMetric = metric;
487
+ this.cdr.detectChanges();
488
+ }
489
+ OnChartBucketClick(_bucket) {
490
+ // Future: drill into time range
491
+ }
492
+ OnSortChange(field) {
493
+ if (this.SortField === field) {
494
+ this.SortDirection = this.SortDirection === 'asc' ? 'desc' : 'asc';
495
+ }
496
+ else {
497
+ this.SortField = field;
498
+ this.SortDirection = 'desc';
499
+ }
500
+ this.resetPagination();
501
+ this.cdr.detectChanges();
502
+ }
503
+ OnPageChange(page) {
504
+ if (page >= 1 && page <= this.TotalPages) {
505
+ this.CurrentPage = page;
506
+ this.cdr.detectChanges();
507
+ }
508
+ }
509
+ ApplyModelFilter(modelId) {
510
+ this.Filters = { ...this.Filters, Models: [modelId] };
511
+ this.FiltersChange.emit(this.Filters);
512
+ }
513
+ ApplyPromptFilter(promptId) {
514
+ this.Filters = { ...this.Filters, Prompts: [promptId] };
515
+ this.FiltersChange.emit(this.Filters);
516
+ }
517
+ ApplyStatusFilter(status) {
518
+ this.Filters = { ...this.Filters, Statuses: [status] };
519
+ this.FiltersChange.emit(this.Filters);
520
+ }
521
+ ExportCSV() {
522
+ const headers = ['Timestamp', 'Prompt', 'Model', 'Status', 'Duration(ms)', 'Tokens', 'Cost'];
523
+ const rows = this.FilteredRuns.map(r => [
524
+ r.RunAt,
525
+ r.Prompt ?? '',
526
+ r.Model ?? '',
527
+ r.Status,
528
+ r.ExecutionTimeMS?.toString() ?? '',
529
+ r.TokensUsed?.toString() ?? '',
530
+ r.Cost?.toString() ?? ''
531
+ ]);
532
+ const csv = [headers, ...rows].map(r => r.join(',')).join('\n');
533
+ const blob = new Blob([csv], { type: 'text/csv' });
534
+ const url = URL.createObjectURL(blob);
535
+ const anchor = document.createElement('a');
536
+ anchor.href = url;
537
+ anchor.download = `prompt-runs-${new Date().toISOString().slice(0, 10)}.csv`;
538
+ anchor.click();
539
+ URL.revokeObjectURL(url);
540
+ }
541
+ // ── Formatting Helpers ──
542
+ FormatCurrency(value, decimals) {
543
+ if (value == null || isNaN(value))
544
+ return '$0.00';
545
+ return '$' + value.toFixed(decimals);
546
+ }
547
+ FormatTimestamp(dateStr) {
548
+ const d = new Date(dateStr);
549
+ return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) +
550
+ ' ' + d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
551
+ }
552
+ FormatDuration(ms) {
553
+ if (ms == null)
554
+ return '-';
555
+ if (ms < 1000)
556
+ return ms + 'ms';
557
+ return (ms / 1000).toFixed(2) + 's';
558
+ }
559
+ FormatChartValue(value) {
560
+ if (this.ActiveChartMetric === 'cost') {
561
+ return '$' + value.toFixed(2);
562
+ }
563
+ if (value >= 1000) {
564
+ return (value / 1000).toFixed(1) + 'k';
565
+ }
566
+ return value.toFixed(0);
567
+ }
568
+ GetStatusClass(status) {
569
+ const key = status.toLowerCase();
570
+ return 'pill-' + key;
571
+ }
572
+ // ── Data Loading ──
573
+ async LoadData() {
574
+ this.IsLoading = true;
575
+ this.cdr.detectChanges();
576
+ try {
577
+ const rv = new RunView();
578
+ const cutoff = this.getTimeRangeCutoff(this._timeRange);
579
+ const filter = cutoff ? `RunAt >= '${cutoff.toISOString()}'` : '';
580
+ const result = await rv.RunView({
581
+ EntityName: 'MJ: AI Prompt Runs',
582
+ ExtraFilter: filter,
583
+ OrderBy: 'RunAt DESC',
584
+ Fields: FIELDS,
585
+ ResultType: 'simple'
586
+ });
587
+ if (result.Success) {
588
+ this.allRuns = result.Results;
589
+ }
590
+ else {
591
+ this.allRuns = [];
592
+ }
593
+ }
594
+ catch {
595
+ this.allRuns = [];
596
+ }
597
+ this.resetPagination();
598
+ this.IsLoading = false;
599
+ this.cdr.detectChanges();
600
+ }
601
+ // ── Private Helpers ──
602
+ applyFilters(runs) {
603
+ let filtered = runs;
604
+ if (this._filters.Models.length > 0) {
605
+ filtered = filtered.filter(r => r.ModelID != null && this._filters.Models.includes(r.ModelID));
606
+ }
607
+ if (this._filters.Agents.length > 0) {
608
+ filtered = filtered.filter(r => r.AgentID != null && this._filters.Agents.includes(r.AgentID));
609
+ }
610
+ if (this._filters.Prompts.length > 0) {
611
+ filtered = filtered.filter(r => r.PromptID != null && this._filters.Prompts.includes(r.PromptID));
612
+ }
613
+ if (this._filters.Statuses.length > 0) {
614
+ filtered = filtered.filter(r => this._filters.Statuses.includes(r.Status));
615
+ }
616
+ return filtered;
617
+ }
618
+ computeStats(runs) {
619
+ const total = runs.length;
620
+ if (total === 0) {
621
+ return { TotalRuns: 0, AvgCost: 0, AvgTokens: 0, AvgLatencySeconds: 0, SuccessRate: 0, P95LatencySeconds: 0, TotalCost: 0 };
622
+ }
623
+ const totalCost = this.sumNullable(runs, r => r.Cost);
624
+ const totalTokens = this.sumNullable(runs, r => r.TokensUsed);
625
+ const latencies = this.collectNonNull(runs, r => r.ExecutionTimeMS);
626
+ const avgLatencyMs = latencies.length > 0 ? latencies.reduce((a, b) => a + b, 0) / latencies.length : 0;
627
+ const successCount = runs.filter(r => r.Status === 'Completed').length;
628
+ const p95 = this.percentile(latencies, 95);
629
+ return {
630
+ TotalRuns: total,
631
+ AvgCost: totalCost / total,
632
+ AvgTokens: totalTokens / total,
633
+ AvgLatencySeconds: avgLatencyMs / 1000,
634
+ SuccessRate: (successCount / total) * 100,
635
+ P95LatencySeconds: p95 / 1000,
636
+ TotalCost: totalCost,
637
+ };
638
+ }
639
+ computeChartBuckets(runs) {
640
+ if (runs.length === 0)
641
+ return [];
642
+ const bucketCount = this.getBucketCount();
643
+ const cutoff = this.getTimeRangeCutoff(this._timeRange) ?? new Date(runs[runs.length - 1].RunAt);
644
+ const now = new Date();
645
+ const rangeMs = now.getTime() - cutoff.getTime();
646
+ const bucketMs = rangeMs / bucketCount;
647
+ const buckets = [];
648
+ for (let i = 0; i < bucketCount; i++) {
649
+ const start = new Date(cutoff.getTime() + i * bucketMs);
650
+ const end = new Date(cutoff.getTime() + (i + 1) * bucketMs);
651
+ buckets.push({
652
+ label: this.formatBucketLabel(start),
653
+ total: 0,
654
+ start,
655
+ end,
656
+ });
657
+ }
658
+ for (const run of runs) {
659
+ const runTime = new Date(run.RunAt).getTime();
660
+ const idx = Math.min(Math.floor((runTime - cutoff.getTime()) / bucketMs), bucketCount - 1);
661
+ if (idx >= 0 && idx < bucketCount) {
662
+ buckets[idx].total += this.getChartMetricValue(run);
663
+ }
664
+ }
665
+ const max = Math.max(...buckets.map(b => b.total), 1);
666
+ return buckets.map(b => ({
667
+ label: b.label,
668
+ value: Math.round(b.total * 100) / 100,
669
+ heightPercent: Math.max((b.total / max) * 100, 1),
670
+ startTime: b.start,
671
+ endTime: b.end,
672
+ }));
673
+ }
674
+ getChartMetricValue(run) {
675
+ switch (this.ActiveChartMetric) {
676
+ case 'cost': return run.Cost ?? 0;
677
+ case 'tokens': return run.TokensUsed ?? 0;
678
+ default: return 1; // volume = count
679
+ }
680
+ }
681
+ computeBreakdown(runs, nameKey, idKey) {
682
+ const counts = new Map();
683
+ for (const run of runs) {
684
+ const id = run[idKey];
685
+ const name = run[nameKey];
686
+ if (id != null && name != null) {
687
+ const existing = counts.get(id);
688
+ if (existing) {
689
+ existing.count++;
690
+ }
691
+ else {
692
+ counts.set(id, { name, count: 1 });
693
+ }
694
+ }
695
+ }
696
+ const sorted = Array.from(counts.entries())
697
+ .sort((a, b) => b[1].count - a[1].count)
698
+ .slice(0, 4);
699
+ const maxCount = sorted.length > 0 ? sorted[0][1].count : 1;
700
+ return sorted.map(([id, data]) => ({
701
+ id,
702
+ name: data.name,
703
+ count: data.count,
704
+ percentage: (data.count / maxCount) * 100,
705
+ }));
706
+ }
707
+ computeStatusBreakdown(runs) {
708
+ const total = runs.length;
709
+ if (total === 0)
710
+ return [];
711
+ const counts = new Map();
712
+ for (const run of runs) {
713
+ counts.set(run.Status, (counts.get(run.Status) ?? 0) + 1);
714
+ }
715
+ return Array.from(counts.entries())
716
+ .sort((a, b) => b[1] - a[1])
717
+ .map(([status, count]) => ({
718
+ id: status,
719
+ name: status,
720
+ count,
721
+ percentage: (count / total) * 100,
722
+ cssClass: 'dot-' + status.toLowerCase(),
723
+ }));
724
+ }
725
+ sortRuns(runs) {
726
+ const dir = this.SortDirection === 'asc' ? 1 : -1;
727
+ return [...runs].sort((a, b) => {
728
+ const aVal = a[this.SortField];
729
+ const bVal = b[this.SortField];
730
+ if (aVal == null && bVal == null)
731
+ return 0;
732
+ if (aVal == null)
733
+ return 1;
734
+ if (bVal == null)
735
+ return -1;
736
+ if (typeof aVal === 'string' && typeof bVal === 'string') {
737
+ return aVal.localeCompare(bVal) * dir;
738
+ }
739
+ if (typeof aVal === 'number' && typeof bVal === 'number') {
740
+ return (aVal - bVal) * dir;
741
+ }
742
+ return String(aVal).localeCompare(String(bVal)) * dir;
743
+ });
744
+ }
745
+ getTimeRangeCutoff(range) {
746
+ const now = new Date();
747
+ switch (range) {
748
+ case '1h': return new Date(now.getTime() - 60 * 60 * 1000);
749
+ case '6h': return new Date(now.getTime() - 6 * 60 * 60 * 1000);
750
+ case '24h': return new Date(now.getTime() - 24 * 60 * 60 * 1000);
751
+ case '7d': return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
752
+ case '30d': return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
753
+ default: return null;
754
+ }
755
+ }
756
+ getBucketCount() {
757
+ switch (this._timeRange) {
758
+ case '1h': return 12; // 5-min buckets
759
+ case '6h': return 12; // 30-min buckets
760
+ case '24h': return 24; // 1-hour buckets
761
+ case '7d': return 14; // 12-hour buckets
762
+ case '30d': return 30; // 1-day buckets
763
+ default: return 24;
764
+ }
765
+ }
766
+ formatBucketLabel(date) {
767
+ switch (this._timeRange) {
768
+ case '1h':
769
+ case '6h':
770
+ return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
771
+ case '24h':
772
+ return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
773
+ case '7d':
774
+ case '30d':
775
+ return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
776
+ default:
777
+ return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
778
+ }
779
+ }
780
+ sumNullable(runs, getter) {
781
+ return runs.reduce((sum, r) => sum + (getter(r) ?? 0), 0);
782
+ }
783
+ collectNonNull(runs, getter) {
784
+ const result = [];
785
+ for (const r of runs) {
786
+ const val = getter(r);
787
+ if (val != null) {
788
+ result.push(val);
789
+ }
790
+ }
791
+ return result;
792
+ }
793
+ percentile(values, pct) {
794
+ if (values.length === 0)
795
+ return 0;
796
+ const sorted = [...values].sort((a, b) => a - b);
797
+ const index = Math.ceil((pct / 100) * sorted.length) - 1;
798
+ return sorted[Math.max(0, index)];
799
+ }
800
+ resetPagination() {
801
+ this.CurrentPage = 1;
802
+ }
803
+ static ɵfac = function AnalyticsPromptRunsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AnalyticsPromptRunsComponent)(); };
804
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AnalyticsPromptRunsComponent, selectors: [["app-analytics-prompt-runs"]], inputs: { TimeRange: "TimeRange", Filters: "Filters" }, outputs: { TimeRangeChange: "TimeRangeChange", FiltersChange: "FiltersChange" }, standalone: false, decls: 3, vars: 5, consts: [[3, "TimeRangeChange", "FiltersChange", "ExportClicked", "TimeRange", "Filters", "ShowExportButton", "ShowCompareToggle"], [1, "loading-container"], ["text", "Loading prompt runs..."], [1, "stats-grid"], [1, "stat-card", "accent-brand"], [1, "stat-label"], [1, "stat-value"], [1, "stat-card"], [1, "stat-card", "accent-success"], [1, "stat-card", "accent-warning"], [1, "chart-panel"], [1, "chart-header"], [1, "chart-title"], [1, "chart-toggles"], [1, "toggle-chip", 3, "active"], [1, "chart-area"], [1, "chart-empty"], [1, "chart-bars"], [1, "breakdown-grid"], [1, "breakdown-card"], [1, "breakdown-title"], [1, "breakdown-row"], [1, "breakdown-empty"], [1, "table-panel"], [1, "table-header"], [1, "table-title"], [1, "table-count"], [1, "table-scroll"], [1, "runs-table"], [3, "sortable", "sorted"], [3, "row-even"], [1, "pagination"], [1, "toggle-chip", 3, "click"], [1, "chart-bar-wrapper", 3, "title"], [1, "chart-bar-wrapper", 3, "click", "title"], [1, "chart-bar-value"], [1, "chart-bar"], [1, "chart-bar-label"], [1, "breakdown-row", 3, "click"], [1, "breakdown-name"], [1, "breakdown-count"], [1, "breakdown-bar-track"], [1, "breakdown-bar-fill"], [1, "status-dot"], [3, "click"], [3, "class"], [1, "cell-timestamp"], [1, "cell-prompt"], [1, "model-tag"], [1, "status-pill"], [1, "cell-number"], ["colspan", "7", 1, "empty-row"], [1, "page-btn", 3, "click", "disabled"], [1, "fa-solid", "fa-chevron-left"], [1, "page-info"], [1, "fa-solid", "fa-chevron-right"]], template: function AnalyticsPromptRunsComponent_Template(rf, ctx) { if (rf & 1) {
805
+ i0.ɵɵelementStart(0, "app-analytics-filter-bar", 0);
806
+ i0.ɵɵlistener("TimeRangeChange", function AnalyticsPromptRunsComponent_Template_app_analytics_filter_bar_TimeRangeChange_0_listener($event) { return ctx.OnTimeRangeChange($event); })("FiltersChange", function AnalyticsPromptRunsComponent_Template_app_analytics_filter_bar_FiltersChange_0_listener($event) { return ctx.OnFiltersChange($event); })("ExportClicked", function AnalyticsPromptRunsComponent_Template_app_analytics_filter_bar_ExportClicked_0_listener() { return ctx.ExportCSV(); });
807
+ i0.ɵɵelementEnd();
808
+ i0.ɵɵconditionalCreate(1, AnalyticsPromptRunsComponent_Conditional_1_Template, 2, 0, "div", 1)(2, AnalyticsPromptRunsComponent_Conditional_2_Template, 88, 30);
809
+ } if (rf & 2) {
810
+ i0.ɵɵproperty("TimeRange", ctx.TimeRange)("Filters", ctx.Filters)("ShowExportButton", true)("ShowCompareToggle", false);
811
+ i0.ɵɵadvance();
812
+ i0.ɵɵconditional(ctx.IsLoading ? 1 : 2);
813
+ } }, dependencies: [i1.LoadingComponent, i2.AnalyticsFilterBarComponent, i3.DecimalPipe], styles: ["[_nghost-%COMP%] {\n display: block;\n }\n\n .loading-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 80px 0;\n }\n\n \n\n\n .stats-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 12px;\n margin-top: 16px;\n }\n\n .stat-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 14px 16px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .stat-card.accent-brand[_ngcontent-%COMP%] {\n border-left: 3px solid var(--mj-brand-primary);\n }\n\n .stat-card.accent-success[_ngcontent-%COMP%] {\n border-left: 3px solid var(--mj-status-success);\n }\n\n .stat-card.accent-warning[_ngcontent-%COMP%] {\n border-left: 3px solid var(--mj-status-warning);\n }\n\n .stat-label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n }\n\n .stat-value[_ngcontent-%COMP%] {\n font-size: 20px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n\n \n\n\n .chart-panel[_ngcontent-%COMP%] {\n margin-top: 16px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n }\n\n .chart-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n }\n\n .chart-title[_ngcontent-%COMP%] {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .chart-toggles[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n }\n\n .toggle-chip[_ngcontent-%COMP%] {\n padding: 4px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 16px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s, color 0.15s, border-color 0.15s;\n }\n\n .toggle-chip[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .toggle-chip.active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n font-weight: 600;\n }\n\n .chart-area[_ngcontent-%COMP%] {\n height: 220px;\n display: flex;\n align-items: flex-end;\n }\n\n .chart-empty[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n color: var(--mj-text-muted);\n font-size: 13px;\n }\n\n .chart-bars[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-end;\n gap: 4px;\n width: 100%;\n height: 100%;\n padding-bottom: 24px;\n }\n\n .chart-bar-wrapper[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n cursor: pointer;\n position: relative;\n height: 100%;\n justify-content: flex-end;\n }\n\n .chart-bar-value[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-muted);\n margin-bottom: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n }\n\n .chart-bar[_ngcontent-%COMP%] {\n width: 80%;\n min-height: 2px;\n background: color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-bg-surface));\n border-radius: 4px 4px 0 0;\n transition: background 0.15s, height 0.3s;\n }\n\n .chart-bar-wrapper[_ngcontent-%COMP%]:hover .chart-bar[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 50%, var(--mj-bg-surface));\n }\n\n .chart-bar-label[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-muted);\n margin-top: 6px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n text-align: center;\n position: absolute;\n bottom: 0;\n }\n\n \n\n\n .breakdown-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n margin-top: 16px;\n }\n\n .breakdown-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 16px;\n }\n\n .breakdown-title[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 12px;\n }\n\n .breakdown-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 0;\n cursor: pointer;\n border-radius: 4px;\n transition: background 0.1s;\n }\n\n .breakdown-row[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .breakdown-name[_ngcontent-%COMP%] {\n flex: 1;\n font-size: 13px;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n }\n\n .breakdown-count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-secondary);\n font-weight: 600;\n white-space: nowrap;\n }\n\n .breakdown-bar-track[_ngcontent-%COMP%] {\n width: 60px;\n height: 6px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 3px;\n overflow: hidden;\n flex-shrink: 0;\n }\n\n .breakdown-bar-fill[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 3px;\n transition: width 0.3s;\n }\n\n .breakdown-empty[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-muted);\n padding: 8px 0;\n }\n\n .status-dot[_ngcontent-%COMP%] {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n\n .status-dot.dot-completed[_ngcontent-%COMP%] { background: var(--mj-status-success); }\n .status-dot.dot-failed[_ngcontent-%COMP%] { background: var(--mj-status-error); }\n .status-dot.dot-running[_ngcontent-%COMP%] { background: var(--mj-brand-primary); }\n .status-dot.dot-pending[_ngcontent-%COMP%] { background: var(--mj-status-warning); }\n .status-dot.dot-cancelled[_ngcontent-%COMP%] { background: var(--mj-text-muted); }\n\n \n\n\n .table-panel[_ngcontent-%COMP%] {\n margin-top: 16px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n overflow: hidden;\n }\n\n .table-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .table-title[_ngcontent-%COMP%] {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .table-count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n font-weight: 500;\n }\n\n .table-scroll[_ngcontent-%COMP%] {\n overflow-x: auto;\n }\n\n .runs-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .runs-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n padding: 10px 14px;\n text-align: left;\n font-weight: 600;\n font-size: 12px;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n white-space: nowrap;\n user-select: none;\n }\n\n .runs-table[_ngcontent-%COMP%] th.sortable[_ngcontent-%COMP%] {\n cursor: pointer;\n }\n\n .runs-table[_ngcontent-%COMP%] th.sortable[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n }\n\n .runs-table[_ngcontent-%COMP%] th.sorted[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n }\n\n .runs-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n margin-left: 4px;\n font-size: 10px;\n }\n\n .runs-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 10px 14px;\n color: var(--mj-text-primary);\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .runs-table[_ngcontent-%COMP%] tr.row-even[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n }\n\n .runs-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .cell-timestamp[_ngcontent-%COMP%] {\n white-space: nowrap;\n color: var(--mj-text-secondary);\n font-size: 12px;\n }\n\n .cell-prompt[_ngcontent-%COMP%] {\n font-weight: 600;\n max-width: 220px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .cell-number[_ngcontent-%COMP%] {\n text-align: right;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n .model-tag[_ngcontent-%COMP%] {\n display: inline-flex;\n padding: 2px 8px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n font-size: 12px;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n }\n\n .status-pill[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 2px 10px;\n border-radius: 10px;\n font-size: 12px;\n font-weight: 600;\n white-space: nowrap;\n }\n\n .pill-completed[_ngcontent-%COMP%] {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n }\n\n .pill-failed[_ngcontent-%COMP%] {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n }\n\n .pill-running[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n }\n\n .pill-pending[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n }\n\n .pill-cancelled[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n }\n\n .empty-row[_ngcontent-%COMP%] {\n text-align: center;\n color: var(--mj-text-muted);\n padding: 24px 14px;\n }\n\n \n\n\n .pagination[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 12px 20px;\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .page-btn[_ngcontent-%COMP%] {\n padding: 6px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 13px;\n transition: background 0.15s, color 0.15s;\n }\n\n .page-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .page-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.4;\n cursor: default;\n }\n\n .page-info[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-secondary);\n }\n\n \n\n\n @media (max-width: 1200px) {\n .breakdown-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n }\n\n @media (max-width: 768px) {\n .stats-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .breakdown-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .chart-header[_ngcontent-%COMP%] {\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n }"] });
814
+ }
815
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AnalyticsPromptRunsComponent, [{
816
+ type: Component,
817
+ args: [{ standalone: false, selector: 'app-analytics-prompt-runs', template: `
818
+ <!-- Filter Bar -->
819
+ <app-analytics-filter-bar
820
+ [TimeRange]="TimeRange"
821
+ [Filters]="Filters"
822
+ [ShowExportButton]="true"
823
+ [ShowCompareToggle]="false"
824
+ (TimeRangeChange)="OnTimeRangeChange($event)"
825
+ (FiltersChange)="OnFiltersChange($event)"
826
+ (ExportClicked)="ExportCSV()"
827
+ ></app-analytics-filter-bar>
828
+
829
+ @if (IsLoading) {
830
+ <div class="loading-container">
831
+ <mj-loading text="Loading prompt runs..."></mj-loading>
832
+ </div>
833
+ } @else {
834
+ <!-- Stats Summary Bar -->
835
+ <div class="stats-grid">
836
+ <div class="stat-card accent-brand">
837
+ <div class="stat-label">Total Runs</div>
838
+ <div class="stat-value">{{ Stats.TotalRuns | number }}</div>
839
+ </div>
840
+ <div class="stat-card">
841
+ <div class="stat-label">Avg Cost</div>
842
+ <div class="stat-value">{{ FormatCurrency(Stats.AvgCost, 4) }}</div>
843
+ </div>
844
+ <div class="stat-card">
845
+ <div class="stat-label">Avg Tokens</div>
846
+ <div class="stat-value">{{ Stats.AvgTokens | number:'1.0-0' }}</div>
847
+ </div>
848
+ <div class="stat-card">
849
+ <div class="stat-label">Avg Latency</div>
850
+ <div class="stat-value">{{ Stats.AvgLatencySeconds | number:'1.2-2' }}s</div>
851
+ </div>
852
+ <div class="stat-card accent-success">
853
+ <div class="stat-label">Success Rate</div>
854
+ <div class="stat-value">{{ Stats.SuccessRate | number:'1.1-1' }}%</div>
855
+ </div>
856
+ <div class="stat-card accent-warning">
857
+ <div class="stat-label">P95 Latency</div>
858
+ <div class="stat-value">{{ Stats.P95LatencySeconds | number:'1.2-2' }}s</div>
859
+ </div>
860
+ <div class="stat-card">
861
+ <div class="stat-label">Total Cost</div>
862
+ <div class="stat-value">{{ FormatCurrency(Stats.TotalCost, 2) }}</div>
863
+ </div>
864
+ </div>
865
+
866
+ <!-- Chart Panel -->
867
+ <div class="chart-panel">
868
+ <div class="chart-header">
869
+ <h3 class="chart-title">Runs Over Time</h3>
870
+ <div class="chart-toggles">
871
+ @for (metric of ChartMetricOptions; track metric.key) {
872
+ <button
873
+ class="toggle-chip"
874
+ [class.active]="ActiveChartMetric === metric.key"
875
+ (click)="OnChartMetricChange(metric.key)">
876
+ {{ metric.label }}
877
+ </button>
878
+ }
879
+ </div>
880
+ </div>
881
+ <div class="chart-area">
882
+ @if (ChartBuckets.length === 0) {
883
+ <div class="chart-empty">No data for selected time range</div>
884
+ } @else {
885
+ <div class="chart-bars">
886
+ @for (bucket of ChartBuckets; track bucket.label) {
887
+ <div
888
+ class="chart-bar-wrapper"
889
+ [title]="bucket.label + ': ' + bucket.value"
890
+ (click)="OnChartBucketClick(bucket)">
891
+ <div class="chart-bar-value">{{ FormatChartValue(bucket.value) }}</div>
892
+ <div class="chart-bar" [style.height.%]="bucket.heightPercent"></div>
893
+ <div class="chart-bar-label">{{ bucket.label }}</div>
894
+ </div>
895
+ }
896
+ </div>
897
+ }
898
+ </div>
899
+ </div>
900
+
901
+ <!-- Breakdown Cards -->
902
+ <div class="breakdown-grid">
903
+ <!-- By Model -->
904
+ <div class="breakdown-card">
905
+ <h4 class="breakdown-title">By Model</h4>
906
+ @for (item of ModelBreakdown; track item.id) {
907
+ <div class="breakdown-row" (click)="ApplyModelFilter(item.id)">
908
+ <span class="breakdown-name">{{ item.name }}</span>
909
+ <span class="breakdown-count">{{ item.count }}</span>
910
+ <div class="breakdown-bar-track">
911
+ <div class="breakdown-bar-fill" [style.width.%]="item.percentage"></div>
912
+ </div>
913
+ </div>
914
+ }
915
+ @if (ModelBreakdown.length === 0) {
916
+ <div class="breakdown-empty">No data</div>
917
+ }
918
+ </div>
919
+
920
+ <!-- By Prompt -->
921
+ <div class="breakdown-card">
922
+ <h4 class="breakdown-title">By Prompt</h4>
923
+ @for (item of PromptBreakdown; track item.id) {
924
+ <div class="breakdown-row" (click)="ApplyPromptFilter(item.id)">
925
+ <span class="breakdown-name">{{ item.name }}</span>
926
+ <span class="breakdown-count">{{ item.count }}</span>
927
+ <div class="breakdown-bar-track">
928
+ <div class="breakdown-bar-fill" [style.width.%]="item.percentage"></div>
929
+ </div>
930
+ </div>
931
+ }
932
+ @if (PromptBreakdown.length === 0) {
933
+ <div class="breakdown-empty">No data</div>
934
+ }
935
+ </div>
936
+
937
+ <!-- By Status -->
938
+ <div class="breakdown-card">
939
+ <h4 class="breakdown-title">By Status</h4>
940
+ @for (item of StatusBreakdown; track item.name) {
941
+ <div class="breakdown-row" (click)="ApplyStatusFilter(item.name)">
942
+ <span class="status-dot" [class]="item.cssClass"></span>
943
+ <span class="breakdown-name">{{ item.name }}</span>
944
+ <span class="breakdown-count">{{ item.count }} ({{ item.percentage | number:'1.1-1' }}%)</span>
945
+ </div>
946
+ }
947
+ @if (StatusBreakdown.length === 0) {
948
+ <div class="breakdown-empty">No data</div>
949
+ }
950
+ </div>
951
+ </div>
952
+
953
+ <!-- Run Details Table -->
954
+ <div class="table-panel">
955
+ <div class="table-header">
956
+ <h3 class="table-title">Run Details</h3>
957
+ <span class="table-count">{{ FilteredRuns.length | number }} runs</span>
958
+ </div>
959
+ <div class="table-scroll">
960
+ <table class="runs-table">
961
+ <thead>
962
+ <tr>
963
+ @for (col of TableColumns; track col.field) {
964
+ <th
965
+ [class.sortable]="col.sortable"
966
+ [class.sorted]="SortField === col.field"
967
+ (click)="col.sortable ? OnSortChange(col.field) : null">
968
+ {{ col.label }}
969
+ @if (col.sortable && SortField === col.field) {
970
+ <i [class]="SortDirection === 'asc' ? 'fa-solid fa-caret-up' : 'fa-solid fa-caret-down'"></i>
971
+ }
972
+ </th>
973
+ }
974
+ </tr>
975
+ </thead>
976
+ <tbody>
977
+ @for (run of PagedRuns; track run.ID; let i = $index) {
978
+ <tr [class.row-even]="i % 2 === 0">
979
+ <td class="cell-timestamp">{{ FormatTimestamp(run.RunAt) }}</td>
980
+ <td class="cell-prompt">{{ run.Prompt ?? '(unnamed)' }}</td>
981
+ <td><span class="model-tag">{{ run.Model ?? 'N/A' }}</span></td>
982
+ <td><span class="status-pill" [class]="GetStatusClass(run.Status)">{{ run.Status }}</span></td>
983
+ <td class="cell-number">{{ FormatDuration(run.ExecutionTimeMS) }}</td>
984
+ <td class="cell-number">{{ run.TokensUsed != null ? (run.TokensUsed | number) : '-' }}</td>
985
+ <td class="cell-number">{{ FormatCurrency(run.Cost, 4) }}</td>
986
+ </tr>
987
+ }
988
+ @if (PagedRuns.length === 0) {
989
+ <tr>
990
+ <td colspan="7" class="empty-row">No prompt runs found for the selected filters.</td>
991
+ </tr>
992
+ }
993
+ </tbody>
994
+ </table>
995
+ </div>
996
+
997
+ @if (TotalPages > 1) {
998
+ <div class="pagination">
999
+ <button
1000
+ class="page-btn"
1001
+ [disabled]="CurrentPage === 1"
1002
+ (click)="OnPageChange(CurrentPage - 1)">
1003
+ <i class="fa-solid fa-chevron-left"></i>
1004
+ </button>
1005
+ <span class="page-info">Page {{ CurrentPage }} of {{ TotalPages }}</span>
1006
+ <button
1007
+ class="page-btn"
1008
+ [disabled]="CurrentPage === TotalPages"
1009
+ (click)="OnPageChange(CurrentPage + 1)">
1010
+ <i class="fa-solid fa-chevron-right"></i>
1011
+ </button>
1012
+ </div>
1013
+ }
1014
+ </div>
1015
+ }
1016
+ `, styles: ["\n :host {\n display: block;\n }\n\n .loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 80px 0;\n }\n\n /* \u2500\u2500 Stats Grid \u2500\u2500 */\n\n .stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 12px;\n margin-top: 16px;\n }\n\n .stat-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 14px 16px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .stat-card.accent-brand {\n border-left: 3px solid var(--mj-brand-primary);\n }\n\n .stat-card.accent-success {\n border-left: 3px solid var(--mj-status-success);\n }\n\n .stat-card.accent-warning {\n border-left: 3px solid var(--mj-status-warning);\n }\n\n .stat-label {\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n }\n\n .stat-value {\n font-size: 20px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n\n /* \u2500\u2500 Chart Panel \u2500\u2500 */\n\n .chart-panel {\n margin-top: 16px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n }\n\n .chart-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n }\n\n .chart-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .chart-toggles {\n display: flex;\n gap: 4px;\n }\n\n .toggle-chip {\n padding: 4px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 16px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s, color 0.15s, border-color 0.15s;\n }\n\n .toggle-chip:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .toggle-chip.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n font-weight: 600;\n }\n\n .chart-area {\n height: 220px;\n display: flex;\n align-items: flex-end;\n }\n\n .chart-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n color: var(--mj-text-muted);\n font-size: 13px;\n }\n\n .chart-bars {\n display: flex;\n align-items: flex-end;\n gap: 4px;\n width: 100%;\n height: 100%;\n padding-bottom: 24px;\n }\n\n .chart-bar-wrapper {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n cursor: pointer;\n position: relative;\n height: 100%;\n justify-content: flex-end;\n }\n\n .chart-bar-value {\n font-size: 10px;\n color: var(--mj-text-muted);\n margin-bottom: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n }\n\n .chart-bar {\n width: 80%;\n min-height: 2px;\n background: color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-bg-surface));\n border-radius: 4px 4px 0 0;\n transition: background 0.15s, height 0.3s;\n }\n\n .chart-bar-wrapper:hover .chart-bar {\n background: color-mix(in srgb, var(--mj-brand-primary) 50%, var(--mj-bg-surface));\n }\n\n .chart-bar-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n margin-top: 6px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n text-align: center;\n position: absolute;\n bottom: 0;\n }\n\n /* \u2500\u2500 Breakdown Cards \u2500\u2500 */\n\n .breakdown-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n margin-top: 16px;\n }\n\n .breakdown-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 16px;\n }\n\n .breakdown-title {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 12px;\n }\n\n .breakdown-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 0;\n cursor: pointer;\n border-radius: 4px;\n transition: background 0.1s;\n }\n\n .breakdown-row:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .breakdown-name {\n flex: 1;\n font-size: 13px;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n }\n\n .breakdown-count {\n font-size: 12px;\n color: var(--mj-text-secondary);\n font-weight: 600;\n white-space: nowrap;\n }\n\n .breakdown-bar-track {\n width: 60px;\n height: 6px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 3px;\n overflow: hidden;\n flex-shrink: 0;\n }\n\n .breakdown-bar-fill {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 3px;\n transition: width 0.3s;\n }\n\n .breakdown-empty {\n font-size: 13px;\n color: var(--mj-text-muted);\n padding: 8px 0;\n }\n\n .status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n\n .status-dot.dot-completed { background: var(--mj-status-success); }\n .status-dot.dot-failed { background: var(--mj-status-error); }\n .status-dot.dot-running { background: var(--mj-brand-primary); }\n .status-dot.dot-pending { background: var(--mj-status-warning); }\n .status-dot.dot-cancelled { background: var(--mj-text-muted); }\n\n /* \u2500\u2500 Table Panel \u2500\u2500 */\n\n .table-panel {\n margin-top: 16px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n overflow: hidden;\n }\n\n .table-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .table-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .table-count {\n font-size: 12px;\n color: var(--mj-text-muted);\n font-weight: 500;\n }\n\n .table-scroll {\n overflow-x: auto;\n }\n\n .runs-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .runs-table th {\n padding: 10px 14px;\n text-align: left;\n font-weight: 600;\n font-size: 12px;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n white-space: nowrap;\n user-select: none;\n }\n\n .runs-table th.sortable {\n cursor: pointer;\n }\n\n .runs-table th.sortable:hover {\n color: var(--mj-text-primary);\n }\n\n .runs-table th.sorted {\n color: var(--mj-brand-primary);\n }\n\n .runs-table th i {\n margin-left: 4px;\n font-size: 10px;\n }\n\n .runs-table td {\n padding: 10px 14px;\n color: var(--mj-text-primary);\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .runs-table tr.row-even {\n background: var(--mj-bg-surface-card);\n }\n\n .runs-table tbody tr:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .cell-timestamp {\n white-space: nowrap;\n color: var(--mj-text-secondary);\n font-size: 12px;\n }\n\n .cell-prompt {\n font-weight: 600;\n max-width: 220px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .cell-number {\n text-align: right;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n .model-tag {\n display: inline-flex;\n padding: 2px 8px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n font-size: 12px;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n }\n\n .status-pill {\n display: inline-block;\n padding: 2px 10px;\n border-radius: 10px;\n font-size: 12px;\n font-weight: 600;\n white-space: nowrap;\n }\n\n .pill-completed {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n }\n\n .pill-failed {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n }\n\n .pill-running {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n }\n\n .pill-pending {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n }\n\n .pill-cancelled {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n }\n\n .empty-row {\n text-align: center;\n color: var(--mj-text-muted);\n padding: 24px 14px;\n }\n\n /* \u2500\u2500 Pagination \u2500\u2500 */\n\n .pagination {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 12px 20px;\n border-top: 1px solid var(--mj-border-subtle);\n }\n\n .page-btn {\n padding: 6px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 13px;\n transition: background 0.15s, color 0.15s;\n }\n\n .page-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .page-btn:disabled {\n opacity: 0.4;\n cursor: default;\n }\n\n .page-info {\n font-size: 13px;\n color: var(--mj-text-secondary);\n }\n\n /* \u2500\u2500 Responsive \u2500\u2500 */\n\n @media (max-width: 1200px) {\n .breakdown-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n }\n\n @media (max-width: 768px) {\n .stats-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .breakdown-grid {\n grid-template-columns: 1fr;\n }\n\n .chart-header {\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n }\n "] }]
1017
+ }], null, { TimeRange: [{
1018
+ type: Input
1019
+ }], Filters: [{
1020
+ type: Input
1021
+ }], TimeRangeChange: [{
1022
+ type: Output
1023
+ }], FiltersChange: [{
1024
+ type: Output
1025
+ }] }); })();
1026
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AnalyticsPromptRunsComponent, { className: "AnalyticsPromptRunsComponent", filePath: "src/AI/components/analytics/prompt-runs/prompt-run-analysis.component.ts", lineNumber: 774 }); })();
1027
+ export function LoadAnalyticsPromptRuns() {
1028
+ // Prevents tree-shaking of the component
1029
+ }
1030
+ //# sourceMappingURL=prompt-run-analysis.component.js.map