This commit is contained in:
Ishaan Jaffer 2026-02-11 19:19:00 -08:00
parent 547a5815fc
commit bc15f99940
2 changed files with 0 additions and 415 deletions

View file

@ -1,222 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import type { Row } from "@tanstack/react-table";
import { Tooltip } from "antd";
import { TableRow, TableCell } from "@tremor/react";
import { getSpendString } from "@/utils/dataUtils";
import { sessionSpendLogsCall } from "../networking";
import type { LogEntry } from "./columns";
import { TimeCell } from "./time_cell";
import { getProviderLogoAndName } from "../provider_info_helpers";
interface SessionChildRowsProps {
row: Row<LogEntry>;
accessToken: string;
onChildClick?: (log: LogEntry) => void;
}
const MCP_CALL_TYPES = ["call_mcp_tool", "list_mcp_tools"];
const LlmBadge = () => (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-blue-50 text-blue-700 border border-blue-200 rounded-full text-[11px] font-medium whitespace-nowrap">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="flex-shrink-0 text-gray-400">
<path d="M12 3l1.912 5.813a2 2 0 0 0 1.275 1.275L21 12l-5.813 1.912a2 2 0 0 0-1.275 1.275L12 21l-1.912-5.813a2 2 0 0 0-1.275-1.275L3 12l5.813-1.912a2 2 0 0 0 1.275-1.275L12 3z" />
</svg>
LLM
</span>
);
const McpBadge = () => (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-amber-50 text-amber-700 border border-amber-200 rounded-full text-[11px] font-medium whitespace-nowrap">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="flex-shrink-0">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
</svg>
MCP
</span>
);
export function SessionChildRows({ row, accessToken, onChildClick }: SessionChildRowsProps) {
const sessionId = row.original.session_id;
const { data: children, isLoading } = useQuery({
queryKey: ["sessionChildren", sessionId],
queryFn: async () => {
if (!sessionId) return [];
const response = await sessionSpendLogsCall(accessToken, sessionId);
const allLogs: LogEntry[] = response.data || response || [];
// Sort chronologically
return allLogs.sort((a, b) =>
new Date(a.startTime).getTime() - new Date(b.startTime).getTime()
);
},
enabled: !!sessionId && !!accessToken,
staleTime: 60_000,
});
if (isLoading) {
return (
<TableRow className="bg-gray-50">
<TableCell colSpan={99} className="py-2 pl-12 text-sm text-gray-400">
Loading session calls...
</TableCell>
</TableRow>
);
}
if (!children || children.length === 0) {
return (
<TableRow className="bg-gray-50">
<TableCell colSpan={99} className="py-2 pl-12 text-sm text-gray-400">
No calls found
</TableCell>
</TableRow>
);
}
return (
<>
{children.map((child) => {
const isMcp = MCP_CALL_TYPES.includes(child.call_type);
const modelOrTool = isMcp
? (child.model?.replace("MCP: ", "") || "unknown")
: (child.model || "-");
const serverName = isMcp
? (child.metadata?.mcp_tool_call_metadata?.mcp_server_name || "")
: "";
const provider = child.custom_llm_provider || "";
const logoUrl = isMcp
? (child.metadata?.mcp_tool_call_metadata?.mcp_server_logo_url || "")
: (provider ? getProviderLogoAndName(provider).logo : "");
const duration =
child.startTime && child.endTime
? ((Date.parse(child.endTime) - Date.parse(child.startTime)) / 1000).toFixed(3)
: "-";
const status = (child.metadata?.status || "Success").toLowerCase();
const isSuccess = status !== "failure";
return (
<TableRow
key={child.request_id}
className="h-8 bg-gray-50 cursor-pointer hover:bg-gray-100"
onClick={() => onChildClick?.(child)}
>
{/* Expander: branch connector */}
<TableCell className="py-0.5 max-h-8 overflow-hidden pl-3">
<div className="flex items-center justify-center text-gray-400">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" className="text-indigo-400">
<path d="M4 0 L4 8 L12 8" stroke="currentColor" strokeWidth="1.5" fill="none" />
<path d="M10 5.5 L12.5 8 L10 10.5" stroke="currentColor" strokeWidth="1.5" fill="none" />
</svg>
</div>
</TableCell>
{/* Time */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<TimeCell utcTime={child.startTime} />
</TableCell>
{/* Type */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
{isMcp ? <McpBadge /> : <LlmBadge />}
</TableCell>
{/* Status */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span
className={`px-2 py-1 rounded-md text-xs font-medium inline-block text-center w-16 ${
isSuccess ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"
}`}
>
{isSuccess ? "Success" : "Failure"}
</span>
</TableCell>
{/* Session ID */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span className="font-mono text-xs max-w-[15ch] truncate block text-gray-400">
{child.session_id || ""}
</span>
</TableCell>
{/* Request ID */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<Tooltip title={child.request_id}>
<span className="font-mono text-xs max-w-[15ch] truncate block">{child.request_id}</span>
</Tooltip>
</TableCell>
{/* Cost */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span>{getSpendString(child.spend || 0)}</span>
</TableCell>
{/* Duration */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span>{duration}</span>
</TableCell>
{/* Team Name */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span className="max-w-[15ch] truncate block">
{child.metadata?.user_api_key_team_alias || "-"}
</span>
</TableCell>
{/* Key Hash */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span className="font-mono max-w-[15ch] truncate block">
{child.metadata?.user_api_key || "-"}
</span>
</TableCell>
{/* Key Name */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span className="max-w-[15ch] truncate block">
{child.metadata?.user_api_key_alias || "-"}
</span>
</TableCell>
{/* Model / Tool */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<div className="flex items-center space-x-2">
{logoUrl && (
<img
src={logoUrl}
alt=""
className="w-4 h-4"
onError={(e) => { (e.target as HTMLImageElement).style.display = "none"; }}
/>
)}
<Tooltip title={`${modelOrTool}${serverName ? ` (${serverName})` : ""}`}>
<span className="max-w-[15ch] truncate block font-semibold">{modelOrTool}</span>
</Tooltip>
{serverName && (
<span className="text-xs text-gray-400">{serverName}</span>
)}
</div>
</TableCell>
{/* Tokens */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span className="text-sm">{child.total_tokens || "0"}</span>
</TableCell>
{/* Internal User */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span className="max-w-[15ch] truncate block">{child.user || "-"}</span>
</TableCell>
{/* End User */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span className="max-w-[15ch] truncate block">{child.end_user || "-"}</span>
</TableCell>
{/* Tags */}
<TableCell className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<span>-</span>
</TableCell>
</TableRow>
);
})}
</>
);
}

View file

@ -1,193 +0,0 @@
import React, { useState } from "react";
import { LogEntry } from "./columns";
import { DataTable } from "./table";
import { columns } from "./columns";
import { Card, Title, Text, Metric, Button as TremorButton } from "@tremor/react";
import { RequestViewer } from "./index";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import { ArrowLeftIcon } from "@heroicons/react/outline";
import { Button } from "antd";
import { copyToClipboard as utilCopyToClipboard } from "../../utils/dataUtils";
import { CheckIcon, CopyIcon } from "lucide-react";
import { Tooltip } from "antd";
interface SessionViewProps {
sessionId: string;
logs: LogEntry[];
onBack: () => void;
}
export const SessionView: React.FC<SessionViewProps> = ({ sessionId, logs, onBack }) => {
// Track which log row is expanded
const [expandedRequestId, setExpandedRequestId] = useState<string | null>(null);
const [copiedStates, setCopiedStates] = useState<Record<string, boolean>>({});
// Calculate session metrics
const totalCost = logs.reduce((sum, log) => sum + (log.spend || 0), 0);
const totalTokens = logs.reduce((sum, log) => sum + (log.total_tokens || 0), 0);
// Calculate cache token totals from metadata
const totalCacheReadTokens = logs.reduce((sum, log) => {
const cacheReadTokens = log.metadata?.additional_usage_values?.cache_read_input_tokens || 0;
return sum + cacheReadTokens;
}, 0);
const totalCacheCreationTokens = logs.reduce((sum, log) => {
const cacheCreationTokens = log.metadata?.additional_usage_values?.cache_creation_input_tokens || 0;
return sum + cacheCreationTokens;
}, 0);
// Calculate total tokens including cache tokens
const totalTokensWithCache = totalTokens + totalCacheReadTokens + totalCacheCreationTokens;
const startTime = logs.length > 0 ? new Date(logs[0].startTime) : new Date();
const endTime = logs.length > 0 ? new Date(logs[logs.length - 1].endTime) : new Date();
const durationMs = endTime.getTime() - startTime.getTime();
const durationSec = (durationMs / 1000).toFixed(2);
// Prepare data for the timeline chart
const timelineData = logs.map((log) => ({
time: new Date(log.startTime).toISOString(),
tokens: log.total_tokens || 0,
cost: log.spend || 0,
}));
const copyToClipboard = async (text: string, key: string) => {
const success = await utilCopyToClipboard(text);
if (success) {
setCopiedStates((prev) => ({ ...prev, [key]: true }));
setTimeout(() => {
setCopiedStates((prev) => ({ ...prev, [key]: false }));
}, 2000);
}
};
return (
<div className="space-y-6">
{/* Header with back button */}
<div className="mb-8">
<TremorButton icon={ArrowLeftIcon} variant="light" onClick={onBack} className="mb-4">
Back to All Logs
</TremorButton>
<div className="mt-4">
<h1 className="text-2xl font-semibold text-gray-900">Session Details</h1>
<div className="space-y-2">
<div className="flex items-center cursor-pointer">
<p className="text-sm text-gray-500 font-mono">{sessionId}</p>
<Button
type="text"
size="small"
icon={copiedStates["session-id"] ? <CheckIcon size={12} /> : <CopyIcon size={12} />}
onClick={() => copyToClipboard(sessionId, "session-id")}
className={`left-2 z-10 transition-all duration-200 ${
copiedStates["session-id"]
? "text-green-600 bg-green-50 border-green-200"
: "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
}`}
/>
</div>
<a
href="https://docs.litellm.ai/docs/proxy/ui_logs_sessions"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1"
>
Get started with session management here
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
</div>
</div>
</div>
{/* Session Overview Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<Text>Total Requests</Text>
<Metric>{logs.length}</Metric>
</Card>
<Card>
<Text>Total Cost</Text>
<Metric>${formatNumberWithCommas(totalCost, 6)}</Metric>
</Card>
<Tooltip
title={
<div className="text-white min-w-[200px]">
<div className="text-lg font-medium mb-3">Usage breakdown</div>
<div className="space-y-4">
<div>
<div className="text-base font-medium mb-2">Input usage:</div>
<div className="space-y-2 text-sm text-gray-300">
<div className="flex justify-between">
<span>input:</span>
<span className="ml-8">
{formatNumberWithCommas(logs.reduce((sum, log) => sum + (log.prompt_tokens || 0), 0))}
</span>
</div>
{totalCacheReadTokens > 0 && (
<div className="flex justify-between">
<span>input_cached_tokens:</span>
<span className="ml-8">{formatNumberWithCommas(totalCacheReadTokens)}</span>
</div>
)}
{totalCacheCreationTokens > 0 && (
<div className="flex justify-between">
<span>input_cache_creation_tokens:</span>
<span className="ml-8">{formatNumberWithCommas(totalCacheCreationTokens)}</span>
</div>
)}
</div>
</div>
<div className="border-t border-gray-600 pt-3">
<div className="text-base font-medium mb-2">Output usage:</div>
<div className="space-y-2 text-sm text-gray-300">
<div className="flex justify-between">
<span>output:</span>
<span className="ml-8">
{formatNumberWithCommas(logs.reduce((sum, log) => sum + (log.completion_tokens || 0), 0))}
</span>
</div>
</div>
</div>
<div className="border-t border-gray-600 pt-3">
<div className="flex justify-between items-center">
<span className="text-base font-medium">Total usage:</span>
<span className="text-sm text-gray-300">{formatNumberWithCommas(totalTokensWithCache)}</span>
</div>
</div>
</div>
</div>
}
placement="top"
overlayStyle={{ minWidth: "300px" }}
>
<Card>
<div className="flex items-center justify-between">
<Text>Total Tokens</Text>
<span className="text-gray-400 text-sm"></span>
</div>
<Metric>{formatNumberWithCommas(totalTokensWithCache)}</Metric>
</Card>
</Tooltip>
</div>
{/* Request Timeline */}
<Title>Session Logs</Title>
<div className="mt-4">
<DataTable
columns={columns}
data={logs}
renderSubComponent={RequestViewer}
getRowCanExpand={() => true}
loadingMessage="Loading logs..."
noDataMessage="No logs found"
/>
</div>
</div>
);
};