@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.
- package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.d.ts +96 -0
- package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.js +710 -0
- package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.js.map +1 -0
- package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts +52 -0
- package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/ai-analytics-resource.component.js +356 -0
- package/dist/AI/components/analytics/ai-analytics-resource.component.js.map +1 -0
- package/dist/AI/components/analytics/analytics-filter-bar.component.d.ts +52 -0
- package/dist/AI/components/analytics/analytics-filter-bar.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/analytics-filter-bar.component.js +306 -0
- package/dist/AI/components/analytics/analytics-filter-bar.component.js.map +1 -0
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts +81 -0
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.js +744 -0
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.js.map +1 -0
- package/dist/AI/components/analytics/error-analysis/error-analysis.component.d.ts +61 -0
- package/dist/AI/components/analytics/error-analysis/error-analysis.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/error-analysis/error-analysis.component.js +490 -0
- package/dist/AI/components/analytics/error-analysis/error-analysis.component.js.map +1 -0
- package/dist/AI/components/analytics/executive-summary/executive-summary.component.d.ts +77 -0
- package/dist/AI/components/analytics/executive-summary/executive-summary.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/executive-summary/executive-summary.component.js +673 -0
- package/dist/AI/components/analytics/executive-summary/executive-summary.component.js.map +1 -0
- package/dist/AI/components/analytics/model-performance/model-performance.component.d.ts +65 -0
- package/dist/AI/components/analytics/model-performance/model-performance.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/model-performance/model-performance.component.js +537 -0
- package/dist/AI/components/analytics/model-performance/model-performance.component.js.map +1 -0
- package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.d.ts +131 -0
- package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.js +1030 -0
- package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.js.map +1 -0
- package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.d.ts +78 -0
- package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.js +569 -0
- package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.js.map +1 -0
- package/dist/AI/components/execution-monitoring.component.d.ts.map +1 -1
- package/dist/AI/components/execution-monitoring.component.js +4 -14
- package/dist/AI/components/execution-monitoring.component.js.map +1 -1
- package/dist/AI/components/overview/ai-overview-hub.component.d.ts +58 -0
- package/dist/AI/components/overview/ai-overview-hub.component.d.ts.map +1 -0
- package/dist/AI/components/overview/ai-overview-hub.component.js +315 -0
- package/dist/AI/components/overview/ai-overview-hub.component.js.map +1 -0
- package/dist/AI/components/prompts/prompt-management.component.js +1 -1
- package/dist/AI/components/prompts/prompt-management.component.js.map +1 -1
- package/dist/AI/index.d.ts +11 -0
- package/dist/AI/index.d.ts.map +1 -1
- package/dist/AI/index.js +13 -0
- package/dist/AI/index.js.map +1 -1
- package/dist/AI/interfaces/analytics-preferences.interface.d.ts +50 -0
- package/dist/AI/interfaces/analytics-preferences.interface.d.ts.map +1 -0
- package/dist/AI/interfaces/analytics-preferences.interface.js +9 -0
- package/dist/AI/interfaces/analytics-preferences.interface.js.map +1 -0
- package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -1
- package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
- package/dist/Home/home-dashboard.component.js +2 -2
- package/dist/MCP/index.d.ts +1 -0
- package/dist/MCP/index.d.ts.map +1 -1
- package/dist/MCP/index.js +2 -0
- package/dist/MCP/index.js.map +1 -1
- package/dist/MCP/mcp-dashboard.component.d.ts +1 -0
- package/dist/MCP/mcp-dashboard.component.d.ts.map +1 -1
- package/dist/MCP/mcp-dashboard.component.js +5 -4
- package/dist/MCP/mcp-dashboard.component.js.map +1 -1
- package/dist/MCP/mcp-resource.component.d.ts +6 -5
- package/dist/MCP/mcp-resource.component.d.ts.map +1 -1
- package/dist/MCP/mcp-resource.component.js +7 -8
- package/dist/MCP/mcp-resource.component.js.map +1 -1
- package/dist/ai-dashboards.module.d.ts +27 -17
- package/dist/ai-dashboards.module.d.ts.map +1 -1
- package/dist/ai-dashboards.module.js +66 -3
- package/dist/ai-dashboards.module.js.map +1 -1
- package/dist/public-api.d.ts +1 -1
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -1
- package/dist/public-api.js.map +1 -1
- package/package.json +48 -48
- package/dist/__tests__/analytics-resource.test.d.ts +0 -2
- package/dist/__tests__/analytics-resource.test.d.ts.map +0 -1
- package/dist/__tests__/analytics-resource.test.js +0 -181
- package/dist/__tests__/analytics-resource.test.js.map +0 -1
- package/dist/__tests__/dashboards.test.d.ts +0 -2
- package/dist/__tests__/dashboards.test.d.ts.map +0 -1
- package/dist/__tests__/dashboards.test.js +0 -40
- package/dist/__tests__/dashboards.test.js.map +0 -1
- package/dist/__tests__/integration-data-service.test.d.ts +0 -2
- package/dist/__tests__/integration-data-service.test.d.ts.map +0 -1
- package/dist/__tests__/integration-data-service.test.js +0 -132
- package/dist/__tests__/integration-data-service.test.js.map +0 -1
- package/dist/__tests__/mapping-validation.test.d.ts +0 -2
- package/dist/__tests__/mapping-validation.test.d.ts.map +0 -1
- package/dist/__tests__/mapping-validation.test.js +0 -170
- package/dist/__tests__/mapping-validation.test.js.map +0 -1
- package/dist/__tests__/scheduling.test.d.ts +0 -2
- package/dist/__tests__/scheduling.test.d.ts.map +0 -1
- package/dist/__tests__/scheduling.test.js +0 -205
- package/dist/__tests__/scheduling.test.js.map +0 -1
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Usage Patterns Analytics.
|
|
3
|
+
*
|
|
4
|
+
* Displays a time-of-day heatmap (7 days × 24 hours), day-of-week distribution bars,
|
|
5
|
+
* peak hours summary cards, and an hourly throughput bar chart.
|
|
6
|
+
* Data loaded from MJ: AI Prompt Runs for the last 30 days.
|
|
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.DayIndex;
|
|
16
|
+
const _forTrack1 = ($index, $item) => $item.Label;
|
|
17
|
+
const _forTrack2 = ($index, $item) => $item.Hour;
|
|
18
|
+
function AnalyticsUsagePatternsComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
19
|
+
i0.ɵɵelementStart(0, "div", 1);
|
|
20
|
+
i0.ɵɵelement(1, "mj-loading", 3);
|
|
21
|
+
i0.ɵɵelementEnd();
|
|
22
|
+
} }
|
|
23
|
+
function AnalyticsUsagePatternsComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
24
|
+
i0.ɵɵelementStart(0, "div", 2);
|
|
25
|
+
i0.ɵɵelement(1, "i", 4);
|
|
26
|
+
i0.ɵɵelementStart(2, "div", 5);
|
|
27
|
+
i0.ɵɵtext(3, "No Data Available");
|
|
28
|
+
i0.ɵɵelementEnd();
|
|
29
|
+
i0.ɵɵelementStart(4, "div", 6);
|
|
30
|
+
i0.ɵɵtext(5, "No prompt runs found in the last 30 days.");
|
|
31
|
+
i0.ɵɵelementEnd()();
|
|
32
|
+
} }
|
|
33
|
+
function AnalyticsUsagePatternsComponent_Conditional_3_For_10_Template(rf, ctx) { if (rf & 1) {
|
|
34
|
+
i0.ɵɵelementStart(0, "div", 14);
|
|
35
|
+
i0.ɵɵtext(1);
|
|
36
|
+
i0.ɵɵelementEnd();
|
|
37
|
+
} if (rf & 2) {
|
|
38
|
+
const h_r1 = ctx.$implicit;
|
|
39
|
+
i0.ɵɵadvance();
|
|
40
|
+
i0.ɵɵtextInterpolate(h_r1);
|
|
41
|
+
} }
|
|
42
|
+
function AnalyticsUsagePatternsComponent_Conditional_3_For_12_For_3_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
43
|
+
i0.ɵɵelementStart(0, "span", 26);
|
|
44
|
+
i0.ɵɵtext(1);
|
|
45
|
+
i0.ɵɵelementEnd();
|
|
46
|
+
} if (rf & 2) {
|
|
47
|
+
const h_r2 = i0.ɵɵnextContext().$implicit;
|
|
48
|
+
const ɵ$index_40_r3 = i0.ɵɵnextContext().$index;
|
|
49
|
+
const ctx_r3 = i0.ɵɵnextContext(2);
|
|
50
|
+
i0.ɵɵadvance();
|
|
51
|
+
i0.ɵɵtextInterpolate(ctx_r3.getCellCount(ɵ$index_40_r3, h_r2));
|
|
52
|
+
} }
|
|
53
|
+
function AnalyticsUsagePatternsComponent_Conditional_3_For_12_For_3_Template(rf, ctx) { if (rf & 1) {
|
|
54
|
+
i0.ɵɵelementStart(0, "div", 25);
|
|
55
|
+
i0.ɵɵconditionalCreate(1, AnalyticsUsagePatternsComponent_Conditional_3_For_12_For_3_Conditional_1_Template, 2, 1, "span", 26);
|
|
56
|
+
i0.ɵɵelementEnd();
|
|
57
|
+
} if (rf & 2) {
|
|
58
|
+
const h_r2 = ctx.$implicit;
|
|
59
|
+
const ɵ$index_40_r3 = i0.ɵɵnextContext().$index;
|
|
60
|
+
const ctx_r3 = i0.ɵɵnextContext(2);
|
|
61
|
+
i0.ɵɵstyleProp("background", ctx_r3.getCellBackground(ɵ$index_40_r3, h_r2));
|
|
62
|
+
i0.ɵɵproperty("title", ctx_r3.getCellTooltip(ɵ$index_40_r3, h_r2));
|
|
63
|
+
i0.ɵɵadvance();
|
|
64
|
+
i0.ɵɵconditional(ctx_r3.getCellCount(ɵ$index_40_r3, h_r2) > 0 ? 1 : -1);
|
|
65
|
+
} }
|
|
66
|
+
function AnalyticsUsagePatternsComponent_Conditional_3_For_12_Template(rf, ctx) { if (rf & 1) {
|
|
67
|
+
i0.ɵɵelementStart(0, "div", 23);
|
|
68
|
+
i0.ɵɵtext(1);
|
|
69
|
+
i0.ɵɵelementEnd();
|
|
70
|
+
i0.ɵɵrepeaterCreate(2, AnalyticsUsagePatternsComponent_Conditional_3_For_12_For_3_Template, 2, 4, "div", 24, i0.ɵɵrepeaterTrackByIdentity);
|
|
71
|
+
} if (rf & 2) {
|
|
72
|
+
const day_r5 = ctx.$implicit;
|
|
73
|
+
const ctx_r3 = i0.ɵɵnextContext(2);
|
|
74
|
+
i0.ɵɵadvance();
|
|
75
|
+
i0.ɵɵtextInterpolate(day_r5);
|
|
76
|
+
i0.ɵɵadvance();
|
|
77
|
+
i0.ɵɵrepeater(ctx_r3.Hours);
|
|
78
|
+
} }
|
|
79
|
+
function AnalyticsUsagePatternsComponent_Conditional_3_For_20_Template(rf, ctx) { if (rf & 1) {
|
|
80
|
+
i0.ɵɵelementStart(0, "div", 17)(1, "span", 27);
|
|
81
|
+
i0.ɵɵtext(2);
|
|
82
|
+
i0.ɵɵelementEnd();
|
|
83
|
+
i0.ɵɵelementStart(3, "div", 28);
|
|
84
|
+
i0.ɵɵelement(4, "div", 29);
|
|
85
|
+
i0.ɵɵelementEnd();
|
|
86
|
+
i0.ɵɵelementStart(5, "span", 30);
|
|
87
|
+
i0.ɵɵtext(6);
|
|
88
|
+
i0.ɵɵpipe(7, "number");
|
|
89
|
+
i0.ɵɵelementEnd()();
|
|
90
|
+
} if (rf & 2) {
|
|
91
|
+
const d_r6 = ctx.$implicit;
|
|
92
|
+
i0.ɵɵadvance(2);
|
|
93
|
+
i0.ɵɵtextInterpolate(d_r6.DayName);
|
|
94
|
+
i0.ɵɵadvance(2);
|
|
95
|
+
i0.ɵɵstyleProp("width", d_r6.WidthPercent, "%");
|
|
96
|
+
i0.ɵɵadvance(2);
|
|
97
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(7, 4, d_r6.Count));
|
|
98
|
+
} }
|
|
99
|
+
function AnalyticsUsagePatternsComponent_Conditional_3_For_27_Template(rf, ctx) { if (rf & 1) {
|
|
100
|
+
i0.ɵɵelementStart(0, "div", 19)(1, "div", 31);
|
|
101
|
+
i0.ɵɵelement(2, "i");
|
|
102
|
+
i0.ɵɵelementEnd();
|
|
103
|
+
i0.ɵɵelementStart(3, "div", 32)(4, "div", 33);
|
|
104
|
+
i0.ɵɵtext(5);
|
|
105
|
+
i0.ɵɵelementEnd();
|
|
106
|
+
i0.ɵɵelementStart(6, "div", 34);
|
|
107
|
+
i0.ɵɵtext(7);
|
|
108
|
+
i0.ɵɵelementEnd();
|
|
109
|
+
i0.ɵɵelementStart(8, "div", 35);
|
|
110
|
+
i0.ɵɵtext(9);
|
|
111
|
+
i0.ɵɵpipe(10, "number");
|
|
112
|
+
i0.ɵɵelementEnd()()();
|
|
113
|
+
} if (rf & 2) {
|
|
114
|
+
const peak_r7 = ctx.$implicit;
|
|
115
|
+
i0.ɵɵadvance(2);
|
|
116
|
+
i0.ɵɵclassMap(peak_r7.Icon);
|
|
117
|
+
i0.ɵɵadvance(3);
|
|
118
|
+
i0.ɵɵtextInterpolate(peak_r7.Label);
|
|
119
|
+
i0.ɵɵadvance(2);
|
|
120
|
+
i0.ɵɵtextInterpolate(peak_r7.Value);
|
|
121
|
+
i0.ɵɵadvance(2);
|
|
122
|
+
i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind1(10, 5, peak_r7.Count), " runs");
|
|
123
|
+
} }
|
|
124
|
+
function AnalyticsUsagePatternsComponent_Conditional_3_For_37_Template(rf, ctx) { if (rf & 1) {
|
|
125
|
+
i0.ɵɵelementStart(0, "div", 22);
|
|
126
|
+
i0.ɵɵelement(1, "div", 36);
|
|
127
|
+
i0.ɵɵelementStart(2, "span", 37);
|
|
128
|
+
i0.ɵɵtext(3);
|
|
129
|
+
i0.ɵɵelementEnd()();
|
|
130
|
+
} if (rf & 2) {
|
|
131
|
+
const bar_r8 = ctx.$implicit;
|
|
132
|
+
i0.ɵɵproperty("title", bar_r8.Label + ": " + bar_r8.Count + " runs");
|
|
133
|
+
i0.ɵɵadvance();
|
|
134
|
+
i0.ɵɵstyleProp("height", bar_r8.HeightPercent, "%");
|
|
135
|
+
i0.ɵɵadvance(2);
|
|
136
|
+
i0.ɵɵtextInterpolate(bar_r8.Label);
|
|
137
|
+
} }
|
|
138
|
+
function AnalyticsUsagePatternsComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
139
|
+
i0.ɵɵelementStart(0, "div", 7)(1, "div", 8)(2, "h4", 9);
|
|
140
|
+
i0.ɵɵtext(3, "Time-of-Day Heatmap");
|
|
141
|
+
i0.ɵɵelementEnd();
|
|
142
|
+
i0.ɵɵelementStart(4, "span", 10);
|
|
143
|
+
i0.ɵɵtext(5, "Execution count by day and hour");
|
|
144
|
+
i0.ɵɵelementEnd()();
|
|
145
|
+
i0.ɵɵelementStart(6, "div", 11)(7, "div", 12);
|
|
146
|
+
i0.ɵɵelement(8, "div", 13);
|
|
147
|
+
i0.ɵɵrepeaterCreate(9, AnalyticsUsagePatternsComponent_Conditional_3_For_10_Template, 2, 1, "div", 14, i0.ɵɵrepeaterTrackByIdentity);
|
|
148
|
+
i0.ɵɵrepeaterCreate(11, AnalyticsUsagePatternsComponent_Conditional_3_For_12_Template, 4, 1, null, null, i0.ɵɵrepeaterTrackByIdentity);
|
|
149
|
+
i0.ɵɵelementEnd()()();
|
|
150
|
+
i0.ɵɵelementStart(13, "div", 15)(14, "div", 7)(15, "div", 8)(16, "h4", 9);
|
|
151
|
+
i0.ɵɵtext(17, "Day-of-Week Distribution");
|
|
152
|
+
i0.ɵɵelementEnd()();
|
|
153
|
+
i0.ɵɵelementStart(18, "div", 16);
|
|
154
|
+
i0.ɵɵrepeaterCreate(19, AnalyticsUsagePatternsComponent_Conditional_3_For_20_Template, 8, 6, "div", 17, _forTrack0);
|
|
155
|
+
i0.ɵɵelementEnd()();
|
|
156
|
+
i0.ɵɵelementStart(21, "div", 7)(22, "div", 8)(23, "h4", 9);
|
|
157
|
+
i0.ɵɵtext(24, "Peak Hours Summary");
|
|
158
|
+
i0.ɵɵelementEnd()();
|
|
159
|
+
i0.ɵɵelementStart(25, "div", 18);
|
|
160
|
+
i0.ɵɵrepeaterCreate(26, AnalyticsUsagePatternsComponent_Conditional_3_For_27_Template, 11, 7, "div", 19, _forTrack1);
|
|
161
|
+
i0.ɵɵelementEnd()()();
|
|
162
|
+
i0.ɵɵelementStart(28, "div", 7)(29, "div", 8)(30, "h4", 9);
|
|
163
|
+
i0.ɵɵtext(31, "Hourly Throughput");
|
|
164
|
+
i0.ɵɵelementEnd();
|
|
165
|
+
i0.ɵɵelementStart(32, "span", 10);
|
|
166
|
+
i0.ɵɵtext(33, "Total runs per hour (all days combined)");
|
|
167
|
+
i0.ɵɵelementEnd()();
|
|
168
|
+
i0.ɵɵelementStart(34, "div", 20)(35, "div", 21);
|
|
169
|
+
i0.ɵɵrepeaterCreate(36, AnalyticsUsagePatternsComponent_Conditional_3_For_37_Template, 4, 4, "div", 22, _forTrack2);
|
|
170
|
+
i0.ɵɵelementEnd()()();
|
|
171
|
+
} if (rf & 2) {
|
|
172
|
+
const ctx_r3 = i0.ɵɵnextContext();
|
|
173
|
+
i0.ɵɵadvance(9);
|
|
174
|
+
i0.ɵɵrepeater(ctx_r3.Hours);
|
|
175
|
+
i0.ɵɵadvance(2);
|
|
176
|
+
i0.ɵɵrepeater(ctx_r3.DayNames);
|
|
177
|
+
i0.ɵɵadvance(8);
|
|
178
|
+
i0.ɵɵrepeater(ctx_r3.DayDistributions);
|
|
179
|
+
i0.ɵɵadvance(7);
|
|
180
|
+
i0.ɵɵrepeater(ctx_r3.PeakSummaries);
|
|
181
|
+
i0.ɵɵadvance(10);
|
|
182
|
+
i0.ɵɵrepeater(ctx_r3.HourlyBars);
|
|
183
|
+
} }
|
|
184
|
+
const DAY_NAMES = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
185
|
+
const HOURS = Array.from({ length: 24 }, (_, i) => i);
|
|
186
|
+
const FIELDS = [
|
|
187
|
+
'ID', 'RunAt', 'Success', 'Cost', 'TokensUsed', 'ExecutionTimeMS'
|
|
188
|
+
];
|
|
189
|
+
export class AnalyticsUsagePatternsComponent {
|
|
190
|
+
_timeRange = '30d';
|
|
191
|
+
isInitialized = false;
|
|
192
|
+
set TimeRange(value) {
|
|
193
|
+
if (value !== this._timeRange) {
|
|
194
|
+
this._timeRange = value;
|
|
195
|
+
if (this.isInitialized) {
|
|
196
|
+
this.loadData();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
get TimeRange() { return this._timeRange; }
|
|
201
|
+
TimeRangeChange = new EventEmitter();
|
|
202
|
+
// ── Public state ──
|
|
203
|
+
IsLoading = true;
|
|
204
|
+
TotalRuns = 0;
|
|
205
|
+
HeatmapCells = []; // [day][hour]
|
|
206
|
+
DayDistributions = [];
|
|
207
|
+
PeakSummaries = [];
|
|
208
|
+
HourlyBars = [];
|
|
209
|
+
DayNames = DAY_NAMES;
|
|
210
|
+
Hours = HOURS;
|
|
211
|
+
// ── Private ──
|
|
212
|
+
destroy$ = new Subject();
|
|
213
|
+
cdr = inject(ChangeDetectorRef);
|
|
214
|
+
async ngOnInit() {
|
|
215
|
+
await this.loadData();
|
|
216
|
+
this.isInitialized = true;
|
|
217
|
+
}
|
|
218
|
+
OnTimeRangeChange(range) {
|
|
219
|
+
this.TimeRange = range;
|
|
220
|
+
this.TimeRangeChange.emit(range);
|
|
221
|
+
}
|
|
222
|
+
ngOnDestroy() {
|
|
223
|
+
this.destroy$.next();
|
|
224
|
+
this.destroy$.complete();
|
|
225
|
+
}
|
|
226
|
+
// ── Heatmap helpers (called from template) ──
|
|
227
|
+
getCellBackground(day, hour) {
|
|
228
|
+
const intensity = this.HeatmapCells[day]?.[hour]?.Intensity ?? 0;
|
|
229
|
+
return `color-mix(in srgb, var(--mj-brand-primary) ${intensity}%, var(--mj-bg-surface))`;
|
|
230
|
+
}
|
|
231
|
+
getCellTooltip(day, hour) {
|
|
232
|
+
const count = this.HeatmapCells[day]?.[hour]?.Count ?? 0;
|
|
233
|
+
return `${DAY_NAMES[day]} ${this.formatHourLabel(hour)}: ${count} runs`;
|
|
234
|
+
}
|
|
235
|
+
getCellCount(day, hour) {
|
|
236
|
+
return this.HeatmapCells[day]?.[hour]?.Count ?? 0;
|
|
237
|
+
}
|
|
238
|
+
// ── Data Loading ──
|
|
239
|
+
async loadData() {
|
|
240
|
+
this.IsLoading = true;
|
|
241
|
+
this.cdr.detectChanges();
|
|
242
|
+
try {
|
|
243
|
+
const runs = await this.fetchPromptRuns();
|
|
244
|
+
this.TotalRuns = runs.length;
|
|
245
|
+
if (runs.length > 0) {
|
|
246
|
+
const dayCounts = this.buildDayHourCounts(runs);
|
|
247
|
+
this.HeatmapCells = this.buildHeatmap(dayCounts);
|
|
248
|
+
this.DayDistributions = this.buildDayDistributions(dayCounts);
|
|
249
|
+
this.HourlyBars = this.buildHourlyBars(dayCounts);
|
|
250
|
+
this.PeakSummaries = this.buildPeakSummaries(dayCounts);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
console.error('Usage patterns data load failed:', err);
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
this.IsLoading = false;
|
|
258
|
+
this.cdr.detectChanges();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async fetchPromptRuns() {
|
|
262
|
+
const rv = new RunView();
|
|
263
|
+
const since = new Date();
|
|
264
|
+
switch (this.TimeRange) {
|
|
265
|
+
case '1h':
|
|
266
|
+
since.setHours(since.getHours() - 1);
|
|
267
|
+
break;
|
|
268
|
+
case '6h':
|
|
269
|
+
since.setHours(since.getHours() - 6);
|
|
270
|
+
break;
|
|
271
|
+
case '24h':
|
|
272
|
+
since.setDate(since.getDate() - 1);
|
|
273
|
+
break;
|
|
274
|
+
case '7d':
|
|
275
|
+
since.setDate(since.getDate() - 7);
|
|
276
|
+
break;
|
|
277
|
+
case '30d':
|
|
278
|
+
default:
|
|
279
|
+
since.setDate(since.getDate() - 30);
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
const sinceStr = since.toISOString();
|
|
283
|
+
const result = await rv.RunView({
|
|
284
|
+
EntityName: 'MJ: AI Prompt Runs',
|
|
285
|
+
ExtraFilter: `RunAt >= '${sinceStr}'`,
|
|
286
|
+
Fields: FIELDS,
|
|
287
|
+
ResultType: 'simple'
|
|
288
|
+
});
|
|
289
|
+
if (!result.Success) {
|
|
290
|
+
console.error('Failed to load prompt runs:', result.ErrorMessage);
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
return result.Results ?? [];
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Build a 7×24 count matrix: dayCounts[dayIndex][hour] = count.
|
|
297
|
+
* dayIndex 0 = Monday, 6 = Sunday. Uses the JS Date getDay() offset.
|
|
298
|
+
*/
|
|
299
|
+
buildDayHourCounts(runs) {
|
|
300
|
+
const counts = Array.from({ length: 7 }, () => new Array(24).fill(0));
|
|
301
|
+
for (const run of runs) {
|
|
302
|
+
const dt = new Date(run.RunAt);
|
|
303
|
+
const jsDay = dt.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
|
304
|
+
const dayIndex = jsDay === 0 ? 6 : jsDay - 1; // Convert to 0=Mon, 6=Sun
|
|
305
|
+
const hour = dt.getHours();
|
|
306
|
+
counts[dayIndex][hour]++;
|
|
307
|
+
}
|
|
308
|
+
return counts;
|
|
309
|
+
}
|
|
310
|
+
buildHeatmap(dayCounts) {
|
|
311
|
+
const maxCount = this.findMaxCount(dayCounts);
|
|
312
|
+
return dayCounts.map((hours, day) => hours.map((count, hour) => ({
|
|
313
|
+
Day: day,
|
|
314
|
+
Hour: hour,
|
|
315
|
+
Count: count,
|
|
316
|
+
Intensity: maxCount > 0 ? Math.round((count / maxCount) * 100) : 0
|
|
317
|
+
})));
|
|
318
|
+
}
|
|
319
|
+
buildDayDistributions(dayCounts) {
|
|
320
|
+
const daySums = dayCounts.map(hours => hours.reduce((sum, c) => sum + c, 0));
|
|
321
|
+
const maxDay = Math.max(...daySums, 1);
|
|
322
|
+
return daySums.map((count, i) => ({
|
|
323
|
+
DayIndex: i,
|
|
324
|
+
DayName: DAY_NAMES[i],
|
|
325
|
+
Count: count,
|
|
326
|
+
WidthPercent: Math.round((count / maxDay) * 100)
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
329
|
+
buildHourlyBars(dayCounts) {
|
|
330
|
+
const hourSums = HOURS.map(h => dayCounts.reduce((sum, dayHours) => sum + dayHours[h], 0));
|
|
331
|
+
const maxHour = Math.max(...hourSums, 1);
|
|
332
|
+
return hourSums.map((count, h) => ({
|
|
333
|
+
Hour: h,
|
|
334
|
+
Label: this.formatHourLabel(h),
|
|
335
|
+
Count: count,
|
|
336
|
+
HeightPercent: Math.round((count / maxHour) * 100)
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
buildPeakSummaries(dayCounts) {
|
|
340
|
+
const hourSums = HOURS.map(h => dayCounts.reduce((sum, dayHours) => sum + dayHours[h], 0));
|
|
341
|
+
const daySums = dayCounts.map(hours => hours.reduce((sum, c) => sum + c, 0));
|
|
342
|
+
// Busiest hour
|
|
343
|
+
const busiestHourIdx = this.indexOfMax(hourSums);
|
|
344
|
+
const busiestHour = {
|
|
345
|
+
Label: 'Busiest Hour',
|
|
346
|
+
Value: `${this.formatHourRange(busiestHourIdx)}`,
|
|
347
|
+
Count: hourSums[busiestHourIdx],
|
|
348
|
+
Icon: 'fa-solid fa-fire'
|
|
349
|
+
};
|
|
350
|
+
// Busiest day
|
|
351
|
+
const busiestDayIdx = this.indexOfMax(daySums);
|
|
352
|
+
const busiestDay = {
|
|
353
|
+
Label: 'Busiest Day',
|
|
354
|
+
Value: this.fullDayName(busiestDayIdx),
|
|
355
|
+
Count: daySums[busiestDayIdx],
|
|
356
|
+
Icon: 'fa-solid fa-calendar-day'
|
|
357
|
+
};
|
|
358
|
+
// Quietest cell (day+hour combo)
|
|
359
|
+
let minCount = Infinity;
|
|
360
|
+
let quietDay = 0;
|
|
361
|
+
let quietHour = 0;
|
|
362
|
+
for (let d = 0; d < 7; d++) {
|
|
363
|
+
for (let h = 0; h < 24; h++) {
|
|
364
|
+
if (dayCounts[d][h] < minCount) {
|
|
365
|
+
minCount = dayCounts[d][h];
|
|
366
|
+
quietDay = d;
|
|
367
|
+
quietHour = h;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const quietest = {
|
|
372
|
+
Label: 'Quietest Period',
|
|
373
|
+
Value: `${DAY_NAMES[quietDay]} ${this.formatHourLabel(quietHour)}`,
|
|
374
|
+
Count: minCount,
|
|
375
|
+
Icon: 'fa-solid fa-moon'
|
|
376
|
+
};
|
|
377
|
+
return [busiestHour, busiestDay, quietest];
|
|
378
|
+
}
|
|
379
|
+
// ── Utility helpers ──
|
|
380
|
+
findMaxCount(dayCounts) {
|
|
381
|
+
let max = 0;
|
|
382
|
+
for (const hours of dayCounts) {
|
|
383
|
+
for (const c of hours) {
|
|
384
|
+
if (c > max)
|
|
385
|
+
max = c;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return max;
|
|
389
|
+
}
|
|
390
|
+
indexOfMax(arr) {
|
|
391
|
+
let maxIdx = 0;
|
|
392
|
+
for (let i = 1; i < arr.length; i++) {
|
|
393
|
+
if (arr[i] > arr[maxIdx])
|
|
394
|
+
maxIdx = i;
|
|
395
|
+
}
|
|
396
|
+
return maxIdx;
|
|
397
|
+
}
|
|
398
|
+
formatHourLabel(h) {
|
|
399
|
+
if (h === 0)
|
|
400
|
+
return '12a';
|
|
401
|
+
if (h < 12)
|
|
402
|
+
return `${h}a`;
|
|
403
|
+
if (h === 12)
|
|
404
|
+
return '12p';
|
|
405
|
+
return `${h - 12}p`;
|
|
406
|
+
}
|
|
407
|
+
formatHourRange(h) {
|
|
408
|
+
const start = this.formatHour12(h);
|
|
409
|
+
const end = this.formatHour12((h + 1) % 24);
|
|
410
|
+
return `${start} - ${end}`;
|
|
411
|
+
}
|
|
412
|
+
formatHour12(h) {
|
|
413
|
+
if (h === 0)
|
|
414
|
+
return '12:00 AM';
|
|
415
|
+
if (h < 12)
|
|
416
|
+
return `${h}:00 AM`;
|
|
417
|
+
if (h === 12)
|
|
418
|
+
return '12:00 PM';
|
|
419
|
+
return `${h - 12}:00 PM`;
|
|
420
|
+
}
|
|
421
|
+
fullDayName(idx) {
|
|
422
|
+
const names = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
|
423
|
+
return names[idx] ?? 'Unknown';
|
|
424
|
+
}
|
|
425
|
+
static ɵfac = function AnalyticsUsagePatternsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AnalyticsUsagePatternsComponent)(); };
|
|
426
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AnalyticsUsagePatternsComponent, selectors: [["app-analytics-usage-patterns"]], inputs: { TimeRange: "TimeRange" }, outputs: { TimeRangeChange: "TimeRangeChange" }, standalone: false, decls: 4, vars: 7, consts: [[3, "TimeRangeChange", "TimeRange", "ShowModelFilter", "ShowAgentFilter", "ShowPromptFilter", "ShowStatusFilter", "ShowCompareToggle"], [1, "loading-container"], [1, "empty-state"], ["text", "Analyzing usage patterns..."], [1, "fa-solid", "fa-chart-line", "empty-state__icon"], [1, "empty-state__title"], [1, "empty-state__subtitle"], [1, "panel"], [1, "panel__header"], [1, "panel__title"], [1, "panel__subtitle"], [1, "heatmap-wrapper"], [1, "heatmap-grid"], [1, "heatmap-corner"], [1, "heatmap-hour-label"], [1, "two-col-row"], [1, "day-distribution"], [1, "day-bar-row"], [1, "peak-cards"], [1, "peak-card"], [1, "hourly-chart"], [1, "hourly-bars"], [1, "hourly-bar-col", 3, "title"], [1, "heatmap-day-label"], [1, "heatmap-cell", 3, "background", "title"], [1, "heatmap-cell", 3, "title"], [1, "heatmap-cell__count"], [1, "day-bar-label"], [1, "day-bar-track"], [1, "day-bar-fill"], [1, "day-bar-count"], [1, "peak-card__icon"], [1, "peak-card__content"], [1, "peak-card__label"], [1, "peak-card__value"], [1, "peak-card__count"], [1, "hourly-bar"], [1, "hourly-bar-label"]], template: function AnalyticsUsagePatternsComponent_Template(rf, ctx) { if (rf & 1) {
|
|
427
|
+
i0.ɵɵelementStart(0, "app-analytics-filter-bar", 0);
|
|
428
|
+
i0.ɵɵlistener("TimeRangeChange", function AnalyticsUsagePatternsComponent_Template_app_analytics_filter_bar_TimeRangeChange_0_listener($event) { return ctx.OnTimeRangeChange($event); });
|
|
429
|
+
i0.ɵɵelementEnd();
|
|
430
|
+
i0.ɵɵconditionalCreate(1, AnalyticsUsagePatternsComponent_Conditional_1_Template, 2, 0, "div", 1)(2, AnalyticsUsagePatternsComponent_Conditional_2_Template, 6, 0, "div", 2)(3, AnalyticsUsagePatternsComponent_Conditional_3_Template, 38, 0);
|
|
431
|
+
} if (rf & 2) {
|
|
432
|
+
i0.ɵɵproperty("TimeRange", ctx.TimeRange)("ShowModelFilter", false)("ShowAgentFilter", false)("ShowPromptFilter", false)("ShowStatusFilter", false)("ShowCompareToggle", false);
|
|
433
|
+
i0.ɵɵadvance();
|
|
434
|
+
i0.ɵɵconditional(ctx.IsLoading ? 1 : ctx.TotalRuns === 0 ? 2 : 3);
|
|
435
|
+
} }, dependencies: [i1.LoadingComponent, i2.AnalyticsFilterBarComponent, i3.DecimalPipe], styles: ["[_nghost-%COMP%] { display: block; }\n\n \n\n .section-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n }\n\n .section-header__left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .section-header__icon[_ngcontent-%COMP%] {\n font-size: 18px;\n color: var(--mj-brand-primary);\n }\n\n .section-header__title[_ngcontent-%COMP%] {\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .time-range-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 5px 12px;\n border-radius: 20px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n }\n\n \n\n .loading-container[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 24px;\n text-align: center;\n }\n\n .empty-state__icon[_ngcontent-%COMP%] {\n font-size: 36px;\n color: var(--mj-text-muted);\n margin-bottom: 12px;\n }\n\n .empty-state__title[_ngcontent-%COMP%] {\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 4px;\n }\n\n .empty-state__subtitle[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-muted);\n }\n\n \n\n .panel[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n margin-bottom: 16px;\n }\n\n .panel__header[_ngcontent-%COMP%] {\n display: flex;\n align-items: baseline;\n gap: 10px;\n margin-bottom: 16px;\n }\n\n .panel__title[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .panel__subtitle[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n\n \n\n .heatmap-wrapper[_ngcontent-%COMP%] {\n overflow-x: auto;\n }\n\n .heatmap-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 44px repeat(24, 1fr);\n gap: 2px;\n min-width: 600px;\n }\n\n .heatmap-corner[_ngcontent-%COMP%] {\n \n\n }\n\n .heatmap-hour-label[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-muted);\n text-align: center;\n padding-bottom: 4px;\n font-weight: 500;\n }\n\n .heatmap-day-label[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-secondary);\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding-right: 6px;\n }\n\n .heatmap-cell[_ngcontent-%COMP%] {\n min-height: 28px;\n border-radius: 3px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: default;\n transition: opacity 0.15s ease;\n }\n\n .heatmap-cell[_ngcontent-%COMP%]:hover {\n opacity: 0.8;\n outline: 1px solid var(--mj-border-strong);\n }\n\n .heatmap-cell__count[_ngcontent-%COMP%] {\n font-size: 9px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n \n\n .two-col-row[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n }\n\n @media (max-width: 1024px) {\n .two-col-row[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n }\n\n \n\n .day-distribution[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .day-bar-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .day-bar-label[_ngcontent-%COMP%] {\n width: 36px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-align: right;\n flex-shrink: 0;\n }\n\n .day-bar-track[_ngcontent-%COMP%] {\n flex: 1;\n height: 22px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n overflow: hidden;\n }\n\n .day-bar-fill[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 4px;\n transition: width 0.3s ease;\n min-width: 2px;\n }\n\n .day-bar-count[_ngcontent-%COMP%] {\n width: 48px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-align: right;\n flex-shrink: 0;\n }\n\n \n\n .peak-cards[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .peak-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n }\n\n .peak-card__icon[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n flex-shrink: 0;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n }\n\n .peak-card[_ngcontent-%COMP%]:nth-child(2) .peak-card__icon[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n }\n\n .peak-card[_ngcontent-%COMP%]:nth-child(3) .peak-card__icon[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n }\n\n .peak-card__content[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .peak-card__label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n }\n\n .peak-card__value[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n\n .peak-card__count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n\n \n\n .hourly-chart[_ngcontent-%COMP%] {\n padding-top: 8px;\n }\n\n .hourly-bars[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-end;\n gap: 3px;\n height: 160px;\n }\n\n .hourly-bar-col[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n height: 100%;\n cursor: default;\n }\n\n .hourly-bar[_ngcontent-%COMP%] {\n width: 100%;\n min-height: 2px;\n background: color-mix(in srgb, var(--mj-brand-primary) 35%, var(--mj-bg-surface));\n border-radius: 3px 3px 0 0;\n transition: background 0.15s ease, height 0.3s ease;\n }\n\n .hourly-bar-col[_ngcontent-%COMP%]:hover .hourly-bar[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 60%, var(--mj-bg-surface));\n }\n\n .hourly-bar-label[_ngcontent-%COMP%] {\n font-size: 9px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n white-space: nowrap;\n }\n\n \n\n @media (max-width: 1024px) {\n .heatmap-grid[_ngcontent-%COMP%] {\n grid-template-columns: 36px repeat(24, 1fr);\n }\n\n .heatmap-cell[_ngcontent-%COMP%] {\n min-height: 22px;\n }\n\n .heatmap-cell__count[_ngcontent-%COMP%] {\n font-size: 8px;\n }\n\n .hourly-bars[_ngcontent-%COMP%] {\n height: 120px;\n }\n }"] });
|
|
436
|
+
}
|
|
437
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AnalyticsUsagePatternsComponent, [{
|
|
438
|
+
type: Component,
|
|
439
|
+
args: [{ standalone: false, selector: 'app-analytics-usage-patterns', template: `
|
|
440
|
+
<!-- Filter Bar -->
|
|
441
|
+
<app-analytics-filter-bar
|
|
442
|
+
[TimeRange]="TimeRange"
|
|
443
|
+
[ShowModelFilter]="false"
|
|
444
|
+
[ShowAgentFilter]="false"
|
|
445
|
+
[ShowPromptFilter]="false"
|
|
446
|
+
[ShowStatusFilter]="false"
|
|
447
|
+
[ShowCompareToggle]="false"
|
|
448
|
+
(TimeRangeChange)="OnTimeRangeChange($event)"
|
|
449
|
+
></app-analytics-filter-bar>
|
|
450
|
+
|
|
451
|
+
@if (IsLoading) {
|
|
452
|
+
<div class="loading-container">
|
|
453
|
+
<mj-loading text="Analyzing usage patterns..."></mj-loading>
|
|
454
|
+
</div>
|
|
455
|
+
} @else if (TotalRuns === 0) {
|
|
456
|
+
<div class="empty-state">
|
|
457
|
+
<i class="fa-solid fa-chart-line empty-state__icon"></i>
|
|
458
|
+
<div class="empty-state__title">No Data Available</div>
|
|
459
|
+
<div class="empty-state__subtitle">No prompt runs found in the last 30 days.</div>
|
|
460
|
+
</div>
|
|
461
|
+
} @else {
|
|
462
|
+
<!-- Heatmap -->
|
|
463
|
+
<div class="panel">
|
|
464
|
+
<div class="panel__header">
|
|
465
|
+
<h4 class="panel__title">Time-of-Day Heatmap</h4>
|
|
466
|
+
<span class="panel__subtitle">Execution count by day and hour</span>
|
|
467
|
+
</div>
|
|
468
|
+
<div class="heatmap-wrapper">
|
|
469
|
+
<div class="heatmap-grid">
|
|
470
|
+
<!-- Corner spacer -->
|
|
471
|
+
<div class="heatmap-corner"></div>
|
|
472
|
+
<!-- Hour labels -->
|
|
473
|
+
@for (h of Hours; track h) {
|
|
474
|
+
<div class="heatmap-hour-label">{{ h }}</div>
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
<!-- Rows: one per day -->
|
|
478
|
+
@for (day of DayNames; track day; let d = $index) {
|
|
479
|
+
<div class="heatmap-day-label">{{ day }}</div>
|
|
480
|
+
@for (h of Hours; track h) {
|
|
481
|
+
<div
|
|
482
|
+
class="heatmap-cell"
|
|
483
|
+
[style.background]="getCellBackground(d, h)"
|
|
484
|
+
[title]="getCellTooltip(d, h)">
|
|
485
|
+
@if (getCellCount(d, h) > 0) {
|
|
486
|
+
<span class="heatmap-cell__count">{{ getCellCount(d, h) }}</span>
|
|
487
|
+
}
|
|
488
|
+
</div>
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
|
|
495
|
+
<!-- Day Distribution + Peak Summary -->
|
|
496
|
+
<div class="two-col-row">
|
|
497
|
+
<!-- Day-of-Week Distribution -->
|
|
498
|
+
<div class="panel">
|
|
499
|
+
<div class="panel__header">
|
|
500
|
+
<h4 class="panel__title">Day-of-Week Distribution</h4>
|
|
501
|
+
</div>
|
|
502
|
+
<div class="day-distribution">
|
|
503
|
+
@for (d of DayDistributions; track d.DayIndex) {
|
|
504
|
+
<div class="day-bar-row">
|
|
505
|
+
<span class="day-bar-label">{{ d.DayName }}</span>
|
|
506
|
+
<div class="day-bar-track">
|
|
507
|
+
<div
|
|
508
|
+
class="day-bar-fill"
|
|
509
|
+
[style.width.%]="d.WidthPercent">
|
|
510
|
+
</div>
|
|
511
|
+
</div>
|
|
512
|
+
<span class="day-bar-count">{{ d.Count | number }}</span>
|
|
513
|
+
</div>
|
|
514
|
+
}
|
|
515
|
+
</div>
|
|
516
|
+
</div>
|
|
517
|
+
|
|
518
|
+
<!-- Peak Hours Summary -->
|
|
519
|
+
<div class="panel">
|
|
520
|
+
<div class="panel__header">
|
|
521
|
+
<h4 class="panel__title">Peak Hours Summary</h4>
|
|
522
|
+
</div>
|
|
523
|
+
<div class="peak-cards">
|
|
524
|
+
@for (peak of PeakSummaries; track peak.Label) {
|
|
525
|
+
<div class="peak-card">
|
|
526
|
+
<div class="peak-card__icon">
|
|
527
|
+
<i [class]="peak.Icon"></i>
|
|
528
|
+
</div>
|
|
529
|
+
<div class="peak-card__content">
|
|
530
|
+
<div class="peak-card__label">{{ peak.Label }}</div>
|
|
531
|
+
<div class="peak-card__value">{{ peak.Value }}</div>
|
|
532
|
+
<div class="peak-card__count">{{ peak.Count | number }} runs</div>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
}
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
<!-- Hourly Throughput -->
|
|
541
|
+
<div class="panel">
|
|
542
|
+
<div class="panel__header">
|
|
543
|
+
<h4 class="panel__title">Hourly Throughput</h4>
|
|
544
|
+
<span class="panel__subtitle">Total runs per hour (all days combined)</span>
|
|
545
|
+
</div>
|
|
546
|
+
<div class="hourly-chart">
|
|
547
|
+
<div class="hourly-bars">
|
|
548
|
+
@for (bar of HourlyBars; track bar.Hour) {
|
|
549
|
+
<div class="hourly-bar-col" [title]="bar.Label + ': ' + bar.Count + ' runs'">
|
|
550
|
+
<div
|
|
551
|
+
class="hourly-bar"
|
|
552
|
+
[style.height.%]="bar.HeightPercent">
|
|
553
|
+
</div>
|
|
554
|
+
<span class="hourly-bar-label">{{ bar.Label }}</span>
|
|
555
|
+
</div>
|
|
556
|
+
}
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
}
|
|
561
|
+
`, styles: ["\n :host { display: block; }\n\n /* \u2500\u2500 Header \u2500\u2500 */\n .section-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n }\n\n .section-header__left {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .section-header__icon {\n font-size: 18px;\n color: var(--mj-brand-primary);\n }\n\n .section-header__title {\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .time-range-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 5px 12px;\n border-radius: 20px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n }\n\n /* \u2500\u2500 Loading / Empty \u2500\u2500 */\n .loading-container {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 24px;\n text-align: center;\n }\n\n .empty-state__icon {\n font-size: 36px;\n color: var(--mj-text-muted);\n margin-bottom: 12px;\n }\n\n .empty-state__title {\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 4px;\n }\n\n .empty-state__subtitle {\n font-size: 13px;\n color: var(--mj-text-muted);\n }\n\n /* \u2500\u2500 Panels \u2500\u2500 */\n .panel {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n margin-bottom: 16px;\n }\n\n .panel__header {\n display: flex;\n align-items: baseline;\n gap: 10px;\n margin-bottom: 16px;\n }\n\n .panel__title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .panel__subtitle {\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n\n /* \u2500\u2500 Heatmap \u2500\u2500 */\n .heatmap-wrapper {\n overflow-x: auto;\n }\n\n .heatmap-grid {\n display: grid;\n grid-template-columns: 44px repeat(24, 1fr);\n gap: 2px;\n min-width: 600px;\n }\n\n .heatmap-corner {\n /* empty top-left cell */\n }\n\n .heatmap-hour-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n text-align: center;\n padding-bottom: 4px;\n font-weight: 500;\n }\n\n .heatmap-day-label {\n font-size: 11px;\n color: var(--mj-text-secondary);\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding-right: 6px;\n }\n\n .heatmap-cell {\n min-height: 28px;\n border-radius: 3px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: default;\n transition: opacity 0.15s ease;\n }\n\n .heatmap-cell:hover {\n opacity: 0.8;\n outline: 1px solid var(--mj-border-strong);\n }\n\n .heatmap-cell__count {\n font-size: 9px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n /* \u2500\u2500 Two Column Row \u2500\u2500 */\n .two-col-row {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n }\n\n @media (max-width: 1024px) {\n .two-col-row {\n grid-template-columns: 1fr;\n }\n }\n\n /* \u2500\u2500 Day Distribution \u2500\u2500 */\n .day-distribution {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .day-bar-row {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .day-bar-label {\n width: 36px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-align: right;\n flex-shrink: 0;\n }\n\n .day-bar-track {\n flex: 1;\n height: 22px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n overflow: hidden;\n }\n\n .day-bar-fill {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 4px;\n transition: width 0.3s ease;\n min-width: 2px;\n }\n\n .day-bar-count {\n width: 48px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-align: right;\n flex-shrink: 0;\n }\n\n /* \u2500\u2500 Peak Cards \u2500\u2500 */\n .peak-cards {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .peak-card {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n }\n\n .peak-card__icon {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n flex-shrink: 0;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n }\n\n .peak-card:nth-child(2) .peak-card__icon {\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n }\n\n .peak-card:nth-child(3) .peak-card__icon {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n }\n\n .peak-card__content {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .peak-card__label {\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n }\n\n .peak-card__value {\n font-size: 14px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n\n .peak-card__count {\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n\n /* \u2500\u2500 Hourly Throughput Chart \u2500\u2500 */\n .hourly-chart {\n padding-top: 8px;\n }\n\n .hourly-bars {\n display: flex;\n align-items: flex-end;\n gap: 3px;\n height: 160px;\n }\n\n .hourly-bar-col {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n height: 100%;\n cursor: default;\n }\n\n .hourly-bar {\n width: 100%;\n min-height: 2px;\n background: color-mix(in srgb, var(--mj-brand-primary) 35%, var(--mj-bg-surface));\n border-radius: 3px 3px 0 0;\n transition: background 0.15s ease, height 0.3s ease;\n }\n\n .hourly-bar-col:hover .hourly-bar {\n background: color-mix(in srgb, var(--mj-brand-primary) 60%, var(--mj-bg-surface));\n }\n\n .hourly-bar-label {\n font-size: 9px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n white-space: nowrap;\n }\n\n /* \u2500\u2500 Responsive \u2500\u2500 */\n @media (max-width: 1024px) {\n .heatmap-grid {\n grid-template-columns: 36px repeat(24, 1fr);\n }\n\n .heatmap-cell {\n min-height: 22px;\n }\n\n .heatmap-cell__count {\n font-size: 8px;\n }\n\n .hourly-bars {\n height: 120px;\n }\n }\n "] }]
|
|
562
|
+
}], null, { TimeRange: [{
|
|
563
|
+
type: Input
|
|
564
|
+
}], TimeRangeChange: [{
|
|
565
|
+
type: Output
|
|
566
|
+
}] }); })();
|
|
567
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AnalyticsUsagePatternsComponent, { className: "AnalyticsUsagePatternsComponent", filePath: "src/AI/components/analytics/usage-patterns/usage-patterns.component.ts", lineNumber: 534 }); })();
|
|
568
|
+
export function LoadAnalyticsUsagePatterns() { }
|
|
569
|
+
//# sourceMappingURL=usage-patterns.component.js.map
|