@carlonicora/nextjs-jsonapi 2.4.0 → 2.5.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/{BlockNoteEditor-7WGCH2KB.js → BlockNoteEditor-TIMGYKAA.js} +19 -19
- package/dist/{BlockNoteEditor-7WGCH2KB.js.map → BlockNoteEditor-TIMGYKAA.js.map} +1 -1
- package/dist/{BlockNoteEditor-GWAV5ESO.mjs → BlockNoteEditor-YFJMXJEU.mjs} +4 -4
- package/dist/billing/index.js +362 -362
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-GYGMEDVS.js → chunk-7HKJNUW7.js} +755 -746
- package/dist/chunk-7HKJNUW7.js.map +1 -0
- package/dist/{chunk-3LVSA7IM.mjs → chunk-DJWNNV52.mjs} +2 -2
- package/dist/{chunk-LCCRUWWY.mjs → chunk-FS4RVV3K.mjs} +241 -1
- package/dist/chunk-FS4RVV3K.mjs.map +1 -0
- package/dist/{chunk-M2EGUO2F.mjs → chunk-G4YS52SO.mjs} +16 -7
- package/dist/chunk-G4YS52SO.mjs.map +1 -0
- package/dist/{chunk-TOKHHZSP.js → chunk-OTEDZ6YW.js} +7 -7
- package/dist/{chunk-TOKHHZSP.js.map → chunk-OTEDZ6YW.js.map} +1 -1
- package/dist/{chunk-V66AZWPG.js → chunk-RTOC53EZ.js} +246 -6
- package/dist/chunk-RTOC53EZ.js.map +1 -0
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +16 -1
- package/dist/components/index.d.ts +16 -1
- package/dist/components/index.js +4 -4
- package/dist/components/index.mjs +3 -3
- package/dist/{tokenusage-admin.module-5wJ_tZTj.d.ts → config-BIjrg5wd.d.ts} +39 -1
- package/dist/{tokenusage-admin.module-DQVT4O4j.d.mts → config-BRUjtCd1.d.mts} +39 -1
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +5 -2
- package/dist/core/index.d.ts +5 -2
- 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.js +37 -37
- package/dist/features/help/index.mjs +3 -3
- package/dist/features/tokenusage/index.d.mts +413 -14
- package/dist/features/tokenusage/index.d.ts +413 -14
- package/dist/features/tokenusage/index.js +481 -120
- package/dist/features/tokenusage/index.js.map +1 -1
- package/dist/features/tokenusage/index.mjs +431 -70
- package/dist/features/tokenusage/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -2
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -1
- package/src/core/index.ts +10 -0
- package/src/core/registry/ModuleRegistry.ts +4 -0
- package/src/features/tokenusage/components/TokenUsageAdminContainer.tsx +20 -4
- package/src/features/tokenusage/components/TokenUsageAdminTiles.tsx +31 -18
- package/src/features/tokenusage/components/TokenUsageBreakdownTable.tsx +13 -13
- package/src/features/tokenusage/components/TokenUsageRankedBar.tsx +24 -7
- package/src/features/tokenusage/components/TokenUsageReportContainer.tsx +102 -0
- package/src/features/tokenusage/components/TokenUsageReportFilterBar.tsx +34 -0
- package/src/features/tokenusage/components/TokenUsageReportTiles.tsx +161 -0
- package/src/features/tokenusage/components/TokenUsageTimelineChart.tsx +15 -13
- package/src/features/tokenusage/components/__tests__/TokenUsageAdminTiles.spec.tsx +4 -2
- package/src/features/tokenusage/components/__tests__/TokenUsageRankedBar.spec.tsx +3 -1
- package/src/features/tokenusage/contexts/TokenUsageAdminContext.tsx +6 -3
- package/src/features/tokenusage/contexts/TokenUsageReportContext.tsx +199 -0
- package/src/features/tokenusage/data/TokenUsageReportService.ts +65 -0
- package/src/features/tokenusage/data/index.ts +9 -0
- package/src/features/tokenusage/data/tokenusage-report-breakdown.interface.ts +12 -0
- package/src/features/tokenusage/data/tokenusage-report-breakdown.ts +94 -0
- package/src/features/tokenusage/data/tokenusage-report-summary.interface.ts +12 -0
- package/src/features/tokenusage/data/tokenusage-report-summary.ts +87 -0
- package/src/features/tokenusage/data/tokenusage-report-timeline.interface.ts +13 -0
- package/src/features/tokenusage/data/tokenusage-report-timeline.ts +94 -0
- package/src/features/tokenusage/data/tokenusage-report.types.ts +51 -0
- package/src/features/tokenusage/i18n-keys.ts +28 -0
- package/src/features/tokenusage/index.ts +18 -0
- package/src/features/tokenusage/lib/config.ts +22 -0
- package/src/features/tokenusage/lib/formatters.ts +100 -0
- package/src/features/tokenusage/lib/metrics.ts +8 -26
- package/src/features/tokenusage/lib/palette.ts +41 -14
- package/src/features/tokenusage/tokenusage-admin.module.ts +4 -0
- package/src/features/tokenusage/tokenusage.modules.ts +40 -0
- package/src/shadcnui/ui/chart.tsx +30 -3
- package/dist/chunk-GYGMEDVS.js.map +0 -1
- package/dist/chunk-LCCRUWWY.mjs.map +0 -1
- package/dist/chunk-M2EGUO2F.mjs.map +0 -1
- package/dist/chunk-V66AZWPG.js.map +0 -1
- /package/dist/{BlockNoteEditor-GWAV5ESO.mjs.map → BlockNoteEditor-YFJMXJEU.mjs.map} +0 -0
- /package/dist/{chunk-3LVSA7IM.mjs.map → chunk-DJWNNV52.mjs.map} +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
|
|
2
|
+
import { TokenUsageReportBreakdownInterface } from "./tokenusage-report-breakdown.interface";
|
|
3
|
+
import { TokenUsageReportSummaryInterface } from "./tokenusage-report-summary.interface";
|
|
4
|
+
import { TokenUsageReportTimelineInterface } from "./tokenusage-report-timeline.interface";
|
|
5
|
+
import { ReportDimension, ReportMetric, TokenUsageReportFilters } from "./tokenusage-report.types";
|
|
6
|
+
|
|
7
|
+
function withFilters(endpoint: EndpointCreator, filters: TokenUsageReportFilters): EndpointCreator {
|
|
8
|
+
endpoint.addAdditionalParam("from", filters.from);
|
|
9
|
+
endpoint.addAdditionalParam("to", filters.to);
|
|
10
|
+
return endpoint;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The self-service token-usage endpoints.
|
|
15
|
+
*
|
|
16
|
+
* No companyId parameter anywhere: the backend scopes every query to the
|
|
17
|
+
* caller's own company through the CLS preamble, so there is nothing for the
|
|
18
|
+
* client to name. No `metric: "cost"` either — the controller rejects it.
|
|
19
|
+
*/
|
|
20
|
+
export class TokenUsageReportService extends AbstractService {
|
|
21
|
+
/** Two rows: window "current" and "previous". */
|
|
22
|
+
static async getSummary(filters: TokenUsageReportFilters): Promise<TokenUsageReportSummaryInterface[]> {
|
|
23
|
+
const endpoint = withFilters(new EndpointCreator({ endpoint: Modules.TokenUsageReportSummary }), filters);
|
|
24
|
+
|
|
25
|
+
return this.callApi<TokenUsageReportSummaryInterface[]>({
|
|
26
|
+
type: Modules.TokenUsageReportSummary,
|
|
27
|
+
method: HttpMethod.GET,
|
|
28
|
+
endpoint: endpoint.generate(),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static async getTimeline(
|
|
33
|
+
params: TokenUsageReportFilters & { granularity: "day" },
|
|
34
|
+
): Promise<TokenUsageReportTimelineInterface[]> {
|
|
35
|
+
const endpoint = withFilters(new EndpointCreator({ endpoint: Modules.TokenUsageReportTimeline }), params);
|
|
36
|
+
endpoint.addAdditionalParam("granularity", params.granularity);
|
|
37
|
+
|
|
38
|
+
return this.callApi<TokenUsageReportTimelineInterface[]>({
|
|
39
|
+
type: Modules.TokenUsageReportTimeline,
|
|
40
|
+
method: HttpMethod.GET,
|
|
41
|
+
endpoint: endpoint.generate(),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static async getBreakdown(
|
|
46
|
+
params: TokenUsageReportFilters & {
|
|
47
|
+
dimension: ReportDimension;
|
|
48
|
+
targetLabel?: string;
|
|
49
|
+
metric: ReportMetric;
|
|
50
|
+
limit?: number;
|
|
51
|
+
},
|
|
52
|
+
): Promise<TokenUsageReportBreakdownInterface[]> {
|
|
53
|
+
const endpoint = withFilters(new EndpointCreator({ endpoint: Modules.TokenUsageReportBreakdown }), params);
|
|
54
|
+
endpoint.addAdditionalParam("dimension", params.dimension);
|
|
55
|
+
if (params.targetLabel) endpoint.addAdditionalParam("targetLabel", params.targetLabel);
|
|
56
|
+
endpoint.addAdditionalParam("metric", params.metric);
|
|
57
|
+
if (params.limit !== undefined) endpoint.addAdditionalParam("limit", String(params.limit));
|
|
58
|
+
|
|
59
|
+
return this.callApi<TokenUsageReportBreakdownInterface[]>({
|
|
60
|
+
type: Modules.TokenUsageReportBreakdown,
|
|
61
|
+
method: HttpMethod.GET,
|
|
62
|
+
endpoint: endpoint.generate(),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -6,3 +6,12 @@ export type { TokenUsageAdminTimelineInterface } from "./tokenusage-admin-timeli
|
|
|
6
6
|
export { TokenUsageAdminBreakdown } from "./tokenusage-admin-breakdown";
|
|
7
7
|
export type { TokenUsageAdminBreakdownInterface } from "./tokenusage-admin-breakdown.interface";
|
|
8
8
|
export { TokenUsageAdminService } from "./TokenUsageAdminService";
|
|
9
|
+
|
|
10
|
+
export * from "./tokenusage-report.types";
|
|
11
|
+
export { TokenUsageReportSummary } from "./tokenusage-report-summary";
|
|
12
|
+
export type { TokenUsageReportSummaryInterface } from "./tokenusage-report-summary.interface";
|
|
13
|
+
export { TokenUsageReportTimeline } from "./tokenusage-report-timeline";
|
|
14
|
+
export type { TokenUsageReportTimelineInterface } from "./tokenusage-report-timeline.interface";
|
|
15
|
+
export { TokenUsageReportBreakdown } from "./tokenusage-report-breakdown";
|
|
16
|
+
export type { TokenUsageReportBreakdownInterface } from "./tokenusage-report-breakdown.interface";
|
|
17
|
+
export { TokenUsageReportService } from "./TokenUsageReportService";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ApiDataInterface } from "../../../core";
|
|
2
|
+
|
|
3
|
+
export interface TokenUsageReportBreakdownInterface extends ApiDataInterface {
|
|
4
|
+
get label(): string;
|
|
5
|
+
get sublabel(): string | undefined;
|
|
6
|
+
get cost(): number;
|
|
7
|
+
get credits(): number;
|
|
8
|
+
get tokensIn(): number;
|
|
9
|
+
get tokensOut(): number;
|
|
10
|
+
get cached(): number;
|
|
11
|
+
get calls(): number;
|
|
12
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
|
|
2
|
+
import { TokenUsageReportBreakdownInterface } from "./tokenusage-report-breakdown.interface";
|
|
3
|
+
import { TokenUsageReportBreakdownInput } from "./tokenusage-report.types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One ranked row of a self-service breakdown. The same shape serves both
|
|
7
|
+
* dimensions — operation and target — which is why the two "by-X" panels
|
|
8
|
+
* collapse into a single `breakdown?dimension=` call.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately narrower than the administrative row: no `activeUsers`,
|
|
11
|
+
* `monthlyCredits` or `availableMonthlyCredits`, which are company-fleet
|
|
12
|
+
* figures a single company has no business seeing.
|
|
13
|
+
*/
|
|
14
|
+
export class TokenUsageReportBreakdown extends AbstractApiData implements TokenUsageReportBreakdownInterface {
|
|
15
|
+
private _label: string = "";
|
|
16
|
+
private _sublabel?: string;
|
|
17
|
+
private _cost: number = 0;
|
|
18
|
+
private _credits: number = 0;
|
|
19
|
+
private _tokensIn: number = 0;
|
|
20
|
+
private _tokensOut: number = 0;
|
|
21
|
+
private _cached: number = 0;
|
|
22
|
+
private _calls: number = 0;
|
|
23
|
+
|
|
24
|
+
get label(): string {
|
|
25
|
+
return this._label;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get sublabel(): string | undefined {
|
|
29
|
+
return this._sublabel;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get cost(): number {
|
|
33
|
+
return this._cost;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get credits(): number {
|
|
37
|
+
return this._credits;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get tokensIn(): number {
|
|
41
|
+
return this._tokensIn;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get tokensOut(): number {
|
|
45
|
+
return this._tokensOut;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get cached(): number {
|
|
49
|
+
return this._cached;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get calls(): number {
|
|
53
|
+
return this._calls;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
57
|
+
super.rehydrate(data);
|
|
58
|
+
|
|
59
|
+
const attrs = data.jsonApi.attributes;
|
|
60
|
+
this._label = attrs.label ?? "";
|
|
61
|
+
this._sublabel = attrs.sublabel ?? undefined;
|
|
62
|
+
this._cost = attrs.cost ?? 0;
|
|
63
|
+
this._credits = attrs.credits ?? 0;
|
|
64
|
+
this._tokensIn = attrs.tokensIn ?? 0;
|
|
65
|
+
this._tokensOut = attrs.tokensOut ?? 0;
|
|
66
|
+
this._cached = attrs.cached ?? 0;
|
|
67
|
+
this._calls = attrs.calls ?? 0;
|
|
68
|
+
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
createJsonApi(data: TokenUsageReportBreakdownInput) {
|
|
73
|
+
const response: any = {
|
|
74
|
+
data: {
|
|
75
|
+
type: Modules.TokenUsageReportBreakdown.name,
|
|
76
|
+
id: data.id,
|
|
77
|
+
attributes: {},
|
|
78
|
+
relationships: {},
|
|
79
|
+
},
|
|
80
|
+
included: [],
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (data.label !== undefined) response.data.attributes.label = data.label;
|
|
84
|
+
if (data.sublabel !== undefined) response.data.attributes.sublabel = data.sublabel;
|
|
85
|
+
if (data.cost !== undefined) response.data.attributes.cost = data.cost;
|
|
86
|
+
if (data.credits !== undefined) response.data.attributes.credits = data.credits;
|
|
87
|
+
if (data.tokensIn !== undefined) response.data.attributes.tokensIn = data.tokensIn;
|
|
88
|
+
if (data.tokensOut !== undefined) response.data.attributes.tokensOut = data.tokensOut;
|
|
89
|
+
if (data.cached !== undefined) response.data.attributes.cached = data.cached;
|
|
90
|
+
if (data.calls !== undefined) response.data.attributes.calls = data.calls;
|
|
91
|
+
|
|
92
|
+
return response;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ApiDataInterface } from "../../../core";
|
|
2
|
+
|
|
3
|
+
export interface TokenUsageReportSummaryInterface extends ApiDataInterface {
|
|
4
|
+
/** "current" or "previous" — the equal-length preceding span. */
|
|
5
|
+
get window(): string;
|
|
6
|
+
get cost(): number;
|
|
7
|
+
get credits(): number;
|
|
8
|
+
get tokensIn(): number;
|
|
9
|
+
get tokensOut(): number;
|
|
10
|
+
get cached(): number;
|
|
11
|
+
get calls(): number;
|
|
12
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
|
|
2
|
+
import { TokenUsageReportSummaryInterface } from "./tokenusage-report-summary.interface";
|
|
3
|
+
import { TokenUsageReportSummaryInput } from "./tokenusage-report.types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One row of the self-service summary: the caller's own company observed over
|
|
7
|
+
* one time window. Two rows are returned per request — "current" and the
|
|
8
|
+
* equal-length "previous" span — so the KPI tiles can render a value and its
|
|
9
|
+
* delta without a second call.
|
|
10
|
+
*
|
|
11
|
+
* There is no `scope` here: a company only ever sees itself, so the
|
|
12
|
+
* customer/platform split that the administrative summary carries is absent.
|
|
13
|
+
*/
|
|
14
|
+
export class TokenUsageReportSummary extends AbstractApiData implements TokenUsageReportSummaryInterface {
|
|
15
|
+
private _window: string = "";
|
|
16
|
+
private _cost: number = 0;
|
|
17
|
+
private _credits: number = 0;
|
|
18
|
+
private _tokensIn: number = 0;
|
|
19
|
+
private _tokensOut: number = 0;
|
|
20
|
+
private _cached: number = 0;
|
|
21
|
+
private _calls: number = 0;
|
|
22
|
+
|
|
23
|
+
get window(): string {
|
|
24
|
+
return this._window;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get cost(): number {
|
|
28
|
+
return this._cost;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get credits(): number {
|
|
32
|
+
return this._credits;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get tokensIn(): number {
|
|
36
|
+
return this._tokensIn;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get tokensOut(): number {
|
|
40
|
+
return this._tokensOut;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get cached(): number {
|
|
44
|
+
return this._cached;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get calls(): number {
|
|
48
|
+
return this._calls;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
52
|
+
super.rehydrate(data);
|
|
53
|
+
|
|
54
|
+
const attrs = data.jsonApi.attributes;
|
|
55
|
+
this._window = attrs.window ?? "";
|
|
56
|
+
this._cost = attrs.cost ?? 0;
|
|
57
|
+
this._credits = attrs.credits ?? 0;
|
|
58
|
+
this._tokensIn = attrs.tokensIn ?? 0;
|
|
59
|
+
this._tokensOut = attrs.tokensOut ?? 0;
|
|
60
|
+
this._cached = attrs.cached ?? 0;
|
|
61
|
+
this._calls = attrs.calls ?? 0;
|
|
62
|
+
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
createJsonApi(data: TokenUsageReportSummaryInput) {
|
|
67
|
+
const response: any = {
|
|
68
|
+
data: {
|
|
69
|
+
type: Modules.TokenUsageReportSummary.name,
|
|
70
|
+
id: data.id,
|
|
71
|
+
attributes: {},
|
|
72
|
+
relationships: {},
|
|
73
|
+
},
|
|
74
|
+
included: [],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (data.window !== undefined) response.data.attributes.window = data.window;
|
|
78
|
+
if (data.cost !== undefined) response.data.attributes.cost = data.cost;
|
|
79
|
+
if (data.credits !== undefined) response.data.attributes.credits = data.credits;
|
|
80
|
+
if (data.tokensIn !== undefined) response.data.attributes.tokensIn = data.tokensIn;
|
|
81
|
+
if (data.tokensOut !== undefined) response.data.attributes.tokensOut = data.tokensOut;
|
|
82
|
+
if (data.cached !== undefined) response.data.attributes.cached = data.cached;
|
|
83
|
+
if (data.calls !== undefined) response.data.attributes.calls = data.calls;
|
|
84
|
+
|
|
85
|
+
return response;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ApiDataInterface } from "../../../core";
|
|
2
|
+
|
|
3
|
+
export interface TokenUsageReportTimelineInterface extends ApiDataInterface {
|
|
4
|
+
/** Backend declares this `type: "date"`, so it is a Date in memory. */
|
|
5
|
+
get bucket(): Date;
|
|
6
|
+
get series(): string;
|
|
7
|
+
get cost(): number;
|
|
8
|
+
get credits(): number;
|
|
9
|
+
get tokensIn(): number;
|
|
10
|
+
get tokensOut(): number;
|
|
11
|
+
get cached(): number;
|
|
12
|
+
get calls(): number;
|
|
13
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { AbstractApiData, formatLocalDate, JsonApiHydratedDataInterface, Modules } from "../../../core";
|
|
2
|
+
import { TokenUsageReportTimelineInterface } from "./tokenusage-report-timeline.interface";
|
|
3
|
+
import { TokenUsageReportTimelineInput } from "./tokenusage-report.types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One (bucket, series) cell of the self-service usage-over-time chart. Rows
|
|
7
|
+
* arrive flat; the chart pivots them into stacked columns.
|
|
8
|
+
*/
|
|
9
|
+
export class TokenUsageReportTimeline extends AbstractApiData implements TokenUsageReportTimelineInterface {
|
|
10
|
+
private _bucket: Date = new Date(0);
|
|
11
|
+
private _series: string = "";
|
|
12
|
+
private _cost: number = 0;
|
|
13
|
+
private _credits: number = 0;
|
|
14
|
+
private _tokensIn: number = 0;
|
|
15
|
+
private _tokensOut: number = 0;
|
|
16
|
+
private _cached: number = 0;
|
|
17
|
+
private _calls: number = 0;
|
|
18
|
+
|
|
19
|
+
get bucket(): Date {
|
|
20
|
+
return this._bucket;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get series(): string {
|
|
24
|
+
return this._series;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get cost(): number {
|
|
28
|
+
return this._cost;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get credits(): number {
|
|
32
|
+
return this._credits;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get tokensIn(): number {
|
|
36
|
+
return this._tokensIn;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get tokensOut(): number {
|
|
40
|
+
return this._tokensOut;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get cached(): number {
|
|
44
|
+
return this._cached;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get calls(): number {
|
|
48
|
+
return this._calls;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
52
|
+
super.rehydrate(data);
|
|
53
|
+
|
|
54
|
+
const attrs = data.jsonApi.attributes;
|
|
55
|
+
// Wire format is "YYYY-MM-DD"; the interface promises a Date.
|
|
56
|
+
this._bucket = attrs.bucket ? new Date(attrs.bucket) : new Date(0);
|
|
57
|
+
this._series = attrs.series ?? "";
|
|
58
|
+
this._cost = attrs.cost ?? 0;
|
|
59
|
+
this._credits = attrs.credits ?? 0;
|
|
60
|
+
this._tokensIn = attrs.tokensIn ?? 0;
|
|
61
|
+
this._tokensOut = attrs.tokensOut ?? 0;
|
|
62
|
+
this._cached = attrs.cached ?? 0;
|
|
63
|
+
this._calls = attrs.calls ?? 0;
|
|
64
|
+
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
createJsonApi(data: TokenUsageReportTimelineInput) {
|
|
69
|
+
const response: any = {
|
|
70
|
+
data: {
|
|
71
|
+
type: Modules.TokenUsageReportTimeline.name,
|
|
72
|
+
id: data.id,
|
|
73
|
+
attributes: {},
|
|
74
|
+
relationships: {},
|
|
75
|
+
},
|
|
76
|
+
included: [],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// `bucket` is type: "date" on the backend descriptor. formatLocalDate is
|
|
80
|
+
// mandatory — passing the raw Date lets JSON.stringify call .toISOString(),
|
|
81
|
+
// which UTC-shifts and can move the bucket a day. Imported from the package
|
|
82
|
+
// core, never reimplemented inline.
|
|
83
|
+
if (data.bucket !== undefined) response.data.attributes.bucket = formatLocalDate(data.bucket);
|
|
84
|
+
if (data.series !== undefined) response.data.attributes.series = data.series;
|
|
85
|
+
if (data.cost !== undefined) response.data.attributes.cost = data.cost;
|
|
86
|
+
if (data.credits !== undefined) response.data.attributes.credits = data.credits;
|
|
87
|
+
if (data.tokensIn !== undefined) response.data.attributes.tokensIn = data.tokensIn;
|
|
88
|
+
if (data.tokensOut !== undefined) response.data.attributes.tokensOut = data.tokensOut;
|
|
89
|
+
if (data.cached !== undefined) response.data.attributes.cached = data.cached;
|
|
90
|
+
if (data.calls !== undefined) response.data.attributes.calls = data.calls;
|
|
91
|
+
|
|
92
|
+
return response;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Metrics the self-service surface may request. `cost` is deliberately absent. */
|
|
2
|
+
export type ReportMetric = "credits" | "tokens";
|
|
3
|
+
|
|
4
|
+
/** Dimensions the self-service breakdown can group by. */
|
|
5
|
+
export type ReportDimension = "operation" | "target";
|
|
6
|
+
|
|
7
|
+
export type TokenUsageReportFilters = {
|
|
8
|
+
/** ISO 8601 instant. */
|
|
9
|
+
from: string;
|
|
10
|
+
/** ISO 8601 instant. */
|
|
11
|
+
to: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Input types for createJsonApi(). `id` is always required — JSON:API resource
|
|
15
|
+
// objects are identified.
|
|
16
|
+
|
|
17
|
+
export type TokenUsageReportSummaryInput = {
|
|
18
|
+
id: string;
|
|
19
|
+
window: string;
|
|
20
|
+
cost: number;
|
|
21
|
+
credits: number;
|
|
22
|
+
tokensIn: number;
|
|
23
|
+
tokensOut: number;
|
|
24
|
+
cached: number;
|
|
25
|
+
calls: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type TokenUsageReportTimelineInput = {
|
|
29
|
+
id: string;
|
|
30
|
+
/** Backend field type is "date" — emitted via formatLocalDate, never raw. */
|
|
31
|
+
bucket: Date;
|
|
32
|
+
series: string;
|
|
33
|
+
cost: number;
|
|
34
|
+
credits: number;
|
|
35
|
+
tokensIn: number;
|
|
36
|
+
tokensOut: number;
|
|
37
|
+
cached: number;
|
|
38
|
+
calls: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type TokenUsageReportBreakdownInput = {
|
|
42
|
+
id: string;
|
|
43
|
+
label: string;
|
|
44
|
+
sublabel?: string;
|
|
45
|
+
cost: number;
|
|
46
|
+
credits: number;
|
|
47
|
+
tokensIn: number;
|
|
48
|
+
tokensOut: number;
|
|
49
|
+
cached: number;
|
|
50
|
+
calls: number;
|
|
51
|
+
};
|
|
@@ -63,3 +63,31 @@ export const TOKEN_USAGE_ADMIN_I18N_KEYS = [
|
|
|
63
63
|
"token_usage.series.other",
|
|
64
64
|
"token_usage.timeline.empty",
|
|
65
65
|
] as const;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The fixed namespace of i18n keys the self-service token-usage feature reads.
|
|
69
|
+
* Consuming apps must define each entry in their messages/<locale>.json — this
|
|
70
|
+
* list is the contract between the package and the app.
|
|
71
|
+
*
|
|
72
|
+
* The target panel's title is NOT listed: its key is supplied per app through
|
|
73
|
+
* the provider's `targetPanelTitleKey`, because what usage is attributed to is
|
|
74
|
+
* application-specific. Operation labels resolve through
|
|
75
|
+
* `token_usage.types.<camelCase>` with the raw key as fallback, so that
|
|
76
|
+
* namespace stays the app's own vocabulary.
|
|
77
|
+
*/
|
|
78
|
+
export const TOKEN_USAGE_REPORT_I18N_KEYS = [
|
|
79
|
+
// Page + KPI tiles
|
|
80
|
+
"token_usage.report.title",
|
|
81
|
+
"token_usage.report.used_in_period",
|
|
82
|
+
"token_usage.report.vs_previous",
|
|
83
|
+
"token_usage.report.monthly_left",
|
|
84
|
+
"token_usage.report.extra_credits",
|
|
85
|
+
"token_usage.report.calls",
|
|
86
|
+
|
|
87
|
+
// Panel titles
|
|
88
|
+
"token_usage.report.usage_over_time",
|
|
89
|
+
"token_usage.report.by_operation",
|
|
90
|
+
|
|
91
|
+
// Shared states
|
|
92
|
+
"token_usage.report.no_data",
|
|
93
|
+
] as const;
|
|
@@ -28,3 +28,21 @@ export {
|
|
|
28
28
|
} from "./tokenusage-admin.module";
|
|
29
29
|
|
|
30
30
|
export { TOKEN_USAGE_ADMIN_I18N_KEYS } from "./i18n-keys";
|
|
31
|
+
|
|
32
|
+
// Self-service ("report") token-usage dashboard — the company-scoped mirror of
|
|
33
|
+
// the administrative trio above, reusing its chart components verbatim.
|
|
34
|
+
|
|
35
|
+
export { TokenUsageReportProvider, useTokenUsageReport } from "./contexts/TokenUsageReportContext";
|
|
36
|
+
export type { TokenUsageReportContextType, TokenUsageReportFilterState } from "./contexts/TokenUsageReportContext";
|
|
37
|
+
|
|
38
|
+
export { TokenUsageReportContainer } from "./components/TokenUsageReportContainer";
|
|
39
|
+
export { TokenUsageReportFilterBar } from "./components/TokenUsageReportFilterBar";
|
|
40
|
+
export { TokenUsageReportTiles } from "./components/TokenUsageReportTiles";
|
|
41
|
+
export type { TokenUsageBalances } from "./components/TokenUsageReportTiles";
|
|
42
|
+
|
|
43
|
+
export { tokenUsageModules } from "./tokenusage.modules";
|
|
44
|
+
export { configureTokenUsage, getTokenUsageCurrency } from "./lib/config";
|
|
45
|
+
export { createUsageFormatters, useUsageFormatters } from "./lib/formatters";
|
|
46
|
+
export type { UsageFormatters } from "./lib/formatters";
|
|
47
|
+
|
|
48
|
+
export { TOKEN_USAGE_REPORT_I18N_KEYS } from "./i18n-keys";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The currency the token-usage surfaces render monetary values in.
|
|
3
|
+
*
|
|
4
|
+
* The package cannot infer this: the figures come from whatever per-million-token
|
|
5
|
+
* rates the consuming app configured, and those are denominated in whatever
|
|
6
|
+
* currency that app buys inference in — EUR for one consumer, USD for another
|
|
7
|
+
* whose rates are OpenRouter list prices. So the app declares it once at
|
|
8
|
+
* bootstrap, next to configureJsonApi().
|
|
9
|
+
*
|
|
10
|
+
* EUR is the default purely for backward compatibility: the previous
|
|
11
|
+
* implementation hard-coded a euro sign, so an app that never calls this keeps
|
|
12
|
+
* rendering exactly what it rendered before.
|
|
13
|
+
*/
|
|
14
|
+
let currency = "EUR";
|
|
15
|
+
|
|
16
|
+
export function configureTokenUsage(config: { currency: string }): void {
|
|
17
|
+
currency = config.currency;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getTokenUsageCurrency(): string {
|
|
21
|
+
return currency;
|
|
22
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useLocale } from "next-intl";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
import type { Metric } from "../data/tokenusage-admin.types";
|
|
6
|
+
import { getTokenUsageCurrency } from "./config";
|
|
7
|
+
|
|
8
|
+
export type UsageFormatters = {
|
|
9
|
+
/** Locale-aware fixed-decimal number, e.g. 9.46 → "9,46" in it-IT. */
|
|
10
|
+
decimal(value: number, decimals: number): string;
|
|
11
|
+
/** Currency for cost, 2 decimals for credits, whole numbers for tokens. */
|
|
12
|
+
metricValue(value: number, metric: Metric): string;
|
|
13
|
+
/** Currency with an explicit decimal count — the per-call tile needs 4. */
|
|
14
|
+
currency(value: number, decimals: number): string;
|
|
15
|
+
/** Percentage with one decimal by default, e.g. 60 → "60,0 %" in it-IT. */
|
|
16
|
+
percent(value: number, decimals?: number): string;
|
|
17
|
+
/** Compact notation for axis ticks, e.g. 1500000 → "1,5 Mln" in it-IT. */
|
|
18
|
+
compact(value: number): string;
|
|
19
|
+
/** A "YYYY-MM-DD" bucket key rendered as an axis or tooltip label, in UTC. */
|
|
20
|
+
bucketDate(iso: string, granularity: "day" | "week" | "month"): string;
|
|
21
|
+
/** Locale collation for client-side table sorting. */
|
|
22
|
+
compare(a: string, b: string): number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds every formatter the token-usage surfaces need, for one locale and one
|
|
27
|
+
* currency.
|
|
28
|
+
*
|
|
29
|
+
* A pure factory on purpose: it takes no React context, so the arithmetic and
|
|
30
|
+
* the formatting are unit-testable without rendering, and a server component or
|
|
31
|
+
* a test can construct a set for an arbitrary locale.
|
|
32
|
+
*
|
|
33
|
+
* The currency symbol is placed with a space rather than through
|
|
34
|
+
* `style: "currency"`, which is what the previous hard-coded implementation
|
|
35
|
+
* rendered ("€ 9,46"). Keeping that shape means a consuming app on EUR sees
|
|
36
|
+
* byte-identical output after this refactor.
|
|
37
|
+
*
|
|
38
|
+
* `bucketDate` formats in UTC, always. The wire value is a `type: "date"` — a
|
|
39
|
+
* calendar day — parsed to UTC midnight; reading it back with local getters
|
|
40
|
+
* would shift the label a day early west of UTC.
|
|
41
|
+
*/
|
|
42
|
+
export function createUsageFormatters(locale: string, currency: string): UsageFormatters {
|
|
43
|
+
const symbol =
|
|
44
|
+
new Intl.NumberFormat(locale, { style: "currency", currency, currencyDisplay: "narrowSymbol" })
|
|
45
|
+
.formatToParts(0)
|
|
46
|
+
.find((part) => part.type === "currency")?.value ?? currency;
|
|
47
|
+
|
|
48
|
+
const decimal = (value: number, decimals: number): string =>
|
|
49
|
+
value.toLocaleString(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
|
50
|
+
|
|
51
|
+
const dayFormat = new Intl.DateTimeFormat(locale, { day: "numeric", month: "short", timeZone: "UTC" });
|
|
52
|
+
const monthFormat = new Intl.DateTimeFormat(locale, { month: "short", year: "numeric", timeZone: "UTC" });
|
|
53
|
+
const compactFormat = new Intl.NumberFormat(locale, { notation: "compact", maximumFractionDigits: 1 });
|
|
54
|
+
const collator = new Intl.Collator(locale);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
decimal,
|
|
58
|
+
|
|
59
|
+
metricValue(value, metric) {
|
|
60
|
+
if (metric === "cost") return `${symbol} ${decimal(value, 2)}`;
|
|
61
|
+
// Credits are stored to 4 decimals (round4(cost / creditCost)), but that
|
|
62
|
+
// precision is noise to a reader comparing rows: one decimal is enough to
|
|
63
|
+
// separate two values and keeps the columns narrow enough to scan.
|
|
64
|
+
if (metric === "credits") return decimal(value, 1);
|
|
65
|
+
return decimal(value, 0);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
currency(value, decimals) {
|
|
69
|
+
return `${symbol} ${decimal(value, decimals)}`;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
percent(value, decimals = 1) {
|
|
73
|
+
return `${decimal(value, decimals)} %`;
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
compact(value) {
|
|
77
|
+
return compactFormat.format(value);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
bucketDate(iso, granularity) {
|
|
81
|
+
const date = new Date(`${iso}T00:00:00.000Z`);
|
|
82
|
+
return granularity === "month" ? monthFormat.format(date) : dayFormat.format(date);
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
compare(a, b) {
|
|
86
|
+
return collator.compare(a, b);
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The formatter set for the current request's locale and the app's configured
|
|
93
|
+
* currency. Memoised on both, so the Intl objects are built once per locale
|
|
94
|
+
* rather than on every render.
|
|
95
|
+
*/
|
|
96
|
+
export function useUsageFormatters(): UsageFormatters {
|
|
97
|
+
const locale = useLocale();
|
|
98
|
+
const currency = getTokenUsageCurrency();
|
|
99
|
+
return useMemo(() => createUsageFormatters(locale, currency), [locale, currency]);
|
|
100
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { Metric } from "../data/tokenusage-admin.types";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Locale-free arithmetic for the token-usage surfaces.
|
|
5
|
+
*
|
|
6
|
+
* Everything that turns a number into a STRING lives in `./formatters` instead,
|
|
7
|
+
* because it depends on the request's locale and the app's configured currency.
|
|
8
|
+
* This file stays pure arithmetic so it needs neither.
|
|
9
|
+
*/
|
|
10
|
+
|
|
3
11
|
/**
|
|
4
12
|
* The metric field set every admin token-usage resource carries. Declared
|
|
5
13
|
* structurally so it accepts the summary, timeline and breakdown interfaces
|
|
@@ -14,9 +22,6 @@ export type TokenUsageMetrics = {
|
|
|
14
22
|
calls: number;
|
|
15
23
|
};
|
|
16
24
|
|
|
17
|
-
/** UI copy is Italian, so every figure is formatted with the Italian locale. */
|
|
18
|
-
const LOCALE = "it-IT";
|
|
19
|
-
|
|
20
25
|
/** Reads the single number a row contributes for the currently selected metric. */
|
|
21
26
|
export function metricValue(row: TokenUsageMetrics, metric: Metric): number {
|
|
22
27
|
if (metric === "cost") return row.cost;
|
|
@@ -24,29 +29,6 @@ export function metricValue(row: TokenUsageMetrics, metric: Metric): number {
|
|
|
24
29
|
return row.tokensIn + row.tokensOut;
|
|
25
30
|
}
|
|
26
31
|
|
|
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
32
|
/**
|
|
51
33
|
* Share of cached input tokens. Returns 0 rather than NaN when the window holds
|
|
52
34
|
* no input tokens at all, so the tile renders "0,0 %" instead of "NaN %".
|