@memberjunction/ng-dashboards 6.1.0-edge.4 → 6.1.0-edge.5
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/cost-budget/cost-budget.component.d.ts +7 -1
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts.map +1 -1
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.js +35 -12
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.js.map +1 -1
- package/dist/Admin/base-admin-container.component.d.ts +8 -0
- package/dist/Admin/base-admin-container.component.d.ts.map +1 -1
- package/dist/Admin/base-admin-container.component.js +18 -0
- package/dist/Admin/base-admin-container.component.js.map +1 -1
- package/package.json +67 -63
|
@@ -79,7 +79,13 @@ export declare class AnalyticsCostBudgetComponent extends BaseAngularComponent i
|
|
|
79
79
|
private rateKey;
|
|
80
80
|
/** The cache rate for a run's model+vendor, or undefined when no active cost row is configured. */
|
|
81
81
|
private rateFor;
|
|
82
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Build the per-model+vendor rate lookup, normalizing each per-unit price to currency-per-token.
|
|
84
|
+
*
|
|
85
|
+
* The scale comes from the unit type's DriverClass, not its display name — the name is editable
|
|
86
|
+
* metadata (`Per 1M Tokens`) while the driver class is the contract the pricing drivers register
|
|
87
|
+
* under, so this cannot drift the way a hardcoded name table does.
|
|
88
|
+
*/
|
|
83
89
|
private buildCacheRateMap;
|
|
84
90
|
/** Sum net cache savings across a set of runs using each run's model+vendor rate. */
|
|
85
91
|
private sumCacheSavings;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cost-budget.component.d.ts","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/cost-budget/cost-budget.component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACuB,YAAY,EACtC,MAAM,EAAE,SAAS,EACpB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"cost-budget.component.d.ts","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/cost-budget/cost-budget.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ import { Component, Input, Output, EventEmitter, ChangeDetectorRef, inject } fro
|
|
|
8
8
|
import { Subject } from 'rxjs';
|
|
9
9
|
import { RunView } from '@memberjunction/core';
|
|
10
10
|
import { NormalizeUUID } from '@memberjunction/global';
|
|
11
|
+
import { TOKEN_PRICE_UNIT_TYPE_DIVISORS } from '@memberjunction/ai-engine-base';
|
|
11
12
|
import { cacheHitRate, hasCacheActivity, netCacheSavings } from '../../../services/cache-metrics';
|
|
12
13
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
13
14
|
import * as i0 from "@angular/core";
|
|
@@ -294,12 +295,6 @@ const FIELDS = [
|
|
|
294
295
|
'ID', 'RunAt', 'Cost', 'TotalCost', 'TokensPrompt', 'TokensCompletion',
|
|
295
296
|
'TokensUsed', 'TokensCacheRead', 'TokensCacheWrite', 'ModelID', 'Model', 'VendorID', 'Vendor', 'Success'
|
|
296
297
|
];
|
|
297
|
-
// AIModelCost UnitType name -> token divisor, matching the BasePriceUnitType driver classes.
|
|
298
|
-
const UNIT_DIVISORS = {
|
|
299
|
-
'Per Million Tokens': 1_000_000,
|
|
300
|
-
'Per Hundred Thousand Tokens': 100_000,
|
|
301
|
-
'Per Thousand Tokens': 1_000
|
|
302
|
-
};
|
|
303
298
|
const TIME_RANGE_OPTIONS = ['Today', '7d', '30d', 'MTD'];
|
|
304
299
|
const TREEMAP_COLORS = [
|
|
305
300
|
'var(--mj-brand-primary)',
|
|
@@ -391,7 +386,7 @@ export class AnalyticsCostBudgetComponent extends BaseAngularComponent {
|
|
|
391
386
|
const modelFilter = this.buildModelFilter();
|
|
392
387
|
const currentFilter = this.combineDateAndModelFilter(currentStart, now, modelFilter);
|
|
393
388
|
const prevFilter = this.combineDateAndModelFilter(previousStart, currentStart, modelFilter);
|
|
394
|
-
const [currentResult, prevResult, rateResult] = await rv.RunViews([
|
|
389
|
+
const [currentResult, prevResult, rateResult, unitTypeResult] = await rv.RunViews([
|
|
395
390
|
{
|
|
396
391
|
EntityName: 'MJ: AI Prompt Runs',
|
|
397
392
|
ExtraFilter: currentFilter,
|
|
@@ -409,13 +404,26 @@ export class AnalyticsCostBudgetComponent extends BaseAngularComponent {
|
|
|
409
404
|
{
|
|
410
405
|
EntityName: 'MJ: AI Model Costs',
|
|
411
406
|
ExtraFilter: `Status='Active' AND ProcessingType='Realtime'`,
|
|
412
|
-
Fields: ['ModelID', 'VendorID', 'InputPricePerUnit', 'OutputPricePerUnit', 'CacheReadPricePerUnit', 'CacheWritePricePerUnit', '
|
|
407
|
+
Fields: ['ModelID', 'VendorID', 'InputPricePerUnit', 'OutputPricePerUnit', 'CacheReadPricePerUnit', 'CacheWritePricePerUnit', 'UnitTypeID'],
|
|
408
|
+
ResultType: 'simple'
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
EntityName: 'MJ: AI Model Price Unit Types',
|
|
412
|
+
Fields: ['ID', 'DriverClass'],
|
|
413
413
|
ResultType: 'simple'
|
|
414
414
|
}
|
|
415
415
|
]);
|
|
416
416
|
this.allRuns = (currentResult?.Results ?? []);
|
|
417
417
|
this.previousPeriodRuns = (prevResult?.Results ?? []);
|
|
418
|
-
|
|
418
|
+
// A failed unit-type view is NOT the same as "these rows are unpriceable". Without the
|
|
419
|
+
// driver classes every rate row falls into the `continue` below, and cache savings
|
|
420
|
+
// render as a confident 0 instead of an error — the figure most likely to be believed.
|
|
421
|
+
// Say so rather than let the empty map speak for it.
|
|
422
|
+
if (unitTypeResult && !unitTypeResult.Success) {
|
|
423
|
+
console.error('Cost & Budget: price unit types failed to load; cache-savings figures will read 0. ' +
|
|
424
|
+
unitTypeResult.ErrorMessage);
|
|
425
|
+
}
|
|
426
|
+
this.buildCacheRateMap(rateResult?.Results ?? [], unitTypeResult?.Results ?? []);
|
|
419
427
|
this.computeKpis();
|
|
420
428
|
this.computeDailyBars();
|
|
421
429
|
this.computeTreemap();
|
|
@@ -438,11 +446,26 @@ export class AnalyticsCostBudgetComponent extends BaseAngularComponent {
|
|
|
438
446
|
rateFor(run) {
|
|
439
447
|
return this.cacheRates.get(this.rateKey(run.ModelID, run.VendorID));
|
|
440
448
|
}
|
|
441
|
-
/**
|
|
442
|
-
|
|
449
|
+
/**
|
|
450
|
+
* Build the per-model+vendor rate lookup, normalizing each per-unit price to currency-per-token.
|
|
451
|
+
*
|
|
452
|
+
* The scale comes from the unit type's DriverClass, not its display name — the name is editable
|
|
453
|
+
* metadata (`Per 1M Tokens`) while the driver class is the contract the pricing drivers register
|
|
454
|
+
* under, so this cannot drift the way a hardcoded name table does.
|
|
455
|
+
*/
|
|
456
|
+
buildCacheRateMap(rows, unitTypes) {
|
|
443
457
|
this.cacheRates.clear();
|
|
458
|
+
const driverClassByUnitType = new Map(unitTypes.filter(u => u.DriverClass).map(u => [NormalizeUUID(u.ID), u.DriverClass]));
|
|
444
459
|
for (const row of rows) {
|
|
445
|
-
const
|
|
460
|
+
const driverClass = driverClassByUnitType.get(NormalizeUUID(row.UnitTypeID ?? ''));
|
|
461
|
+
const divisor = TOKEN_PRICE_UNIT_TYPE_DIVISORS[driverClass ?? ''];
|
|
462
|
+
if (divisor === undefined) {
|
|
463
|
+
// A non-token unit type (per minute/hour/image), or one this build has no driver
|
|
464
|
+
// for. Defaulting to the per-1M-token divisor would divide an hourly audio rate by
|
|
465
|
+
// a million and report a savings figure that is pure noise; no rate at all is the
|
|
466
|
+
// honest answer.
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
446
469
|
const inputRate = (row.InputPricePerUnit ?? 0) / divisor;
|
|
447
470
|
// Cache read/write fall back to the input rate when no distinct rate is recorded — exactly
|
|
448
471
|
// as the server-side cost calculator does — which makes the corresponding savings term 0.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cost-budget.component.js","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/cost-budget/cost-budget.component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACH,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EACnB,iBAAiB,EAAE,MAAM,EAC/C,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAA+B,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAC/H,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;;;;;;;;IAsGzD,8BAA+B;IAC3B,gCAAqD;IACzD,iBAAM;;;IAiBsB,wBAAoC;;;IAEpC,wBAAsC;;;IAN9C,+BAE6D;IAGvD,AAFF,+HAAmC,yGAES;IAG5C,YACJ;;IAAA,iBAAM;;;IAPD,AADA,+DAAmD,qDACI;IACxD,cAIC;IAJD,gGAIC;IACD,eACJ;IADI,qFACJ;;;IAhBR,AADJ,+BAAsE,cAC5C;IAClB,oBAA0B;IAC9B,iBAAM;IAEF,AADJ,+BAAyB,cACE;IAAA,YAAe;IAAA,iBAAM;IAC5C,+BAAuB;IAAA,YAAe;IAAA,iBAAM;IAC5C,mHAAyB;IAajC,AADI,iBAAM,EACJ;;;IApBgB,2DAA+C;IAE1D,eAAkB;IAAlB,0BAAkB;IAGE,eAAe;IAAf,kCAAe;IACf,eAAe;IAAf,kCAAe;IACtC,cAWC;IAXD,+CAWC;;;IAkBD,qCAC+C;;;IAM/B,AADJ,+BAAwD,eAC5B;IAAA,mBAAG;IAC/B,AAD+B,iBAAO,EAChC;;;IAFgB,oDAAiC;;;IAKvD,+BAA2E;IACvE,0BAIO;IACP,+BAAuB;IAAA,YAAe;IAC1C,AAD0C,iBAAM,EAC1C;;;;IAPe,gFAAqD;IAIlE,cAAoC;IAApC,mDAAoC;IADpC,gDAAoC;IAGjB,eAAe;IAAf,kCAAe;;;IAblD,AADJ,+BAAuB,cACS;IACxB,4HAA0B;IAK1B,6HASC;IAET,AADI,iBAAM,EACJ;;;IAhBE,eAIC;IAJD,oDAIC;IACD,cASC;IATD,+BASC;;;IAiBT,qCACkC;;;IAI1B,+BAI4G;;IACxG,gCAA4B;IAAA,YAAgB;IAAA,iBAAO;IACnD,gCAA4B;IAAA,YAA+B;IAAA,iBAAO;IAClE,gCAA0B;IAAA,YAAoC;;IAClE,AADkE,iBAAO,EACnE;;;;IALF,AADA,2CAA+B,oCACI;IACnC,iJAAuG;IAC3E,eAAgB;IAAhB,mCAAgB;IAChB,eAA+B;IAA/B,yDAA+B;IACjC,eAAoC;IAApC,+EAAoC;;;IAT1E,+BAA0B;IACtB,8HAUC;IACL,iBAAM;;;IAXF,cAUC;IAVD,kCAUC;;;IAsCG,AAAJ,0BAAI,aAAmC;IAAA,iCAAiB;IAAK,AAAL,iBAAK,EAAK;;;IAI9D,AADJ,0BAAI,aACuB;IAAA,YAAe;IAAA,iBAAK;IAC3C,8BAAwB;IAAA,YAAgB;IAAA,iBAAK;IAC7C,8BAAyB;IAAA,YAAuB;;IAAA,iBAAK;IACrD,8BAAyB;IAAA,YAA8B;;IAAA,iBAAK;IAC5D,+BAAyB;IAAA,aAA+B;;IAAA,iBAAK;IAC7D,+BAAyB;IAAA,aAAgH;;IAAA,iBAAK;IAC9I,+BAAyB;IAAA,aAAsC;IAAA,iBAAK;IACpE,+BAAyB;IAAA,aAAuC;IAAA,iBAAK;IACrE,+BAAmC;IAAA,aAAmC;IAAA,iBAAK;IAC3E,+BAAyB;IAAA,aAAsE;IAAA,iBAAK;IACpG,+BAAyB;IAAA,aAA0C;;IACvE,AADuE,iBAAK,EACvE;;;;IAXsB,eAAe;IAAf,kCAAe;IACd,eAAgB;IAAhB,mCAAgB;IACf,eAAuB;IAAvB,wDAAuB;IACvB,eAA8B;IAA9B,gEAA8B;IAC9B,eAA+B;IAA/B,iEAA+B;IAC/B,eAAgH;IAAhH,wJAAgH;IAChH,eAAsC;IAAtC,gEAAsC;IACtC,eAAuC;IAAvC,iEAAuC;IAC7B,eAAmC;IAAnC,6DAAmC;IAC7C,eAAsE;IAAtE,wGAAsE;IACtE,eAA0C;IAA1C,sFAA0C;;;;IA5I3F,8BAAqB;IACjB,6GAsBC;IACL,iBAAM;IAOM,AADJ,AADJ,AAFJ,8BAAqB,aAEc,aACD,aACW;IAC7B,uBAA2D;IAC3D,kCACJ;IACJ,AADI,iBAAM,EACJ;IACN,8BAAwB;IAIlB,AAHF,0HAA8B,yFAGrB;IAsBjB,AADI,iBAAM,EACJ;IAKE,AADJ,AADJ,gCAAiC,cACH,cACW;IAC7B,yBAAwD;IACxD,iCACJ;IACJ,AADI,iBAAM,EACJ;IACN,gCAA0B;IAIpB,AAHF,0HAAiC,yFAGxB;IAiBrB,AADI,AADI,iBAAM,EACJ,EACJ;IAKE,AADJ,AADJ,gCAAmB,cACW,cACW;IAC7B,yBAAoD;IACpD,gCACJ;IAAA,iBAAM;IACN,mCAAiD;IAAtB,iMAAS,kBAAW,KAAC;IAC5C,yBAAoC;IACpC,6BACJ;IACJ,AADI,iBAAS,EACP;IAKU,AADJ,AADJ,AADJ,AADJ,gCAA2B,iBACG,aACf,UACC,UACI;IAAA,sBAAK;IAAA,iBAAK;IACd,2BAAI;IAAA,uBAAM;IAAA,iBAAK;IACf,+BAAwB;IAAA,qBAAI;IAAA,iBAAK;IACjC,+BAAwB;IAAA,6BAAY;IAAA,iBAAK;IACzC,+BAAwB;IAAA,8BAAa;IAAA,iBAAK;IAC1C,+BAA8F;IAAA,0BAAS;IAAA,iBAAK;IAC5G,+BAAwB;IAAA,2BAAU;IAAA,iBAAK;IACvC,+BAAwB;IAAA,4BAAW;IAAA,iBAAK;IACxC,+BAAwB;IAAA,2BAAU;IAAA,iBAAK;IACvC,+BAA6H;IAAA,4BAAW;IAAA,iBAAK;IAC7I,+BAAwB;IAAA,2BAAU;IAE1C,AADI,AADsC,iBAAK,EACtC,EACD;IACR,8BAAO;IACH,0GAAoC;IAGpC,mHAcC;IAIjB,AADI,AADI,AADI,iBAAQ,EACJ,EACN,EACJ;;;IAjJF,cAsBC;IAtBD,8BAsBC;IAcO,eAuBC;IAvBD,yDAuBC;IAaD,eAiBC;IAjBD,4DAiBC;IAmCG,gBAEC;IAFD,+DAEC;IACD,cAcC;IAdD,qCAcC;;AA1L7B,MAAM,MAAM,GAAG;IACX,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,kBAAkB;IACtE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS;CAC3G,CAAC;AAYF,6FAA6F;AAC7F,MAAM,aAAa,GAA2B;IAC1C,oBAAoB,EAAE,SAAS;IAC/B,6BAA6B,EAAE,OAAO;IACtC,qBAAqB,EAAE,KAAK;CAC/B,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAEzD,MAAM,cAAc,GAAG;IACnB,yBAAyB;IACzB,uDAAuD;IACvD,uBAAuB;IACvB,0BAA0B;IAC1B,0BAA0B;IAC1B,yBAAyB;CAC5B,CAAC;AAufF,MAAM,OAAO,4BAA6B,SAAQ,oBAAoB;IAC1D,UAAU,GAAG,IAAI,CAAC;IAC1B,IACI,SAAS,CAAC,KAAa;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;IAC5D,CAAC;IACD,IAAI,SAAS,KAAa,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAE3C,QAAQ,GAAsB,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC5F,IACI,OAAO,CAAC,KAAwB;QAChC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAC5E,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC/D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,OAAO,IAAI,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;IACrD,CAAC;IACD,IAAI,OAAO,KAAwB,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAElD,mBAAmB,CAAC,CAAoB,EAAE,CAAoB;QAClE,MAAM,OAAO,GAAG,CAAC,CAAW,EAAE,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrG,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC1I,CAAC;IAES,eAAe,GAAG,IAAI,YAAY,EAAU,CAAC;IAC7C,aAAa,GAAG,IAAI,YAAY,EAAqB,CAAC;IAExD,WAAW,GAAG,KAAK,CAAC;IAEpB,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAChC,QAAQ,GAAG,IAAI,OAAO,EAAQ,CAAC;IAEhC,SAAS,GAAG,KAAK,CAAC;IAClB,oBAAoB,GAAG,kBAAkB,CAAC;IAE1C,QAAQ,GAAc,EAAE,CAAC;IACzB,SAAS,GAAe,EAAE,CAAC;IAC3B,cAAc,GAAG,CAAC,CAAC;IACnB,YAAY,GAAkB,EAAE,CAAC;IACjC,eAAe,GAAqB,EAAE,CAAC;IAEtC,OAAO,GAAsB,EAAE,CAAC;IAChC,kBAAkB,GAAsB,EAAE,CAAC;IAEnD,+FAA+F;IAC/F,2FAA2F;IACnF,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;IAElD,QAAQ;QACJ,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,QAAQ,EAAE,CAAC;IACpB,CAAC;IAED,WAAW;QACP,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;IAED,wBAAwB;IAEjB,iBAAiB,CAAC,KAAa;QAClC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;IACpB,CAAC;IAEM,eAAe,CAAC,OAA0B;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;IACpB,CAAC;IAEM,cAAc,CAAC,KAAa,EAAE,QAAQ,GAAG,CAAC;QAC7C,IAAI,KAAK,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QAChC,IAAI,KAAK,GAAG,IAAI,IAAI,QAAQ,GAAG,CAAC;YAAE,QAAQ,GAAG,CAAC,CAAC;QAC/C,OAAO,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAEM,SAAS;QACZ,MAAM,MAAM,GAAG,6JAA6J,CAAC;QAC7K,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CACtC,IAAI,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CACvS,CAAC;QACF,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;IAC/C,CAAC;IAED,qBAAqB;IAEb,KAAK,CAAC,QAAQ;QAClB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAEzB,IAAI,CAAC;YACD,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC5D,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YAC7D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC5C,MAAM,aAAa,GAAG,IAAI,CAAC,yBAAyB,CAAC,YAAY,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;YACrF,MAAM,UAAU,GAAG,IAAI,CAAC,yBAAyB,CAAC,aAAa,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;YAE5F,MAAM,CAAC,aAAa,EAAE,UAAU,EAAE,UAAU,CAAC,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC;gBAC9D;oBACI,UAAU,EAAE,oBAAoB;oBAChC,WAAW,EAAE,aAAa;oBAC1B,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,WAAW;oBACpB,UAAU,EAAE,QAAQ;iBACvB;gBACD;oBACI,UAAU,EAAE,oBAAoB;oBAChC,WAAW,EAAE,UAAU;oBACvB,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,WAAW;oBACpB,UAAU,EAAE,QAAQ;iBACvB;gBACD;oBACI,UAAU,EAAE,oBAAoB;oBAChC,WAAW,EAAE,+CAA+C;oBAC5D,MAAM,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,UAAU,CAAC;oBACzI,UAAU,EAAE,QAAQ;iBACvB;aACJ,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,GAAG,CAAC,aAAa,EAAE,OAAO,IAAI,EAAE,CAAsB,CAAC;YACnE,IAAI,CAAC,kBAAkB,GAAG,CAAC,UAAU,EAAE,OAAO,IAAI,EAAE,CAAsB,CAAC;YAC3E,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;YAElD,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,IAAI,CAAC,cAAc,EAAE,CAAC;YACtB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC;IACL,CAAC;IAED,sBAAsB;IAEtB,+FAA+F;IACvF,OAAO,CAAC,OAAsB,EAAE,QAAuB;QAC3D,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,EAAE,CAAC,IAAI,aAAa,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;IAC9E,CAAC;IAED,mGAAmG;IAC3F,OAAO,CAAC,GAAoB;QAChC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,qGAAqG;IAC7F,iBAAiB,CAAC,IAAoB;QAC1C,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACxB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC;YAC/D,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC;YACzD,2FAA2F;YAC3F,0FAA0F;YAC1F,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC,qBAAqB,IAAI,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC;YAC1F,MAAM,cAAc,GAAG,CAAC,GAAG,CAAC,sBAAsB,IAAI,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC;YAC5F,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC,CAAC;QAC/G,CAAC;IACL,CAAC;IAED,qFAAqF;IAC7E,eAAe,CAAC,IAAuB;QAC3C,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,KAAK,GAAG,eAAe,CAAC;YACvD,mBAAmB,EAAE,CAAC;YACtB,eAAe,EAAE,GAAG,CAAC,eAAe,IAAI,CAAC;YACzC,gBAAgB,EAAE,GAAG,CAAC,gBAAgB,IAAI,CAAC;SAC9C,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9B,CAAC;IAED,qBAAqB;IAEb,WAAW;QACf,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9E,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;QAChE,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;QAElE,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;QACtE,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACpE,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;QAEtE,MAAM,aAAa,GAAG,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpG,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAE5F,sDAAsD;QACtD,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACjD,MAAM,gBAAgB,GAAG,CAAC,UAAU,GAAG,aAAa,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAErH,MAAM,KAAK,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB,GAAG,aAAa,CAAC,GAAG,aAAa,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QAEpG,IAAI,CAAC,QAAQ,GAAG;YACZ;gBACI,KAAK,EAAE,eAAe;gBACtB,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC;gBACtC,KAAK,EAAE,IAAI;gBACX,cAAc,EAAE,QAAQ;gBACxB,WAAW,EAAE,KAAK;gBAClB,IAAI,EAAE,0BAA0B;aACnC;YACD;gBACI,KAAK,EAAE,WAAW;gBAClB,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC;gBACrC,KAAK,EAAE,IAAI;gBACX,cAAc,EAAE,QAAQ;gBACxB,WAAW,EAAE,KAAK;gBAClB,IAAI,EAAE,2BAA2B;aACpC;YACD;gBACI,KAAK,EAAE,YAAY;gBACnB,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC;gBACtC,KAAK,EAAE,KAAK;gBACZ,cAAc,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ;gBAC7F,WAAW,EAAE,KAAK;gBAClB,IAAI,EAAE,sBAAsB;aAC/B;YACD;gBACI,KAAK,EAAE,mBAAmB;gBAC1B,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC;gBAC5C,KAAK,EAAE,IAAI;gBACX,cAAc,EAAE,QAAQ;gBACxB,WAAW,EAAE,IAAI;gBACjB,IAAI,EAAE,wBAAwB;aACjC;SACJ,CAAC;QAEF,IAAI,CAAC,eAAe,EAAE,CAAC;IAC3B,CAAC;IAED,gGAAgG;IACxF,eAAe;QACnB,MAAM,MAAM,GAAqB,EAAE,mBAAmB,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC;QACrG,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,CAAC,mBAAmB,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC;YAClD,MAAM,CAAC,eAAe,IAAI,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC;YACjD,MAAM,CAAC,gBAAgB,IAAI,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC;QACvD,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAE1C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,gBAAgB;YACvB,KAAK,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG;YACpD,KAAK,EAAE,IAAI;YACX,cAAc,EAAE,QAAQ;YACxB,WAAW,EAAE,KAAK;YAClB,IAAI,EAAE,kBAAkB;SAC3B,CAAC,CAAC;QAEH,2FAA2F;QAC3F,mFAAmF;QACnF,MAAM,YAAY,GAAG,OAAO,GAAG,CAAC;YAC5B,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;YAC9B,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,iBAAiB;YACxB,KAAK,EAAE,YAAY;YACnB,KAAK,EAAE,IAAI;YACX,cAAc,EAAE,QAAQ;YACxB,WAAW,EAAE,OAAO,GAAG,CAAC;YACxB,IAAI,EAAE,wBAAwB;SACjC,CAAC,CAAC;IACP,CAAC;IAEO,gBAAgB;QACpB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC1C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,CAAC;QAE1C,uDAAuD;QACvD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACvF,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC;YAC9B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM;YACvE,CAAC,CAAC,CAAC,CAAC;QACR,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC;QAE3C,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACzC,IAAI,EAAE,GAAG;YACT,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC;YAC/B,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;YACf,aAAa,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YAC1D,SAAS,EAAE,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,gBAAgB;SACxD,CAAC,CAAC,CAAC;QAEJ,IAAI,CAAC,cAAc,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IAEO,cAAc;QAClB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC9C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC;YACrC,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/F,CAAC;QAED,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1E,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7E,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACnD,KAAK,EAAE,MAAM;YACb,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YAC7C,KAAK,EAAE,cAAc,CAAC,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC;YAChD,QAAQ,EAAE,EAAE;SACf,CAAC,CAAC,CAAC;IACR,CAAC;IAEO,kBAAkB;QACtB,MAAM,MAAM,GAAG,IAAI,GAAG,EAA6B,CAAC;QACpD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC;YACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC1C,MAAM,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAErF,MAAM,IAAI,GAAqB,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,EAAE,SAAS,CAAC,IAAI,MAAM,EAAE,CAAC;YACjC,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7E,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAClF,MAAM,eAAe,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACpF,MAAM,gBAAgB,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACtF,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAE7E,2DAA2D;YAC3D,MAAM,OAAO,GAAG,WAAW,GAAG,YAAY,CAAC;YAC3C,MAAM,SAAS,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACnE,MAAM,UAAU,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAErE,IAAI,CAAC,IAAI,CAAC;gBACN,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS;gBACtC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,SAAS;gBACxC,IAAI,EAAE,SAAS,CAAC,MAAM;gBACtB,WAAW,EAAE,WAAW;gBACxB,YAAY,EAAE,YAAY;gBAC1B,eAAe,EAAE,eAAe;gBAChC,gBAAgB,EAAE,gBAAgB;gBAClC,YAAY,EAAE,YAAY,CAAC,EAAE,mBAAmB,EAAE,WAAW,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC;gBACnG,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC;gBAC7C,SAAS,EAAE,SAAS;gBACpB,UAAU,EAAE,UAAU;gBACtB,SAAS,EAAE,IAAI;gBACf,cAAc,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;aAC/D,CAAC,CAAC;QACP,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IAC1E,CAAC;IAED,gBAAgB;IAER,cAAc,CAAC,IAAuB,EAAE,KAAW,EAAE,GAAS;QAClE,OAAO,IAAI;aACN,MAAM,CAAC,CAAC,CAAC,EAAE;YACR,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC;QAClC,CAAC,CAAC;aACD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IAEO,aAAa;QACjB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAA2B;YAClC,OAAO,EAAE,QAAQ;YACjB,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,UAAU;YACjB,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,QAAQ;SAClC,CAAC;QACF,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC;QAC9C,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;IAC3C,CAAC;IAEO,gBAAgB;QACpB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/D,OAAO,eAAe,GAAG,GAAG,CAAC;IACjC,CAAC;IAEO,yBAAyB,CAAC,KAAW,EAAE,GAAS,EAAE,WAAmB;QACzE,MAAM,KAAK,GAAG;YACV,aAAa,KAAK,CAAC,WAAW,EAAE,GAAG;YACnC,aAAa,GAAG,CAAC,WAAW,EAAE,GAAG;SACpC,CAAC;QACF,IAAI,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACzC,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAEO,cAAc,CAAC,OAAe;QAClC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QACpG,OAAO,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC;IAEO,WAAW,CAAC,GAAW,EAAE,QAAgB;QAC7C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;6RAjaQ,4BAA4B,yBAA5B,4BAA4B;6DAA5B,4BAA4B;YA5e/B,AAJF,8FAAiB,+DAIR;;YAJT,uCAyJC;;;iFAuVI,4BAA4B;cArfxC,SAAS;6BACM,KAAK,YACP,2BAA2B,YAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA4JT;;kBAwVA,KAAK;;kBASL,KAAK;;kBAcL,MAAM;;kBACN,MAAM;;kFA1BE,4BAA4B;AAoazC,MAAM,UAAU,uBAAuB,KAAmC,CAAC","sourcesContent":["/**\n * @fileoverview Cost & Budget Analytics -- Option B.\n *\n * Displays cost KPIs with period deltas, a daily cost bar chart with anomaly detection,\n * a cost breakdown treemap, and a detailed cost-by-model table with CSV export.\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 { NormalizeUUID } from '@memberjunction/global';\nimport { CacheRate, CacheTokenTotals, cacheHitRate, hasCacheActivity, netCacheSavings } from '../../../services/cache-metrics';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { GlobalFilterState } from '../../../interfaces/analytics-preferences.interface';\n\n// ── Interfaces ──\n\ninterface PromptRunRecord {\n ID: string;\n RunAt: string;\n Cost: number | null;\n TotalCost: number | null;\n TokensPrompt: number | null;\n TokensCompletion: number | null;\n TokensUsed: number | null;\n TokensCacheRead: number | null;\n TokensCacheWrite: number | null;\n ModelID: string | null;\n Model: string | null;\n VendorID: string | null;\n Vendor: string | null;\n Success: boolean;\n}\n\ninterface CostKpi {\n Label: string;\n Value: string;\n Delta: number | null;\n DeltaDirection: 'up' | 'down' | 'stable';\n Highlighted: boolean;\n Icon: string;\n}\n\ninterface DailyBar {\n Date: string;\n Label: string;\n Cost: number;\n HeightPercent: number;\n IsAnomaly: boolean;\n}\n\ninterface TreemapCell {\n Label: string;\n Cost: number;\n Percent: number;\n Color: string;\n GridArea: string;\n}\n\ninterface CostByModelRow {\n Model: string;\n Vendor: string;\n Runs: number;\n InputTokens: number;\n OutputTokens: number;\n CacheReadTokens: number;\n CacheWriteTokens: number;\n CacheHitRate: number;\n CacheSavings: number;\n InputCost: number;\n OutputCost: number;\n TotalCost: number;\n PercentOfTotal: number;\n}\n\nconst FIELDS = [\n 'ID', 'RunAt', 'Cost', 'TotalCost', 'TokensPrompt', 'TokensCompletion',\n 'TokensUsed', 'TokensCacheRead', 'TokensCacheWrite', 'ModelID', 'Model', 'VendorID', 'Vendor', 'Success'\n];\n\ninterface ModelCostRow {\n ModelID: string | null;\n VendorID: string | null;\n InputPricePerUnit: number | null;\n OutputPricePerUnit: number | null;\n CacheReadPricePerUnit: number | null;\n CacheWritePricePerUnit: number | null;\n UnitType: string | null;\n}\n\n// AIModelCost UnitType name -> token divisor, matching the BasePriceUnitType driver classes.\nconst UNIT_DIVISORS: Record<string, number> = {\n 'Per Million Tokens': 1_000_000,\n 'Per Hundred Thousand Tokens': 100_000,\n 'Per Thousand Tokens': 1_000\n};\n\nconst TIME_RANGE_OPTIONS = ['Today', '7d', '30d', 'MTD'];\n\nconst TREEMAP_COLORS = [\n 'var(--mj-brand-primary)',\n 'var(--mj-brand-accent, var(--mj-brand-primary-hover))',\n 'var(--mj-status-info)',\n 'var(--mj-status-success)',\n 'var(--mj-status-warning)',\n 'var(--mj-text-disabled)'\n];\n\n@Component({\n standalone: false,\n selector: 'app-analytics-cost-budget',\n template: `\n\n @if (IsLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading cost data...\"></mj-loading>\n </div>\n } @else {\n <!-- Cost KPI Row -->\n <div class=\"kpi-row\">\n @for (kpi of CostKpis; track kpi.Label) {\n <div class=\"kpi-card\" [class.kpi-card--highlighted]=\"kpi.Highlighted\">\n <div class=\"kpi-icon\">\n <i [class]=\"kpi.Icon\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-label\">{{ kpi.Label }}</div>\n <div class=\"kpi-value\">{{ kpi.Value }}</div>\n @if (kpi.Delta != null) {\n <div class=\"kpi-delta\"\n [class.kpi-delta--up]=\"kpi.DeltaDirection === 'up'\"\n [class.kpi-delta--down]=\"kpi.DeltaDirection === 'down'\">\n @if (kpi.DeltaDirection === 'up') {\n <i class=\"fa-solid fa-arrow-up\"></i>\n } @else if (kpi.DeltaDirection === 'down') {\n <i class=\"fa-solid fa-arrow-down\"></i>\n }\n {{ kpi.Delta | number:'1.1-1' }}% vs prev\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Two-Column Layout -->\n <div class=\"two-col\">\n <!-- Daily Cost Trend -->\n <div class=\"panel panel-chart\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-chart-column panel-header__icon\"></i>\n Daily Cost Trend\n </div>\n </div>\n <div class=\"chart-body\">\n @if (DailyBars.length === 0) {\n <mj-empty-state Size=\"compact\" Variant=\"empty\" Icon=\"fa-solid fa-chart-column\"\n Title=\"No cost data for selected period\" />\n } @else {\n <div class=\"bar-chart\">\n <div class=\"bar-chart-area\">\n @if (AvgLinePercent > 0) {\n <div class=\"avg-line\" [style.bottom.%]=\"AvgLinePercent\">\n <span class=\"avg-label\">avg</span>\n </div>\n }\n @for (bar of DailyBars; track bar.Date) {\n <div class=\"bar-col\" [title]=\"bar.Label + ': ' + FormatCurrency(bar.Cost)\">\n <div\n class=\"bar\"\n [class.bar--anomaly]=\"bar.IsAnomaly\"\n [style.height.%]=\"bar.HeightPercent\"\n ></div>\n <div class=\"bar-label\">{{ bar.Label }}</div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Cost Breakdown Treemap -->\n <div class=\"panel panel-treemap\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-chart-pie panel-header__icon\"></i>\n Cost by Vendor\n </div>\n </div>\n <div class=\"treemap-body\">\n @if (TreemapCells.length === 0) {\n <mj-empty-state Size=\"compact\" Variant=\"empty\" Icon=\"fa-solid fa-chart-pie\"\n Title=\"No vendor cost data\" />\n } @else {\n <div class=\"treemap-grid\">\n @for (cell of TreemapCells; track cell.Label) {\n <div\n class=\"treemap-cell\"\n [style.background]=\"cell.Color\"\n [style.flex-basis.%]=\"cell.Percent\"\n [title]=\"cell.Label + ': ' + FormatCurrency(cell.Cost) + ' (' + (cell.Percent | number:'1.0-0') + '%)'\">\n <span class=\"treemap-label\">{{ cell.Label }}</span>\n <span class=\"treemap-value\">{{ FormatCurrency(cell.Cost) }}</span>\n <span class=\"treemap-pct\">{{ cell.Percent | number:'1.0-0' }}%</span>\n </div>\n }\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Cost by Model Table -->\n <div class=\"panel\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-table panel-header__icon\"></i>\n Cost by Model\n </div>\n <button class=\"export-btn\" (click)=\"ExportCSV()\">\n <i class=\"fa-solid fa-download\"></i>\n Export CSV\n </button>\n </div>\n <div class=\"table-wrapper\">\n <table class=\"data-table\">\n <thead>\n <tr>\n <th>Model</th>\n <th>Vendor</th>\n <th class=\"col-numeric\">Runs</th>\n <th class=\"col-numeric\">Input Tokens</th>\n <th class=\"col-numeric\">Output Tokens</th>\n <th class=\"col-numeric\" title=\"Share of input tokens served from the provider's prompt cache\">Cache Hit</th>\n <th class=\"col-numeric\">Input Cost</th>\n <th class=\"col-numeric\">Output Cost</th>\n <th class=\"col-numeric\">Total Cost</th>\n <th class=\"col-numeric\" title=\"Net dollars saved by caching vs. full input pricing (requires cache rates on AI Model Costs)\">Cache Saved</th>\n <th class=\"col-numeric\">% of Total</th>\n </tr>\n </thead>\n <tbody>\n @if (CostByModelRows.length === 0) {\n <tr><td colspan=\"11\" class=\"empty-row\">No data available</td></tr>\n }\n @for (row of CostByModelRows; track row.Model) {\n <tr>\n <td class=\"cell-model\">{{ row.Model }}</td>\n <td class=\"cell-vendor\">{{ row.Vendor }}</td>\n <td class=\"cell-numeric\">{{ row.Runs | number }}</td>\n <td class=\"cell-numeric\">{{ row.InputTokens | number }}</td>\n <td class=\"cell-numeric\">{{ row.OutputTokens | number }}</td>\n <td class=\"cell-numeric\">{{ (row.CacheReadTokens + row.CacheWriteTokens) > 0 ? ((row.CacheHitRate * 100 | number:'1.0-0') + '%') : '—' }}</td>\n <td class=\"cell-numeric\">{{ FormatCurrency(row.InputCost, 4) }}</td>\n <td class=\"cell-numeric\">{{ FormatCurrency(row.OutputCost, 4) }}</td>\n <td class=\"cell-numeric cell-cost\">{{ FormatCurrency(row.TotalCost) }}</td>\n <td class=\"cell-numeric\">{{ row.CacheSavings > 0 ? FormatCurrency(row.CacheSavings, 4) : '—' }}</td>\n <td class=\"cell-numeric\">{{ row.PercentOfTotal | number:'1.1-1' }}%</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 /* ── KPI Row ── */\n .kpi-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 12px;\n margin: 16px 0;\n }\n\n .kpi-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 16px;\n display: flex;\n align-items: flex-start;\n gap: 12px;\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n }\n\n .kpi-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n }\n\n .kpi-card--highlighted {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n }\n\n .kpi-icon {\n width: 36px;\n height: 36px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 14px;\n flex-shrink: 0;\n }\n\n .kpi-card--highlighted .kpi-icon {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n }\n\n .kpi-label {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .kpi-value {\n font-size: 22px;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin: 2px 0;\n letter-spacing: -0.02em;\n }\n\n .kpi-delta {\n font-size: 11px;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 3px;\n }\n\n .kpi-delta--up {\n color: var(--mj-status-error);\n }\n\n .kpi-delta--down {\n color: var(--mj-status-success);\n }\n\n /* ── Two-Column Layout ── */\n .two-col {\n display: grid;\n grid-template-columns: 3fr 2fr;\n gap: 16px;\n margin-bottom: 16px;\n }\n\n /* ── Panel ── */\n .panel {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 16px;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .panel-header__title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .panel-header__icon {\n font-size: 13px;\n color: var(--mj-brand-primary);\n }\n\n .export-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 13px;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n }\n\n .export-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n /* ── Bar Chart ── */\n .chart-body {\n padding: 16px 18px;\n }\n\n .bar-chart {\n width: 100%;\n }\n\n .bar-chart-area {\n display: flex;\n align-items: flex-end;\n gap: 4px;\n height: 180px;\n position: relative;\n padding-bottom: 24px;\n }\n\n .bar-col {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n height: 100%;\n justify-content: flex-end;\n }\n\n .bar {\n width: 100%;\n max-width: 32px;\n border-radius: 4px 4px 0 0;\n background: var(--mj-brand-primary);\n transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 2px;\n }\n\n .bar--anomaly {\n background: var(--mj-status-error);\n }\n\n .bar-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n white-space: nowrap;\n }\n\n .avg-line {\n position: absolute;\n left: 0;\n right: 0;\n border-top: 2px dashed var(--mj-text-disabled);\n z-index: 1;\n pointer-events: none;\n }\n\n .avg-label {\n position: absolute;\n right: 0;\n top: -14px;\n font-size: 10px;\n color: var(--mj-text-disabled);\n font-weight: 600;\n }\n\n /* ── Treemap ── */\n .treemap-body {\n padding: 16px 18px;\n }\n\n .treemap-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n min-height: 160px;\n }\n\n .treemap-cell {\n border-radius: 8px;\n padding: 12px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n min-width: 80px;\n min-height: 70px;\n flex-grow: 1;\n color: var(--mj-text-inverse, white);\n transition: opacity 0.2s;\n }\n\n .treemap-cell:hover {\n opacity: 0.85;\n }\n\n .treemap-label {\n font-size: 12px;\n font-weight: 600;\n opacity: 0.95;\n }\n\n .treemap-value {\n font-size: 16px;\n font-weight: 700;\n margin-top: 2px;\n }\n\n .treemap-pct {\n font-size: 11px;\n opacity: 0.8;\n }\n\n /* ── Table ── */\n .table-wrapper {\n overflow-x: auto;\n }\n\n .data-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .data-table th,\n .data-table td {\n padding: 10px 14px;\n text-align: left;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .data-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-numeric { text-align: right; }\n\n .data-table tbody tr {\n transition: background 0.15s;\n }\n\n .data-table tbody tr:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .cell-model {\n font-weight: 500;\n color: var(--mj-text-primary);\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 .empty-row {\n text-align: center;\n color: var(--mj-text-disabled);\n padding: 24px;\n }\n\n /* ── Responsive ── */\n @media (max-width: 1200px) {\n .two-col {\n grid-template-columns: 1fr;\n }\n }\n\n @media (max-width: 768px) {\n .kpi-row {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .kpi-value {\n font-size: 18px;\n }\n }\n `]\n})\nexport class AnalyticsCostBudgetComponent extends BaseAngularComponent implements OnInit, OnDestroy {\n private _timeRange = '7d';\n @Input()\n set TimeRange(value: string) {\n const prev = this._timeRange;\n this._timeRange = value;\n if (prev !== value && this.initialized) this.LoadData();\n }\n get TimeRange(): string { return this._timeRange; }\n\n private _filters: GlobalFilterState = { Models: [], Agents: [], Prompts: [], Statuses: [] };\n @Input()\n set Filters(value: GlobalFilterState) {\n const next = value ?? { Models: [], Agents: [], Prompts: [], Statuses: [] };\n const changed = !this.shallowFiltersEqual(this._filters, next);\n this._filters = next;\n if (changed && this.initialized) this.LoadData();\n }\n get Filters(): GlobalFilterState { return this._filters; }\n\n private shallowFiltersEqual(a: GlobalFilterState, b: GlobalFilterState): boolean {\n const sameArr = (x: string[], y: string[]) => x.length === y.length && x.every((v, i) => v === y[i]);\n return sameArr(a.Models, b.Models) && sameArr(a.Agents, b.Agents) && sameArr(a.Prompts, b.Prompts) && sameArr(a.Statuses, b.Statuses);\n }\n\n @Output() TimeRangeChange = new EventEmitter<string>();\n @Output() FiltersChange = new EventEmitter<GlobalFilterState>();\n\n private initialized = false;\n\n private cdr = inject(ChangeDetectorRef);\n private destroy$ = new Subject<void>();\n\n public IsLoading = false;\n public TimeRangeOptionsList = TIME_RANGE_OPTIONS;\n\n public CostKpis: CostKpi[] = [];\n public DailyBars: DailyBar[] = [];\n public AvgLinePercent = 0;\n public TreemapCells: TreemapCell[] = [];\n public CostByModelRows: CostByModelRow[] = [];\n\n private allRuns: PromptRunRecord[] = [];\n private previousPeriodRuns: PromptRunRecord[] = [];\n\n // Per model+vendor cache pricing (rates already normalized to currency-per-token). Empty until\n // AIModelCost cache rates are configured — savings then stays 0 (surfaced as \"Set rates\").\n private cacheRates = new Map<string, CacheRate>();\n\n ngOnInit(): void {\n this.initialized = true;\n this.LoadData();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n // ── Public Handlers ──\n\n public OnTimeRangeChange(range: string): void {\n this.TimeRange = range;\n this.TimeRangeChange.emit(range);\n this.LoadData();\n }\n\n public OnFiltersChange(filters: GlobalFilterState): void {\n this.Filters = filters;\n this.FiltersChange.emit(filters);\n this.LoadData();\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 public ExportCSV(): void {\n const header = 'Model,Vendor,Runs,Input Tokens,Output Tokens,Cache Read Tokens,Cache Write Tokens,Cache Hit Rate %,Input Cost,Output Cost,Total Cost,Cache Saved,% of Total';\n const rows = this.CostByModelRows.map(r =>\n `\"${r.Model}\",\"${r.Vendor}\",${r.Runs},${r.InputTokens},${r.OutputTokens},${r.CacheReadTokens},${r.CacheWriteTokens},${(r.CacheHitRate * 100).toFixed(1)},${r.InputCost.toFixed(6)},${r.OutputCost.toFixed(6)},${r.TotalCost.toFixed(6)},${r.CacheSavings.toFixed(6)},${r.PercentOfTotal.toFixed(1)}`\n );\n const csv = [header, ...rows].join('\\n');\n this.downloadCSV(csv, 'cost-by-model.csv');\n }\n\n // ── Data Loading ──\n\n private async LoadData(): Promise<void> {\n this.IsLoading = true;\n this.cdr.detectChanges();\n\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const { currentStart, previousStart } = this.getDateBounds();\n const now = new Date();\n const modelFilter = this.buildModelFilter();\n const currentFilter = this.combineDateAndModelFilter(currentStart, now, modelFilter);\n const prevFilter = this.combineDateAndModelFilter(previousStart, currentStart, modelFilter);\n\n const [currentResult, prevResult, rateResult] = await rv.RunViews([\n {\n EntityName: 'MJ: AI Prompt Runs',\n ExtraFilter: currentFilter,\n Fields: FIELDS,\n OrderBy: 'RunAt ASC',\n ResultType: 'simple'\n },\n {\n EntityName: 'MJ: AI Prompt Runs',\n ExtraFilter: prevFilter,\n Fields: FIELDS,\n OrderBy: 'RunAt ASC',\n ResultType: 'simple'\n },\n {\n EntityName: 'MJ: AI Model Costs',\n ExtraFilter: `Status='Active' AND ProcessingType='Realtime'`,\n Fields: ['ModelID', 'VendorID', 'InputPricePerUnit', 'OutputPricePerUnit', 'CacheReadPricePerUnit', 'CacheWritePricePerUnit', 'UnitType'],\n ResultType: 'simple'\n }\n ]);\n\n this.allRuns = (currentResult?.Results ?? []) as PromptRunRecord[];\n this.previousPeriodRuns = (prevResult?.Results ?? []) as PromptRunRecord[];\n this.buildCacheRateMap(rateResult?.Results ?? []);\n\n this.computeKpis();\n this.computeDailyBars();\n this.computeTreemap();\n this.computeCostByModel();\n } catch (e) {\n console.error('Cost & Budget load error:', e);\n } finally {\n this.IsLoading = false;\n this.cdr.detectChanges();\n }\n }\n\n // ── Cache pricing ──\n\n /** Stable map key for a model+vendor pair (UUIDs normalized for case-insensitive matching). */\n private rateKey(modelID: string | null, vendorID: string | null): string {\n return `${NormalizeUUID(modelID ?? '')}|${NormalizeUUID(vendorID ?? '')}`;\n }\n\n /** The cache rate for a run's model+vendor, or undefined when no active cost row is configured. */\n private rateFor(run: PromptRunRecord): CacheRate | undefined {\n return this.cacheRates.get(this.rateKey(run.ModelID, run.VendorID));\n }\n\n /** Build the per-model+vendor rate lookup, normalizing each per-unit price to currency-per-token. */\n private buildCacheRateMap(rows: ModelCostRow[]): void {\n this.cacheRates.clear();\n for (const row of rows) {\n const divisor = UNIT_DIVISORS[row.UnitType ?? ''] ?? 1_000_000;\n const inputRate = (row.InputPricePerUnit ?? 0) / divisor;\n // Cache read/write fall back to the input rate when no distinct rate is recorded — exactly\n // as the server-side cost calculator does — which makes the corresponding savings term 0.\n const cacheReadRate = (row.CacheReadPricePerUnit ?? row.InputPricePerUnit ?? 0) / divisor;\n const cacheWriteRate = (row.CacheWritePricePerUnit ?? row.InputPricePerUnit ?? 0) / divisor;\n this.cacheRates.set(this.rateKey(row.ModelID, row.VendorID), { inputRate, cacheReadRate, cacheWriteRate });\n }\n }\n\n /** Sum net cache savings across a set of runs using each run's model+vendor rate. */\n private sumCacheSavings(runs: PromptRunRecord[]): number {\n return runs.reduce((total, run) => total + netCacheSavings({\n uncachedInputTokens: 0,\n cacheReadTokens: run.TokensCacheRead ?? 0,\n cacheWriteTokens: run.TokensCacheWrite ?? 0\n }, this.rateFor(run)), 0);\n }\n\n // ── Computations ──\n\n private computeKpis(): void {\n const now = new Date();\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const weekStart = new Date(todayStart.getTime() - 6 * 86400000);\n const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);\n\n const todaySpend = this.sumCostInRange(this.allRuns, todayStart, now);\n const weekSpend = this.sumCostInRange(this.allRuns, weekStart, now);\n const monthSpend = this.sumCostInRange(this.allRuns, monthStart, now);\n\n const prevTotalCost = this.previousPeriodRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n const currentTotalCost = this.allRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n // Project monthly cost based on current daily average\n const daysIntoMonth = Math.max(1, now.getDate());\n const projectedMonthly = (monthSpend / daysIntoMonth) * new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();\n\n const delta = prevTotalCost > 0 ? ((currentTotalCost - prevTotalCost) / prevTotalCost) * 100 : null;\n\n this.CostKpis = [\n {\n Label: \"Today's Spend\",\n Value: this.FormatCurrency(todaySpend),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar-day'\n },\n {\n Label: 'This Week',\n Value: this.FormatCurrency(weekSpend),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar-week'\n },\n {\n Label: 'This Month',\n Value: this.FormatCurrency(monthSpend),\n Delta: delta,\n DeltaDirection: delta != null ? (delta > 0 ? 'up' : delta < 0 ? 'down' : 'stable') : 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar'\n },\n {\n Label: 'Projected Monthly',\n Value: this.FormatCurrency(projectedMonthly),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: true,\n Icon: 'fa-solid fa-chart-line'\n }\n ];\n\n this.appendCacheKpis();\n }\n\n /** Append the cache hit-rate and cache-savings KPIs (computed from the current-period runs). */\n private appendCacheKpis(): void {\n const totals: CacheTokenTotals = { uncachedInputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };\n for (const r of this.allRuns) {\n totals.uncachedInputTokens += r.TokensPrompt ?? 0;\n totals.cacheReadTokens += r.TokensCacheRead ?? 0;\n totals.cacheWriteTokens += r.TokensCacheWrite ?? 0;\n }\n const savings = this.sumCacheSavings(this.allRuns);\n const activity = hasCacheActivity(totals);\n\n this.CostKpis.push({\n Label: 'Cache Hit Rate',\n Value: (cacheHitRate(totals) * 100).toFixed(1) + '%',\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-bolt'\n });\n\n // Savings requires cache rates on AIModelCost. When cache engaged but no savings computed,\n // it means rates aren't configured yet — say so rather than implying $0 was saved.\n const savingsValue = savings > 0\n ? this.FormatCurrency(savings)\n : (activity ? 'Set rates' : '$0.00');\n this.CostKpis.push({\n Label: 'Saved via Cache',\n Value: savingsValue,\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: savings > 0,\n Icon: 'fa-solid fa-piggy-bank'\n });\n }\n\n private computeDailyBars(): void {\n const buckets = new Map<string, number>();\n for (const run of this.allRuns) {\n const date = new Date(run.RunAt);\n const key = date.toISOString().slice(0, 10);\n buckets.set(key, (buckets.get(key) ?? 0) + (run.Cost ?? run.TotalCost ?? 0));\n }\n\n const sortedKeys = Array.from(buckets.keys()).sort();\n const values = sortedKeys.map(k => buckets.get(k) ?? 0);\n const maxVal = Math.max(...values, 0.001);\n\n // Anomaly detection: > 2 standard deviations from mean\n const mean = values.length > 0 ? values.reduce((s, v) => s + v, 0) / values.length : 0;\n const variance = values.length > 1\n ? values.reduce((s, v) => s + Math.pow(v - mean, 2), 0) / values.length\n : 0;\n const stdDev = Math.sqrt(variance);\n const anomalyThreshold = mean + 2 * stdDev;\n\n this.DailyBars = sortedKeys.map((key, i) => ({\n Date: key,\n Label: this.formatBarLabel(key),\n Cost: values[i],\n HeightPercent: maxVal > 0 ? (values[i] / maxVal) * 100 : 0,\n IsAnomaly: stdDev > 0 && values[i] > anomalyThreshold\n }));\n\n this.AvgLinePercent = maxVal > 0 ? (mean / maxVal) * 100 : 0;\n }\n\n private computeTreemap(): void {\n const vendorCosts = new Map<string, number>();\n for (const run of this.allRuns) {\n const vendor = run.Vendor ?? 'Other';\n vendorCosts.set(vendor, (vendorCosts.get(vendor) ?? 0) + (run.Cost ?? run.TotalCost ?? 0));\n }\n\n const total = Array.from(vendorCosts.values()).reduce((s, v) => s + v, 0);\n const sorted = Array.from(vendorCosts.entries()).sort((a, b) => b[1] - a[1]);\n\n this.TreemapCells = sorted.map(([vendor, cost], i) => ({\n Label: vendor,\n Cost: cost,\n Percent: total > 0 ? (cost / total) * 100 : 0,\n Color: TREEMAP_COLORS[i % TREEMAP_COLORS.length],\n GridArea: ''\n }));\n }\n\n private computeCostByModel(): void {\n const groups = new Map<string, PromptRunRecord[]>();\n for (const run of this.allRuns) {\n const key = run.ModelID ?? 'unknown';\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key)!.push(run);\n }\n\n const totalCost = this.allRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n const rows: CostByModelRow[] = [];\n for (const [, modelRuns] of groups) {\n const inputTokens = modelRuns.reduce((s, r) => s + (r.TokensPrompt ?? 0), 0);\n const outputTokens = modelRuns.reduce((s, r) => s + (r.TokensCompletion ?? 0), 0);\n const cacheReadTokens = modelRuns.reduce((s, r) => s + (r.TokensCacheRead ?? 0), 0);\n const cacheWriteTokens = modelRuns.reduce((s, r) => s + (r.TokensCacheWrite ?? 0), 0);\n const cost = modelRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n // Approximate input/output cost split based on token ratio\n const totalTk = inputTokens + outputTokens;\n const inputCost = totalTk > 0 ? cost * (inputTokens / totalTk) : 0;\n const outputCost = totalTk > 0 ? cost * (outputTokens / totalTk) : 0;\n\n rows.push({\n Model: modelRuns[0].Model ?? 'Unknown',\n Vendor: modelRuns[0].Vendor ?? 'Unknown',\n Runs: modelRuns.length,\n InputTokens: inputTokens,\n OutputTokens: outputTokens,\n CacheReadTokens: cacheReadTokens,\n CacheWriteTokens: cacheWriteTokens,\n CacheHitRate: cacheHitRate({ uncachedInputTokens: inputTokens, cacheReadTokens, cacheWriteTokens }),\n CacheSavings: this.sumCacheSavings(modelRuns),\n InputCost: inputCost,\n OutputCost: outputCost,\n TotalCost: cost,\n PercentOfTotal: totalCost > 0 ? (cost / totalCost) * 100 : 0\n });\n }\n\n this.CostByModelRows = rows.sort((a, b) => b.TotalCost - a.TotalCost);\n }\n\n // ── Helpers ──\n\n private sumCostInRange(runs: PromptRunRecord[], start: Date, end: Date): number {\n return runs\n .filter(r => {\n const d = new Date(r.RunAt);\n return d >= start && d <= end;\n })\n .reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n }\n\n private getDateBounds(): { currentStart: Date; previousStart: Date } {\n const now = new Date();\n const msMap: Record<string, number> = {\n 'Today': 86400000,\n '7d': 604800000,\n '30d': 2592000000,\n 'MTD': now.getDate() * 86400000\n };\n const ms = msMap[this.TimeRange] ?? 604800000;\n const currentStart = new Date(now.getTime() - ms);\n const previousStart = new Date(currentStart.getTime() - ms);\n return { currentStart, previousStart };\n }\n\n private buildModelFilter(): string {\n if (this.Filters.Models.length === 0) return '';\n const ids = this.Filters.Models.map(id => `'${id}'`).join(',');\n return `ModelID IN (${ids})`;\n }\n\n private combineDateAndModelFilter(start: Date, end: Date, modelFilter: string): string {\n const parts = [\n `RunAt >= '${start.toISOString()}'`,\n `RunAt <= '${end.toISOString()}'`\n ];\n if (modelFilter) parts.push(modelFilter);\n return parts.join(' AND ');\n }\n\n private formatBarLabel(dateStr: string): string {\n const d = new Date(dateStr + 'T00:00:00');\n const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n return months[d.getMonth()] + ' ' + d.getDate();\n }\n\n private downloadCSV(csv: string, filename: string): void {\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.click();\n URL.revokeObjectURL(url);\n }\n}\n\nexport function LoadAnalyticsCostBudget() { /* tree-shaking prevention */ }\n"]}
|
|
1
|
+
{"version":3,"file":"cost-budget.component.js","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/cost-budget/cost-budget.component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACH,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EACnB,iBAAiB,EAAE,MAAM,EAC/C,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAA+B,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAC/H,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;;;;;;;;IAqGzD,8BAA+B;IAC3B,gCAAqD;IACzD,iBAAM;;;IAiBsB,wBAAoC;;;IAEpC,wBAAsC;;;IAN9C,+BAE6D;IAGvD,AAFF,+HAAmC,yGAES;IAG5C,YACJ;;IAAA,iBAAM;;;IAPD,AADA,+DAAmD,qDACI;IACxD,cAIC;IAJD,gGAIC;IACD,eACJ;IADI,qFACJ;;;IAhBR,AADJ,+BAAsE,cAC5C;IAClB,oBAA0B;IAC9B,iBAAM;IAEF,AADJ,+BAAyB,cACE;IAAA,YAAe;IAAA,iBAAM;IAC5C,+BAAuB;IAAA,YAAe;IAAA,iBAAM;IAC5C,mHAAyB;IAajC,AADI,iBAAM,EACJ;;;IApBgB,2DAA+C;IAE1D,eAAkB;IAAlB,0BAAkB;IAGE,eAAe;IAAf,kCAAe;IACf,eAAe;IAAf,kCAAe;IACtC,cAWC;IAXD,+CAWC;;;IAkBD,qCAC+C;;;IAM/B,AADJ,+BAAwD,eAC5B;IAAA,mBAAG;IAC/B,AAD+B,iBAAO,EAChC;;;IAFgB,oDAAiC;;;IAKvD,+BAA2E;IACvE,0BAIO;IACP,+BAAuB;IAAA,YAAe;IAC1C,AAD0C,iBAAM,EAC1C;;;;IAPe,gFAAqD;IAIlE,cAAoC;IAApC,mDAAoC;IADpC,gDAAoC;IAGjB,eAAe;IAAf,kCAAe;;;IAblD,AADJ,+BAAuB,cACS;IACxB,4HAA0B;IAK1B,6HASC;IAET,AADI,iBAAM,EACJ;;;IAhBE,eAIC;IAJD,oDAIC;IACD,cASC;IATD,+BASC;;;IAiBT,qCACkC;;;IAI1B,+BAI4G;;IACxG,gCAA4B;IAAA,YAAgB;IAAA,iBAAO;IACnD,gCAA4B;IAAA,YAA+B;IAAA,iBAAO;IAClE,gCAA0B;IAAA,YAAoC;;IAClE,AADkE,iBAAO,EACnE;;;;IALF,AADA,2CAA+B,oCACI;IACnC,iJAAuG;IAC3E,eAAgB;IAAhB,mCAAgB;IAChB,eAA+B;IAA/B,yDAA+B;IACjC,eAAoC;IAApC,+EAAoC;;;IAT1E,+BAA0B;IACtB,8HAUC;IACL,iBAAM;;;IAXF,cAUC;IAVD,kCAUC;;;IAsCG,AAAJ,0BAAI,aAAmC;IAAA,iCAAiB;IAAK,AAAL,iBAAK,EAAK;;;IAI9D,AADJ,0BAAI,aACuB;IAAA,YAAe;IAAA,iBAAK;IAC3C,8BAAwB;IAAA,YAAgB;IAAA,iBAAK;IAC7C,8BAAyB;IAAA,YAAuB;;IAAA,iBAAK;IACrD,8BAAyB;IAAA,YAA8B;;IAAA,iBAAK;IAC5D,+BAAyB;IAAA,aAA+B;;IAAA,iBAAK;IAC7D,+BAAyB;IAAA,aAAgH;;IAAA,iBAAK;IAC9I,+BAAyB;IAAA,aAAsC;IAAA,iBAAK;IACpE,+BAAyB;IAAA,aAAuC;IAAA,iBAAK;IACrE,+BAAmC;IAAA,aAAmC;IAAA,iBAAK;IAC3E,+BAAyB;IAAA,aAAsE;IAAA,iBAAK;IACpG,+BAAyB;IAAA,aAA0C;;IACvE,AADuE,iBAAK,EACvE;;;;IAXsB,eAAe;IAAf,kCAAe;IACd,eAAgB;IAAhB,mCAAgB;IACf,eAAuB;IAAvB,wDAAuB;IACvB,eAA8B;IAA9B,gEAA8B;IAC9B,eAA+B;IAA/B,iEAA+B;IAC/B,eAAgH;IAAhH,wJAAgH;IAChH,eAAsC;IAAtC,gEAAsC;IACtC,eAAuC;IAAvC,iEAAuC;IAC7B,eAAmC;IAAnC,6DAAmC;IAC7C,eAAsE;IAAtE,wGAAsE;IACtE,eAA0C;IAA1C,sFAA0C;;;;IA5I3F,8BAAqB;IACjB,6GAsBC;IACL,iBAAM;IAOM,AADJ,AADJ,AAFJ,8BAAqB,aAEc,aACD,aACW;IAC7B,uBAA2D;IAC3D,kCACJ;IACJ,AADI,iBAAM,EACJ;IACN,8BAAwB;IAIlB,AAHF,0HAA8B,yFAGrB;IAsBjB,AADI,iBAAM,EACJ;IAKE,AADJ,AADJ,gCAAiC,cACH,cACW;IAC7B,yBAAwD;IACxD,iCACJ;IACJ,AADI,iBAAM,EACJ;IACN,gCAA0B;IAIpB,AAHF,0HAAiC,yFAGxB;IAiBrB,AADI,AADI,iBAAM,EACJ,EACJ;IAKE,AADJ,AADJ,gCAAmB,cACW,cACW;IAC7B,yBAAoD;IACpD,gCACJ;IAAA,iBAAM;IACN,mCAAiD;IAAtB,iMAAS,kBAAW,KAAC;IAC5C,yBAAoC;IACpC,6BACJ;IACJ,AADI,iBAAS,EACP;IAKU,AADJ,AADJ,AADJ,AADJ,gCAA2B,iBACG,aACf,UACC,UACI;IAAA,sBAAK;IAAA,iBAAK;IACd,2BAAI;IAAA,uBAAM;IAAA,iBAAK;IACf,+BAAwB;IAAA,qBAAI;IAAA,iBAAK;IACjC,+BAAwB;IAAA,6BAAY;IAAA,iBAAK;IACzC,+BAAwB;IAAA,8BAAa;IAAA,iBAAK;IAC1C,+BAA8F;IAAA,0BAAS;IAAA,iBAAK;IAC5G,+BAAwB;IAAA,2BAAU;IAAA,iBAAK;IACvC,+BAAwB;IAAA,4BAAW;IAAA,iBAAK;IACxC,+BAAwB;IAAA,2BAAU;IAAA,iBAAK;IACvC,+BAA6H;IAAA,4BAAW;IAAA,iBAAK;IAC7I,+BAAwB;IAAA,2BAAU;IAE1C,AADI,AADsC,iBAAK,EACtC,EACD;IACR,8BAAO;IACH,0GAAoC;IAGpC,mHAcC;IAIjB,AADI,AADI,AADI,iBAAQ,EACJ,EACN,EACJ;;;IAjJF,cAsBC;IAtBD,8BAsBC;IAcO,eAuBC;IAvBD,yDAuBC;IAaD,eAiBC;IAjBD,4DAiBC;IAmCG,gBAEC;IAFD,+DAEC;IACD,cAcC;IAdD,qCAcC;;AAzL7B,MAAM,MAAM,GAAG;IACX,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,kBAAkB;IACtE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS;CAC3G,CAAC;AAkBF,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAEzD,MAAM,cAAc,GAAG;IACnB,yBAAyB;IACzB,uDAAuD;IACvD,uBAAuB;IACvB,0BAA0B;IAC1B,0BAA0B;IAC1B,yBAAyB;CAC5B,CAAC;AAufF,MAAM,OAAO,4BAA6B,SAAQ,oBAAoB;IAC1D,UAAU,GAAG,IAAI,CAAC;IAC1B,IACI,SAAS,CAAC,KAAa;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;IAC5D,CAAC;IACD,IAAI,SAAS,KAAa,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAE3C,QAAQ,GAAsB,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC5F,IACI,OAAO,CAAC,KAAwB;QAChC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAC5E,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC/D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,OAAO,IAAI,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;IACrD,CAAC;IACD,IAAI,OAAO,KAAwB,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAElD,mBAAmB,CAAC,CAAoB,EAAE,CAAoB;QAClE,MAAM,OAAO,GAAG,CAAC,CAAW,EAAE,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrG,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC1I,CAAC;IAES,eAAe,GAAG,IAAI,YAAY,EAAU,CAAC;IAC7C,aAAa,GAAG,IAAI,YAAY,EAAqB,CAAC;IAExD,WAAW,GAAG,KAAK,CAAC;IAEpB,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAChC,QAAQ,GAAG,IAAI,OAAO,EAAQ,CAAC;IAEhC,SAAS,GAAG,KAAK,CAAC;IAClB,oBAAoB,GAAG,kBAAkB,CAAC;IAE1C,QAAQ,GAAc,EAAE,CAAC;IACzB,SAAS,GAAe,EAAE,CAAC;IAC3B,cAAc,GAAG,CAAC,CAAC;IACnB,YAAY,GAAkB,EAAE,CAAC;IACjC,eAAe,GAAqB,EAAE,CAAC;IAEtC,OAAO,GAAsB,EAAE,CAAC;IAChC,kBAAkB,GAAsB,EAAE,CAAC;IAEnD,+FAA+F;IAC/F,2FAA2F;IACnF,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;IAElD,QAAQ;QACJ,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,QAAQ,EAAE,CAAC;IACpB,CAAC;IAED,WAAW;QACP,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;IAED,wBAAwB;IAEjB,iBAAiB,CAAC,KAAa;QAClC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;IACpB,CAAC;IAEM,eAAe,CAAC,OAA0B;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;IACpB,CAAC;IAEM,cAAc,CAAC,KAAa,EAAE,QAAQ,GAAG,CAAC;QAC7C,IAAI,KAAK,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QAChC,IAAI,KAAK,GAAG,IAAI,IAAI,QAAQ,GAAG,CAAC;YAAE,QAAQ,GAAG,CAAC,CAAC;QAC/C,OAAO,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAEM,SAAS;QACZ,MAAM,MAAM,GAAG,6JAA6J,CAAC;QAC7K,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CACtC,IAAI,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CACvS,CAAC;QACF,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;IAC/C,CAAC;IAED,qBAAqB;IAEb,KAAK,CAAC,QAAQ;QAClB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAEzB,IAAI,CAAC;YACD,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC5D,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YAC7D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC5C,MAAM,aAAa,GAAG,IAAI,CAAC,yBAAyB,CAAC,YAAY,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;YACrF,MAAM,UAAU,GAAG,IAAI,CAAC,yBAAyB,CAAC,aAAa,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;YAE5F,MAAM,CAAC,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,CAAC,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC;gBAC9E;oBACI,UAAU,EAAE,oBAAoB;oBAChC,WAAW,EAAE,aAAa;oBAC1B,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,WAAW;oBACpB,UAAU,EAAE,QAAQ;iBACvB;gBACD;oBACI,UAAU,EAAE,oBAAoB;oBAChC,WAAW,EAAE,UAAU;oBACvB,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,WAAW;oBACpB,UAAU,EAAE,QAAQ;iBACvB;gBACD;oBACI,UAAU,EAAE,oBAAoB;oBAChC,WAAW,EAAE,+CAA+C;oBAC5D,MAAM,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,YAAY,CAAC;oBAC3I,UAAU,EAAE,QAAQ;iBACvB;gBACD;oBACI,UAAU,EAAE,+BAA+B;oBAC3C,MAAM,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC;oBAC7B,UAAU,EAAE,QAAQ;iBACvB;aACJ,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,GAAG,CAAC,aAAa,EAAE,OAAO,IAAI,EAAE,CAAsB,CAAC;YACnE,IAAI,CAAC,kBAAkB,GAAG,CAAC,UAAU,EAAE,OAAO,IAAI,EAAE,CAAsB,CAAC;YAC3E,uFAAuF;YACvF,mFAAmF;YACnF,uFAAuF;YACvF,qDAAqD;YACrD,IAAI,cAAc,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;gBAC5C,OAAO,CAAC,KAAK,CAAC,qFAAqF;oBAC/F,cAAc,CAAC,YAAY,CAAC,CAAC;YACrC,CAAC;YACD,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,OAAO,IAAI,EAAE,EAAE,cAAc,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;YAEjF,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,IAAI,CAAC,cAAc,EAAE,CAAC;YACtB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC;IACL,CAAC;IAED,sBAAsB;IAEtB,+FAA+F;IACvF,OAAO,CAAC,OAAsB,EAAE,QAAuB;QAC3D,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,EAAE,CAAC,IAAI,aAAa,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;IAC9E,CAAC;IAED,mGAAmG;IAC3F,OAAO,CAAC,GAAoB;QAChC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxE,CAAC;IAED;;;;;;OAMG;IACK,iBAAiB,CAAC,IAAoB,EAAE,SAA6B;QACzE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACxB,MAAM,qBAAqB,GAAG,IAAI,GAAG,CACjC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,WAAY,CAAC,CAAC,CACvF,CAAC;QACF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,WAAW,GAAG,qBAAqB,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC;YACnF,MAAM,OAAO,GAAG,8BAA8B,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YAClE,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBACxB,iFAAiF;gBACjF,mFAAmF;gBACnF,kFAAkF;gBAClF,iBAAiB;gBACjB,SAAS;YACb,CAAC;YACD,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC;YACzD,2FAA2F;YAC3F,0FAA0F;YAC1F,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC,qBAAqB,IAAI,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC;YAC1F,MAAM,cAAc,GAAG,CAAC,GAAG,CAAC,sBAAsB,IAAI,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC;YAC5F,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC,CAAC;QAC/G,CAAC;IACL,CAAC;IAED,qFAAqF;IAC7E,eAAe,CAAC,IAAuB;QAC3C,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,KAAK,GAAG,eAAe,CAAC;YACvD,mBAAmB,EAAE,CAAC;YACtB,eAAe,EAAE,GAAG,CAAC,eAAe,IAAI,CAAC;YACzC,gBAAgB,EAAE,GAAG,CAAC,gBAAgB,IAAI,CAAC;SAC9C,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9B,CAAC;IAED,qBAAqB;IAEb,WAAW;QACf,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9E,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;QAChE,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;QAElE,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;QACtE,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACpE,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;QAEtE,MAAM,aAAa,GAAG,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpG,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAE5F,sDAAsD;QACtD,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACjD,MAAM,gBAAgB,GAAG,CAAC,UAAU,GAAG,aAAa,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAErH,MAAM,KAAK,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB,GAAG,aAAa,CAAC,GAAG,aAAa,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QAEpG,IAAI,CAAC,QAAQ,GAAG;YACZ;gBACI,KAAK,EAAE,eAAe;gBACtB,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC;gBACtC,KAAK,EAAE,IAAI;gBACX,cAAc,EAAE,QAAQ;gBACxB,WAAW,EAAE,KAAK;gBAClB,IAAI,EAAE,0BAA0B;aACnC;YACD;gBACI,KAAK,EAAE,WAAW;gBAClB,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC;gBACrC,KAAK,EAAE,IAAI;gBACX,cAAc,EAAE,QAAQ;gBACxB,WAAW,EAAE,KAAK;gBAClB,IAAI,EAAE,2BAA2B;aACpC;YACD;gBACI,KAAK,EAAE,YAAY;gBACnB,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC;gBACtC,KAAK,EAAE,KAAK;gBACZ,cAAc,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ;gBAC7F,WAAW,EAAE,KAAK;gBAClB,IAAI,EAAE,sBAAsB;aAC/B;YACD;gBACI,KAAK,EAAE,mBAAmB;gBAC1B,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC;gBAC5C,KAAK,EAAE,IAAI;gBACX,cAAc,EAAE,QAAQ;gBACxB,WAAW,EAAE,IAAI;gBACjB,IAAI,EAAE,wBAAwB;aACjC;SACJ,CAAC;QAEF,IAAI,CAAC,eAAe,EAAE,CAAC;IAC3B,CAAC;IAED,gGAAgG;IACxF,eAAe;QACnB,MAAM,MAAM,GAAqB,EAAE,mBAAmB,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC;QACrG,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,CAAC,mBAAmB,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC;YAClD,MAAM,CAAC,eAAe,IAAI,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC;YACjD,MAAM,CAAC,gBAAgB,IAAI,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC;QACvD,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAE1C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,gBAAgB;YACvB,KAAK,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG;YACpD,KAAK,EAAE,IAAI;YACX,cAAc,EAAE,QAAQ;YACxB,WAAW,EAAE,KAAK;YAClB,IAAI,EAAE,kBAAkB;SAC3B,CAAC,CAAC;QAEH,2FAA2F;QAC3F,mFAAmF;QACnF,MAAM,YAAY,GAAG,OAAO,GAAG,CAAC;YAC5B,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;YAC9B,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,iBAAiB;YACxB,KAAK,EAAE,YAAY;YACnB,KAAK,EAAE,IAAI;YACX,cAAc,EAAE,QAAQ;YACxB,WAAW,EAAE,OAAO,GAAG,CAAC;YACxB,IAAI,EAAE,wBAAwB;SACjC,CAAC,CAAC;IACP,CAAC;IAEO,gBAAgB;QACpB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC1C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,CAAC;QAE1C,uDAAuD;QACvD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACvF,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC;YAC9B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM;YACvE,CAAC,CAAC,CAAC,CAAC;QACR,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC;QAE3C,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACzC,IAAI,EAAE,GAAG;YACT,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC;YAC/B,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;YACf,aAAa,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YAC1D,SAAS,EAAE,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,gBAAgB;SACxD,CAAC,CAAC,CAAC;QAEJ,IAAI,CAAC,cAAc,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IAEO,cAAc;QAClB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC9C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC;YACrC,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/F,CAAC;QAED,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1E,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7E,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACnD,KAAK,EAAE,MAAM;YACb,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YAC7C,KAAK,EAAE,cAAc,CAAC,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC;YAChD,QAAQ,EAAE,EAAE;SACf,CAAC,CAAC,CAAC;IACR,CAAC;IAEO,kBAAkB;QACtB,MAAM,MAAM,GAAG,IAAI,GAAG,EAA6B,CAAC;QACpD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC;YACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC1C,MAAM,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAErF,MAAM,IAAI,GAAqB,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,EAAE,SAAS,CAAC,IAAI,MAAM,EAAE,CAAC;YACjC,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7E,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAClF,MAAM,eAAe,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACpF,MAAM,gBAAgB,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACtF,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAE7E,2DAA2D;YAC3D,MAAM,OAAO,GAAG,WAAW,GAAG,YAAY,CAAC;YAC3C,MAAM,SAAS,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACnE,MAAM,UAAU,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAErE,IAAI,CAAC,IAAI,CAAC;gBACN,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS;gBACtC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,SAAS;gBACxC,IAAI,EAAE,SAAS,CAAC,MAAM;gBACtB,WAAW,EAAE,WAAW;gBACxB,YAAY,EAAE,YAAY;gBAC1B,eAAe,EAAE,eAAe;gBAChC,gBAAgB,EAAE,gBAAgB;gBAClC,YAAY,EAAE,YAAY,CAAC,EAAE,mBAAmB,EAAE,WAAW,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC;gBACnG,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC;gBAC7C,SAAS,EAAE,SAAS;gBACpB,UAAU,EAAE,UAAU;gBACtB,SAAS,EAAE,IAAI;gBACf,cAAc,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;aAC/D,CAAC,CAAC;QACP,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IAC1E,CAAC;IAED,gBAAgB;IAER,cAAc,CAAC,IAAuB,EAAE,KAAW,EAAE,GAAS;QAClE,OAAO,IAAI;aACN,MAAM,CAAC,CAAC,CAAC,EAAE;YACR,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC;QAClC,CAAC,CAAC;aACD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IAEO,aAAa;QACjB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAA2B;YAClC,OAAO,EAAE,QAAQ;YACjB,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,UAAU;YACjB,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,QAAQ;SAClC,CAAC;QACF,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC;QAC9C,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;IAC3C,CAAC;IAEO,gBAAgB;QACpB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/D,OAAO,eAAe,GAAG,GAAG,CAAC;IACjC,CAAC;IAEO,yBAAyB,CAAC,KAAW,EAAE,GAAS,EAAE,WAAmB;QACzE,MAAM,KAAK,GAAG;YACV,aAAa,KAAK,CAAC,WAAW,EAAE,GAAG;YACnC,aAAa,GAAG,CAAC,WAAW,EAAE,GAAG;SACpC,CAAC;QACF,IAAI,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACzC,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAEO,cAAc,CAAC,OAAe;QAClC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QACpG,OAAO,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC;IAEO,WAAW,CAAC,GAAW,EAAE,QAAgB;QAC7C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;6RA/bQ,4BAA4B,yBAA5B,4BAA4B;6DAA5B,4BAA4B;YA5e/B,AAJF,8FAAiB,+DAIR;;YAJT,uCAyJC;;;iFAuVI,4BAA4B;cArfxC,SAAS;6BACM,KAAK,YACP,2BAA2B,YAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA4JT;;kBAwVA,KAAK;;kBASL,KAAK;;kBAcL,MAAM;;kBACN,MAAM;;kFA1BE,4BAA4B;AAkczC,MAAM,UAAU,uBAAuB,KAAmC,CAAC","sourcesContent":["/**\n * @fileoverview Cost & Budget Analytics -- Option B.\n *\n * Displays cost KPIs with period deltas, a daily cost bar chart with anomaly detection,\n * a cost breakdown treemap, and a detailed cost-by-model table with CSV export.\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 { NormalizeUUID } from '@memberjunction/global';\nimport { TOKEN_PRICE_UNIT_TYPE_DIVISORS } from '@memberjunction/ai-engine-base';\nimport { CacheRate, CacheTokenTotals, cacheHitRate, hasCacheActivity, netCacheSavings } from '../../../services/cache-metrics';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { GlobalFilterState } from '../../../interfaces/analytics-preferences.interface';\n\n// ── Interfaces ──\n\ninterface PromptRunRecord {\n ID: string;\n RunAt: string;\n Cost: number | null;\n TotalCost: number | null;\n TokensPrompt: number | null;\n TokensCompletion: number | null;\n TokensUsed: number | null;\n TokensCacheRead: number | null;\n TokensCacheWrite: number | null;\n ModelID: string | null;\n Model: string | null;\n VendorID: string | null;\n Vendor: string | null;\n Success: boolean;\n}\n\ninterface CostKpi {\n Label: string;\n Value: string;\n Delta: number | null;\n DeltaDirection: 'up' | 'down' | 'stable';\n Highlighted: boolean;\n Icon: string;\n}\n\ninterface DailyBar {\n Date: string;\n Label: string;\n Cost: number;\n HeightPercent: number;\n IsAnomaly: boolean;\n}\n\ninterface TreemapCell {\n Label: string;\n Cost: number;\n Percent: number;\n Color: string;\n GridArea: string;\n}\n\ninterface CostByModelRow {\n Model: string;\n Vendor: string;\n Runs: number;\n InputTokens: number;\n OutputTokens: number;\n CacheReadTokens: number;\n CacheWriteTokens: number;\n CacheHitRate: number;\n CacheSavings: number;\n InputCost: number;\n OutputCost: number;\n TotalCost: number;\n PercentOfTotal: number;\n}\n\nconst FIELDS = [\n 'ID', 'RunAt', 'Cost', 'TotalCost', 'TokensPrompt', 'TokensCompletion',\n 'TokensUsed', 'TokensCacheRead', 'TokensCacheWrite', 'ModelID', 'Model', 'VendorID', 'Vendor', 'Success'\n];\n\ninterface ModelCostRow {\n ModelID: string | null;\n VendorID: string | null;\n InputPricePerUnit: number | null;\n OutputPricePerUnit: number | null;\n CacheReadPricePerUnit: number | null;\n CacheWritePricePerUnit: number | null;\n UnitTypeID: string | null;\n}\n\n/** A price unit type, reduced to what the scale lookup needs. */\ninterface PriceUnitTypeRow {\n ID: string;\n DriverClass: string | null;\n}\n\nconst TIME_RANGE_OPTIONS = ['Today', '7d', '30d', 'MTD'];\n\nconst TREEMAP_COLORS = [\n 'var(--mj-brand-primary)',\n 'var(--mj-brand-accent, var(--mj-brand-primary-hover))',\n 'var(--mj-status-info)',\n 'var(--mj-status-success)',\n 'var(--mj-status-warning)',\n 'var(--mj-text-disabled)'\n];\n\n@Component({\n standalone: false,\n selector: 'app-analytics-cost-budget',\n template: `\n\n @if (IsLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading cost data...\"></mj-loading>\n </div>\n } @else {\n <!-- Cost KPI Row -->\n <div class=\"kpi-row\">\n @for (kpi of CostKpis; track kpi.Label) {\n <div class=\"kpi-card\" [class.kpi-card--highlighted]=\"kpi.Highlighted\">\n <div class=\"kpi-icon\">\n <i [class]=\"kpi.Icon\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-label\">{{ kpi.Label }}</div>\n <div class=\"kpi-value\">{{ kpi.Value }}</div>\n @if (kpi.Delta != null) {\n <div class=\"kpi-delta\"\n [class.kpi-delta--up]=\"kpi.DeltaDirection === 'up'\"\n [class.kpi-delta--down]=\"kpi.DeltaDirection === 'down'\">\n @if (kpi.DeltaDirection === 'up') {\n <i class=\"fa-solid fa-arrow-up\"></i>\n } @else if (kpi.DeltaDirection === 'down') {\n <i class=\"fa-solid fa-arrow-down\"></i>\n }\n {{ kpi.Delta | number:'1.1-1' }}% vs prev\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Two-Column Layout -->\n <div class=\"two-col\">\n <!-- Daily Cost Trend -->\n <div class=\"panel panel-chart\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-chart-column panel-header__icon\"></i>\n Daily Cost Trend\n </div>\n </div>\n <div class=\"chart-body\">\n @if (DailyBars.length === 0) {\n <mj-empty-state Size=\"compact\" Variant=\"empty\" Icon=\"fa-solid fa-chart-column\"\n Title=\"No cost data for selected period\" />\n } @else {\n <div class=\"bar-chart\">\n <div class=\"bar-chart-area\">\n @if (AvgLinePercent > 0) {\n <div class=\"avg-line\" [style.bottom.%]=\"AvgLinePercent\">\n <span class=\"avg-label\">avg</span>\n </div>\n }\n @for (bar of DailyBars; track bar.Date) {\n <div class=\"bar-col\" [title]=\"bar.Label + ': ' + FormatCurrency(bar.Cost)\">\n <div\n class=\"bar\"\n [class.bar--anomaly]=\"bar.IsAnomaly\"\n [style.height.%]=\"bar.HeightPercent\"\n ></div>\n <div class=\"bar-label\">{{ bar.Label }}</div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Cost Breakdown Treemap -->\n <div class=\"panel panel-treemap\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-chart-pie panel-header__icon\"></i>\n Cost by Vendor\n </div>\n </div>\n <div class=\"treemap-body\">\n @if (TreemapCells.length === 0) {\n <mj-empty-state Size=\"compact\" Variant=\"empty\" Icon=\"fa-solid fa-chart-pie\"\n Title=\"No vendor cost data\" />\n } @else {\n <div class=\"treemap-grid\">\n @for (cell of TreemapCells; track cell.Label) {\n <div\n class=\"treemap-cell\"\n [style.background]=\"cell.Color\"\n [style.flex-basis.%]=\"cell.Percent\"\n [title]=\"cell.Label + ': ' + FormatCurrency(cell.Cost) + ' (' + (cell.Percent | number:'1.0-0') + '%)'\">\n <span class=\"treemap-label\">{{ cell.Label }}</span>\n <span class=\"treemap-value\">{{ FormatCurrency(cell.Cost) }}</span>\n <span class=\"treemap-pct\">{{ cell.Percent | number:'1.0-0' }}%</span>\n </div>\n }\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Cost by Model Table -->\n <div class=\"panel\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-table panel-header__icon\"></i>\n Cost by Model\n </div>\n <button class=\"export-btn\" (click)=\"ExportCSV()\">\n <i class=\"fa-solid fa-download\"></i>\n Export CSV\n </button>\n </div>\n <div class=\"table-wrapper\">\n <table class=\"data-table\">\n <thead>\n <tr>\n <th>Model</th>\n <th>Vendor</th>\n <th class=\"col-numeric\">Runs</th>\n <th class=\"col-numeric\">Input Tokens</th>\n <th class=\"col-numeric\">Output Tokens</th>\n <th class=\"col-numeric\" title=\"Share of input tokens served from the provider's prompt cache\">Cache Hit</th>\n <th class=\"col-numeric\">Input Cost</th>\n <th class=\"col-numeric\">Output Cost</th>\n <th class=\"col-numeric\">Total Cost</th>\n <th class=\"col-numeric\" title=\"Net dollars saved by caching vs. full input pricing (requires cache rates on AI Model Costs)\">Cache Saved</th>\n <th class=\"col-numeric\">% of Total</th>\n </tr>\n </thead>\n <tbody>\n @if (CostByModelRows.length === 0) {\n <tr><td colspan=\"11\" class=\"empty-row\">No data available</td></tr>\n }\n @for (row of CostByModelRows; track row.Model) {\n <tr>\n <td class=\"cell-model\">{{ row.Model }}</td>\n <td class=\"cell-vendor\">{{ row.Vendor }}</td>\n <td class=\"cell-numeric\">{{ row.Runs | number }}</td>\n <td class=\"cell-numeric\">{{ row.InputTokens | number }}</td>\n <td class=\"cell-numeric\">{{ row.OutputTokens | number }}</td>\n <td class=\"cell-numeric\">{{ (row.CacheReadTokens + row.CacheWriteTokens) > 0 ? ((row.CacheHitRate * 100 | number:'1.0-0') + '%') : '—' }}</td>\n <td class=\"cell-numeric\">{{ FormatCurrency(row.InputCost, 4) }}</td>\n <td class=\"cell-numeric\">{{ FormatCurrency(row.OutputCost, 4) }}</td>\n <td class=\"cell-numeric cell-cost\">{{ FormatCurrency(row.TotalCost) }}</td>\n <td class=\"cell-numeric\">{{ row.CacheSavings > 0 ? FormatCurrency(row.CacheSavings, 4) : '—' }}</td>\n <td class=\"cell-numeric\">{{ row.PercentOfTotal | number:'1.1-1' }}%</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 /* ── KPI Row ── */\n .kpi-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 12px;\n margin: 16px 0;\n }\n\n .kpi-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 16px;\n display: flex;\n align-items: flex-start;\n gap: 12px;\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n }\n\n .kpi-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n }\n\n .kpi-card--highlighted {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n }\n\n .kpi-icon {\n width: 36px;\n height: 36px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 14px;\n flex-shrink: 0;\n }\n\n .kpi-card--highlighted .kpi-icon {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n }\n\n .kpi-label {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .kpi-value {\n font-size: 22px;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin: 2px 0;\n letter-spacing: -0.02em;\n }\n\n .kpi-delta {\n font-size: 11px;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 3px;\n }\n\n .kpi-delta--up {\n color: var(--mj-status-error);\n }\n\n .kpi-delta--down {\n color: var(--mj-status-success);\n }\n\n /* ── Two-Column Layout ── */\n .two-col {\n display: grid;\n grid-template-columns: 3fr 2fr;\n gap: 16px;\n margin-bottom: 16px;\n }\n\n /* ── Panel ── */\n .panel {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 16px;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .panel-header__title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .panel-header__icon {\n font-size: 13px;\n color: var(--mj-brand-primary);\n }\n\n .export-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 13px;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n }\n\n .export-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n /* ── Bar Chart ── */\n .chart-body {\n padding: 16px 18px;\n }\n\n .bar-chart {\n width: 100%;\n }\n\n .bar-chart-area {\n display: flex;\n align-items: flex-end;\n gap: 4px;\n height: 180px;\n position: relative;\n padding-bottom: 24px;\n }\n\n .bar-col {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n height: 100%;\n justify-content: flex-end;\n }\n\n .bar {\n width: 100%;\n max-width: 32px;\n border-radius: 4px 4px 0 0;\n background: var(--mj-brand-primary);\n transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 2px;\n }\n\n .bar--anomaly {\n background: var(--mj-status-error);\n }\n\n .bar-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n white-space: nowrap;\n }\n\n .avg-line {\n position: absolute;\n left: 0;\n right: 0;\n border-top: 2px dashed var(--mj-text-disabled);\n z-index: 1;\n pointer-events: none;\n }\n\n .avg-label {\n position: absolute;\n right: 0;\n top: -14px;\n font-size: 10px;\n color: var(--mj-text-disabled);\n font-weight: 600;\n }\n\n /* ── Treemap ── */\n .treemap-body {\n padding: 16px 18px;\n }\n\n .treemap-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n min-height: 160px;\n }\n\n .treemap-cell {\n border-radius: 8px;\n padding: 12px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n min-width: 80px;\n min-height: 70px;\n flex-grow: 1;\n color: var(--mj-text-inverse, white);\n transition: opacity 0.2s;\n }\n\n .treemap-cell:hover {\n opacity: 0.85;\n }\n\n .treemap-label {\n font-size: 12px;\n font-weight: 600;\n opacity: 0.95;\n }\n\n .treemap-value {\n font-size: 16px;\n font-weight: 700;\n margin-top: 2px;\n }\n\n .treemap-pct {\n font-size: 11px;\n opacity: 0.8;\n }\n\n /* ── Table ── */\n .table-wrapper {\n overflow-x: auto;\n }\n\n .data-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .data-table th,\n .data-table td {\n padding: 10px 14px;\n text-align: left;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .data-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-numeric { text-align: right; }\n\n .data-table tbody tr {\n transition: background 0.15s;\n }\n\n .data-table tbody tr:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .cell-model {\n font-weight: 500;\n color: var(--mj-text-primary);\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 .empty-row {\n text-align: center;\n color: var(--mj-text-disabled);\n padding: 24px;\n }\n\n /* ── Responsive ── */\n @media (max-width: 1200px) {\n .two-col {\n grid-template-columns: 1fr;\n }\n }\n\n @media (max-width: 768px) {\n .kpi-row {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .kpi-value {\n font-size: 18px;\n }\n }\n `]\n})\nexport class AnalyticsCostBudgetComponent extends BaseAngularComponent implements OnInit, OnDestroy {\n private _timeRange = '7d';\n @Input()\n set TimeRange(value: string) {\n const prev = this._timeRange;\n this._timeRange = value;\n if (prev !== value && this.initialized) this.LoadData();\n }\n get TimeRange(): string { return this._timeRange; }\n\n private _filters: GlobalFilterState = { Models: [], Agents: [], Prompts: [], Statuses: [] };\n @Input()\n set Filters(value: GlobalFilterState) {\n const next = value ?? { Models: [], Agents: [], Prompts: [], Statuses: [] };\n const changed = !this.shallowFiltersEqual(this._filters, next);\n this._filters = next;\n if (changed && this.initialized) this.LoadData();\n }\n get Filters(): GlobalFilterState { return this._filters; }\n\n private shallowFiltersEqual(a: GlobalFilterState, b: GlobalFilterState): boolean {\n const sameArr = (x: string[], y: string[]) => x.length === y.length && x.every((v, i) => v === y[i]);\n return sameArr(a.Models, b.Models) && sameArr(a.Agents, b.Agents) && sameArr(a.Prompts, b.Prompts) && sameArr(a.Statuses, b.Statuses);\n }\n\n @Output() TimeRangeChange = new EventEmitter<string>();\n @Output() FiltersChange = new EventEmitter<GlobalFilterState>();\n\n private initialized = false;\n\n private cdr = inject(ChangeDetectorRef);\n private destroy$ = new Subject<void>();\n\n public IsLoading = false;\n public TimeRangeOptionsList = TIME_RANGE_OPTIONS;\n\n public CostKpis: CostKpi[] = [];\n public DailyBars: DailyBar[] = [];\n public AvgLinePercent = 0;\n public TreemapCells: TreemapCell[] = [];\n public CostByModelRows: CostByModelRow[] = [];\n\n private allRuns: PromptRunRecord[] = [];\n private previousPeriodRuns: PromptRunRecord[] = [];\n\n // Per model+vendor cache pricing (rates already normalized to currency-per-token). Empty until\n // AIModelCost cache rates are configured — savings then stays 0 (surfaced as \"Set rates\").\n private cacheRates = new Map<string, CacheRate>();\n\n ngOnInit(): void {\n this.initialized = true;\n this.LoadData();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n // ── Public Handlers ──\n\n public OnTimeRangeChange(range: string): void {\n this.TimeRange = range;\n this.TimeRangeChange.emit(range);\n this.LoadData();\n }\n\n public OnFiltersChange(filters: GlobalFilterState): void {\n this.Filters = filters;\n this.FiltersChange.emit(filters);\n this.LoadData();\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 public ExportCSV(): void {\n const header = 'Model,Vendor,Runs,Input Tokens,Output Tokens,Cache Read Tokens,Cache Write Tokens,Cache Hit Rate %,Input Cost,Output Cost,Total Cost,Cache Saved,% of Total';\n const rows = this.CostByModelRows.map(r =>\n `\"${r.Model}\",\"${r.Vendor}\",${r.Runs},${r.InputTokens},${r.OutputTokens},${r.CacheReadTokens},${r.CacheWriteTokens},${(r.CacheHitRate * 100).toFixed(1)},${r.InputCost.toFixed(6)},${r.OutputCost.toFixed(6)},${r.TotalCost.toFixed(6)},${r.CacheSavings.toFixed(6)},${r.PercentOfTotal.toFixed(1)}`\n );\n const csv = [header, ...rows].join('\\n');\n this.downloadCSV(csv, 'cost-by-model.csv');\n }\n\n // ── Data Loading ──\n\n private async LoadData(): Promise<void> {\n this.IsLoading = true;\n this.cdr.detectChanges();\n\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const { currentStart, previousStart } = this.getDateBounds();\n const now = new Date();\n const modelFilter = this.buildModelFilter();\n const currentFilter = this.combineDateAndModelFilter(currentStart, now, modelFilter);\n const prevFilter = this.combineDateAndModelFilter(previousStart, currentStart, modelFilter);\n\n const [currentResult, prevResult, rateResult, unitTypeResult] = await rv.RunViews([\n {\n EntityName: 'MJ: AI Prompt Runs',\n ExtraFilter: currentFilter,\n Fields: FIELDS,\n OrderBy: 'RunAt ASC',\n ResultType: 'simple'\n },\n {\n EntityName: 'MJ: AI Prompt Runs',\n ExtraFilter: prevFilter,\n Fields: FIELDS,\n OrderBy: 'RunAt ASC',\n ResultType: 'simple'\n },\n {\n EntityName: 'MJ: AI Model Costs',\n ExtraFilter: `Status='Active' AND ProcessingType='Realtime'`,\n Fields: ['ModelID', 'VendorID', 'InputPricePerUnit', 'OutputPricePerUnit', 'CacheReadPricePerUnit', 'CacheWritePricePerUnit', 'UnitTypeID'],\n ResultType: 'simple'\n },\n {\n EntityName: 'MJ: AI Model Price Unit Types',\n Fields: ['ID', 'DriverClass'],\n ResultType: 'simple'\n }\n ]);\n\n this.allRuns = (currentResult?.Results ?? []) as PromptRunRecord[];\n this.previousPeriodRuns = (prevResult?.Results ?? []) as PromptRunRecord[];\n // A failed unit-type view is NOT the same as \"these rows are unpriceable\". Without the\n // driver classes every rate row falls into the `continue` below, and cache savings\n // render as a confident 0 instead of an error — the figure most likely to be believed.\n // Say so rather than let the empty map speak for it.\n if (unitTypeResult && !unitTypeResult.Success) {\n console.error('Cost & Budget: price unit types failed to load; cache-savings figures will read 0. ' +\n unitTypeResult.ErrorMessage);\n }\n this.buildCacheRateMap(rateResult?.Results ?? [], unitTypeResult?.Results ?? []);\n\n this.computeKpis();\n this.computeDailyBars();\n this.computeTreemap();\n this.computeCostByModel();\n } catch (e) {\n console.error('Cost & Budget load error:', e);\n } finally {\n this.IsLoading = false;\n this.cdr.detectChanges();\n }\n }\n\n // ── Cache pricing ──\n\n /** Stable map key for a model+vendor pair (UUIDs normalized for case-insensitive matching). */\n private rateKey(modelID: string | null, vendorID: string | null): string {\n return `${NormalizeUUID(modelID ?? '')}|${NormalizeUUID(vendorID ?? '')}`;\n }\n\n /** The cache rate for a run's model+vendor, or undefined when no active cost row is configured. */\n private rateFor(run: PromptRunRecord): CacheRate | undefined {\n return this.cacheRates.get(this.rateKey(run.ModelID, run.VendorID));\n }\n\n /**\n * Build the per-model+vendor rate lookup, normalizing each per-unit price to currency-per-token.\n *\n * The scale comes from the unit type's DriverClass, not its display name — the name is editable\n * metadata (`Per 1M Tokens`) while the driver class is the contract the pricing drivers register\n * under, so this cannot drift the way a hardcoded name table does.\n */\n private buildCacheRateMap(rows: ModelCostRow[], unitTypes: PriceUnitTypeRow[]): void {\n this.cacheRates.clear();\n const driverClassByUnitType = new Map<string, string>(\n unitTypes.filter(u => u.DriverClass).map(u => [NormalizeUUID(u.ID), u.DriverClass!])\n );\n for (const row of rows) {\n const driverClass = driverClassByUnitType.get(NormalizeUUID(row.UnitTypeID ?? ''));\n const divisor = TOKEN_PRICE_UNIT_TYPE_DIVISORS[driverClass ?? ''];\n if (divisor === undefined) {\n // A non-token unit type (per minute/hour/image), or one this build has no driver\n // for. Defaulting to the per-1M-token divisor would divide an hourly audio rate by\n // a million and report a savings figure that is pure noise; no rate at all is the\n // honest answer.\n continue;\n }\n const inputRate = (row.InputPricePerUnit ?? 0) / divisor;\n // Cache read/write fall back to the input rate when no distinct rate is recorded — exactly\n // as the server-side cost calculator does — which makes the corresponding savings term 0.\n const cacheReadRate = (row.CacheReadPricePerUnit ?? row.InputPricePerUnit ?? 0) / divisor;\n const cacheWriteRate = (row.CacheWritePricePerUnit ?? row.InputPricePerUnit ?? 0) / divisor;\n this.cacheRates.set(this.rateKey(row.ModelID, row.VendorID), { inputRate, cacheReadRate, cacheWriteRate });\n }\n }\n\n /** Sum net cache savings across a set of runs using each run's model+vendor rate. */\n private sumCacheSavings(runs: PromptRunRecord[]): number {\n return runs.reduce((total, run) => total + netCacheSavings({\n uncachedInputTokens: 0,\n cacheReadTokens: run.TokensCacheRead ?? 0,\n cacheWriteTokens: run.TokensCacheWrite ?? 0\n }, this.rateFor(run)), 0);\n }\n\n // ── Computations ──\n\n private computeKpis(): void {\n const now = new Date();\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const weekStart = new Date(todayStart.getTime() - 6 * 86400000);\n const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);\n\n const todaySpend = this.sumCostInRange(this.allRuns, todayStart, now);\n const weekSpend = this.sumCostInRange(this.allRuns, weekStart, now);\n const monthSpend = this.sumCostInRange(this.allRuns, monthStart, now);\n\n const prevTotalCost = this.previousPeriodRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n const currentTotalCost = this.allRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n // Project monthly cost based on current daily average\n const daysIntoMonth = Math.max(1, now.getDate());\n const projectedMonthly = (monthSpend / daysIntoMonth) * new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();\n\n const delta = prevTotalCost > 0 ? ((currentTotalCost - prevTotalCost) / prevTotalCost) * 100 : null;\n\n this.CostKpis = [\n {\n Label: \"Today's Spend\",\n Value: this.FormatCurrency(todaySpend),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar-day'\n },\n {\n Label: 'This Week',\n Value: this.FormatCurrency(weekSpend),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar-week'\n },\n {\n Label: 'This Month',\n Value: this.FormatCurrency(monthSpend),\n Delta: delta,\n DeltaDirection: delta != null ? (delta > 0 ? 'up' : delta < 0 ? 'down' : 'stable') : 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar'\n },\n {\n Label: 'Projected Monthly',\n Value: this.FormatCurrency(projectedMonthly),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: true,\n Icon: 'fa-solid fa-chart-line'\n }\n ];\n\n this.appendCacheKpis();\n }\n\n /** Append the cache hit-rate and cache-savings KPIs (computed from the current-period runs). */\n private appendCacheKpis(): void {\n const totals: CacheTokenTotals = { uncachedInputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };\n for (const r of this.allRuns) {\n totals.uncachedInputTokens += r.TokensPrompt ?? 0;\n totals.cacheReadTokens += r.TokensCacheRead ?? 0;\n totals.cacheWriteTokens += r.TokensCacheWrite ?? 0;\n }\n const savings = this.sumCacheSavings(this.allRuns);\n const activity = hasCacheActivity(totals);\n\n this.CostKpis.push({\n Label: 'Cache Hit Rate',\n Value: (cacheHitRate(totals) * 100).toFixed(1) + '%',\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-bolt'\n });\n\n // Savings requires cache rates on AIModelCost. When cache engaged but no savings computed,\n // it means rates aren't configured yet — say so rather than implying $0 was saved.\n const savingsValue = savings > 0\n ? this.FormatCurrency(savings)\n : (activity ? 'Set rates' : '$0.00');\n this.CostKpis.push({\n Label: 'Saved via Cache',\n Value: savingsValue,\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: savings > 0,\n Icon: 'fa-solid fa-piggy-bank'\n });\n }\n\n private computeDailyBars(): void {\n const buckets = new Map<string, number>();\n for (const run of this.allRuns) {\n const date = new Date(run.RunAt);\n const key = date.toISOString().slice(0, 10);\n buckets.set(key, (buckets.get(key) ?? 0) + (run.Cost ?? run.TotalCost ?? 0));\n }\n\n const sortedKeys = Array.from(buckets.keys()).sort();\n const values = sortedKeys.map(k => buckets.get(k) ?? 0);\n const maxVal = Math.max(...values, 0.001);\n\n // Anomaly detection: > 2 standard deviations from mean\n const mean = values.length > 0 ? values.reduce((s, v) => s + v, 0) / values.length : 0;\n const variance = values.length > 1\n ? values.reduce((s, v) => s + Math.pow(v - mean, 2), 0) / values.length\n : 0;\n const stdDev = Math.sqrt(variance);\n const anomalyThreshold = mean + 2 * stdDev;\n\n this.DailyBars = sortedKeys.map((key, i) => ({\n Date: key,\n Label: this.formatBarLabel(key),\n Cost: values[i],\n HeightPercent: maxVal > 0 ? (values[i] / maxVal) * 100 : 0,\n IsAnomaly: stdDev > 0 && values[i] > anomalyThreshold\n }));\n\n this.AvgLinePercent = maxVal > 0 ? (mean / maxVal) * 100 : 0;\n }\n\n private computeTreemap(): void {\n const vendorCosts = new Map<string, number>();\n for (const run of this.allRuns) {\n const vendor = run.Vendor ?? 'Other';\n vendorCosts.set(vendor, (vendorCosts.get(vendor) ?? 0) + (run.Cost ?? run.TotalCost ?? 0));\n }\n\n const total = Array.from(vendorCosts.values()).reduce((s, v) => s + v, 0);\n const sorted = Array.from(vendorCosts.entries()).sort((a, b) => b[1] - a[1]);\n\n this.TreemapCells = sorted.map(([vendor, cost], i) => ({\n Label: vendor,\n Cost: cost,\n Percent: total > 0 ? (cost / total) * 100 : 0,\n Color: TREEMAP_COLORS[i % TREEMAP_COLORS.length],\n GridArea: ''\n }));\n }\n\n private computeCostByModel(): void {\n const groups = new Map<string, PromptRunRecord[]>();\n for (const run of this.allRuns) {\n const key = run.ModelID ?? 'unknown';\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key)!.push(run);\n }\n\n const totalCost = this.allRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n const rows: CostByModelRow[] = [];\n for (const [, modelRuns] of groups) {\n const inputTokens = modelRuns.reduce((s, r) => s + (r.TokensPrompt ?? 0), 0);\n const outputTokens = modelRuns.reduce((s, r) => s + (r.TokensCompletion ?? 0), 0);\n const cacheReadTokens = modelRuns.reduce((s, r) => s + (r.TokensCacheRead ?? 0), 0);\n const cacheWriteTokens = modelRuns.reduce((s, r) => s + (r.TokensCacheWrite ?? 0), 0);\n const cost = modelRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n // Approximate input/output cost split based on token ratio\n const totalTk = inputTokens + outputTokens;\n const inputCost = totalTk > 0 ? cost * (inputTokens / totalTk) : 0;\n const outputCost = totalTk > 0 ? cost * (outputTokens / totalTk) : 0;\n\n rows.push({\n Model: modelRuns[0].Model ?? 'Unknown',\n Vendor: modelRuns[0].Vendor ?? 'Unknown',\n Runs: modelRuns.length,\n InputTokens: inputTokens,\n OutputTokens: outputTokens,\n CacheReadTokens: cacheReadTokens,\n CacheWriteTokens: cacheWriteTokens,\n CacheHitRate: cacheHitRate({ uncachedInputTokens: inputTokens, cacheReadTokens, cacheWriteTokens }),\n CacheSavings: this.sumCacheSavings(modelRuns),\n InputCost: inputCost,\n OutputCost: outputCost,\n TotalCost: cost,\n PercentOfTotal: totalCost > 0 ? (cost / totalCost) * 100 : 0\n });\n }\n\n this.CostByModelRows = rows.sort((a, b) => b.TotalCost - a.TotalCost);\n }\n\n // ── Helpers ──\n\n private sumCostInRange(runs: PromptRunRecord[], start: Date, end: Date): number {\n return runs\n .filter(r => {\n const d = new Date(r.RunAt);\n return d >= start && d <= end;\n })\n .reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n }\n\n private getDateBounds(): { currentStart: Date; previousStart: Date } {\n const now = new Date();\n const msMap: Record<string, number> = {\n 'Today': 86400000,\n '7d': 604800000,\n '30d': 2592000000,\n 'MTD': now.getDate() * 86400000\n };\n const ms = msMap[this.TimeRange] ?? 604800000;\n const currentStart = new Date(now.getTime() - ms);\n const previousStart = new Date(currentStart.getTime() - ms);\n return { currentStart, previousStart };\n }\n\n private buildModelFilter(): string {\n if (this.Filters.Models.length === 0) return '';\n const ids = this.Filters.Models.map(id => `'${id}'`).join(',');\n return `ModelID IN (${ids})`;\n }\n\n private combineDateAndModelFilter(start: Date, end: Date, modelFilter: string): string {\n const parts = [\n `RunAt >= '${start.toISOString()}'`,\n `RunAt <= '${end.toISOString()}'`\n ];\n if (modelFilter) parts.push(modelFilter);\n return parts.join(' AND ');\n }\n\n private formatBarLabel(dateStr: string): string {\n const d = new Date(dateStr + 'T00:00:00');\n const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n return months[d.getMonth()] + ' ' + d.getDate();\n }\n\n private downloadCSV(csv: string, filename: string): void {\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.click();\n URL.revokeObjectURL(url);\n }\n}\n\nexport function LoadAnalyticsCostBudget() { /* tree-shaking prevention */ }\n"]}
|
|
@@ -53,6 +53,14 @@ export declare abstract class BaseAdminContainerComponent extends BaseResourceCo
|
|
|
53
53
|
* scroll position, etc. — and avoids expensive re-init.
|
|
54
54
|
*/
|
|
55
55
|
protected cache: Map<string, ComponentRef<unknown>>;
|
|
56
|
+
/**
|
|
57
|
+
* A cache reattach moves this container to another tab without recreating the section components
|
|
58
|
+
* inside it, so every cached child's tab stamp has to move with it — not just the visible one.
|
|
59
|
+
* A detached section is exactly the case this matters for: it is invisible, it still holds a
|
|
60
|
+
* subscription, and left on its birth tab it would read and write that tab's params from inside
|
|
61
|
+
* a tab it no longer belongs to.
|
|
62
|
+
*/
|
|
63
|
+
protected onTabIdRebound(tabId: string): void;
|
|
56
64
|
protected currentSectionId: string | null;
|
|
57
65
|
protected readonly cdr: ChangeDetectorRef;
|
|
58
66
|
ngOnInit(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-admin-container.component.d.ts","sourceRoot":"","sources":["../../src/Admin/base-admin-container.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,MAAM,EACN,SAAS,EACT,iBAAiB,EAEjB,gBAAgB,EAChB,YAAY,EAGf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAiB,MAAM,2BAA2B,CAAC;AAGjF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;;AAEnF,iEAAiE;AACjE,MAAM,WAAW,YAAY;IACzB,iEAAiE;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GACxB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD;;;;;;;;;GASG;AACH,8BACsB,2BAA4B,SAAQ,qBAAsB,YAAW,MAAM,EAAE,SAAS;IACxG,kBAAyB,cAAc,EAAE,MAAM,CAAC;IAChD,kBAAyB,aAAa,EAAE,MAAM,CAAC;IAC/C,kBAAyB,iBAAiB,EAAE,MAAM,CAAC;IACnD,kBAAyB,QAAQ,EAAE,YAAY,EAAE,CAAC;IAGlD,SAAS,CAAC,WAAW,EAAG,gBAAgB,CAAC;IAElC,aAAa,SAAM;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,SAAS,UAAS;IAEzB;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,qCAA4C;
|
|
1
|
+
{"version":3,"file":"base-admin-container.component.d.ts","sourceRoot":"","sources":["../../src/Admin/base-admin-container.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,MAAM,EACN,SAAS,EACT,iBAAiB,EAEjB,gBAAgB,EAChB,YAAY,EAGf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAiB,MAAM,2BAA2B,CAAC;AAGjF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;;AAEnF,iEAAiE;AACjE,MAAM,WAAW,YAAY;IACzB,iEAAiE;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GACxB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD;;;;;;;;;GASG;AACH,8BACsB,2BAA4B,SAAQ,qBAAsB,YAAW,MAAM,EAAE,SAAS;IACxG,kBAAyB,cAAc,EAAE,MAAM,CAAC;IAChD,kBAAyB,aAAa,EAAE,MAAM,CAAC;IAC/C,kBAAyB,iBAAiB,EAAE,MAAM,CAAC;IACnD,kBAAyB,QAAQ,EAAE,YAAY,EAAE,CAAC;IAGlD,SAAS,CAAC,WAAW,EAAG,gBAAgB,CAAC;IAElC,aAAa,SAAM;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,SAAS,UAAS;IAEzB;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,qCAA4C;IAE3D;;;;;;OAMG;cACgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKtD,SAAS,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEjD,SAAS,CAAC,QAAQ,CAAC,GAAG,oBAA6B;IAE7B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB/B,WAAW,IAAI,IAAI;IASb,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;IACzC,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;IAE7D;;;;OAIG;IACH,IAAW,WAAW,IAAI,gBAAgB,EAAE,CAE3C;IAED,gFAAgF;IACzE,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAOlD,yEAAyE;cACtD,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI;IASlG,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAOnD,aAAa;YA+Bb,aAAa;IAwB3B,OAAO,CAAC,aAAa;YAQP,iBAAiB;YAYjB,kBAAkB;IA+ChC,OAAO,CAAC,WAAW;yCAhOD,2BAA2B;2CAA3B,2BAA2B;CAoOhD"}
|
|
@@ -27,6 +27,18 @@ export class BaseAdminContainerComponent extends BaseResourceComponent {
|
|
|
27
27
|
* scroll position, etc. — and avoids expensive re-init.
|
|
28
28
|
*/
|
|
29
29
|
cache = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* A cache reattach moves this container to another tab without recreating the section components
|
|
32
|
+
* inside it, so every cached child's tab stamp has to move with it — not just the visible one.
|
|
33
|
+
* A detached section is exactly the case this matters for: it is invisible, it still holds a
|
|
34
|
+
* subscription, and left on its birth tab it would read and write that tab's params from inside
|
|
35
|
+
* a tab it no longer belongs to.
|
|
36
|
+
*/
|
|
37
|
+
onTabIdRebound(tabId) {
|
|
38
|
+
for (const ref of this.cache.values()) {
|
|
39
|
+
this.rehomeChildToTab(ref.instance, tabId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
30
42
|
currentSectionId = null;
|
|
31
43
|
cdr = inject(ChangeDetectorRef);
|
|
32
44
|
async ngOnInit() {
|
|
@@ -181,6 +193,12 @@ export class BaseAdminContainerComponent extends BaseResourceComponent {
|
|
|
181
193
|
}
|
|
182
194
|
const ref = this.contentHost.createComponent(reg.SubClass);
|
|
183
195
|
const instance = ref.instance;
|
|
196
|
+
// Scope the embedded dashboard's query-param reads/writes to THIS container's tab. The
|
|
197
|
+
// sibling `createResourceRef` path inherits the tab id through `instance.Data`; a dashboard
|
|
198
|
+
// gets no ResourceData, so hand it the tab id explicitly. Without it the dashboard has no
|
|
199
|
+
// tab identity and its URL updates are dropped (previously: applied to whichever tab the
|
|
200
|
+
// user was viewing, which need not be this one).
|
|
201
|
+
instance.ParentTabId = this.getTabId();
|
|
184
202
|
// BaseDashboard catches a failing initDashboard()/loadData(), emits Error and still signals
|
|
185
203
|
// load-complete — so without this subscription an embedded dashboard's load failure would
|
|
186
204
|
// render as a blank content pane with a console-only error. Surface it in LoadError, which
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-admin-container.component.js","sourceRoot":"","sources":["../../src/Admin/base-admin-container.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n OnInit,\n OnDestroy,\n ChangeDetectorRef,\n ViewChild,\n ViewContainerRef,\n ComponentRef,\n Type,\n inject\n} from '@angular/core';\nimport { BaseResourceComponent, BaseDashboard } from '@memberjunction/ng-shared';\nimport { MJGlobal } from '@memberjunction/global';\nimport { DashboardEngine } from '@memberjunction/core-entities';\nimport { MJLeftNavItem, MJLeftNavSection } from '@memberjunction/ng-ui-components';\n\n/** A single sub-section inside an admin container's left-nav. */\nexport interface AdminSection {\n /** Stable identifier — used in the URL `?section=` deep-link. */\n id: string;\n label: string;\n icon: string;\n description: string;\n source: AdminSectionSource;\n}\n\n/**\n * How to resolve and instantiate the component for this sub-section.\n *\n * - `resource`: looks up `@RegisterClass(BaseResourceComponent, driverClass)` and renders it directly.\n * - `dashboard`: looks up the MJ Dashboard record by name, then renders its `DriverClass` via the\n * BaseDashboard registry with a synthetic `{ dashboard }` config (mirrors the shell's flow for\n * `ResourceType: \"Dashboards\"` nav items).\n */\nexport type AdminSectionSource =\n | { kind: 'resource'; driverClass: string }\n | { kind: 'dashboard'; dashboardName: string };\n\n/**\n * Shared base for all Admin app container resources. Subclasses declare:\n * - ContainerTitle / ContainerIcon / ContainerSubtitle\n * - Sections[]\n * - @RegisterClass(BaseResourceComponent, '...')\n *\n * The base handles: left-nav state, URL deep-linking via NavigationService,\n * dynamic component instantiation for both resource and dashboard sections,\n * teardown on switch.\n */\n@Directive()\nexport abstract class BaseAdminContainerComponent extends BaseResourceComponent implements OnInit, OnDestroy {\n public abstract readonly ContainerTitle: string;\n public abstract readonly ContainerIcon: string;\n public abstract readonly ContainerSubtitle: string;\n public abstract readonly Sections: AdminSection[];\n\n @ViewChild('contentHost', { read: ViewContainerRef, static: true })\n protected contentHost!: ViewContainerRef;\n\n public ActiveSection = '';\n public LoadError: string | null = null;\n public IsLoading = false;\n\n /**\n * Cache of section.id → ComponentRef. Once a sub-section is rendered we\n * keep its component alive across switches by detaching + reattaching the\n * view (instead of destroying + recreating). This preserves state — Event\n * Monitor's captured events, GraphQL Console's history, query inputs,\n * scroll position, etc. — and avoids expensive re-init.\n */\n protected cache = new Map<string, ComponentRef<unknown>>();\n protected currentSectionId: string | null = null;\n\n protected readonly cdr = inject(ChangeDetectorRef);\n\n public override async ngOnInit(): Promise<void> {\n super.ngOnInit();\n\n const params = this.GetQueryParams();\n const requested = params['section'];\n const initial = this.findSection(requested)?.id ?? this.Sections[0]?.id ?? '';\n const target = this.findSection(initial);\n if (target) {\n await this.selectSection(target, /*syncUrl*/ false);\n }\n\n if (!requested && this.ActiveSection) {\n this.UpdateQueryParams({ section: this.ActiveSection });\n }\n\n this.NotifyLoadComplete();\n }\n\n public override ngOnDestroy(): void {\n for (const ref of this.cache.values()) {\n try { ref.destroy(); } catch { /* ignore */ }\n }\n this.cache.clear();\n this.currentSectionId = null;\n super.ngOnDestroy();\n }\n\n public override async GetResourceDisplayName(): Promise<string> { return this.ContainerTitle; }\n public override async GetResourceIconClass(): Promise<string> { return this.ContainerIcon; }\n\n /**\n * Single-section view of `Sections` shaped for `<mj-left-nav>`. AdminSection\n * already has the required id / label / icon / description fields, so it's\n * structurally compatible with MJLeftNavItem.\n */\n public get NavSections(): MJLeftNavSection[] {\n return [{ items: this.Sections }];\n }\n\n /** Adapter from `<mj-left-nav>`'s ItemClicked event back to `selectSection`. */\n public OnNavItemClicked(item: MJLeftNavItem): void {\n const section = this.Sections.find(s => s.id === item.id);\n if (section) {\n void this.OnSectionClick(section);\n }\n }\n\n /** Called by the framework on browser back/forward + deep-link entry. */\n protected override OnQueryParamsChanged(params: Record<string, string>, _source: 'popstate' | 'deeplink'): void {\n const section = params['section'];\n if (!section || section === this.ActiveSection) return;\n const target = this.findSection(section);\n if (target) {\n void this.selectSection(target, /*syncUrl*/ false);\n }\n }\n\n public async OnSectionClick(section: AdminSection): Promise<void> {\n if (section.id === this.ActiveSection) return;\n await this.selectSection(section, /*syncUrl*/ true);\n }\n\n // ---------- protected ----------\n\n private async selectSection(section: AdminSection, syncUrl: boolean): Promise<void> {\n // Guard against duplicate calls for the same section. Without this,\n // ngOnInit's own GetQueryParams read races with the workspace stream's\n // synchronous replay through OnQueryParamsChanged on first mount —\n // both call selectSection with the same target, both reach\n // createComponent, and two instances of the sub-page end up mounted\n // in the contentHost. OnSectionClick + OnQueryParamsChanged each\n // guard their own call site, but defense-in-depth here covers any\n // future caller (and the ngOnInit ↔ stream-replay race specifically).\n if (section.id === this.ActiveSection) {\n return;\n }\n this.ActiveSection = section.id;\n this.IsLoading = true;\n this.LoadError = null;\n this.cdr.markForCheck();\n\n if (syncUrl) {\n this.UpdateQueryParams({ section: section.id });\n }\n\n try {\n await this.renderSection(section);\n } catch (err) {\n this.LoadError = err instanceof Error ? err.message : String(err);\n } finally {\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n }\n\n private async renderSection(section: AdminSection): Promise<void> {\n // Detach (don't destroy) the currently visible sub-component so it\n // stays alive in memory with its state intact.\n this.detachCurrent();\n\n // Reattach if cached\n const cached = this.cache.get(section.id);\n if (cached) {\n this.contentHost.insert(cached.hostView);\n this.currentSectionId = section.id;\n return;\n }\n\n // Otherwise, create fresh\n const ref = section.source.kind === 'resource'\n ? await this.createResourceRef(section.source.driverClass)\n : await this.createDashboardRef(section.source.dashboardName);\n\n if (ref) {\n this.cache.set(section.id, ref);\n this.currentSectionId = section.id;\n }\n }\n\n private detachCurrent(): void {\n if (!this.currentSectionId) return;\n const cur = this.cache.get(this.currentSectionId);\n if (!cur) return;\n const idx = this.contentHost.indexOf(cur.hostView);\n if (idx >= 0) this.contentHost.detach(idx);\n }\n\n private async createResourceRef(driverClass: string): Promise<ComponentRef<unknown> | null> {\n const reg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);\n if (!reg) {\n this.LoadError = `Component \"${driverClass}\" is not registered. Make sure its module is imported.`;\n return null;\n }\n const ref = this.contentHost.createComponent(reg.SubClass as Type<BaseResourceComponent>);\n const instance = ref.instance as BaseResourceComponent;\n if (this.Data) instance.Data = this.Data;\n return ref;\n }\n\n private async createDashboardRef(dashboardName: string): Promise<ComponentRef<unknown> | null> {\n await DashboardEngine.Instance.Config(false);\n const dashboard = DashboardEngine.Instance.Dashboards.find(d => d.Name === dashboardName);\n if (!dashboard) {\n this.LoadError = `Dashboard \"${dashboardName}\" was not found in metadata.`;\n return null;\n }\n if (dashboard.Type !== 'Code') {\n this.LoadError = `Dashboard \"${dashboardName}\" has type \"${dashboard.Type}\" — only Code dashboards can be embedded here.`;\n return null;\n }\n if (!dashboard.DriverClass) {\n this.LoadError = `Dashboard \"${dashboardName}\" has no DriverClass set.`;\n return null;\n }\n const reg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseDashboard, dashboard.DriverClass);\n if (!reg) {\n this.LoadError = `Driver class \"${dashboard.DriverClass}\" is not registered against BaseDashboard.`;\n return null;\n }\n const ref = this.contentHost.createComponent(reg.SubClass as Type<BaseDashboard>);\n const instance = ref.instance as BaseDashboard;\n // BaseDashboard catches a failing initDashboard()/loadData(), emits Error and still signals\n // load-complete — so without this subscription an embedded dashboard's load failure would\n // render as a blank content pane with a console-only error. Surface it in LoadError, which\n // the container already renders. Scoped to the INITIAL load (same reasoning as\n // DashboardResource): sections are cached and kept alive across switches, and LoadError only\n // resets on the next selectSection, so a post-mount Refresh() failure would otherwise pin a\n // stale error banner — potentially while a different section is on screen.\n let initialLoadSettled = false;\n const onComplete = instance.LoadCompleteEvent;\n instance.LoadCompleteEvent = () => { initialLoadSettled = true; onComplete?.(); };\n instance.Error.subscribe((err: Error) => {\n if (initialLoadSettled) return; // post-mount refresh failure — already logged by BaseDashboard.runGuardedLoad\n this.LoadError = err.message;\n });\n instance.Config = { dashboard, userState: undefined };\n instance.Refresh();\n return ref;\n }\n\n private findSection(id: string | undefined | null): AdminSection | undefined {\n if (!id) return undefined;\n return this.Sections.find(s => s.id === id);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"base-admin-container.component.js","sourceRoot":"","sources":["../../src/Admin/base-admin-container.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n OnInit,\n OnDestroy,\n ChangeDetectorRef,\n ViewChild,\n ViewContainerRef,\n ComponentRef,\n Type,\n inject\n} from '@angular/core';\nimport { BaseResourceComponent, BaseDashboard } from '@memberjunction/ng-shared';\nimport { MJGlobal } from '@memberjunction/global';\nimport { DashboardEngine } from '@memberjunction/core-entities';\nimport { MJLeftNavItem, MJLeftNavSection } from '@memberjunction/ng-ui-components';\n\n/** A single sub-section inside an admin container's left-nav. */\nexport interface AdminSection {\n /** Stable identifier — used in the URL `?section=` deep-link. */\n id: string;\n label: string;\n icon: string;\n description: string;\n source: AdminSectionSource;\n}\n\n/**\n * How to resolve and instantiate the component for this sub-section.\n *\n * - `resource`: looks up `@RegisterClass(BaseResourceComponent, driverClass)` and renders it directly.\n * - `dashboard`: looks up the MJ Dashboard record by name, then renders its `DriverClass` via the\n * BaseDashboard registry with a synthetic `{ dashboard }` config (mirrors the shell's flow for\n * `ResourceType: \"Dashboards\"` nav items).\n */\nexport type AdminSectionSource =\n | { kind: 'resource'; driverClass: string }\n | { kind: 'dashboard'; dashboardName: string };\n\n/**\n * Shared base for all Admin app container resources. Subclasses declare:\n * - ContainerTitle / ContainerIcon / ContainerSubtitle\n * - Sections[]\n * - @RegisterClass(BaseResourceComponent, '...')\n *\n * The base handles: left-nav state, URL deep-linking via NavigationService,\n * dynamic component instantiation for both resource and dashboard sections,\n * teardown on switch.\n */\n@Directive()\nexport abstract class BaseAdminContainerComponent extends BaseResourceComponent implements OnInit, OnDestroy {\n public abstract readonly ContainerTitle: string;\n public abstract readonly ContainerIcon: string;\n public abstract readonly ContainerSubtitle: string;\n public abstract readonly Sections: AdminSection[];\n\n @ViewChild('contentHost', { read: ViewContainerRef, static: true })\n protected contentHost!: ViewContainerRef;\n\n public ActiveSection = '';\n public LoadError: string | null = null;\n public IsLoading = false;\n\n /**\n * Cache of section.id → ComponentRef. Once a sub-section is rendered we\n * keep its component alive across switches by detaching + reattaching the\n * view (instead of destroying + recreating). This preserves state — Event\n * Monitor's captured events, GraphQL Console's history, query inputs,\n * scroll position, etc. — and avoids expensive re-init.\n */\n protected cache = new Map<string, ComponentRef<unknown>>();\n\n /**\n * A cache reattach moves this container to another tab without recreating the section components\n * inside it, so every cached child's tab stamp has to move with it — not just the visible one.\n * A detached section is exactly the case this matters for: it is invisible, it still holds a\n * subscription, and left on its birth tab it would read and write that tab's params from inside\n * a tab it no longer belongs to.\n */\n protected override onTabIdRebound(tabId: string): void {\n for (const ref of this.cache.values()) {\n this.rehomeChildToTab(ref.instance as BaseResourceComponent | undefined, tabId);\n }\n }\n protected currentSectionId: string | null = null;\n\n protected readonly cdr = inject(ChangeDetectorRef);\n\n public override async ngOnInit(): Promise<void> {\n super.ngOnInit();\n\n const params = this.GetQueryParams();\n const requested = params['section'];\n const initial = this.findSection(requested)?.id ?? this.Sections[0]?.id ?? '';\n const target = this.findSection(initial);\n if (target) {\n await this.selectSection(target, /*syncUrl*/ false);\n }\n\n if (!requested && this.ActiveSection) {\n this.UpdateQueryParams({ section: this.ActiveSection });\n }\n\n this.NotifyLoadComplete();\n }\n\n public override ngOnDestroy(): void {\n for (const ref of this.cache.values()) {\n try { ref.destroy(); } catch { /* ignore */ }\n }\n this.cache.clear();\n this.currentSectionId = null;\n super.ngOnDestroy();\n }\n\n public override async GetResourceDisplayName(): Promise<string> { return this.ContainerTitle; }\n public override async GetResourceIconClass(): Promise<string> { return this.ContainerIcon; }\n\n /**\n * Single-section view of `Sections` shaped for `<mj-left-nav>`. AdminSection\n * already has the required id / label / icon / description fields, so it's\n * structurally compatible with MJLeftNavItem.\n */\n public get NavSections(): MJLeftNavSection[] {\n return [{ items: this.Sections }];\n }\n\n /** Adapter from `<mj-left-nav>`'s ItemClicked event back to `selectSection`. */\n public OnNavItemClicked(item: MJLeftNavItem): void {\n const section = this.Sections.find(s => s.id === item.id);\n if (section) {\n void this.OnSectionClick(section);\n }\n }\n\n /** Called by the framework on browser back/forward + deep-link entry. */\n protected override OnQueryParamsChanged(params: Record<string, string>, _source: 'popstate' | 'deeplink'): void {\n const section = params['section'];\n if (!section || section === this.ActiveSection) return;\n const target = this.findSection(section);\n if (target) {\n void this.selectSection(target, /*syncUrl*/ false);\n }\n }\n\n public async OnSectionClick(section: AdminSection): Promise<void> {\n if (section.id === this.ActiveSection) return;\n await this.selectSection(section, /*syncUrl*/ true);\n }\n\n // ---------- protected ----------\n\n private async selectSection(section: AdminSection, syncUrl: boolean): Promise<void> {\n // Guard against duplicate calls for the same section. Without this,\n // ngOnInit's own GetQueryParams read races with the workspace stream's\n // synchronous replay through OnQueryParamsChanged on first mount —\n // both call selectSection with the same target, both reach\n // createComponent, and two instances of the sub-page end up mounted\n // in the contentHost. OnSectionClick + OnQueryParamsChanged each\n // guard their own call site, but defense-in-depth here covers any\n // future caller (and the ngOnInit ↔ stream-replay race specifically).\n if (section.id === this.ActiveSection) {\n return;\n }\n this.ActiveSection = section.id;\n this.IsLoading = true;\n this.LoadError = null;\n this.cdr.markForCheck();\n\n if (syncUrl) {\n this.UpdateQueryParams({ section: section.id });\n }\n\n try {\n await this.renderSection(section);\n } catch (err) {\n this.LoadError = err instanceof Error ? err.message : String(err);\n } finally {\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n }\n\n private async renderSection(section: AdminSection): Promise<void> {\n // Detach (don't destroy) the currently visible sub-component so it\n // stays alive in memory with its state intact.\n this.detachCurrent();\n\n // Reattach if cached\n const cached = this.cache.get(section.id);\n if (cached) {\n this.contentHost.insert(cached.hostView);\n this.currentSectionId = section.id;\n return;\n }\n\n // Otherwise, create fresh\n const ref = section.source.kind === 'resource'\n ? await this.createResourceRef(section.source.driverClass)\n : await this.createDashboardRef(section.source.dashboardName);\n\n if (ref) {\n this.cache.set(section.id, ref);\n this.currentSectionId = section.id;\n }\n }\n\n private detachCurrent(): void {\n if (!this.currentSectionId) return;\n const cur = this.cache.get(this.currentSectionId);\n if (!cur) return;\n const idx = this.contentHost.indexOf(cur.hostView);\n if (idx >= 0) this.contentHost.detach(idx);\n }\n\n private async createResourceRef(driverClass: string): Promise<ComponentRef<unknown> | null> {\n const reg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);\n if (!reg) {\n this.LoadError = `Component \"${driverClass}\" is not registered. Make sure its module is imported.`;\n return null;\n }\n const ref = this.contentHost.createComponent(reg.SubClass as Type<BaseResourceComponent>);\n const instance = ref.instance as BaseResourceComponent;\n if (this.Data) instance.Data = this.Data;\n return ref;\n }\n\n private async createDashboardRef(dashboardName: string): Promise<ComponentRef<unknown> | null> {\n await DashboardEngine.Instance.Config(false);\n const dashboard = DashboardEngine.Instance.Dashboards.find(d => d.Name === dashboardName);\n if (!dashboard) {\n this.LoadError = `Dashboard \"${dashboardName}\" was not found in metadata.`;\n return null;\n }\n if (dashboard.Type !== 'Code') {\n this.LoadError = `Dashboard \"${dashboardName}\" has type \"${dashboard.Type}\" — only Code dashboards can be embedded here.`;\n return null;\n }\n if (!dashboard.DriverClass) {\n this.LoadError = `Dashboard \"${dashboardName}\" has no DriverClass set.`;\n return null;\n }\n const reg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseDashboard, dashboard.DriverClass);\n if (!reg) {\n this.LoadError = `Driver class \"${dashboard.DriverClass}\" is not registered against BaseDashboard.`;\n return null;\n }\n const ref = this.contentHost.createComponent(reg.SubClass as Type<BaseDashboard>);\n const instance = ref.instance as BaseDashboard;\n // Scope the embedded dashboard's query-param reads/writes to THIS container's tab. The\n // sibling `createResourceRef` path inherits the tab id through `instance.Data`; a dashboard\n // gets no ResourceData, so hand it the tab id explicitly. Without it the dashboard has no\n // tab identity and its URL updates are dropped (previously: applied to whichever tab the\n // user was viewing, which need not be this one).\n instance.ParentTabId = this.getTabId();\n // BaseDashboard catches a failing initDashboard()/loadData(), emits Error and still signals\n // load-complete — so without this subscription an embedded dashboard's load failure would\n // render as a blank content pane with a console-only error. Surface it in LoadError, which\n // the container already renders. Scoped to the INITIAL load (same reasoning as\n // DashboardResource): sections are cached and kept alive across switches, and LoadError only\n // resets on the next selectSection, so a post-mount Refresh() failure would otherwise pin a\n // stale error banner — potentially while a different section is on screen.\n let initialLoadSettled = false;\n const onComplete = instance.LoadCompleteEvent;\n instance.LoadCompleteEvent = () => { initialLoadSettled = true; onComplete?.(); };\n instance.Error.subscribe((err: Error) => {\n if (initialLoadSettled) return; // post-mount refresh failure — already logged by BaseDashboard.runGuardedLoad\n this.LoadError = err.message;\n });\n instance.Config = { dashboard, userState: undefined };\n instance.Refresh();\n return ref;\n }\n\n private findSection(id: string | undefined | null): AdminSection | undefined {\n if (!id) return undefined;\n return this.Sections.find(s => s.id === id);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-dashboards",
|
|
3
|
-
"version": "6.1.0-edge.
|
|
3
|
+
"version": "6.1.0-edge.5",
|
|
4
4
|
"description": "MemberJunction Dashboards",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"types": "./dist/scheduling-dashboards.module.d.ts",
|
|
30
30
|
"default": "./dist/scheduling-dashboards.module.js"
|
|
31
31
|
},
|
|
32
|
+
"./archiving-dashboards.module": {
|
|
33
|
+
"types": "./dist/archiving-dashboards.module.d.ts",
|
|
34
|
+
"default": "./dist/archiving-dashboards.module.js"
|
|
35
|
+
},
|
|
32
36
|
"./communication-dashboards.module": {
|
|
33
37
|
"types": "./dist/communication-dashboards.module.d.ts",
|
|
34
38
|
"default": "./dist/communication-dashboards.module.js"
|
|
@@ -85,7 +89,7 @@
|
|
|
85
89
|
"@angular/compiler-cli": "21.1.3",
|
|
86
90
|
"@types/d3": "^7.4.0",
|
|
87
91
|
"sass": "^1.97.3",
|
|
88
|
-
"@memberjunction/ng-test-utils": "6.1.0-edge.
|
|
92
|
+
"@memberjunction/ng-test-utils": "6.1.0-edge.5"
|
|
89
93
|
},
|
|
90
94
|
"peerDependencies": {
|
|
91
95
|
"@angular/common": "^21.1.3",
|
|
@@ -102,67 +106,67 @@
|
|
|
102
106
|
"@codemirror/merge": "^6.11.2",
|
|
103
107
|
"@codemirror/state": "^6.5.4",
|
|
104
108
|
"@codemirror/view": "^6.39.12",
|
|
105
|
-
"@memberjunction/actions-base": "6.1.0-edge.
|
|
106
|
-
"@memberjunction/ai-core-plus": "6.1.0-edge.
|
|
107
|
-
"@memberjunction/ai-engine-base": "6.1.0-edge.
|
|
108
|
-
"@memberjunction/api-keys-base": "6.1.0-edge.
|
|
109
|
-
"@memberjunction/core": "6.1.0-edge.
|
|
110
|
-
"@memberjunction/core-entities": "6.1.0-edge.
|
|
111
|
-
"@memberjunction/credentials": "6.1.0-edge.
|
|
112
|
-
"@memberjunction/export-engine": "6.1.0-edge.
|
|
113
|
-
"@memberjunction/global": "6.1.0-edge.
|
|
114
|
-
"@memberjunction/graphql-dataprovider": "6.1.0-edge.
|
|
115
|
-
"@memberjunction/integration-engine-base": "6.1.0-edge.
|
|
116
|
-
"@memberjunction/interactive-component-types": "6.1.0-edge.
|
|
117
|
-
"@memberjunction/lists-base": "6.1.0-edge.
|
|
118
|
-
"@memberjunction/ng-action-gallery": "6.1.0-edge.
|
|
119
|
-
"@memberjunction/ng-actions": "6.1.0-edge.
|
|
120
|
-
"@memberjunction/ng-agent-requests": "6.1.0-edge.
|
|
121
|
-
"@memberjunction/ng-agents": "6.1.0-edge.
|
|
122
|
-
"@memberjunction/ng-ai-test-harness": "6.1.0-edge.
|
|
123
|
-
"@memberjunction/ng-archive-manager": "6.1.0-edge.
|
|
124
|
-
"@memberjunction/ng-base-application": "6.1.0-edge.
|
|
125
|
-
"@memberjunction/ng-base-forms": "6.1.0-edge.
|
|
126
|
-
"@memberjunction/ng-base-types": "6.1.0-edge.
|
|
127
|
-
"@memberjunction/ng-clustering": "6.1.0-edge.
|
|
128
|
-
"@memberjunction/ng-code-editor": "6.1.0-edge.
|
|
129
|
-
"@memberjunction/ng-composer": "6.1.0-edge.
|
|
130
|
-
"@memberjunction/ng-container-directives": "6.1.0-edge.
|
|
131
|
-
"@memberjunction/ng-conversations": "6.1.0-edge.
|
|
132
|
-
"@memberjunction/ng-core-entity-forms": "6.1.0-edge.
|
|
133
|
-
"@memberjunction/ng-credentials": "6.1.0-edge.
|
|
134
|
-
"@memberjunction/ng-dashboard-viewer": "6.1.0-edge.
|
|
135
|
-
"@memberjunction/ng-entity-relationship-diagram": "6.1.0-edge.
|
|
136
|
-
"@memberjunction/ng-entity-viewer": "6.1.0-edge.
|
|
137
|
-
"@memberjunction/ng-explorer-settings": "6.1.0-edge.
|
|
138
|
-
"@memberjunction/ng-export-service": "6.1.0-edge.
|
|
139
|
-
"@memberjunction/ng-filter-builder": "6.1.0-edge.
|
|
140
|
-
"@memberjunction/ng-list-management": "6.1.0-edge.
|
|
141
|
-
"@memberjunction/ng-map-view": "6.1.0-edge.
|
|
142
|
-
"@memberjunction/ng-markdown": "6.1.0-edge.
|
|
143
|
-
"@memberjunction/ng-media-player": "6.1.0-edge.
|
|
144
|
-
"@memberjunction/ng-notifications": "6.1.0-edge.
|
|
145
|
-
"@memberjunction/ng-query-viewer": "6.1.0-edge.
|
|
146
|
-
"@memberjunction/ng-react": "6.1.0-edge.
|
|
147
|
-
"@memberjunction/ng-record-process-studio": "6.1.0-edge.
|
|
148
|
-
"@memberjunction/ng-resource-permissions": "6.1.0-edge.
|
|
149
|
-
"@memberjunction/ng-scheduling": "6.1.0-edge.
|
|
150
|
-
"@memberjunction/ng-search": "6.1.0-edge.
|
|
151
|
-
"@memberjunction/ng-shared": "6.1.0-edge.
|
|
152
|
-
"@memberjunction/ng-shared-generic": "6.1.0-edge.
|
|
153
|
-
"@memberjunction/ng-task-graph-editor": "6.1.0-edge.
|
|
154
|
-
"@memberjunction/ng-tabstrip": "6.1.0-edge.
|
|
155
|
-
"@memberjunction/ng-testing": "6.1.0-edge.
|
|
156
|
-
"@memberjunction/ng-trees": "6.1.0-edge.
|
|
157
|
-
"@memberjunction/ng-ui-components": "6.1.0-edge.
|
|
158
|
-
"@memberjunction/ng-user-routines": "6.1.0-edge.
|
|
159
|
-
"@memberjunction/ng-versions": "6.1.0-edge.
|
|
160
|
-
"@memberjunction/ng-word-cloud": "6.1.0-edge.
|
|
161
|
-
"@memberjunction/predictive-studio-core": "6.1.0-edge.
|
|
162
|
-
"@memberjunction/tag-engine-base": "6.1.0-edge.
|
|
163
|
-
"@memberjunction/templates-base-types": "6.1.0-edge.
|
|
164
|
-
"@memberjunction/testing-engine-base": "6.1.0-edge.
|
|
165
|
-
"@memberjunction/theme-engine": "6.1.0-edge.
|
|
109
|
+
"@memberjunction/actions-base": "6.1.0-edge.5",
|
|
110
|
+
"@memberjunction/ai-core-plus": "6.1.0-edge.5",
|
|
111
|
+
"@memberjunction/ai-engine-base": "6.1.0-edge.5",
|
|
112
|
+
"@memberjunction/api-keys-base": "6.1.0-edge.5",
|
|
113
|
+
"@memberjunction/core": "6.1.0-edge.5",
|
|
114
|
+
"@memberjunction/core-entities": "6.1.0-edge.5",
|
|
115
|
+
"@memberjunction/credentials": "6.1.0-edge.5",
|
|
116
|
+
"@memberjunction/export-engine": "6.1.0-edge.5",
|
|
117
|
+
"@memberjunction/global": "6.1.0-edge.5",
|
|
118
|
+
"@memberjunction/graphql-dataprovider": "6.1.0-edge.5",
|
|
119
|
+
"@memberjunction/integration-engine-base": "6.1.0-edge.5",
|
|
120
|
+
"@memberjunction/interactive-component-types": "6.1.0-edge.5",
|
|
121
|
+
"@memberjunction/lists-base": "6.1.0-edge.5",
|
|
122
|
+
"@memberjunction/ng-action-gallery": "6.1.0-edge.5",
|
|
123
|
+
"@memberjunction/ng-actions": "6.1.0-edge.5",
|
|
124
|
+
"@memberjunction/ng-agent-requests": "6.1.0-edge.5",
|
|
125
|
+
"@memberjunction/ng-agents": "6.1.0-edge.5",
|
|
126
|
+
"@memberjunction/ng-ai-test-harness": "6.1.0-edge.5",
|
|
127
|
+
"@memberjunction/ng-archive-manager": "6.1.0-edge.5",
|
|
128
|
+
"@memberjunction/ng-base-application": "6.1.0-edge.5",
|
|
129
|
+
"@memberjunction/ng-base-forms": "6.1.0-edge.5",
|
|
130
|
+
"@memberjunction/ng-base-types": "6.1.0-edge.5",
|
|
131
|
+
"@memberjunction/ng-clustering": "6.1.0-edge.5",
|
|
132
|
+
"@memberjunction/ng-code-editor": "6.1.0-edge.5",
|
|
133
|
+
"@memberjunction/ng-composer": "6.1.0-edge.5",
|
|
134
|
+
"@memberjunction/ng-container-directives": "6.1.0-edge.5",
|
|
135
|
+
"@memberjunction/ng-conversations": "6.1.0-edge.5",
|
|
136
|
+
"@memberjunction/ng-core-entity-forms": "6.1.0-edge.5",
|
|
137
|
+
"@memberjunction/ng-credentials": "6.1.0-edge.5",
|
|
138
|
+
"@memberjunction/ng-dashboard-viewer": "6.1.0-edge.5",
|
|
139
|
+
"@memberjunction/ng-entity-relationship-diagram": "6.1.0-edge.5",
|
|
140
|
+
"@memberjunction/ng-entity-viewer": "6.1.0-edge.5",
|
|
141
|
+
"@memberjunction/ng-explorer-settings": "6.1.0-edge.5",
|
|
142
|
+
"@memberjunction/ng-export-service": "6.1.0-edge.5",
|
|
143
|
+
"@memberjunction/ng-filter-builder": "6.1.0-edge.5",
|
|
144
|
+
"@memberjunction/ng-list-management": "6.1.0-edge.5",
|
|
145
|
+
"@memberjunction/ng-map-view": "6.1.0-edge.5",
|
|
146
|
+
"@memberjunction/ng-markdown": "6.1.0-edge.5",
|
|
147
|
+
"@memberjunction/ng-media-player": "6.1.0-edge.5",
|
|
148
|
+
"@memberjunction/ng-notifications": "6.1.0-edge.5",
|
|
149
|
+
"@memberjunction/ng-query-viewer": "6.1.0-edge.5",
|
|
150
|
+
"@memberjunction/ng-react": "6.1.0-edge.5",
|
|
151
|
+
"@memberjunction/ng-record-process-studio": "6.1.0-edge.5",
|
|
152
|
+
"@memberjunction/ng-resource-permissions": "6.1.0-edge.5",
|
|
153
|
+
"@memberjunction/ng-scheduling": "6.1.0-edge.5",
|
|
154
|
+
"@memberjunction/ng-search": "6.1.0-edge.5",
|
|
155
|
+
"@memberjunction/ng-shared": "6.1.0-edge.5",
|
|
156
|
+
"@memberjunction/ng-shared-generic": "6.1.0-edge.5",
|
|
157
|
+
"@memberjunction/ng-task-graph-editor": "6.1.0-edge.5",
|
|
158
|
+
"@memberjunction/ng-tabstrip": "6.1.0-edge.5",
|
|
159
|
+
"@memberjunction/ng-testing": "6.1.0-edge.5",
|
|
160
|
+
"@memberjunction/ng-trees": "6.1.0-edge.5",
|
|
161
|
+
"@memberjunction/ng-ui-components": "6.1.0-edge.5",
|
|
162
|
+
"@memberjunction/ng-user-routines": "6.1.0-edge.5",
|
|
163
|
+
"@memberjunction/ng-versions": "6.1.0-edge.5",
|
|
164
|
+
"@memberjunction/ng-word-cloud": "6.1.0-edge.5",
|
|
165
|
+
"@memberjunction/predictive-studio-core": "6.1.0-edge.5",
|
|
166
|
+
"@memberjunction/tag-engine-base": "6.1.0-edge.5",
|
|
167
|
+
"@memberjunction/templates-base-types": "6.1.0-edge.5",
|
|
168
|
+
"@memberjunction/testing-engine-base": "6.1.0-edge.5",
|
|
169
|
+
"@memberjunction/theme-engine": "6.1.0-edge.5",
|
|
166
170
|
"ag-grid-angular": "^35.0.1",
|
|
167
171
|
"ag-grid-community": "^35.0.1",
|
|
168
172
|
"angular-split": "^20.0.0",
|