"use client";
import { useState, useEffect } from "react";
import {
PROVIDER_COLORS,
getHttpStatusStyle as getStatusStyle,
getProtocolColor,
} from "@/shared/constants/colors";
import { formatDuration, formatApiKeyLabel } from "@/shared/utils/formatting";
// ─── Payload Code Block ─────────────────────────────────────────────────────
function PayloadSection({ title, json, onCopy }) {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
const success = await onCopy();
if (success !== false) {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
return (
{title}
{json}
);
}
// ─── Detail Modal ───────────────────────────────────────────────────────────
type StreamChunks = Record;
export default function RequestLoggerDetail({
log,
detail,
loading,
debugEnabled,
onClose,
onCopy,
}) {
// Close on Escape key
useEffect(() => {
const handler = (e) => {
if (e.key === "Escape") onClose();
};
globalThis.addEventListener("keydown", handler);
return () => globalThis.removeEventListener("keydown", handler);
}, [onClose]);
const statusStyle = getStatusStyle(log.status);
const protocolKey = log.sourceFormat || log.provider;
const protocol = getProtocolColor(protocolKey, log.provider);
const providerColor = PROVIDER_COLORS[log.provider] || {
bg: "#374151",
text: "#fff",
label: (log.provider || "-").toUpperCase(),
};
const providerStatus = detail?.pipelinePayloads?.providerResponse?.status;
const hasStatusDiscrepancy = providerStatus && providerStatus !== log.status;
const formatDate = (iso) => {
try {
const d = new Date(iso);
return (
d.toLocaleDateString("pt-BR") + ", " + d.toLocaleTimeString("en-US", { hour12: false })
);
} catch {
return iso;
}
};
const toPrettyJson = (payload) => {
if (payload === null || payload === undefined) return null;
try {
return JSON.stringify(payload, null, 2);
} catch {
return String(payload);
}
};
const pipelinePayloads = detail?.pipelinePayloads || null;
const payloadSections = pipelinePayloads
? [
["clientRawRequest", "Client Raw Request"],
["clientRequest", "Client Request"],
["openaiRequest", "OpenAI Request"],
["providerRequest", "Provider Request"],
["providerResponse", "Provider Response"],
["clientResponse", "Client Response"],
["error", "Pipeline Error"],
]
.map(([key, title]) => ({
key,
title,
json: toPrettyJson(pipelinePayloads[key]),
}))
.filter((section) => section.json)
: [];
const requestJson = detail?.requestBody ? toPrettyJson(detail.requestBody) : null;
const responseJson = detail?.responseBody ? toPrettyJson(detail.responseBody) : null;
const streamChunksText = (() => {
if (!debugEnabled || !detail?.pipelinePayloads?.streamChunks) return null;
let chunks: StreamChunks = detail.pipelinePayloads.streamChunks;
// If stored as a JSON string, try to parse it so we can render joined raw chunks
if (typeof chunks === "string") {
try {
const parsed = JSON.parse(chunks);
chunks = parsed;
} catch {
// Keep as string and return raw text (don't JSON-stringify)
return chunks;
}
}
if (chunks && typeof chunks === "object") {
try {
return Object.entries(chunks)
.map(([stage, arr]) => {
const joined = Array.isArray(arr) ? arr.join("") : String(arr);
return `--- ${stage} ---\n${joined}`;
})
.join("\n\n");
} catch {
return toPrettyJson(chunks);
}
}
return null;
})();
const detailIssue =
detail?.detailState === "missing"
? "Detailed payload artifact is no longer available for this log entry."
: detail?.detailState === "corrupt"
? "Detailed payload artifact could not be parsed."
: 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,
compressed: detail?.tokens?.compressed ?? log.tokens?.compressed,
};
const formatTokenValue = (value) => (value != null ? value.toLocaleString() : "N/A");
const cacheSource = detail?.cacheSource || log.cacheSource || "upstream";
const cacheSourceLabel =
cacheSource === "semantic" ? "Semantic (OmniRoute)" : "Upstream (Provider)";
const cacheSourceClassName =
cacheSource === "semantic"
? "bg-emerald-500/20 text-emerald-700 dark:text-emerald-300 border-emerald-500/30"
: "bg-sky-500/20 text-sky-700 dark:text-sky-300 border-sky-500/30";
return (
e.stopPropagation()}
>
{/* Modal Header */}
{log.status}
{hasStatusDiscrepancy && (
Upstream: {providerStatus}
)}
{log.method}
{hasStatusDiscrepancy && (
OmniRoute returned {log.status} even though provider returned {providerStatus}
)}
{log.path}
{/* Metadata Grid */}
Completed Time
{formatDate(log.timestamp)}
Duration
{formatDuration(log.duration)}
Input
Total In: {tokenStats.totalIn.toLocaleString()}
Cache Read: {formatTokenValue(tokenStats.cacheRead)}
Cache Write: {formatTokenValue(tokenStats.cacheWrite)}
{tokenStats.compressed != null && tokenStats.compressed > 0 && (() => {
const fromTokens = tokenStats.totalIn + tokenStats.compressed;
const pct = Math.round((tokenStats.compressed / fromTokens) * 100);
return (
Compressed: {fromTokens.toLocaleString()} → {tokenStats.totalIn.toLocaleString()} (-{pct}%)
);
})()}
Output
Total Out: {tokenStats.totalOut.toLocaleString()}
Reasoning: {formatTokenValue(tokenStats.reasoning)}
Requested Model
{detail?.requestedModel || log.requestedModel || "—"}
Provider
{providerColor.label}
Req Protocol
{protocol.label}
Cache Source
{cacheSourceLabel}
Account
{detail?.account || log.account || "-"}
API Key
{formatApiKeyLabel(
detail?.apiKeyName || log.apiKeyName,
detail?.apiKeyId || log.apiKeyId
)}
Combo
{detail?.comboName || log.comboName ? (
{detail?.comboName || log.comboName}
) : (
—
)}
{/* Error Message */}
{(detail?.error || log.error) && (
Error
{detail?.error || log.error}
)}
{detailIssue && (
Detail Status
{detailIssue}
)}
{loading ? (
Loading request details...
) : (
<>
{payloadSections.length > 0 &&
payloadSections.map((section) => (
onCopy(section.json)}
/>
))}
{streamChunksText && (
onCopy(streamChunksText)}
/>
)}
{payloadSections.length === 0 && responseJson && (
onCopy(responseJson)}
/>
)}
{payloadSections.length === 0 && requestJson && (
onCopy(requestJson)}
/>
)}
{payloadSections.length === 0 && !requestJson && !responseJson && !loading && (
info
No payload data available for this log entry.
Enable detailed logging first if you want the four-stage client/provider payload
view for new requests.
)}
>
)}
);
}