From 9f55159bd5ef2d7817d84aad61d10b4616dcc058 Mon Sep 17 00:00:00 2001 From: Randi <55005611+rdself@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:15:14 -0400 Subject: [PATCH] Fix request log detail token layout (#1096) Integrated into release/v3.5.9 --- src/shared/components/RequestLoggerDetail.tsx | 54 +++++++------ tests/unit/request-log-detail-layout.test.mjs | 75 +++++++++++++++++++ 2 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 tests/unit/request-log-detail-layout.test.mjs diff --git a/src/shared/components/RequestLoggerDetail.tsx b/src/shared/components/RequestLoggerDetail.tsx index c1b2b0aa84..2cd4171c29 100644 --- a/src/shared/components/RequestLoggerDetail.tsx +++ b/src/shared/components/RequestLoggerDetail.tsx @@ -109,6 +109,15 @@ export default function RequestLoggerDetail({ log, detail, loading, onClose, onC : []; const requestJson = detail?.requestBody ? toPrettyJson(detail.requestBody) : null; const responseJson = detail?.responseBody ? toPrettyJson(detail.responseBody) : null; + const tokenStats = { + totalIn: detail?.tokens?.in ?? log.tokens?.in ?? 0, + totalOut: detail?.tokens?.out ?? log.tokens?.out ?? 0, + cacheRead: detail?.tokens?.cacheRead ?? log.tokens?.cacheRead, + cacheWrite: detail?.tokens?.cacheWrite ?? log.tokens?.cacheWrite, + reasoning: detail?.tokens?.reasoning ?? log.tokens?.reasoning, + }; + + const formatTokenValue = (value) => (value != null ? value.toLocaleString() : "N/A"); return (
{/* Metadata Grid */} -
+
-
Time
+
+ Completed Time +
{formatDate(log.timestamp)}
@@ -158,33 +172,29 @@ export default function RequestLoggerDetail({ log, detail, loading, onClose, onC
{formatDuration(log.duration)}
-
- Tokens -
-
+
Input
+
- Total In: {(detail?.tokens?.in ?? log.tokens?.in ?? 0).toLocaleString()} - - - Total Out: {(detail?.tokens?.out ?? log.tokens?.out ?? 0).toLocaleString()} + Total In: {tokenStats.totalIn.toLocaleString()} - Cache Read:{" "} - {(detail?.tokens?.cacheRead ?? log.tokens?.cacheRead) != null - ? (detail?.tokens?.cacheRead ?? log.tokens?.cacheRead).toLocaleString() - : "N/A"} + Cache Read: {formatTokenValue(tokenStats.cacheRead)} - Cache Write:{" "} - {(detail?.tokens?.cacheWrite ?? log.tokens?.cacheWrite) != null - ? (detail?.tokens?.cacheWrite ?? log.tokens?.cacheWrite).toLocaleString() - : "N/A"} + Cache Write: {formatTokenValue(tokenStats.cacheWrite)} + +
+
+
+
+ Output +
+
+ + Total Out: {tokenStats.totalOut.toLocaleString()} - Reasoning:{" "} - {(detail?.tokens?.reasoning ?? log.tokens?.reasoning) != null - ? (detail?.tokens?.reasoning ?? log.tokens?.reasoning).toLocaleString() - : "N/A"} + Reasoning: {formatTokenValue(tokenStats.reasoning)}
diff --git a/tests/unit/request-log-detail-layout.test.mjs b/tests/unit/request-log-detail-layout.test.mjs new file mode 100644 index 0000000000..d6cb98a37a --- /dev/null +++ b/tests/unit/request-log-detail-layout.test.mjs @@ -0,0 +1,75 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import React from "react"; +import { renderToStaticMarkup } from "react-dom/server"; + +const { default: RequestLoggerDetail } = + await import("../../src/shared/components/RequestLoggerDetail.tsx"); + +test("request log detail splits token badges into input and output groups", () => { + const html = renderToStaticMarkup( + React.createElement(RequestLoggerDetail, { + log: { + status: 200, + method: "POST", + path: "/v1/chat/completions", + timestamp: "2026-04-09T21:27:08.000Z", + duration: 2500, + provider: "openai-compatible-sp-openai", + sourceFormat: "openai-chat", + model: "gpt-5.4", + requestedModel: "openai-compatible-sp-openai/gpt-5.4", + account: "main", + apiKeyName: "tools", + apiKeyId: "29d9***7e37", + comboName: "_Latest-Discounted", + tokens: { + in: 21818, + out: 42, + cacheRead: 21632, + cacheWrite: null, + reasoning: null, + }, + }, + detail: { + account: "main", + apiKeyName: "tools", + apiKeyId: "29d9***7e37", + comboName: "_Latest-Discounted", + requestedModel: "openai-compatible-sp-openai/gpt-5.4", + tokens: { + in: 21818, + out: 42, + cacheRead: 21632, + cacheWrite: null, + reasoning: null, + }, + }, + loading: false, + onClose: () => {}, + onCopy: async () => true, + }) + ); + + const inputLabelIndex = html.indexOf(">Input<"); + const outputLabelIndex = html.indexOf(">Output<"); + const modelLabelIndex = html.indexOf(">Model<"); + const requestedModelLabelIndex = html.indexOf(">Requested Model<"); + + assert.notEqual(html.indexOf(">Completed Time<"), -1); + assert.equal(html.includes(">Time<"), false); + assert.notEqual(inputLabelIndex, -1); + assert.notEqual(outputLabelIndex, -1); + assert.notEqual(modelLabelIndex, -1); + assert.notEqual(requestedModelLabelIndex, -1); + assert.equal(html.includes(">Tokens<"), false); + assert.equal(inputLabelIndex < outputLabelIndex, true); + assert.equal(outputLabelIndex < modelLabelIndex, true); + assert.equal(modelLabelIndex < requestedModelLabelIndex, true); + + assert.match( + html, + /data-testid="token-group-input"[\s\S]*Total In: 21,818[\s\S]*Cache Read: 21,632[\s\S]*Cache Write: N\/A/ + ); + assert.match(html, /data-testid="token-group-output"[\s\S]*Total Out: 42[\s\S]*Reasoning: N\/A/); +});