@carlonicora/nextjs-jsonapi 1.142.0 → 1.143.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AbstractService-B0T7h8fX.d.mts +124 -0
- package/dist/AbstractService-Cew0PD0L.d.ts +124 -0
- package/dist/{AuthComponent-DdxCFgUZ.d.mts → AuthComponent-Cik96ElA.d.mts} +1 -1
- package/dist/{AuthComponent-Cd7lcYif.d.ts → AuthComponent-DTTKu3VK.d.ts} +1 -1
- package/dist/{BlockNoteEditor-35DDJH3M.mjs → BlockNoteEditor-34QM4UR5.mjs} +4 -4
- package/dist/{BlockNoteEditor-XXTBZ7DD.js → BlockNoteEditor-LMKWUOJG.js} +19 -19
- package/dist/{BlockNoteEditor-XXTBZ7DD.js.map → BlockNoteEditor-LMKWUOJG.js.map} +1 -1
- package/dist/billing/index.js +357 -357
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-KZHHKPQZ.mjs → chunk-BIL2WQEV.mjs} +247 -1
- package/dist/chunk-BIL2WQEV.mjs.map +1 -0
- package/dist/{chunk-5SFFHIEE.js → chunk-DS4IBFWA.js} +252 -6
- package/dist/chunk-DS4IBFWA.js.map +1 -0
- package/dist/{chunk-7CR4YB7Y.js → chunk-F6PZZ4KI.js} +730 -730
- package/dist/{chunk-7CR4YB7Y.js.map → chunk-F6PZZ4KI.js.map} +1 -1
- package/dist/{chunk-ZV2GZKFR.mjs → chunk-HN5ED7OF.mjs} +2 -2
- package/dist/{chunk-FZ5SPJEU.mjs → chunk-UGSOBBZH.mjs} +4 -4
- package/dist/{chunk-C6UXEPNY.js → chunk-UXJAS6C5.js} +7 -7
- package/dist/{chunk-C6UXEPNY.js.map → chunk-UXJAS6C5.js.map} +1 -1
- package/dist/client/index.d.mts +3 -3
- package/dist/client/index.d.ts +3 -3
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +3 -3
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.js +4 -4
- package/dist/components/index.mjs +3 -3
- package/dist/{config-BrkU_Ynz.d.ts → config-Cd5xTEfZ.d.ts} +1 -1
- package/dist/{config-snd2ToEB.d.mts → config-YyhFHxkU.d.mts} +1 -1
- package/dist/contexts/index.d.mts +1 -1
- package/dist/contexts/index.d.ts +1 -1
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +13 -49
- package/dist/core/index.d.ts +13 -49
- package/dist/core/index.js +8 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +7 -1
- package/dist/features/help/index.d.mts +2 -2
- package/dist/features/help/index.d.ts +2 -2
- package/dist/features/help/index.js +37 -37
- package/dist/features/help/index.mjs +3 -3
- package/dist/features/tokenusage/index.css +29 -0
- package/dist/features/tokenusage/index.css.map +1 -0
- package/dist/features/tokenusage/index.d.mts +388 -0
- package/dist/features/tokenusage/index.d.ts +388 -0
- package/dist/features/tokenusage/index.js +1012 -0
- package/dist/features/tokenusage/index.js.map +1 -0
- package/dist/features/tokenusage/index.mjs +1012 -0
- package/dist/features/tokenusage/index.mjs.map +1 -0
- package/dist/index.d.mts +8 -6
- package/dist/index.d.ts +8 -6
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -2
- package/dist/{s3.service-Dmfxpn-k.d.ts → s3.service-CpwA3sl_.d.ts} +2 -123
- package/dist/{s3.service-DYflLXCt.d.mts → s3.service-Dt6MfuHr.d.mts} +2 -123
- package/dist/server/index.d.mts +3 -2
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/dist/tokenusage-admin.module-5wJ_tZTj.d.ts +61 -0
- package/dist/tokenusage-admin.module-DQVT4O4j.d.mts +61 -0
- package/dist/{types-CQSjy7et.d.mts → types-Bq_UA8Lg.d.mts} +1 -1
- package/dist/{types-DHOxe8rc.d.ts → types-bsm8Fp55.d.ts} +1 -1
- package/dist/{usePageUrlGenerator-tjq2mlDV.d.ts → usePageUrlGenerator-J_ECv-oQ.d.ts} +1 -1
- package/dist/{usePageUrlGenerator-uOnyJ6j2.d.mts → usePageUrlGenerator-uKBjAKqC.d.mts} +1 -1
- package/package.json +6 -1
- package/src/core/index.ts +5 -0
- package/src/core/registry/ModuleRegistry.ts +4 -0
- package/src/features/tokenusage/components/TokenUsageAdminContainer.tsx +189 -0
- package/src/features/tokenusage/components/TokenUsageAdminFilterBar.tsx +138 -0
- package/src/features/tokenusage/components/TokenUsageAdminTiles.tsx +177 -0
- package/src/features/tokenusage/components/TokenUsageBreakdownTable.tsx +160 -0
- package/src/features/tokenusage/components/TokenUsageRankedBar.tsx +99 -0
- package/src/features/tokenusage/components/TokenUsageTimelineChart.tsx +225 -0
- package/src/features/tokenusage/components/__tests__/TokenUsageAdminTiles.spec.tsx +42 -0
- package/src/features/tokenusage/components/__tests__/TokenUsageRankedBar.spec.tsx +41 -0
- package/src/features/tokenusage/components/__tests__/TokenUsageTimelineChart.spec.tsx +47 -0
- package/src/features/tokenusage/contexts/TokenUsageAdminContext.tsx +252 -0
- package/src/features/tokenusage/contexts/__tests__/TokenUsageAdminContext.spec.tsx +80 -0
- package/src/features/tokenusage/data/TokenUsageAdminService.ts +54 -0
- package/src/features/tokenusage/data/__tests__/tokenusage-admin-models.spec.ts +147 -0
- package/src/features/tokenusage/data/index.ts +8 -0
- package/src/features/tokenusage/data/tokenusage-admin-breakdown.interface.ts +18 -0
- package/src/features/tokenusage/data/tokenusage-admin-breakdown.ts +115 -0
- package/src/features/tokenusage/data/tokenusage-admin-summary.interface.ts +14 -0
- package/src/features/tokenusage/data/tokenusage-admin-summary.ts +90 -0
- package/src/features/tokenusage/data/tokenusage-admin-timeline.interface.ts +13 -0
- package/src/features/tokenusage/data/tokenusage-admin-timeline.ts +94 -0
- package/src/features/tokenusage/data/tokenusage-admin.types.ts +56 -0
- package/src/features/tokenusage/i18n-keys.ts +65 -0
- package/src/features/tokenusage/index.ts +30 -0
- package/src/features/tokenusage/lib/metrics.ts +69 -0
- package/src/features/tokenusage/lib/operation-label.ts +41 -0
- package/src/features/tokenusage/lib/palette.ts +197 -0
- package/src/features/tokenusage/tokenusage-admin.module.ts +22 -0
- package/dist/chunk-5SFFHIEE.js.map +0 -1
- package/dist/chunk-KZHHKPQZ.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-35DDJH3M.mjs.map → BlockNoteEditor-34QM4UR5.mjs.map} +0 -0
- /package/dist/{chunk-ZV2GZKFR.mjs.map → chunk-HN5ED7OF.mjs.map} +0 -0
- /package/dist/{chunk-FZ5SPJEU.mjs.map → chunk-UGSOBBZH.mjs.map} +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Client-only public barrel for the administrative token-usage feature.
|
|
2
|
+
// Consumed via `@carlonicora/nextjs-jsonapi/tokenusage`. Tsup adds a top-level
|
|
3
|
+
// "use client" directive to the bundled output via clientEntries.
|
|
4
|
+
//
|
|
5
|
+
// Deliberately NOT re-exported from `src/index.ts` — keeping the feature off the
|
|
6
|
+
// main barrel is what keeps its chart (recharts) out of the main client bundle,
|
|
7
|
+
// exactly as the `help` feature does.
|
|
8
|
+
//
|
|
9
|
+
// Pair this with `@carlonicora/nextjs-jsonapi/tokenusage/server` for the ready
|
|
10
|
+
// made server page (default export + generateMetadata).
|
|
11
|
+
|
|
12
|
+
export { TokenUsageAdminProvider, useTokenUsageAdmin } from "./contexts/TokenUsageAdminContext";
|
|
13
|
+
export type { TokenUsageAdminContextType, TokenUsageAdminFilterState } from "./contexts/TokenUsageAdminContext";
|
|
14
|
+
|
|
15
|
+
export { TokenUsageAdminContainer } from "./components/TokenUsageAdminContainer";
|
|
16
|
+
export { TokenUsageAdminFilterBar } from "./components/TokenUsageAdminFilterBar";
|
|
17
|
+
export { TokenUsageAdminTiles } from "./components/TokenUsageAdminTiles";
|
|
18
|
+
export { TokenUsageRankedBar } from "./components/TokenUsageRankedBar";
|
|
19
|
+
export { TokenUsageBreakdownTable } from "./components/TokenUsageBreakdownTable";
|
|
20
|
+
export { TokenUsageTimelineChart } from "./components/TokenUsageTimelineChart";
|
|
21
|
+
|
|
22
|
+
export * from "./data";
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
TokenUsageAdminBreakdownModule,
|
|
26
|
+
TokenUsageAdminSummaryModule,
|
|
27
|
+
TokenUsageAdminTimelineModule,
|
|
28
|
+
} from "./tokenusage-admin.module";
|
|
29
|
+
|
|
30
|
+
export { TOKEN_USAGE_ADMIN_I18N_KEYS } from "./i18n-keys";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Metric } from "../data/tokenusage-admin.types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The metric field set every admin token-usage resource carries. Declared
|
|
5
|
+
* structurally so it accepts the summary, timeline and breakdown interfaces
|
|
6
|
+
* alike — all three expose exactly these getters.
|
|
7
|
+
*/
|
|
8
|
+
export type TokenUsageMetrics = {
|
|
9
|
+
cost: number;
|
|
10
|
+
credits: number;
|
|
11
|
+
tokensIn: number;
|
|
12
|
+
tokensOut: number;
|
|
13
|
+
cached: number;
|
|
14
|
+
calls: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** UI copy is Italian, so every figure is formatted with the Italian locale. */
|
|
18
|
+
const LOCALE = "it-IT";
|
|
19
|
+
|
|
20
|
+
/** Reads the single number a row contributes for the currently selected metric. */
|
|
21
|
+
export function metricValue(row: TokenUsageMetrics, metric: Metric): number {
|
|
22
|
+
if (metric === "cost") return row.cost;
|
|
23
|
+
if (metric === "credits") return row.credits;
|
|
24
|
+
return row.tokensIn + row.tokensOut;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Locale-aware fixed-decimal number, e.g. 9.46 → "9,46". */
|
|
28
|
+
export function formatDecimal(value: number, decimals: number): string {
|
|
29
|
+
return value.toLocaleString(LOCALE, {
|
|
30
|
+
minimumFractionDigits: decimals,
|
|
31
|
+
maximumFractionDigits: decimals,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Formats a metric value for display: euros carry a "€ " prefix and 2 decimals,
|
|
37
|
+
* credits 2 decimals, token counts are whole numbers.
|
|
38
|
+
*/
|
|
39
|
+
export function formatMetricValue(value: number, metric: Metric): string {
|
|
40
|
+
if (metric === "cost") return `€ ${formatDecimal(value, 2)}`;
|
|
41
|
+
if (metric === "credits") return formatDecimal(value, 2);
|
|
42
|
+
return formatDecimal(value, 0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Percentage with one decimal, e.g. 60 → "60,0 %". */
|
|
46
|
+
export function formatPercent(value: number, decimals = 1): string {
|
|
47
|
+
return `${formatDecimal(value, decimals)} %`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Share of cached input tokens. Returns 0 rather than NaN when the window holds
|
|
52
|
+
* no input tokens at all, so the tile renders "0,0 %" instead of "NaN %".
|
|
53
|
+
*/
|
|
54
|
+
export function cacheHitPercentage(cached: number, tokensIn: number): number {
|
|
55
|
+
if (!tokensIn) return 0;
|
|
56
|
+
return (cached / tokensIn) * 100;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Percentage change of `current` against `previous`, rounded to a whole number.
|
|
61
|
+
*
|
|
62
|
+
* Returns `undefined` when the previous window is zero — there is no meaningful
|
|
63
|
+
* percentage change from nothing, and rendering Infinity would be worse than
|
|
64
|
+
* rendering nothing. Callers show an em dash in that slot.
|
|
65
|
+
*/
|
|
66
|
+
export function percentageDelta(current: number, previous: number): number | undefined {
|
|
67
|
+
if (!previous) return undefined;
|
|
68
|
+
return Math.round(((current - previous) / previous) * 100);
|
|
69
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The i18n namespace under which a consuming app supplies its own vocabulary for
|
|
3
|
+
* token-usage operation types (`summariser`, `massima_extraction`, …).
|
|
4
|
+
*
|
|
5
|
+
* The set of operations is application-specific — the package cannot know that
|
|
6
|
+
* a360ai calls `massima_extraction` "Estrazione Massime" — so the package looks
|
|
7
|
+
* the copy up here and falls back to the raw key when the app has no entry.
|
|
8
|
+
*/
|
|
9
|
+
export const OPERATION_LABEL_NAMESPACE = "token_usage.types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `snake_case` / `kebab-case` → `camelCase`, the shape the i18n keys use.
|
|
13
|
+
*
|
|
14
|
+
* Shared by every surface that renders an operation type — the timeline chart's
|
|
15
|
+
* series labels and the ranked bar's operation rows — so the two can never
|
|
16
|
+
* disagree about which key a given type maps to.
|
|
17
|
+
*/
|
|
18
|
+
export const toCamelCase = (value: string): string => {
|
|
19
|
+
const parts = value.split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
20
|
+
if (parts.length === 0) return value;
|
|
21
|
+
return parts
|
|
22
|
+
.map((part, index) =>
|
|
23
|
+
index === 0 ? part.charAt(0).toLowerCase() + part.slice(1) : part.charAt(0).toUpperCase() + part.slice(1),
|
|
24
|
+
)
|
|
25
|
+
.join("");
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolves an operation type to its Italian label, falling back to the raw key.
|
|
30
|
+
*
|
|
31
|
+
* `has` is passed rather than the whole translator so this stays a pure function
|
|
32
|
+
* and both callers can share it regardless of how they obtained `t`.
|
|
33
|
+
*/
|
|
34
|
+
export const operationLabel = (
|
|
35
|
+
type: string,
|
|
36
|
+
translate: (key: string) => string,
|
|
37
|
+
has: (key: string) => boolean,
|
|
38
|
+
): string => {
|
|
39
|
+
const key = `${OPERATION_LABEL_NAMESPACE}.${toCamelCase(type)}`;
|
|
40
|
+
return has(key) ? translate(key) : type;
|
|
41
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart palette for the administrative token-usage dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Every value here is a documented step of the dataviz skill's reference ramps —
|
|
5
|
+
* nothing was eyeballed, nothing was hand-mixed. The palette was run through the
|
|
6
|
+
* skill's validator against THIS application's real chart surfaces (from
|
|
7
|
+
* `apps/web/src/app/globals.css`: light `oklch(1 0 0)` = `#ffffff`, dark
|
|
8
|
+
* `oklch(0.145 0 0)` = `#0a0a0a`) in both modes. The verbatim output is below.
|
|
9
|
+
*
|
|
10
|
+
* Light and dark are two SELECTED sets of steps, chosen for their own surface —
|
|
11
|
+
* never one set with its lightness flipped at runtime. Consumers pick a set from
|
|
12
|
+
* the resolved theme (`seriesColor(i, mode)` / `sequentialColor(i, mode)`).
|
|
13
|
+
*
|
|
14
|
+
* ---------------------------------------------------------------------------
|
|
15
|
+
* VALIDATOR OUTPUT — dataviz `scripts/validate_palette.js`, run 2026-08-07
|
|
16
|
+
* ---------------------------------------------------------------------------
|
|
17
|
+
*
|
|
18
|
+
* $ node scripts/validate_palette.js "#2a78d6,#eb6834,#1baf7a,#eda100,#e87ba4,#008300,#4a3aa7" --mode light --surface "#ffffff"
|
|
19
|
+
*
|
|
20
|
+
* Palette (light, surface #ffffff, categorical): 7 slots
|
|
21
|
+
* [PASS] Lightness band all 7 inside L 0.43–0.77
|
|
22
|
+
* [PASS] Chroma floor all 7 >= 0.1
|
|
23
|
+
* [PASS] CVD separation worst adjacent #eda100↔#1baf7a ΔE 9.1 (protan) · tritan 5.8
|
|
24
|
+
* [PASS] Normal-vision floor worst adjacent #e87ba4↔#eda100 ΔE 19.6 (normal)
|
|
25
|
+
* [WARN] Contrast vs surface below 3:1 — relief required (visible labels or table view): [["#1baf7a",2.82],["#eda100",2.17],["#e87ba4",2.69]]
|
|
26
|
+
*
|
|
27
|
+
* → ALL CHECKS PASS (CVD in the 6–8 floor band is legal ONLY with secondary encoding: direct labels, gaps, or texture)
|
|
28
|
+
* scope: categorical palettes only. For a lone status/text color check WCAG text contrast; for a sequential ramp, lightness monotonicity.
|
|
29
|
+
*
|
|
30
|
+
* $ node scripts/validate_palette.js "#3987e5,#d95926,#199e70,#c98500,#d55181,#008300,#9085e9" --mode dark --surface "#0a0a0a"
|
|
31
|
+
*
|
|
32
|
+
* Palette (dark, surface #0a0a0a, categorical): 7 slots
|
|
33
|
+
* [PASS] Lightness band all 7 inside L 0.48–0.67
|
|
34
|
+
* [PASS] Chroma floor all 7 >= 0.1
|
|
35
|
+
* [PASS] CVD separation worst adjacent #c98500↔#199e70 ΔE 8.4 (protan) · tritan 8.7
|
|
36
|
+
* [PASS] Normal-vision floor worst adjacent #d55181↔#c98500 ΔE 19.3 (normal)
|
|
37
|
+
* [PASS] Contrast vs surface all 7 >= 3:1
|
|
38
|
+
*
|
|
39
|
+
* → ALL CHECKS PASS (CVD in the 6–8 floor band is legal ONLY with secondary encoding: direct labels, gaps, or texture)
|
|
40
|
+
* scope: categorical palettes only. For a lone status/text color check WCAG text contrast; for a sequential ramp, lightness monotonicity.
|
|
41
|
+
*
|
|
42
|
+
* $ node scripts/validate_palette.js "#104281,#1c5cab,#2a78d6,#5598e7,#86b6ef" --mode light --surface "#ffffff" --ordinal
|
|
43
|
+
*
|
|
44
|
+
* Palette (light, surface #ffffff, ordinal ramp): 5 slots
|
|
45
|
+
* [PASS] Lightness monotone steps read light→dark
|
|
46
|
+
* [PASS] Adjacent ΔL all gaps >= 0.06
|
|
47
|
+
* [PASS] Light-end contrast #86b6ef at 2.11:1 vs surface
|
|
48
|
+
* [PASS] Single hue hue spread 3°
|
|
49
|
+
*
|
|
50
|
+
* → ALL CHECKS PASS (ordinal: one hue, monotone L, visible step gaps, light end clears surface)
|
|
51
|
+
*
|
|
52
|
+
* $ node scripts/validate_palette.js "#9ec5f4,#6da7ec,#3987e5,#256abf,#184f95" --mode dark --surface "#0a0a0a" --ordinal
|
|
53
|
+
*
|
|
54
|
+
* Palette (dark, surface #0a0a0a, ordinal ramp): 5 slots
|
|
55
|
+
* [PASS] Lightness monotone steps read light→dark
|
|
56
|
+
* [PASS] Adjacent ΔL all gaps >= 0.06
|
|
57
|
+
* [PASS] Light-end contrast #184f95 at 2.44:1 vs surface
|
|
58
|
+
* [PASS] Single hue hue spread 3°
|
|
59
|
+
*
|
|
60
|
+
* → ALL CHECKS PASS (ordinal: one hue, monotone L, visible step gaps, light end clears surface)
|
|
61
|
+
*
|
|
62
|
+
* OTHER_COLOR is achromatic, so the categorical checks (which gate hue identity)
|
|
63
|
+
* do not apply to it; it was gated on contrast alone with the validator's own
|
|
64
|
+
* `contrast()` export: 3.59:1 on `#ffffff`, 5.51:1 on `#0a0a0a` — both clear 3:1.
|
|
65
|
+
*
|
|
66
|
+
* ---------------------------------------------------------------------------
|
|
67
|
+
* WHAT THE RESULTS OBLIGE US TO DO
|
|
68
|
+
* ---------------------------------------------------------------------------
|
|
69
|
+
*
|
|
70
|
+
* - The light-mode contrast WARN is NOT dismissable. Three slots (aqua, yellow,
|
|
71
|
+
* magenta) sit below 3:1 on white, so every surface that paints with this
|
|
72
|
+
* palette MUST ship the relief channel: a visible legend, direct labels and a
|
|
73
|
+
* tooltip carrying the value in text. The timeline chart and the breakdown
|
|
74
|
+
* table both do.
|
|
75
|
+
* - CVD separation is measured on ADJACENT pairs, which is the correct pairlist
|
|
76
|
+
* for stacked bars, grouped bars and lines — the only forms this palette paints.
|
|
77
|
+
* A scatter / bubble / small-multiples chart would need `--pairs all`, which
|
|
78
|
+
* caps the usable slot count at three; do not reuse this array there without
|
|
79
|
+
* re-running the validator with that flag.
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/** Which surface the colours are being painted on. */
|
|
83
|
+
export type ChartMode = "light" | "dark";
|
|
84
|
+
|
|
85
|
+
/** The chart surfaces the palette was validated against (globals.css). */
|
|
86
|
+
export const CHART_SURFACE: Readonly<Record<ChartMode, string>> = {
|
|
87
|
+
light: "#ffffff",
|
|
88
|
+
dark: "#0a0a0a",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Categorical slots, in FIXED order. Slot n is series n — the order is the
|
|
93
|
+
* CVD-safety mechanism, not decoration, so it is never re-ordered and never
|
|
94
|
+
* cycled. The two-series default (customer / platform) takes slots 0 and 1.
|
|
95
|
+
*
|
|
96
|
+
* Hues, in order: blue, orange, aqua, yellow, magenta, green, violet.
|
|
97
|
+
*/
|
|
98
|
+
const CATEGORICAL_LIGHT: readonly string[] = [
|
|
99
|
+
"#2a78d6",
|
|
100
|
+
"#eb6834",
|
|
101
|
+
"#1baf7a",
|
|
102
|
+
"#eda100",
|
|
103
|
+
"#e87ba4",
|
|
104
|
+
"#008300",
|
|
105
|
+
"#4a3aa7",
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
/** The same seven hues, re-stepped for the dark surface. Not a lightness flip. */
|
|
109
|
+
const CATEGORICAL_DARK: readonly string[] = [
|
|
110
|
+
"#3987e5",
|
|
111
|
+
"#d95926",
|
|
112
|
+
"#199e70",
|
|
113
|
+
"#c98500",
|
|
114
|
+
"#d55181",
|
|
115
|
+
"#008300",
|
|
116
|
+
"#9085e9",
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The default (light-surface) categorical set.
|
|
121
|
+
*
|
|
122
|
+
* Prefer `seriesColor(index, mode)` — this array exists for consumers that only
|
|
123
|
+
* need the documented default order.
|
|
124
|
+
*/
|
|
125
|
+
export const CATEGORICAL: readonly string[] = CATEGORICAL_LIGHT;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* How many series may carry their own identity hue. An 8th series is NEVER a
|
|
129
|
+
* generated hue: it folds into `other`.
|
|
130
|
+
*/
|
|
131
|
+
export const CATEGORICAL_CEILING = CATEGORICAL_LIGHT.length;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The "other" rollup colour: deliberately achromatic so it reads as "not an
|
|
135
|
+
* identity" beside the seven hues. Same step in both modes — it clears 3:1 on
|
|
136
|
+
* both surfaces (3.59:1 light, 5.51:1 dark).
|
|
137
|
+
*/
|
|
138
|
+
export const OTHER_COLOR = "#898781";
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Sequential (single-hue, blue) ramp for MAGNITUDE, indexed by rank position:
|
|
142
|
+
* index 0 is rank 1 — the largest value — so the strongest step leads.
|
|
143
|
+
*
|
|
144
|
+
* On the light surface "strongest" is the darkest step; on the dark surface it
|
|
145
|
+
* is the lightest. The anchor flips with the surface, which is why there are two
|
|
146
|
+
* selected sets rather than one array reversed at runtime.
|
|
147
|
+
*
|
|
148
|
+
* Five steps, not eight: with the adjacent-ΔL ≥ 0.06 gate and the ≥ 2:1
|
|
149
|
+
* surface-contrast gate on the step nearest the surface, the blue ramp fits
|
|
150
|
+
* exactly five distinguishable steps. Consumers clamp past the end.
|
|
151
|
+
*/
|
|
152
|
+
const SEQUENTIAL_RAMP_LIGHT: readonly string[] = ["#104281", "#1c5cab", "#2a78d6", "#5598e7", "#86b6ef"];
|
|
153
|
+
|
|
154
|
+
const SEQUENTIAL_RAMP_DARK: readonly string[] = ["#9ec5f4", "#6da7ec", "#3987e5", "#256abf", "#184f95"];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The default (light-surface) sequential ramp.
|
|
158
|
+
*
|
|
159
|
+
* Prefer `sequentialColor(index, mode)` — this array exists for consumers that
|
|
160
|
+
* only need the documented default.
|
|
161
|
+
*/
|
|
162
|
+
export const SEQUENTIAL_RAMP: readonly string[] = SEQUENTIAL_RAMP_LIGHT;
|
|
163
|
+
|
|
164
|
+
const categoricalSet = (mode: ChartMode): readonly string[] => (mode === "dark" ? CATEGORICAL_DARK : CATEGORICAL_LIGHT);
|
|
165
|
+
|
|
166
|
+
const sequentialSet = (mode: ChartMode): readonly string[] =>
|
|
167
|
+
mode === "dark" ? SEQUENTIAL_RAMP_DARK : SEQUENTIAL_RAMP_LIGHT;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The identity colour for series `index`.
|
|
171
|
+
*
|
|
172
|
+
* Never generates a hue and never cycles: anything at or past the ceiling gets
|
|
173
|
+
* `OTHER_COLOR`, because a series past the ceiling should already have been
|
|
174
|
+
* folded into the "other" bucket by the caller.
|
|
175
|
+
*/
|
|
176
|
+
export function seriesColor(index: number, mode: ChartMode = "light"): string {
|
|
177
|
+
if (index < 0 || index >= CATEGORICAL_CEILING) return OTHER_COLOR;
|
|
178
|
+
return categoricalSet(mode)[index];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Both mode steps for series `index`, shaped for a shadcn `ChartConfig` entry's
|
|
183
|
+
* `theme` field.
|
|
184
|
+
*/
|
|
185
|
+
export function seriesTheme(index: number): { light: string; dark: string } {
|
|
186
|
+
return { light: seriesColor(index, "light"), dark: seriesColor(index, "dark") };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The magnitude colour for rank position `index`, clamped to the last step so a
|
|
191
|
+
* long list keeps its weakest step rather than falling off the ramp.
|
|
192
|
+
*/
|
|
193
|
+
export function sequentialColor(index: number, mode: ChartMode = "light"): string {
|
|
194
|
+
const ramp = sequentialSet(mode);
|
|
195
|
+
const clamped = Math.min(Math.max(index, 0), ramp.length - 1);
|
|
196
|
+
return ramp[clamped];
|
|
197
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ModuleFactory } from "../../permissions";
|
|
2
|
+
import { TokenUsageAdminBreakdown } from "./data/tokenusage-admin-breakdown";
|
|
3
|
+
import { TokenUsageAdminSummary } from "./data/tokenusage-admin-summary";
|
|
4
|
+
import { TokenUsageAdminTimeline } from "./data/tokenusage-admin-timeline";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Read-only reporting resources behind the administrative token-usage page.
|
|
8
|
+
* `name` is the endpoint path EndpointCreator builds URLs from; there is no
|
|
9
|
+
* pageUrl because these are not navigable resources.
|
|
10
|
+
*
|
|
11
|
+
* Distinct from the consuming app's own TokenUsage module (name "tokenusages",
|
|
12
|
+
* pageUrl "/tokenusage") — that one is an AppModuleDefinitions entry the package
|
|
13
|
+
* cannot reference, which is why these three exist.
|
|
14
|
+
*/
|
|
15
|
+
export const TokenUsageAdminSummaryModule = (factory: ModuleFactory) =>
|
|
16
|
+
factory({ name: "tokenusages/administration/summary", model: TokenUsageAdminSummary });
|
|
17
|
+
|
|
18
|
+
export const TokenUsageAdminTimelineModule = (factory: ModuleFactory) =>
|
|
19
|
+
factory({ name: "tokenusages/administration/timeline", model: TokenUsageAdminTimeline });
|
|
20
|
+
|
|
21
|
+
export const TokenUsageAdminBreakdownModule = (factory: ModuleFactory) =>
|
|
22
|
+
factory({ name: "tokenusages/administration/breakdown", model: TokenUsageAdminBreakdown });
|