@memberjunction/ng-dashboards 5.25.0 → 5.27.0

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 +49 -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,537 @@
1
+ /**
2
+ * @fileoverview Model Performance Leaderboard.
3
+ *
4
+ * Ranks AI models by configurable metrics (Cost Efficiency, Speed, Reliability, Usage Volume).
5
+ * Data is loaded from MJ: AI Prompt Runs and augmented with model metadata from AIEngineBase.
6
+ */
7
+ import { Component, Input, Output, EventEmitter, ChangeDetectorRef, inject } from '@angular/core';
8
+ import { Subject } from 'rxjs';
9
+ import { RunView } from '@memberjunction/core';
10
+ import { UUIDsEqual } from '@memberjunction/global';
11
+ import { AIEngineBase } from '@memberjunction/ai-engine-base';
12
+ import * as i0 from "@angular/core";
13
+ import * as i1 from "@angular/forms";
14
+ import * as i2 from "@memberjunction/ng-shared-generic";
15
+ import * as i3 from "@angular/common";
16
+ const _forTrack0 = ($index, $item) => $item.value;
17
+ const _forTrack1 = ($index, $item) => $item.id;
18
+ const _forTrack2 = ($index, $item) => $item.ModelName;
19
+ function AnalyticsModelPerformanceComponent_For_7_Template(rf, ctx) { if (rf & 1) {
20
+ i0.ɵɵelementStart(0, "option", 5);
21
+ i0.ɵɵtext(1);
22
+ i0.ɵɵelementEnd();
23
+ } if (rf & 2) {
24
+ const opt_r1 = ctx.$implicit;
25
+ i0.ɵɵproperty("value", opt_r1.value);
26
+ i0.ɵɵadvance();
27
+ i0.ɵɵtextInterpolate(opt_r1.label);
28
+ } }
29
+ function AnalyticsModelPerformanceComponent_For_12_Template(rf, ctx) { if (rf & 1) {
30
+ i0.ɵɵelementStart(0, "option", 5);
31
+ i0.ɵɵtext(1);
32
+ i0.ɵɵelementEnd();
33
+ } if (rf & 2) {
34
+ const v_r2 = ctx.$implicit;
35
+ i0.ɵɵproperty("value", v_r2.id);
36
+ i0.ɵɵadvance();
37
+ i0.ɵɵtextInterpolate(v_r2.name);
38
+ } }
39
+ function AnalyticsModelPerformanceComponent_For_15_Template(rf, ctx) { if (rf & 1) {
40
+ const _r3 = i0.ɵɵgetCurrentView();
41
+ i0.ɵɵelementStart(0, "button", 11);
42
+ i0.ɵɵlistener("click", function AnalyticsModelPerformanceComponent_For_15_Template_button_click_0_listener() { const opt_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r4 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r4.OnTimeRangeSelect(opt_r4)); });
43
+ i0.ɵɵtext(1);
44
+ i0.ɵɵelementEnd();
45
+ } if (rf & 2) {
46
+ const opt_r4 = ctx.$implicit;
47
+ const ctx_r4 = i0.ɵɵnextContext();
48
+ i0.ɵɵclassProp("active", ctx_r4.TimeRange === opt_r4);
49
+ i0.ɵɵadvance();
50
+ i0.ɵɵtextInterpolate1(" ", opt_r4, " ");
51
+ } }
52
+ function AnalyticsModelPerformanceComponent_Conditional_16_Template(rf, ctx) { if (rf & 1) {
53
+ i0.ɵɵelementStart(0, "div", 9);
54
+ i0.ɵɵelement(1, "mj-loading", 12);
55
+ i0.ɵɵelementEnd();
56
+ } }
57
+ function AnalyticsModelPerformanceComponent_Conditional_17_Conditional_24_Template(rf, ctx) { if (rf & 1) {
58
+ i0.ɵɵelementStart(0, "tr")(1, "td", 19);
59
+ i0.ɵɵtext(2, "No model data for selected period");
60
+ i0.ɵɵelementEnd()();
61
+ } }
62
+ function AnalyticsModelPerformanceComponent_Conditional_17_For_26_Conditional_7_Template(rf, ctx) { if (rf & 1) {
63
+ i0.ɵɵelementStart(0, "div", 23);
64
+ i0.ɵɵtext(1);
65
+ i0.ɵɵelementEnd();
66
+ } if (rf & 2) {
67
+ const row_r6 = i0.ɵɵnextContext().$implicit;
68
+ i0.ɵɵadvance();
69
+ i0.ɵɵtextInterpolate(row_r6.ApiId);
70
+ } }
71
+ function AnalyticsModelPerformanceComponent_Conditional_17_For_26_Template(rf, ctx) { if (rf & 1) {
72
+ i0.ɵɵelementStart(0, "tr")(1, "td")(2, "span", 20);
73
+ i0.ɵɵtext(3);
74
+ i0.ɵɵelementEnd()();
75
+ i0.ɵɵelementStart(4, "td", 21)(5, "div", 22);
76
+ i0.ɵɵtext(6);
77
+ i0.ɵɵelementEnd();
78
+ i0.ɵɵconditionalCreate(7, AnalyticsModelPerformanceComponent_Conditional_17_For_26_Conditional_7_Template, 2, 1, "div", 23);
79
+ i0.ɵɵelementEnd();
80
+ i0.ɵɵelementStart(8, "td", 24);
81
+ i0.ɵɵtext(9);
82
+ i0.ɵɵelementEnd();
83
+ i0.ɵɵelementStart(10, "td", 25);
84
+ i0.ɵɵtext(11);
85
+ i0.ɵɵpipe(12, "number");
86
+ i0.ɵɵelementEnd();
87
+ i0.ɵɵelementStart(13, "td", 25)(14, "span");
88
+ i0.ɵɵtext(15);
89
+ i0.ɵɵelementEnd()();
90
+ i0.ɵɵelementStart(16, "td", 25);
91
+ i0.ɵɵtext(17);
92
+ i0.ɵɵelementEnd();
93
+ i0.ɵɵelementStart(18, "td", 25)(19, "div", 26)(20, "span", 27);
94
+ i0.ɵɵtext(21);
95
+ i0.ɵɵpipe(22, "number");
96
+ i0.ɵɵelementEnd();
97
+ i0.ɵɵelementStart(23, "div", 28);
98
+ i0.ɵɵelement(24, "div", 29);
99
+ i0.ɵɵelementEnd()()();
100
+ i0.ɵɵelementStart(25, "td", 25);
101
+ i0.ɵɵtext(26);
102
+ i0.ɵɵelementEnd();
103
+ i0.ɵɵelementStart(27, "td", 30);
104
+ i0.ɵɵtext(28);
105
+ i0.ɵɵelementEnd()();
106
+ } if (rf & 2) {
107
+ const row_r6 = ctx.$implicit;
108
+ const ctx_r4 = i0.ɵɵnextContext(2);
109
+ i0.ɵɵadvance(2);
110
+ i0.ɵɵclassMap(row_r6.RankClass);
111
+ i0.ɵɵadvance();
112
+ i0.ɵɵtextInterpolate1(" ", row_r6.Rank, " ");
113
+ i0.ɵɵadvance(3);
114
+ i0.ɵɵtextInterpolate(row_r6.ModelName);
115
+ i0.ɵɵadvance();
116
+ i0.ɵɵconditional(row_r6.ApiId && row_r6.ApiId !== row_r6.ModelName ? 7 : -1);
117
+ i0.ɵɵadvance(2);
118
+ i0.ɵɵtextInterpolate(row_r6.Vendor);
119
+ i0.ɵɵadvance(2);
120
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(12, 16, row_r6.Runs));
121
+ i0.ɵɵadvance(3);
122
+ i0.ɵɵstyleProp("color", row_r6.AvgLatencyColor);
123
+ i0.ɵɵadvance();
124
+ i0.ɵɵtextInterpolate1(" ", ctx_r4.FormatLatency(row_r6.AvgLatencyMs), " ");
125
+ i0.ɵɵadvance(2);
126
+ i0.ɵɵtextInterpolate(ctx_r4.FormatLatency(row_r6.P95LatencyMs));
127
+ i0.ɵɵadvance(4);
128
+ i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind2(22, 18, row_r6.SuccessRate, "1.1-1"), "%");
129
+ i0.ɵɵadvance(3);
130
+ i0.ɵɵstyleProp("width", row_r6.SuccessRate, "%");
131
+ i0.ɵɵadvance(2);
132
+ i0.ɵɵtextInterpolate(ctx_r4.FormatCurrency(row_r6.CostPer1KTokens, 4));
133
+ i0.ɵɵadvance(2);
134
+ i0.ɵɵtextInterpolate(ctx_r4.FormatCurrency(row_r6.TotalCost));
135
+ } }
136
+ function AnalyticsModelPerformanceComponent_Conditional_17_Template(rf, ctx) { if (rf & 1) {
137
+ i0.ɵɵelementStart(0, "div", 10)(1, "div", 13)(2, "table", 14)(3, "thead")(4, "tr")(5, "th", 15);
138
+ i0.ɵɵtext(6, "Rank");
139
+ i0.ɵɵelementEnd();
140
+ i0.ɵɵelementStart(7, "th", 16);
141
+ i0.ɵɵtext(8, "Model");
142
+ i0.ɵɵelementEnd();
143
+ i0.ɵɵelementStart(9, "th", 17);
144
+ i0.ɵɵtext(10, "Vendor");
145
+ i0.ɵɵelementEnd();
146
+ i0.ɵɵelementStart(11, "th", 18);
147
+ i0.ɵɵtext(12, "Runs");
148
+ i0.ɵɵelementEnd();
149
+ i0.ɵɵelementStart(13, "th", 18);
150
+ i0.ɵɵtext(14, "Avg Latency");
151
+ i0.ɵɵelementEnd();
152
+ i0.ɵɵelementStart(15, "th", 18);
153
+ i0.ɵɵtext(16, "P95 Latency");
154
+ i0.ɵɵelementEnd();
155
+ i0.ɵɵelementStart(17, "th", 18);
156
+ i0.ɵɵtext(18, "Success Rate");
157
+ i0.ɵɵelementEnd();
158
+ i0.ɵɵelementStart(19, "th", 18);
159
+ i0.ɵɵtext(20, "$/1K Tokens");
160
+ i0.ɵɵelementEnd();
161
+ i0.ɵɵelementStart(21, "th", 18);
162
+ i0.ɵɵtext(22, "Total Cost");
163
+ i0.ɵɵelementEnd()()();
164
+ i0.ɵɵelementStart(23, "tbody");
165
+ i0.ɵɵconditionalCreate(24, AnalyticsModelPerformanceComponent_Conditional_17_Conditional_24_Template, 3, 0, "tr");
166
+ i0.ɵɵrepeaterCreate(25, AnalyticsModelPerformanceComponent_Conditional_17_For_26_Template, 29, 21, "tr", null, _forTrack2);
167
+ i0.ɵɵelementEnd()()()();
168
+ } if (rf & 2) {
169
+ const ctx_r4 = i0.ɵɵnextContext();
170
+ i0.ɵɵadvance(24);
171
+ i0.ɵɵconditional(ctx_r4.Rows.length === 0 ? 24 : -1);
172
+ i0.ɵɵadvance();
173
+ i0.ɵɵrepeater(ctx_r4.Rows);
174
+ } }
175
+ const FIELDS = [
176
+ 'ID', 'RunAt', 'CompletedAt', 'ExecutionTimeMS', 'Success',
177
+ 'Cost', 'TotalCost', 'TokensUsed', 'TokensPrompt', 'TokensCompletion',
178
+ 'ModelID', 'Model', 'VendorID', 'Vendor'
179
+ ];
180
+ export class AnalyticsModelPerformanceComponent {
181
+ TimeRange = '7d';
182
+ TimeRangeChange = new EventEmitter();
183
+ cdr = inject(ChangeDetectorRef);
184
+ destroy$ = new Subject();
185
+ IsLoading = false;
186
+ SortBy = 'cost-efficiency';
187
+ SelectedVendor = '';
188
+ Rows = [];
189
+ TimeRangeOptions = ['24h', '7d', '30d'];
190
+ SortByOptions = [
191
+ { value: 'cost-efficiency', label: 'Cost Efficiency' },
192
+ { value: 'speed', label: 'Speed' },
193
+ { value: 'reliability', label: 'Reliability' },
194
+ { value: 'usage-volume', label: 'Usage Volume' }
195
+ ];
196
+ VendorOptions = [];
197
+ allRuns = [];
198
+ ngOnInit() {
199
+ this.loadVendorOptions();
200
+ this.LoadData();
201
+ }
202
+ ngOnDestroy() {
203
+ this.destroy$.next();
204
+ this.destroy$.complete();
205
+ }
206
+ // ── Public Handlers ──
207
+ OnTimeRangeSelect(range) {
208
+ this.TimeRange = range;
209
+ this.TimeRangeChange.emit(range);
210
+ this.LoadData();
211
+ }
212
+ OnSortByChange(event) {
213
+ this.SortBy = event.target.value;
214
+ this.buildRows();
215
+ this.cdr.detectChanges();
216
+ }
217
+ OnVendorChange(event) {
218
+ this.SelectedVendor = event.target.value;
219
+ this.buildRows();
220
+ this.cdr.detectChanges();
221
+ }
222
+ FormatLatency(ms) {
223
+ if (ms < 1000)
224
+ return Math.round(ms) + 'ms';
225
+ return (ms / 1000).toFixed(2) + 's';
226
+ }
227
+ FormatCurrency(value, decimals = 2) {
228
+ if (value === 0)
229
+ return '$0.00';
230
+ if (value < 0.01 && decimals < 4)
231
+ decimals = 4;
232
+ return '$' + value.toFixed(decimals);
233
+ }
234
+ // ── Data Loading ──
235
+ loadVendorOptions() {
236
+ try {
237
+ this.VendorOptions = AIEngineBase.Instance.Vendors.map(v => ({
238
+ id: v.ID,
239
+ name: v.Name
240
+ }));
241
+ }
242
+ catch {
243
+ this.VendorOptions = [];
244
+ }
245
+ }
246
+ async LoadData() {
247
+ this.IsLoading = true;
248
+ this.cdr.detectChanges();
249
+ try {
250
+ const rv = new RunView();
251
+ const dateFilter = this.buildDateFilter();
252
+ const result = await rv.RunView({
253
+ EntityName: 'MJ: AI Prompt Runs',
254
+ ExtraFilter: dateFilter,
255
+ Fields: FIELDS,
256
+ OrderBy: 'RunAt DESC',
257
+ ResultType: 'simple'
258
+ });
259
+ this.allRuns = (result?.Results ?? []);
260
+ this.buildRows();
261
+ }
262
+ catch (e) {
263
+ console.error('Model Performance load error:', e);
264
+ }
265
+ finally {
266
+ this.IsLoading = false;
267
+ this.cdr.detectChanges();
268
+ }
269
+ }
270
+ // ── Row Building ──
271
+ buildRows() {
272
+ // Filter by vendor if needed
273
+ let runs = this.allRuns;
274
+ if (this.SelectedVendor) {
275
+ runs = runs.filter(r => r.VendorID === this.SelectedVendor);
276
+ }
277
+ // Group by model
278
+ const groups = new Map();
279
+ for (const run of runs) {
280
+ const key = run.ModelID ?? 'unknown';
281
+ if (!groups.has(key))
282
+ groups.set(key, []);
283
+ groups.get(key).push(run);
284
+ }
285
+ // Compute metrics per model
286
+ const modelRows = [];
287
+ for (const [modelId, modelRuns] of groups) {
288
+ const row = this.computeModelMetrics(modelId, modelRuns);
289
+ modelRows.push(row);
290
+ }
291
+ // Sort by chosen metric
292
+ this.sortRows(modelRows);
293
+ // Assign ranks
294
+ modelRows.forEach((row, i) => {
295
+ row.Rank = i + 1;
296
+ row.RankClass = this.getRankClass(i + 1);
297
+ });
298
+ this.Rows = modelRows;
299
+ }
300
+ computeModelMetrics(modelId, runs) {
301
+ const totalRuns = runs.length;
302
+ const successCount = runs.filter(r => r.Success).length;
303
+ const successRate = totalRuns > 0 ? (successCount / totalRuns) * 100 : 0;
304
+ const latencies = runs
305
+ .filter(r => r.ExecutionTimeMS != null && r.ExecutionTimeMS > 0)
306
+ .map(r => r.ExecutionTimeMS);
307
+ const avgLatency = latencies.length > 0
308
+ ? latencies.reduce((s, l) => s + l, 0) / latencies.length
309
+ : 0;
310
+ const p95Latency = this.computePercentile(latencies, 0.95);
311
+ const totalTokens = runs.reduce((s, r) => s + (r.TokensUsed ?? 0), 0);
312
+ const totalCost = runs.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);
313
+ const costPer1K = totalTokens > 0 ? (totalCost / totalTokens) * 1000 : 0;
314
+ const firstName = runs.find(r => r.Model != null);
315
+ const firstVendor = runs.find(r => r.Vendor != null);
316
+ // Look up model API name from engine
317
+ let apiId = '';
318
+ try {
319
+ const model = AIEngineBase.Instance.Models.find(m => UUIDsEqual(m.ID, modelId));
320
+ apiId = model?.APIName ?? '';
321
+ }
322
+ catch {
323
+ // engine might not be ready
324
+ }
325
+ return {
326
+ Rank: 0,
327
+ RankClass: 'rank-neutral',
328
+ ModelName: firstName?.Model ?? 'Unknown',
329
+ ApiId: apiId,
330
+ Vendor: firstVendor?.Vendor ?? 'Unknown',
331
+ VendorID: firstVendor?.VendorID ?? '',
332
+ Runs: totalRuns,
333
+ AvgLatencyMs: avgLatency,
334
+ AvgLatencyColor: this.getLatencyColor(avgLatency),
335
+ P95LatencyMs: p95Latency,
336
+ SuccessRate: successRate,
337
+ CostPer1KTokens: costPer1K,
338
+ TotalCost: totalCost
339
+ };
340
+ }
341
+ sortRows(rows) {
342
+ switch (this.SortBy) {
343
+ case 'cost-efficiency':
344
+ rows.sort((a, b) => a.CostPer1KTokens - b.CostPer1KTokens);
345
+ break;
346
+ case 'speed':
347
+ rows.sort((a, b) => a.AvgLatencyMs - b.AvgLatencyMs);
348
+ break;
349
+ case 'reliability':
350
+ rows.sort((a, b) => b.SuccessRate - a.SuccessRate);
351
+ break;
352
+ case 'usage-volume':
353
+ rows.sort((a, b) => b.Runs - a.Runs);
354
+ break;
355
+ }
356
+ }
357
+ // ── Helpers ──
358
+ computePercentile(sortedValues, percentile) {
359
+ if (sortedValues.length === 0)
360
+ return 0;
361
+ const sorted = [...sortedValues].sort((a, b) => a - b);
362
+ const idx = Math.ceil(sorted.length * percentile) - 1;
363
+ return sorted[Math.max(0, idx)];
364
+ }
365
+ getRankClass(rank) {
366
+ if (rank === 1)
367
+ return 'rank-badge rank-gold';
368
+ if (rank === 2)
369
+ return 'rank-badge rank-silver';
370
+ if (rank === 3)
371
+ return 'rank-badge rank-bronze';
372
+ return 'rank-badge rank-neutral';
373
+ }
374
+ getLatencyColor(ms) {
375
+ if (ms < 1000)
376
+ return 'var(--mj-status-success)';
377
+ if (ms < 3000)
378
+ return 'var(--mj-status-warning)';
379
+ return 'var(--mj-status-error)';
380
+ }
381
+ buildDateFilter() {
382
+ const now = new Date();
383
+ const msMap = {
384
+ '24h': 86400000,
385
+ '7d': 604800000,
386
+ '30d': 2592000000
387
+ };
388
+ const ms = msMap[this.TimeRange] ?? 604800000;
389
+ const start = new Date(now.getTime() - ms);
390
+ return `RunAt >= '${start.toISOString()}'`;
391
+ }
392
+ static ɵfac = function AnalyticsModelPerformanceComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AnalyticsModelPerformanceComponent)(); };
393
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AnalyticsModelPerformanceComponent, selectors: [["app-analytics-model-performance"]], inputs: { TimeRange: "TimeRange" }, outputs: { TimeRangeChange: "TimeRangeChange" }, standalone: false, decls: 18, vars: 3, consts: [[1, "filter-bar"], [1, "filter-controls"], [1, "filter-label"], [1, "fa-solid", "fa-trophy"], [1, "filter-select", 3, "change", "value"], [3, "value"], ["value", ""], [1, "time-chips"], [1, "time-chip", 3, "active"], [1, "loading-container"], [1, "leaderboard-panel"], [1, "time-chip", 3, "click"], ["text", "Loading model performance..."], [1, "table-wrapper"], [1, "leaderboard-table"], [1, "col-rank"], [1, "col-model"], [1, "col-vendor"], [1, "col-numeric"], ["colspan", "9", 1, "empty-row"], [1, "rank-badge"], [1, "cell-model"], [1, "model-name"], [1, "model-api-id"], [1, "cell-vendor"], [1, "cell-numeric"], [1, "success-rate-cell"], [1, "success-rate-value"], [1, "success-bar-bg"], [1, "success-bar-fill"], [1, "cell-numeric", "cell-cost"]], template: function AnalyticsModelPerformanceComponent_Template(rf, ctx) { if (rf & 1) {
394
+ i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "span", 2);
395
+ i0.ɵɵelement(3, "i", 3);
396
+ i0.ɵɵtext(4, " Leaderboard ");
397
+ i0.ɵɵelementEnd();
398
+ i0.ɵɵelementStart(5, "select", 4);
399
+ i0.ɵɵlistener("change", function AnalyticsModelPerformanceComponent_Template_select_change_5_listener($event) { return ctx.OnSortByChange($event); });
400
+ i0.ɵɵrepeaterCreate(6, AnalyticsModelPerformanceComponent_For_7_Template, 2, 2, "option", 5, _forTrack0);
401
+ i0.ɵɵelementEnd();
402
+ i0.ɵɵelementStart(8, "select", 4);
403
+ i0.ɵɵlistener("change", function AnalyticsModelPerformanceComponent_Template_select_change_8_listener($event) { return ctx.OnVendorChange($event); });
404
+ i0.ɵɵelementStart(9, "option", 6);
405
+ i0.ɵɵtext(10, "All Vendors");
406
+ i0.ɵɵelementEnd();
407
+ i0.ɵɵrepeaterCreate(11, AnalyticsModelPerformanceComponent_For_12_Template, 2, 2, "option", 5, _forTrack1);
408
+ i0.ɵɵelementEnd()();
409
+ i0.ɵɵelementStart(13, "div", 7);
410
+ i0.ɵɵrepeaterCreate(14, AnalyticsModelPerformanceComponent_For_15_Template, 2, 3, "button", 8, i0.ɵɵrepeaterTrackByIdentity);
411
+ i0.ɵɵelementEnd()();
412
+ i0.ɵɵconditionalCreate(16, AnalyticsModelPerformanceComponent_Conditional_16_Template, 2, 0, "div", 9)(17, AnalyticsModelPerformanceComponent_Conditional_17_Template, 27, 1, "div", 10);
413
+ } if (rf & 2) {
414
+ i0.ɵɵadvance(5);
415
+ i0.ɵɵproperty("value", ctx.SortBy);
416
+ i0.ɵɵadvance();
417
+ i0.ɵɵrepeater(ctx.SortByOptions);
418
+ i0.ɵɵadvance(2);
419
+ i0.ɵɵproperty("value", ctx.SelectedVendor);
420
+ i0.ɵɵadvance(3);
421
+ i0.ɵɵrepeater(ctx.VendorOptions);
422
+ i0.ɵɵadvance(3);
423
+ i0.ɵɵrepeater(ctx.TimeRangeOptions);
424
+ i0.ɵɵadvance(2);
425
+ i0.ɵɵconditional(ctx.IsLoading ? 16 : 17);
426
+ } }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i2.LoadingComponent, i3.DecimalPipe], styles: ["[_nghost-%COMP%] { display: block; }\n\n .loading-container[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n \n\n .filter-bar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 12px 16px;\n flex-wrap: wrap;\n margin-bottom: 16px;\n }\n\n .filter-controls[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n }\n\n .filter-label[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .filter-label[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-status-warning);\n }\n\n .filter-select[_ngcontent-%COMP%] {\n padding: 5px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n cursor: pointer;\n min-width: 130px;\n outline: none;\n }\n\n .filter-select[_ngcontent-%COMP%]:focus {\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n\n .time-chips[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-left: auto;\n }\n\n .time-chip[_ngcontent-%COMP%] {\n padding: 5px 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 white-space: nowrap;\n }\n\n .time-chip[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .time-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 \n\n .leaderboard-panel[_ngcontent-%COMP%] {\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-wrapper[_ngcontent-%COMP%] {\n overflow-x: auto;\n }\n\n .leaderboard-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .leaderboard-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%], \n .leaderboard-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 12px 14px;\n text-align: left;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .leaderboard-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n background: var(--mj-bg-surface-card);\n position: sticky;\n top: 0;\n white-space: nowrap;\n }\n\n .col-rank[_ngcontent-%COMP%] { width: 60px; text-align: center; }\n .col-model[_ngcontent-%COMP%] { min-width: 180px; }\n .col-vendor[_ngcontent-%COMP%] { min-width: 100px; }\n .col-numeric[_ngcontent-%COMP%] { text-align: right; white-space: nowrap; }\n\n .leaderboard-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] {\n transition: background 0.15s;\n }\n\n .leaderboard-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .empty-row[_ngcontent-%COMP%] {\n text-align: center;\n color: var(--mj-text-disabled);\n padding: 32px;\n }\n\n \n\n .rank-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n font-size: 12px;\n font-weight: 700;\n }\n\n .rank-gold[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, color-mix(in srgb, var(--mj-status-warning) 35%, var(--mj-bg-surface)), color-mix(in srgb, var(--mj-status-warning) 20%, var(--mj-bg-surface)));\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-status-warning) 30%, transparent);\n }\n\n .rank-silver[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-text-muted) 18%, var(--mj-bg-surface));\n color: var(--mj-text-secondary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-text-muted) 20%, transparent);\n }\n\n .rank-bronze[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-warning) 18%, var(--mj-bg-surface));\n color: var(--mj-text-secondary);\n }\n\n .rank-neutral[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n }\n\n \n\n .cell-model[_ngcontent-%COMP%] {\n font-weight: 500;\n color: var(--mj-text-primary);\n }\n\n .model-name[_ngcontent-%COMP%] {\n font-weight: 600;\n }\n\n .model-api-id[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 2px;\n font-family: monospace;\n }\n\n .cell-vendor[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n }\n\n .cell-numeric[_ngcontent-%COMP%] {\n text-align: right;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-secondary);\n }\n\n .cell-cost[_ngcontent-%COMP%] {\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n \n\n .success-rate-cell[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 3px;\n }\n\n .success-rate-value[_ngcontent-%COMP%] {\n font-weight: 600;\n }\n\n .success-bar-bg[_ngcontent-%COMP%] {\n width: 60px;\n height: 4px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 2px;\n overflow: hidden;\n }\n\n .success-bar-fill[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-status-success);\n border-radius: 2px;\n transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n \n\n @media (max-width: 1200px) {\n .leaderboard-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%], \n .leaderboard-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 10px 10px;\n }\n }\n\n @media (max-width: 768px) {\n .filter-bar[_ngcontent-%COMP%] {\n flex-direction: column;\n align-items: stretch;\n }\n\n .time-chips[_ngcontent-%COMP%] {\n margin-left: 0;\n justify-content: flex-start;\n flex-wrap: wrap;\n }\n }"] });
427
+ }
428
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AnalyticsModelPerformanceComponent, [{
429
+ type: Component,
430
+ args: [{ standalone: false, selector: 'app-analytics-model-performance', template: `
431
+ <!-- Filter Bar -->
432
+ <div class="filter-bar">
433
+ <div class="filter-controls">
434
+ <span class="filter-label">
435
+ <i class="fa-solid fa-trophy"></i>
436
+ Leaderboard
437
+ </span>
438
+
439
+ <select class="filter-select" [value]="SortBy" (change)="OnSortByChange($event)">
440
+ @for (opt of SortByOptions; track opt.value) {
441
+ <option [value]="opt.value">{{ opt.label }}</option>
442
+ }
443
+ </select>
444
+
445
+ <select class="filter-select" [value]="SelectedVendor" (change)="OnVendorChange($event)">
446
+ <option value="">All Vendors</option>
447
+ @for (v of VendorOptions; track v.id) {
448
+ <option [value]="v.id">{{ v.name }}</option>
449
+ }
450
+ </select>
451
+ </div>
452
+
453
+ <div class="time-chips">
454
+ @for (opt of TimeRangeOptions; track opt) {
455
+ <button
456
+ class="time-chip"
457
+ [class.active]="TimeRange === opt"
458
+ (click)="OnTimeRangeSelect(opt)">
459
+ {{ opt }}
460
+ </button>
461
+ }
462
+ </div>
463
+ </div>
464
+
465
+ @if (IsLoading) {
466
+ <div class="loading-container">
467
+ <mj-loading text="Loading model performance..."></mj-loading>
468
+ </div>
469
+ } @else {
470
+ <!-- Leaderboard Table -->
471
+ <div class="leaderboard-panel">
472
+ <div class="table-wrapper">
473
+ <table class="leaderboard-table">
474
+ <thead>
475
+ <tr>
476
+ <th class="col-rank">Rank</th>
477
+ <th class="col-model">Model</th>
478
+ <th class="col-vendor">Vendor</th>
479
+ <th class="col-numeric">Runs</th>
480
+ <th class="col-numeric">Avg Latency</th>
481
+ <th class="col-numeric">P95 Latency</th>
482
+ <th class="col-numeric">Success Rate</th>
483
+ <th class="col-numeric">$/1K Tokens</th>
484
+ <th class="col-numeric">Total Cost</th>
485
+ </tr>
486
+ </thead>
487
+ <tbody>
488
+ @if (Rows.length === 0) {
489
+ <tr><td colspan="9" class="empty-row">No model data for selected period</td></tr>
490
+ }
491
+ @for (row of Rows; track row.ModelName) {
492
+ <tr>
493
+ <td>
494
+ <span class="rank-badge" [class]="row.RankClass">
495
+ {{ row.Rank }}
496
+ </span>
497
+ </td>
498
+ <td class="cell-model">
499
+ <div class="model-name">{{ row.ModelName }}</div>
500
+ @if (row.ApiId && row.ApiId !== row.ModelName) {
501
+ <div class="model-api-id">{{ row.ApiId }}</div>
502
+ }
503
+ </td>
504
+ <td class="cell-vendor">{{ row.Vendor }}</td>
505
+ <td class="cell-numeric">{{ row.Runs | number }}</td>
506
+ <td class="cell-numeric">
507
+ <span [style.color]="row.AvgLatencyColor">
508
+ {{ FormatLatency(row.AvgLatencyMs) }}
509
+ </span>
510
+ </td>
511
+ <td class="cell-numeric">{{ FormatLatency(row.P95LatencyMs) }}</td>
512
+ <td class="cell-numeric">
513
+ <div class="success-rate-cell">
514
+ <span class="success-rate-value">{{ row.SuccessRate | number:'1.1-1' }}%</span>
515
+ <div class="success-bar-bg">
516
+ <div class="success-bar-fill" [style.width.%]="row.SuccessRate"></div>
517
+ </div>
518
+ </div>
519
+ </td>
520
+ <td class="cell-numeric">{{ FormatCurrency(row.CostPer1KTokens, 4) }}</td>
521
+ <td class="cell-numeric cell-cost">{{ FormatCurrency(row.TotalCost) }}</td>
522
+ </tr>
523
+ }
524
+ </tbody>
525
+ </table>
526
+ </div>
527
+ </div>
528
+ }
529
+ `, styles: ["\n :host { display: block; }\n\n .loading-container {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n /* \u2500\u2500 Filter Bar \u2500\u2500 */\n .filter-bar {\n display: flex;\n align-items: center;\n gap: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 12px 16px;\n flex-wrap: wrap;\n margin-bottom: 16px;\n }\n\n .filter-controls {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n }\n\n .filter-label {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .filter-label i {\n font-size: 13px;\n color: var(--mj-status-warning);\n }\n\n .filter-select {\n padding: 5px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n cursor: pointer;\n min-width: 130px;\n outline: none;\n }\n\n .filter-select:focus {\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n\n .time-chips {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-left: auto;\n }\n\n .time-chip {\n padding: 5px 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 white-space: nowrap;\n }\n\n .time-chip:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .time-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 /* \u2500\u2500 Leaderboard \u2500\u2500 */\n .leaderboard-panel {\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-wrapper {\n overflow-x: auto;\n }\n\n .leaderboard-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .leaderboard-table th,\n .leaderboard-table td {\n padding: 12px 14px;\n text-align: left;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .leaderboard-table th {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n background: var(--mj-bg-surface-card);\n position: sticky;\n top: 0;\n white-space: nowrap;\n }\n\n .col-rank { width: 60px; text-align: center; }\n .col-model { min-width: 180px; }\n .col-vendor { min-width: 100px; }\n .col-numeric { text-align: right; white-space: nowrap; }\n\n .leaderboard-table tbody tr {\n transition: background 0.15s;\n }\n\n .leaderboard-table tbody tr:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .empty-row {\n text-align: center;\n color: var(--mj-text-disabled);\n padding: 32px;\n }\n\n /* \u2500\u2500 Rank Badge \u2500\u2500 */\n .rank-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n font-size: 12px;\n font-weight: 700;\n }\n\n .rank-gold {\n background: linear-gradient(135deg, color-mix(in srgb, var(--mj-status-warning) 35%, var(--mj-bg-surface)), color-mix(in srgb, var(--mj-status-warning) 20%, var(--mj-bg-surface)));\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-status-warning) 30%, transparent);\n }\n\n .rank-silver {\n background: color-mix(in srgb, var(--mj-text-muted) 18%, var(--mj-bg-surface));\n color: var(--mj-text-secondary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-text-muted) 20%, transparent);\n }\n\n .rank-bronze {\n background: color-mix(in srgb, var(--mj-status-warning) 18%, var(--mj-bg-surface));\n color: var(--mj-text-secondary);\n }\n\n .rank-neutral {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n }\n\n /* \u2500\u2500 Model Cell \u2500\u2500 */\n .cell-model {\n font-weight: 500;\n color: var(--mj-text-primary);\n }\n\n .model-name {\n font-weight: 600;\n }\n\n .model-api-id {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 2px;\n font-family: monospace;\n }\n\n .cell-vendor {\n color: var(--mj-text-secondary);\n }\n\n .cell-numeric {\n text-align: right;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-secondary);\n }\n\n .cell-cost {\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n /* \u2500\u2500 Success Rate Mini Bar \u2500\u2500 */\n .success-rate-cell {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 3px;\n }\n\n .success-rate-value {\n font-weight: 600;\n }\n\n .success-bar-bg {\n width: 60px;\n height: 4px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 2px;\n overflow: hidden;\n }\n\n .success-bar-fill {\n height: 100%;\n background: var(--mj-status-success);\n border-radius: 2px;\n transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n /* \u2500\u2500 Responsive \u2500\u2500 */\n @media (max-width: 1200px) {\n .leaderboard-table th,\n .leaderboard-table td {\n padding: 10px 10px;\n }\n }\n\n @media (max-width: 768px) {\n .filter-bar {\n flex-direction: column;\n align-items: stretch;\n }\n\n .time-chips {\n margin-left: 0;\n justify-content: flex-start;\n flex-wrap: wrap;\n }\n }\n "] }]
530
+ }], null, { TimeRange: [{
531
+ type: Input
532
+ }], TimeRangeChange: [{
533
+ type: Output
534
+ }] }); })();
535
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AnalyticsModelPerformanceComponent, { className: "AnalyticsModelPerformanceComponent", filePath: "src/AI/components/analytics/model-performance/model-performance.component.ts", lineNumber: 428 }); })();
536
+ export function LoadAnalyticsModelPerformance() { }
537
+ //# sourceMappingURL=model-performance.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-performance.component.js","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/model-performance/model-performance.component.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Model Performance Leaderboard.\n *\n * Ranks AI models by configurable metrics (Cost Efficiency, Speed, Reliability, Usage Volume).\n * Data is loaded from MJ: AI Prompt Runs and augmented with model metadata from AIEngineBase.\n */\n\nimport {\n Component, Input, Output, EventEmitter,\n OnInit, OnDestroy, ChangeDetectorRef, inject\n} from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { RunView } from '@memberjunction/core';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\n\n// ── Interfaces ──\n\ninterface PromptRunRecord {\n ID: string;\n RunAt: string;\n CompletedAt: string | null;\n ExecutionTimeMS: number | null;\n Success: boolean;\n Cost: number | null;\n TotalCost: number | null;\n TokensUsed: number | null;\n TokensPrompt: number | null;\n TokensCompletion: number | null;\n ModelID: string | null;\n Model: string | null;\n VendorID: string | null;\n Vendor: string | null;\n}\n\ninterface ModelLeaderboardRow {\n Rank: number;\n RankClass: string;\n ModelName: string;\n ApiId: string;\n Vendor: string;\n VendorID: string;\n Runs: number;\n AvgLatencyMs: number;\n AvgLatencyColor: string;\n P95LatencyMs: number;\n SuccessRate: number;\n CostPer1KTokens: number;\n TotalCost: number;\n}\n\ntype SortByOption = 'cost-efficiency' | 'speed' | 'reliability' | 'usage-volume';\n\nconst FIELDS = [\n 'ID', 'RunAt', 'CompletedAt', 'ExecutionTimeMS', 'Success',\n 'Cost', 'TotalCost', 'TokensUsed', 'TokensPrompt', 'TokensCompletion',\n 'ModelID', 'Model', 'VendorID', 'Vendor'\n];\n\n@Component({\n standalone: false,\n selector: 'app-analytics-model-performance',\n template: `\n <!-- Filter Bar -->\n <div class=\"filter-bar\">\n <div class=\"filter-controls\">\n <span class=\"filter-label\">\n <i class=\"fa-solid fa-trophy\"></i>\n Leaderboard\n </span>\n\n <select class=\"filter-select\" [value]=\"SortBy\" (change)=\"OnSortByChange($event)\">\n @for (opt of SortByOptions; track opt.value) {\n <option [value]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n\n <select class=\"filter-select\" [value]=\"SelectedVendor\" (change)=\"OnVendorChange($event)\">\n <option value=\"\">All Vendors</option>\n @for (v of VendorOptions; track v.id) {\n <option [value]=\"v.id\">{{ v.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"time-chips\">\n @for (opt of TimeRangeOptions; track opt) {\n <button\n class=\"time-chip\"\n [class.active]=\"TimeRange === opt\"\n (click)=\"OnTimeRangeSelect(opt)\">\n {{ opt }}\n </button>\n }\n </div>\n </div>\n\n @if (IsLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading model performance...\"></mj-loading>\n </div>\n } @else {\n <!-- Leaderboard Table -->\n <div class=\"leaderboard-panel\">\n <div class=\"table-wrapper\">\n <table class=\"leaderboard-table\">\n <thead>\n <tr>\n <th class=\"col-rank\">Rank</th>\n <th class=\"col-model\">Model</th>\n <th class=\"col-vendor\">Vendor</th>\n <th class=\"col-numeric\">Runs</th>\n <th class=\"col-numeric\">Avg Latency</th>\n <th class=\"col-numeric\">P95 Latency</th>\n <th class=\"col-numeric\">Success Rate</th>\n <th class=\"col-numeric\">$/1K Tokens</th>\n <th class=\"col-numeric\">Total Cost</th>\n </tr>\n </thead>\n <tbody>\n @if (Rows.length === 0) {\n <tr><td colspan=\"9\" class=\"empty-row\">No model data for selected period</td></tr>\n }\n @for (row of Rows; track row.ModelName) {\n <tr>\n <td>\n <span class=\"rank-badge\" [class]=\"row.RankClass\">\n {{ row.Rank }}\n </span>\n </td>\n <td class=\"cell-model\">\n <div class=\"model-name\">{{ row.ModelName }}</div>\n @if (row.ApiId && row.ApiId !== row.ModelName) {\n <div class=\"model-api-id\">{{ row.ApiId }}</div>\n }\n </td>\n <td class=\"cell-vendor\">{{ row.Vendor }}</td>\n <td class=\"cell-numeric\">{{ row.Runs | number }}</td>\n <td class=\"cell-numeric\">\n <span [style.color]=\"row.AvgLatencyColor\">\n {{ FormatLatency(row.AvgLatencyMs) }}\n </span>\n </td>\n <td class=\"cell-numeric\">{{ FormatLatency(row.P95LatencyMs) }}</td>\n <td class=\"cell-numeric\">\n <div class=\"success-rate-cell\">\n <span class=\"success-rate-value\">{{ row.SuccessRate | number:'1.1-1' }}%</span>\n <div class=\"success-bar-bg\">\n <div class=\"success-bar-fill\" [style.width.%]=\"row.SuccessRate\"></div>\n </div>\n </div>\n </td>\n <td class=\"cell-numeric\">{{ FormatCurrency(row.CostPer1KTokens, 4) }}</td>\n <td class=\"cell-numeric cell-cost\">{{ FormatCurrency(row.TotalCost) }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n `,\n styles: [`\n :host { display: block; }\n\n .loading-container {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n /* ── Filter Bar ── */\n .filter-bar {\n display: flex;\n align-items: center;\n gap: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 12px 16px;\n flex-wrap: wrap;\n margin-bottom: 16px;\n }\n\n .filter-controls {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n }\n\n .filter-label {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .filter-label i {\n font-size: 13px;\n color: var(--mj-status-warning);\n }\n\n .filter-select {\n padding: 5px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n cursor: pointer;\n min-width: 130px;\n outline: none;\n }\n\n .filter-select:focus {\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n }\n\n .time-chips {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-left: auto;\n }\n\n .time-chip {\n padding: 5px 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 white-space: nowrap;\n }\n\n .time-chip:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n .time-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 /* ── Leaderboard ── */\n .leaderboard-panel {\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-wrapper {\n overflow-x: auto;\n }\n\n .leaderboard-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .leaderboard-table th,\n .leaderboard-table td {\n padding: 12px 14px;\n text-align: left;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .leaderboard-table th {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n background: var(--mj-bg-surface-card);\n position: sticky;\n top: 0;\n white-space: nowrap;\n }\n\n .col-rank { width: 60px; text-align: center; }\n .col-model { min-width: 180px; }\n .col-vendor { min-width: 100px; }\n .col-numeric { text-align: right; white-space: nowrap; }\n\n .leaderboard-table tbody tr {\n transition: background 0.15s;\n }\n\n .leaderboard-table tbody tr:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .empty-row {\n text-align: center;\n color: var(--mj-text-disabled);\n padding: 32px;\n }\n\n /* ── Rank Badge ── */\n .rank-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n font-size: 12px;\n font-weight: 700;\n }\n\n .rank-gold {\n background: linear-gradient(135deg, color-mix(in srgb, var(--mj-status-warning) 35%, var(--mj-bg-surface)), color-mix(in srgb, var(--mj-status-warning) 20%, var(--mj-bg-surface)));\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-status-warning) 30%, transparent);\n }\n\n .rank-silver {\n background: color-mix(in srgb, var(--mj-text-muted) 18%, var(--mj-bg-surface));\n color: var(--mj-text-secondary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-text-muted) 20%, transparent);\n }\n\n .rank-bronze {\n background: color-mix(in srgb, var(--mj-status-warning) 18%, var(--mj-bg-surface));\n color: var(--mj-text-secondary);\n }\n\n .rank-neutral {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n }\n\n /* ── Model Cell ── */\n .cell-model {\n font-weight: 500;\n color: var(--mj-text-primary);\n }\n\n .model-name {\n font-weight: 600;\n }\n\n .model-api-id {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 2px;\n font-family: monospace;\n }\n\n .cell-vendor {\n color: var(--mj-text-secondary);\n }\n\n .cell-numeric {\n text-align: right;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-secondary);\n }\n\n .cell-cost {\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n /* ── Success Rate Mini Bar ── */\n .success-rate-cell {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 3px;\n }\n\n .success-rate-value {\n font-weight: 600;\n }\n\n .success-bar-bg {\n width: 60px;\n height: 4px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 2px;\n overflow: hidden;\n }\n\n .success-bar-fill {\n height: 100%;\n background: var(--mj-status-success);\n border-radius: 2px;\n transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n /* ── Responsive ── */\n @media (max-width: 1200px) {\n .leaderboard-table th,\n .leaderboard-table td {\n padding: 10px 10px;\n }\n }\n\n @media (max-width: 768px) {\n .filter-bar {\n flex-direction: column;\n align-items: stretch;\n }\n\n .time-chips {\n margin-left: 0;\n justify-content: flex-start;\n flex-wrap: wrap;\n }\n }\n `]\n})\nexport class AnalyticsModelPerformanceComponent implements OnInit, OnDestroy {\n @Input() TimeRange = '7d';\n @Output() TimeRangeChange = new EventEmitter<string>();\n\n private cdr = inject(ChangeDetectorRef);\n private destroy$ = new Subject<void>();\n\n public IsLoading = false;\n public SortBy: SortByOption = 'cost-efficiency';\n public SelectedVendor = '';\n public Rows: ModelLeaderboardRow[] = [];\n\n public TimeRangeOptions = ['24h', '7d', '30d'];\n\n public SortByOptions: { value: SortByOption; label: string }[] = [\n { value: 'cost-efficiency', label: 'Cost Efficiency' },\n { value: 'speed', label: 'Speed' },\n { value: 'reliability', label: 'Reliability' },\n { value: 'usage-volume', label: 'Usage Volume' }\n ];\n\n public VendorOptions: { id: string; name: string }[] = [];\n\n private allRuns: PromptRunRecord[] = [];\n\n ngOnInit(): void {\n this.loadVendorOptions();\n this.LoadData();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n // ── Public Handlers ──\n\n public OnTimeRangeSelect(range: string): void {\n this.TimeRange = range;\n this.TimeRangeChange.emit(range);\n this.LoadData();\n }\n\n public OnSortByChange(event: Event): void {\n this.SortBy = (event.target as HTMLSelectElement).value as SortByOption;\n this.buildRows();\n this.cdr.detectChanges();\n }\n\n public OnVendorChange(event: Event): void {\n this.SelectedVendor = (event.target as HTMLSelectElement).value;\n this.buildRows();\n this.cdr.detectChanges();\n }\n\n public FormatLatency(ms: number): string {\n if (ms < 1000) return Math.round(ms) + 'ms';\n return (ms / 1000).toFixed(2) + 's';\n }\n\n public FormatCurrency(value: number, decimals = 2): string {\n if (value === 0) return '$0.00';\n if (value < 0.01 && decimals < 4) decimals = 4;\n return '$' + value.toFixed(decimals);\n }\n\n // ── Data Loading ──\n\n private loadVendorOptions(): void {\n try {\n this.VendorOptions = AIEngineBase.Instance.Vendors.map(v => ({\n id: v.ID,\n name: v.Name\n }));\n } catch {\n this.VendorOptions = [];\n }\n }\n\n private async LoadData(): Promise<void> {\n this.IsLoading = true;\n this.cdr.detectChanges();\n\n try {\n const rv = new RunView();\n const dateFilter = this.buildDateFilter();\n\n const result = await rv.RunView<PromptRunRecord>({\n EntityName: 'MJ: AI Prompt Runs',\n ExtraFilter: dateFilter,\n Fields: FIELDS,\n OrderBy: 'RunAt DESC',\n ResultType: 'simple'\n });\n\n this.allRuns = (result?.Results ?? []) as PromptRunRecord[];\n this.buildRows();\n } catch (e) {\n console.error('Model Performance load error:', e);\n } finally {\n this.IsLoading = false;\n this.cdr.detectChanges();\n }\n }\n\n // ── Row Building ──\n\n private buildRows(): void {\n // Filter by vendor if needed\n let runs = this.allRuns;\n if (this.SelectedVendor) {\n runs = runs.filter(r => r.VendorID === this.SelectedVendor);\n }\n\n // Group by model\n const groups = new Map<string, PromptRunRecord[]>();\n for (const run of runs) {\n const key = run.ModelID ?? 'unknown';\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key)!.push(run);\n }\n\n // Compute metrics per model\n const modelRows: ModelLeaderboardRow[] = [];\n for (const [modelId, modelRuns] of groups) {\n const row = this.computeModelMetrics(modelId, modelRuns);\n modelRows.push(row);\n }\n\n // Sort by chosen metric\n this.sortRows(modelRows);\n\n // Assign ranks\n modelRows.forEach((row, i) => {\n row.Rank = i + 1;\n row.RankClass = this.getRankClass(i + 1);\n });\n\n this.Rows = modelRows;\n }\n\n private computeModelMetrics(modelId: string, runs: PromptRunRecord[]): ModelLeaderboardRow {\n const totalRuns = runs.length;\n const successCount = runs.filter(r => r.Success).length;\n const successRate = totalRuns > 0 ? (successCount / totalRuns) * 100 : 0;\n\n const latencies = runs\n .filter(r => r.ExecutionTimeMS != null && r.ExecutionTimeMS > 0)\n .map(r => r.ExecutionTimeMS!);\n\n const avgLatency = latencies.length > 0\n ? latencies.reduce((s, l) => s + l, 0) / latencies.length\n : 0;\n\n const p95Latency = this.computePercentile(latencies, 0.95);\n\n const totalTokens = runs.reduce((s, r) => s + (r.TokensUsed ?? 0), 0);\n const totalCost = runs.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n const costPer1K = totalTokens > 0 ? (totalCost / totalTokens) * 1000 : 0;\n\n const firstName = runs.find(r => r.Model != null);\n const firstVendor = runs.find(r => r.Vendor != null);\n\n // Look up model API name from engine\n let apiId = '';\n try {\n const model = AIEngineBase.Instance.Models.find(m => UUIDsEqual(m.ID, modelId));\n apiId = model?.APIName ?? '';\n } catch {\n // engine might not be ready\n }\n\n return {\n Rank: 0,\n RankClass: 'rank-neutral',\n ModelName: firstName?.Model ?? 'Unknown',\n ApiId: apiId,\n Vendor: firstVendor?.Vendor ?? 'Unknown',\n VendorID: firstVendor?.VendorID ?? '',\n Runs: totalRuns,\n AvgLatencyMs: avgLatency,\n AvgLatencyColor: this.getLatencyColor(avgLatency),\n P95LatencyMs: p95Latency,\n SuccessRate: successRate,\n CostPer1KTokens: costPer1K,\n TotalCost: totalCost\n };\n }\n\n private sortRows(rows: ModelLeaderboardRow[]): void {\n switch (this.SortBy) {\n case 'cost-efficiency':\n rows.sort((a, b) => a.CostPer1KTokens - b.CostPer1KTokens);\n break;\n case 'speed':\n rows.sort((a, b) => a.AvgLatencyMs - b.AvgLatencyMs);\n break;\n case 'reliability':\n rows.sort((a, b) => b.SuccessRate - a.SuccessRate);\n break;\n case 'usage-volume':\n rows.sort((a, b) => b.Runs - a.Runs);\n break;\n }\n }\n\n // ── Helpers ──\n\n private computePercentile(sortedValues: number[], percentile: number): number {\n if (sortedValues.length === 0) return 0;\n const sorted = [...sortedValues].sort((a, b) => a - b);\n const idx = Math.ceil(sorted.length * percentile) - 1;\n return sorted[Math.max(0, idx)];\n }\n\n private getRankClass(rank: number): string {\n if (rank === 1) return 'rank-badge rank-gold';\n if (rank === 2) return 'rank-badge rank-silver';\n if (rank === 3) return 'rank-badge rank-bronze';\n return 'rank-badge rank-neutral';\n }\n\n private getLatencyColor(ms: number): string {\n if (ms < 1000) return 'var(--mj-status-success)';\n if (ms < 3000) return 'var(--mj-status-warning)';\n return 'var(--mj-status-error)';\n }\n\n private buildDateFilter(): string {\n const now = new Date();\n const msMap: Record<string, number> = {\n '24h': 86400000,\n '7d': 604800000,\n '30d': 2592000000\n };\n const ms = msMap[this.TimeRange] ?? 604800000;\n const start = new Date(now.getTime() - ms);\n return `RunAt >= '${start.toISOString()}'`;\n }\n}\n\nexport function LoadAnalyticsModelPerformance() { /* tree-shaking prevention */ }\n"]}