mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
refactor: dedupe extension and ui helpers
This commit is contained in:
@@ -114,6 +114,17 @@ export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderPromptMatch = (ctx: ExtensionContext, match: PromptMatch) => {
|
||||||
|
setWidget(ctx, match);
|
||||||
|
applySessionName(ctx, match);
|
||||||
|
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
||||||
|
const title = meta?.title?.trim();
|
||||||
|
const authorText = formatAuthor(meta?.author);
|
||||||
|
setWidget(ctx, match, title, authorText);
|
||||||
|
applySessionName(ctx, match, title);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
pi.on("before_agent_start", async (event, ctx) => {
|
pi.on("before_agent_start", async (event, ctx) => {
|
||||||
if (!ctx.hasUI) {
|
if (!ctx.hasUI) {
|
||||||
return;
|
return;
|
||||||
@@ -123,14 +134,7 @@ export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWidget(ctx, match);
|
renderPromptMatch(ctx, match);
|
||||||
applySessionName(ctx, match);
|
|
||||||
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
|
||||||
const title = meta?.title?.trim();
|
|
||||||
const authorText = formatAuthor(meta?.author);
|
|
||||||
setWidget(ctx, match, title, authorText);
|
|
||||||
applySessionName(ctx, match, title);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pi.on("session_switch", async (_event, ctx) => {
|
pi.on("session_switch", async (_event, ctx) => {
|
||||||
@@ -177,14 +181,7 @@ export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWidget(ctx, match);
|
renderPromptMatch(ctx, match);
|
||||||
applySessionName(ctx, match);
|
|
||||||
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
|
||||||
const title = meta?.title?.trim();
|
|
||||||
const authorText = formatAuthor(meta?.author);
|
|
||||||
setWidget(ctx, match, title, authorText);
|
|
||||||
applySessionName(ctx, match, title);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pi.on("session_start", async (_event, ctx) => {
|
pi.on("session_start", async (_event, ctx) => {
|
||||||
|
|||||||
@@ -76,6 +76,28 @@ function resolveVersionFromPackage(command: string, cwd: string): string | null
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveVersionCheckResult(params: {
|
||||||
|
expectedVersion?: string;
|
||||||
|
installedVersion: string;
|
||||||
|
installCommand: string;
|
||||||
|
}): AcpxVersionCheckResult {
|
||||||
|
if (params.expectedVersion && params.installedVersion !== params.expectedVersion) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
reason: "version-mismatch",
|
||||||
|
message: `acpx version mismatch: found ${params.installedVersion}, expected ${params.expectedVersion}`,
|
||||||
|
expectedVersion: params.expectedVersion,
|
||||||
|
installCommand: params.installCommand,
|
||||||
|
installedVersion: params.installedVersion,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
version: params.installedVersion,
|
||||||
|
expectedVersion: params.expectedVersion,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function checkAcpxVersion(params: {
|
export async function checkAcpxVersion(params: {
|
||||||
command: string;
|
command: string;
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
@@ -131,21 +153,7 @@ export async function checkAcpxVersion(params: {
|
|||||||
if (hasExpectedVersion && isUnsupportedVersionProbe(result.stdout, result.stderr)) {
|
if (hasExpectedVersion && isUnsupportedVersionProbe(result.stdout, result.stderr)) {
|
||||||
const installedVersion = resolveVersionFromPackage(params.command, cwd);
|
const installedVersion = resolveVersionFromPackage(params.command, cwd);
|
||||||
if (installedVersion) {
|
if (installedVersion) {
|
||||||
if (expectedVersion && installedVersion !== expectedVersion) {
|
return resolveVersionCheckResult({ expectedVersion, installedVersion, installCommand });
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
reason: "version-mismatch",
|
|
||||||
message: `acpx version mismatch: found ${installedVersion}, expected ${expectedVersion}`,
|
|
||||||
expectedVersion,
|
|
||||||
installCommand,
|
|
||||||
installedVersion,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
version: installedVersion,
|
|
||||||
expectedVersion,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const stderr = result.stderr.trim();
|
const stderr = result.stderr.trim();
|
||||||
@@ -179,22 +187,7 @@ export async function checkAcpxVersion(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expectedVersion && installedVersion !== expectedVersion) {
|
return resolveVersionCheckResult({ expectedVersion, installedVersion, installCommand });
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
reason: "version-mismatch",
|
|
||||||
message: `acpx version mismatch: found ${installedVersion}, expected ${expectedVersion}`,
|
|
||||||
expectedVersion,
|
|
||||||
installCommand,
|
|
||||||
installedVersion,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
version: installedVersion,
|
|
||||||
expectedVersion,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let pendingEnsure: Promise<void> | null = null;
|
let pendingEnsure: Promise<void> | null = null;
|
||||||
|
|||||||
@@ -182,6 +182,12 @@ type LoadedState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type LabelTarget = "issue" | "pr";
|
type LabelTarget = "issue" | "pr";
|
||||||
|
type LabelItemBatch = {
|
||||||
|
batchIndex: number;
|
||||||
|
items: LabelItem[];
|
||||||
|
totalCount: number;
|
||||||
|
fetchedCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
function parseArgs(argv: string[]): ScriptOptions {
|
function parseArgs(argv: string[]): ScriptOptions {
|
||||||
let limit = Number.POSITIVE_INFINITY;
|
let limit = Number.POSITIVE_INFINITY;
|
||||||
@@ -408,9 +414,22 @@ function fetchPullRequestPage(repo: RepoInfo, after: string | null): PullRequest
|
|||||||
return pullRequests;
|
return pullRequests;
|
||||||
}
|
}
|
||||||
|
|
||||||
function* fetchOpenIssueBatches(limit: number): Generator<IssueBatch> {
|
function mapNodeToLabelItem(node: IssuePage["nodes"][number]): LabelItem {
|
||||||
|
return {
|
||||||
|
number: node.number,
|
||||||
|
title: node.title,
|
||||||
|
body: node.body ?? "",
|
||||||
|
labels: node.labels?.nodes ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function* fetchOpenLabelItemBatches(params: {
|
||||||
|
limit: number;
|
||||||
|
kindPlural: "issues" | "pull requests";
|
||||||
|
fetchPage: (repo: RepoInfo, after: string | null) => IssuePage | PullRequestPage;
|
||||||
|
}): Generator<LabelItemBatch> {
|
||||||
const repo = resolveRepo();
|
const repo = resolveRepo();
|
||||||
const results: Issue[] = [];
|
const results: LabelItem[] = [];
|
||||||
let page = 1;
|
let page = 1;
|
||||||
let after: string | null = null;
|
let after: string | null = null;
|
||||||
let totalCount = 0;
|
let totalCount = 0;
|
||||||
@@ -419,33 +438,28 @@ function* fetchOpenIssueBatches(limit: number): Generator<IssueBatch> {
|
|||||||
|
|
||||||
logStep(`Repository: ${repo.owner}/${repo.name}`);
|
logStep(`Repository: ${repo.owner}/${repo.name}`);
|
||||||
|
|
||||||
while (fetchedCount < limit) {
|
while (fetchedCount < params.limit) {
|
||||||
const pageData = fetchIssuePage(repo, after);
|
const pageData = params.fetchPage(repo, after);
|
||||||
const nodes = pageData.nodes ?? [];
|
const nodes = pageData.nodes ?? [];
|
||||||
totalCount = pageData.totalCount ?? totalCount;
|
totalCount = pageData.totalCount ?? totalCount;
|
||||||
|
|
||||||
if (page === 1) {
|
if (page === 1) {
|
||||||
logSuccess(`Found ${totalCount} open issues.`);
|
logSuccess(`Found ${totalCount} open ${params.kindPlural}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
logInfo(`Fetched page ${page} (${nodes.length} issues).`);
|
logInfo(`Fetched page ${page} (${nodes.length} ${params.kindPlural}).`);
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
if (fetchedCount >= limit) {
|
if (fetchedCount >= params.limit) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
results.push({
|
results.push(mapNodeToLabelItem(node));
|
||||||
number: node.number,
|
|
||||||
title: node.title,
|
|
||||||
body: node.body ?? "",
|
|
||||||
labels: node.labels?.nodes ?? [],
|
|
||||||
});
|
|
||||||
fetchedCount += 1;
|
fetchedCount += 1;
|
||||||
|
|
||||||
if (results.length >= WORK_BATCH_SIZE) {
|
if (results.length >= WORK_BATCH_SIZE) {
|
||||||
yield {
|
yield {
|
||||||
batchIndex,
|
batchIndex,
|
||||||
issues: results.splice(0, results.length),
|
items: results.splice(0, results.length),
|
||||||
totalCount,
|
totalCount,
|
||||||
fetchedCount,
|
fetchedCount,
|
||||||
};
|
};
|
||||||
@@ -464,72 +478,39 @@ function* fetchOpenIssueBatches(limit: number): Generator<IssueBatch> {
|
|||||||
if (results.length) {
|
if (results.length) {
|
||||||
yield {
|
yield {
|
||||||
batchIndex,
|
batchIndex,
|
||||||
issues: results,
|
items: results,
|
||||||
totalCount,
|
totalCount,
|
||||||
fetchedCount,
|
fetchedCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* fetchOpenPullRequestBatches(limit: number): Generator<PullRequestBatch> {
|
function* fetchOpenIssueBatches(limit: number): Generator<IssueBatch> {
|
||||||
const repo = resolveRepo();
|
for (const batch of fetchOpenLabelItemBatches({
|
||||||
const results: PullRequest[] = [];
|
limit,
|
||||||
let page = 1;
|
kindPlural: "issues",
|
||||||
let after: string | null = null;
|
fetchPage: fetchIssuePage,
|
||||||
let totalCount = 0;
|
})) {
|
||||||
let fetchedCount = 0;
|
|
||||||
let batchIndex = 1;
|
|
||||||
|
|
||||||
logStep(`Repository: ${repo.owner}/${repo.name}`);
|
|
||||||
|
|
||||||
while (fetchedCount < limit) {
|
|
||||||
const pageData = fetchPullRequestPage(repo, after);
|
|
||||||
const nodes = pageData.nodes ?? [];
|
|
||||||
totalCount = pageData.totalCount ?? totalCount;
|
|
||||||
|
|
||||||
if (page === 1) {
|
|
||||||
logSuccess(`Found ${totalCount} open pull requests.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
logInfo(`Fetched page ${page} (${nodes.length} pull requests).`);
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
if (fetchedCount >= limit) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
results.push({
|
|
||||||
number: node.number,
|
|
||||||
title: node.title,
|
|
||||||
body: node.body ?? "",
|
|
||||||
labels: node.labels?.nodes ?? [],
|
|
||||||
});
|
|
||||||
fetchedCount += 1;
|
|
||||||
|
|
||||||
if (results.length >= WORK_BATCH_SIZE) {
|
|
||||||
yield {
|
|
||||||
batchIndex,
|
|
||||||
pullRequests: results.splice(0, results.length),
|
|
||||||
totalCount,
|
|
||||||
fetchedCount,
|
|
||||||
};
|
|
||||||
batchIndex += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pageData.pageInfo.hasNextPage) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
after = pageData.pageInfo.endCursor ?? null;
|
|
||||||
page += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.length) {
|
|
||||||
yield {
|
yield {
|
||||||
batchIndex,
|
batchIndex: batch.batchIndex,
|
||||||
pullRequests: results,
|
issues: batch.items,
|
||||||
totalCount,
|
totalCount: batch.totalCount,
|
||||||
fetchedCount,
|
fetchedCount: batch.fetchedCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* fetchOpenPullRequestBatches(limit: number): Generator<PullRequestBatch> {
|
||||||
|
for (const batch of fetchOpenLabelItemBatches({
|
||||||
|
limit,
|
||||||
|
kindPlural: "pull requests",
|
||||||
|
fetchPage: fetchPullRequestPage,
|
||||||
|
})) {
|
||||||
|
yield {
|
||||||
|
batchIndex: batch.batchIndex,
|
||||||
|
pullRequests: batch.items,
|
||||||
|
totalCount: batch.totalCount,
|
||||||
|
fetchedCount: batch.fetchedCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,23 @@ function createState(request: RequestFn, overrides: Partial<UsageState> = {}): U
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expectSpecificTimezoneCalls(request: ReturnType<typeof vi.fn>, startCall: number): void {
|
||||||
|
expect(request).toHaveBeenNthCalledWith(startCall, "sessions.usage", {
|
||||||
|
startDate: "2026-02-16",
|
||||||
|
endDate: "2026-02-16",
|
||||||
|
mode: "specific",
|
||||||
|
utcOffset: "UTC+5:30",
|
||||||
|
limit: 1000,
|
||||||
|
includeContextWeight: true,
|
||||||
|
});
|
||||||
|
expect(request).toHaveBeenNthCalledWith(startCall + 1, "usage.cost", {
|
||||||
|
startDate: "2026-02-16",
|
||||||
|
endDate: "2026-02-16",
|
||||||
|
mode: "specific",
|
||||||
|
utcOffset: "UTC+5:30",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe("usage controller date interpretation params", () => {
|
describe("usage controller date interpretation params", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
__test.resetLegacyUsageDateParamsCache();
|
__test.resetLegacyUsageDateParamsCache();
|
||||||
@@ -48,20 +65,7 @@ describe("usage controller date interpretation params", () => {
|
|||||||
|
|
||||||
await loadUsage(state);
|
await loadUsage(state);
|
||||||
|
|
||||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.usage", {
|
expectSpecificTimezoneCalls(request, 1);
|
||||||
startDate: "2026-02-16",
|
|
||||||
endDate: "2026-02-16",
|
|
||||||
mode: "specific",
|
|
||||||
utcOffset: "UTC+5:30",
|
|
||||||
limit: 1000,
|
|
||||||
includeContextWeight: true,
|
|
||||||
});
|
|
||||||
expect(request).toHaveBeenNthCalledWith(2, "usage.cost", {
|
|
||||||
startDate: "2026-02-16",
|
|
||||||
endDate: "2026-02-16",
|
|
||||||
mode: "specific",
|
|
||||||
utcOffset: "UTC+5:30",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sends utc mode without offset when usage timezone is utc", async () => {
|
it("sends utc mode without offset when usage timezone is utc", async () => {
|
||||||
@@ -124,20 +128,7 @@ describe("usage controller date interpretation params", () => {
|
|||||||
|
|
||||||
await loadUsage(state);
|
await loadUsage(state);
|
||||||
|
|
||||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.usage", {
|
expectSpecificTimezoneCalls(request, 1);
|
||||||
startDate: "2026-02-16",
|
|
||||||
endDate: "2026-02-16",
|
|
||||||
mode: "specific",
|
|
||||||
utcOffset: "UTC+5:30",
|
|
||||||
limit: 1000,
|
|
||||||
includeContextWeight: true,
|
|
||||||
});
|
|
||||||
expect(request).toHaveBeenNthCalledWith(2, "usage.cost", {
|
|
||||||
startDate: "2026-02-16",
|
|
||||||
endDate: "2026-02-16",
|
|
||||||
mode: "specific",
|
|
||||||
utcOffset: "UTC+5:30",
|
|
||||||
});
|
|
||||||
expect(request).toHaveBeenNthCalledWith(3, "sessions.usage", {
|
expect(request).toHaveBeenNthCalledWith(3, "sessions.usage", {
|
||||||
startDate: "2026-02-16",
|
startDate: "2026-02-16",
|
||||||
endDate: "2026-02-16",
|
endDate: "2026-02-16",
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import {
|
|||||||
defaultTitle,
|
defaultTitle,
|
||||||
formatToolDetailText,
|
formatToolDetailText,
|
||||||
normalizeToolName,
|
normalizeToolName,
|
||||||
resolveActionArg,
|
resolveToolVerbAndDetailForArgs,
|
||||||
resolveToolVerbAndDetail,
|
|
||||||
type ToolDisplaySpec as ToolDisplaySpecBase,
|
type ToolDisplaySpec as ToolDisplaySpecBase,
|
||||||
} from "../../../src/agents/tool-display-common.js";
|
} from "../../../src/agents/tool-display-common.js";
|
||||||
import type { IconName } from "./icons.ts";
|
import type { IconName } from "./icons.ts";
|
||||||
@@ -126,12 +125,10 @@ export function resolveToolDisplay(params: {
|
|||||||
const icon = (spec?.icon ?? FALLBACK.icon ?? "puzzle") as IconName;
|
const icon = (spec?.icon ?? FALLBACK.icon ?? "puzzle") as IconName;
|
||||||
const title = spec?.title ?? defaultTitle(name);
|
const title = spec?.title ?? defaultTitle(name);
|
||||||
const label = spec?.label ?? title;
|
const label = spec?.label ?? title;
|
||||||
const action = resolveActionArg(params.args);
|
let { verb, detail } = resolveToolVerbAndDetailForArgs({
|
||||||
let { verb, detail } = resolveToolVerbAndDetail({
|
|
||||||
toolKey: key,
|
toolKey: key,
|
||||||
args: params.args,
|
args: params.args,
|
||||||
meta: params.meta,
|
meta: params.meta,
|
||||||
action,
|
|
||||||
spec,
|
spec,
|
||||||
fallbackDetailKeys: FALLBACK.detailKeys,
|
fallbackDetailKeys: FALLBACK.detailKeys,
|
||||||
detailMode: "first",
|
detailMode: "first",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export type UpdateAvailable = import("../../../src/infra/update-startup.js").UpdateAvailable;
|
export type UpdateAvailable = import("../../../src/infra/update-startup.js").UpdateAvailable;
|
||||||
|
import type { CronJobBase } from "../../../src/cron/types-shared.js";
|
||||||
import type { ConfigUiHints } from "../../../src/shared/config-ui-hints-types.js";
|
import type { ConfigUiHints } from "../../../src/shared/config-ui-hints-types.js";
|
||||||
import type {
|
import type {
|
||||||
GatewayAgentRow as SharedGatewayAgentRow,
|
GatewayAgentRow as SharedGatewayAgentRow,
|
||||||
@@ -492,22 +493,14 @@ export type CronJobState = {
|
|||||||
lastFailureAlertAtMs?: number;
|
lastFailureAlertAtMs?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CronJob = {
|
export type CronJob = CronJobBase<
|
||||||
id: string;
|
CronSchedule,
|
||||||
agentId?: string;
|
CronSessionTarget,
|
||||||
sessionKey?: string;
|
CronWakeMode,
|
||||||
name: string;
|
CronPayload,
|
||||||
description?: string;
|
CronDelivery,
|
||||||
enabled: boolean;
|
CronFailureAlert | false
|
||||||
deleteAfterRun?: boolean;
|
> & {
|
||||||
createdAtMs: number;
|
|
||||||
updatedAtMs: number;
|
|
||||||
schedule: CronSchedule;
|
|
||||||
sessionTarget: CronSessionTarget;
|
|
||||||
wakeMode: CronWakeMode;
|
|
||||||
payload: CronPayload;
|
|
||||||
delivery?: CronDelivery;
|
|
||||||
failureAlert?: CronFailureAlert | false;
|
|
||||||
state?: CronJobState;
|
state?: CronJobState;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,52 @@ import {
|
|||||||
|
|
||||||
export type { UsageColumnId, SessionLogEntry, SessionLogRole };
|
export type { UsageColumnId, SessionLogEntry, SessionLogRole };
|
||||||
|
|
||||||
|
function createEmptyUsageTotals(): UsageTotals {
|
||||||
|
return {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
totalTokens: 0,
|
||||||
|
totalCost: 0,
|
||||||
|
inputCost: 0,
|
||||||
|
outputCost: 0,
|
||||||
|
cacheReadCost: 0,
|
||||||
|
cacheWriteCost: 0,
|
||||||
|
missingCostEntries: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUsageTotals(
|
||||||
|
acc: UsageTotals,
|
||||||
|
usage: {
|
||||||
|
input: number;
|
||||||
|
output: number;
|
||||||
|
cacheRead: number;
|
||||||
|
cacheWrite: number;
|
||||||
|
totalTokens: number;
|
||||||
|
totalCost: number;
|
||||||
|
inputCost?: number;
|
||||||
|
outputCost?: number;
|
||||||
|
cacheReadCost?: number;
|
||||||
|
cacheWriteCost?: number;
|
||||||
|
missingCostEntries?: number;
|
||||||
|
},
|
||||||
|
): UsageTotals {
|
||||||
|
acc.input += usage.input;
|
||||||
|
acc.output += usage.output;
|
||||||
|
acc.cacheRead += usage.cacheRead;
|
||||||
|
acc.cacheWrite += usage.cacheWrite;
|
||||||
|
acc.totalTokens += usage.totalTokens;
|
||||||
|
acc.totalCost += usage.totalCost;
|
||||||
|
acc.inputCost += usage.inputCost ?? 0;
|
||||||
|
acc.outputCost += usage.outputCost ?? 0;
|
||||||
|
acc.cacheReadCost += usage.cacheReadCost ?? 0;
|
||||||
|
acc.cacheWriteCost += usage.cacheWriteCost ?? 0;
|
||||||
|
acc.missingCostEntries += usage.missingCostEntries ?? 0;
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
export function renderUsage(props: UsageProps) {
|
export function renderUsage(props: UsageProps) {
|
||||||
// Show loading skeleton if loading and no data yet
|
// Show loading skeleton if loading and no data yet
|
||||||
if (props.loading && !props.totals) {
|
if (props.loading && !props.totals) {
|
||||||
@@ -206,69 +252,15 @@ export function renderUsage(props: UsageProps) {
|
|||||||
// Compute totals from sessions
|
// Compute totals from sessions
|
||||||
const computeSessionTotals = (sessions: UsageSessionEntry[]): UsageTotals => {
|
const computeSessionTotals = (sessions: UsageSessionEntry[]): UsageTotals => {
|
||||||
return sessions.reduce(
|
return sessions.reduce(
|
||||||
(acc, s) => {
|
(acc, s) => (s.usage ? addUsageTotals(acc, s.usage) : acc),
|
||||||
if (s.usage) {
|
createEmptyUsageTotals(),
|
||||||
acc.input += s.usage.input;
|
|
||||||
acc.output += s.usage.output;
|
|
||||||
acc.cacheRead += s.usage.cacheRead;
|
|
||||||
acc.cacheWrite += s.usage.cacheWrite;
|
|
||||||
acc.totalTokens += s.usage.totalTokens;
|
|
||||||
acc.totalCost += s.usage.totalCost;
|
|
||||||
acc.inputCost += s.usage.inputCost ?? 0;
|
|
||||||
acc.outputCost += s.usage.outputCost ?? 0;
|
|
||||||
acc.cacheReadCost += s.usage.cacheReadCost ?? 0;
|
|
||||||
acc.cacheWriteCost += s.usage.cacheWriteCost ?? 0;
|
|
||||||
acc.missingCostEntries += s.usage.missingCostEntries ?? 0;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: 0,
|
|
||||||
output: 0,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
totalTokens: 0,
|
|
||||||
totalCost: 0,
|
|
||||||
inputCost: 0,
|
|
||||||
outputCost: 0,
|
|
||||||
cacheReadCost: 0,
|
|
||||||
cacheWriteCost: 0,
|
|
||||||
missingCostEntries: 0,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compute totals from daily data for selected days (more accurate than session totals)
|
// Compute totals from daily data for selected days (more accurate than session totals)
|
||||||
const computeDailyTotals = (days: string[]): UsageTotals => {
|
const computeDailyTotals = (days: string[]): UsageTotals => {
|
||||||
const matchingDays = props.costDaily.filter((d) => days.includes(d.date));
|
const matchingDays = props.costDaily.filter((d) => days.includes(d.date));
|
||||||
return matchingDays.reduce(
|
return matchingDays.reduce((acc, day) => addUsageTotals(acc, day), createEmptyUsageTotals());
|
||||||
(acc, d) => {
|
|
||||||
acc.input += d.input;
|
|
||||||
acc.output += d.output;
|
|
||||||
acc.cacheRead += d.cacheRead;
|
|
||||||
acc.cacheWrite += d.cacheWrite;
|
|
||||||
acc.totalTokens += d.totalTokens;
|
|
||||||
acc.totalCost += d.totalCost;
|
|
||||||
acc.inputCost += d.inputCost ?? 0;
|
|
||||||
acc.outputCost += d.outputCost ?? 0;
|
|
||||||
acc.cacheReadCost += d.cacheReadCost ?? 0;
|
|
||||||
acc.cacheWriteCost += d.cacheWriteCost ?? 0;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: 0,
|
|
||||||
output: 0,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
totalTokens: 0,
|
|
||||||
totalCost: 0,
|
|
||||||
inputCost: 0,
|
|
||||||
outputCost: 0,
|
|
||||||
cacheReadCost: 0,
|
|
||||||
cacheWriteCost: 0,
|
|
||||||
missingCostEntries: 0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compute display totals and count based on filters
|
// Compute display totals and count based on filters
|
||||||
|
|||||||
Reference in New Issue
Block a user