import moment from "moment"; import { useQuery } from "@tanstack/react-query"; import { useState, useRef, useEffect } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { uiSpendLogsCall, keyInfoV1Call, sessionSpendLogsCall } from "../networking"; import { DataTable } from "./table"; import { columns, LogEntry } from "./columns"; import { Row } from "@tanstack/react-table"; import { prefetchLogDetails } from "./prefetch"; import { RequestResponsePanel } from "./columns"; import { ErrorViewer } from './ErrorViewer'; import { internalUserRoles } from "../../utils/roles"; import { ConfigInfoMessage } from './ConfigInfoMessage'; import { Tooltip } from "antd"; import { KeyResponse, Team } from "../key_team_helpers/key_list"; import KeyInfoView from "../key_info_view"; import { SessionView } from './SessionView'; interface SpendLogsTableProps { accessToken: string | null; token: string | null; userRole: string | null; userID: string | null; allTeams: Team[]; } interface PaginatedResponse { data: LogEntry[]; total: number; page: number; page_size: number; total_pages: number; } interface PrefetchedLog { messages: any[]; response: any; } export default function SpendLogsTable({ accessToken, token, userRole, userID, allTeams, }: SpendLogsTableProps) { const [searchTerm, setSearchTerm] = useState(""); const [showFilters, setShowFilters] = useState(false); const [showColumnDropdown, setShowColumnDropdown] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [pageSize] = useState(50); const dropdownRef = useRef(null); const filtersRef = useRef(null); const quickSelectRef = useRef(null); // New state variables for Start and End Time const [startTime, setStartTime] = useState( moment().subtract(24, "hours").format("YYYY-MM-DDTHH:mm") ); const [endTime, setEndTime] = useState( moment().format("YYYY-MM-DDTHH:mm") ); // Add these new state variables at the top with other useState declarations const [isCustomDate, setIsCustomDate] = useState(false); const [quickSelectOpen, setQuickSelectOpen] = useState(false); const [tempTeamId, setTempTeamId] = useState(""); const [tempKeyHash, setTempKeyHash] = useState(""); const [selectedTeamId, setSelectedTeamId] = useState(""); const [selectedKeyHash, setSelectedKeyHash] = useState(""); const [selectedKeyInfo, setSelectedKeyInfo] = useState(null); const [selectedKeyIdInfoView, setSelectedKeyIdInfoView] = useState(null); const [selectedFilter, setSelectedFilter] = useState("Team ID"); const [filterByCurrentUser, setFilterByCurrentUser] = useState( userRole && internalUserRoles.includes(userRole) ); const [expandedRequestId, setExpandedRequestId] = useState(null); const [selectedSessionId, setSelectedSessionId] = useState(null); const queryClient = useQueryClient(); useEffect(() => { const fetchKeyInfo = async () => { if (selectedKeyIdInfoView && accessToken) { const keyData = await keyInfoV1Call(accessToken, selectedKeyIdInfoView); console.log("keyData", keyData); const keyResponse: KeyResponse = { ...keyData["info"], "token": selectedKeyIdInfoView, "api_key": selectedKeyIdInfoView, }; setSelectedKeyInfo(keyResponse); } }; fetchKeyInfo(); }, [selectedKeyIdInfoView, accessToken]); // Close dropdown when clicking outside useEffect(() => { function handleClickOutside(event: MouseEvent) { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setShowColumnDropdown(false); } if ( filtersRef.current && !filtersRef.current.contains(event.target as Node) ) { setShowFilters(false); } if ( quickSelectRef.current && !quickSelectRef.current.contains(event.target as Node) ) { setQuickSelectOpen(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); useEffect(() => { if (userRole && internalUserRoles.includes(userRole)) { setFilterByCurrentUser(true); } }, [userRole]); const logs = useQuery({ queryKey: [ "logs", "table", currentPage, pageSize, startTime, endTime, selectedTeamId, selectedKeyHash, filterByCurrentUser ? userID : null, ], queryFn: async () => { if (!accessToken || !token || !userRole || !userID) { console.log("Missing required auth parameters"); return { data: [], total: 0, page: 1, page_size: pageSize, total_pages: 0, }; } const formattedStartTime = moment(startTime).utc().format("YYYY-MM-DD HH:mm:ss"); const formattedEndTime = isCustomDate ? moment(endTime).utc().format("YYYY-MM-DD HH:mm:ss") : moment().utc().format("YYYY-MM-DD HH:mm:ss"); // Get base response from API const response = await uiSpendLogsCall( accessToken, selectedKeyHash || undefined, selectedTeamId || undefined, undefined, formattedStartTime, formattedEndTime, currentPage, pageSize, filterByCurrentUser ? userID : undefined ); // Trigger prefetch for all logs await prefetchLogDetails( response.data, formattedStartTime, accessToken, queryClient ); // Update logs with prefetched data if available response.data = response.data.map((log: LogEntry) => { const prefetchedData = queryClient.getQueryData( ["logDetails", log.request_id, formattedStartTime] ); if (prefetchedData?.messages && prefetchedData?.response) { log.messages = prefetchedData.messages; log.response = prefetchedData.response; return log; } return log; }); return response; }, enabled: !!accessToken && !!token && !!userRole && !!userID, refetchInterval: 5000, refetchIntervalInBackground: true, }); // Fetch logs for a session if selected const sessionLogs = useQuery({ queryKey: ["sessionLogs", selectedSessionId], queryFn: async () => { if (!accessToken || !selectedSessionId) return { data: [], total: 0, page: 1, page_size: 50, total_pages: 1 }; const response = await sessionSpendLogsCall(accessToken, selectedSessionId); // If the API returns an array, wrap it in the same shape as PaginatedResponse return { data: response.data || response || [], total: (response.data || response || []).length, page: 1, page_size: 1000, total_pages: 1, }; }, enabled: !!accessToken && !!selectedSessionId, }); // Add this effect to preserve expanded state when data refreshes useEffect(() => { if (logs.data?.data && expandedRequestId) { // Check if the expanded request ID still exists in the new data const stillExists = logs.data.data.some(log => log.request_id === expandedRequestId); if (!stillExists) { // If the request ID no longer exists in the data, clear the expanded state setExpandedRequestId(null); } } }, [logs.data?.data, expandedRequestId]); if (!accessToken || !token || !userRole || !userID) { console.log( "got None values for one of accessToken, token, userRole, userID", ); return null; } const filteredData = logs.data?.data?.filter((log) => { const matchesSearch = !searchTerm || log.request_id.includes(searchTerm) || log.model.includes(searchTerm) || (log.user && log.user.includes(searchTerm)); // No need for additional filtering since we're now handling this in the API call return matchesSearch; }).map(log => ({ ...log, onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash), onSessionClick: (sessionId: string) => { if (sessionId) setSelectedSessionId(sessionId); }, })) || []; // For session logs, add onKeyHashClick/onSessionClick as well const sessionData = sessionLogs.data?.data?.map(log => ({ ...log, onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash), onSessionClick: (sessionId: string) => {}, })) || []; // Add this function to handle manual refresh const handleRefresh = () => { logs.refetch(); }; // Add this function to format the time range display const getTimeRangeDisplay = () => { if (isCustomDate) { return `${moment(startTime).format('MMM D, h:mm A')} - ${moment(endTime).format('MMM D, h:mm A')}`; } const now = moment(); const start = moment(startTime); const diffMinutes = now.diff(start, 'minutes'); if (diffMinutes <= 15) return 'Last 15 Minutes'; if (diffMinutes <= 60) return 'Last Hour'; const diffHours = now.diff(start, 'hours'); if (diffHours <= 4) return 'Last 4 Hours'; if (diffHours <= 24) return 'Last 24 Hours'; if (diffHours <= 168) return 'Last 7 Days'; return `${start.format('MMM D')} - ${now.format('MMM D')}`; }; const handleRowExpand = (requestId: string | null) => { setExpandedRequestId(requestId); }; // When a session is selected, render the SessionView component if (selectedSessionId && sessionLogs.data) { return (
setSelectedSessionId(null)} />
); } return (

{selectedSessionId ? ( <> Session: {selectedSessionId} ) : ( "Request Logs" )}

{selectedKeyInfo && selectedKeyIdInfoView && selectedKeyInfo.api_key === selectedKeyIdInfoView ? ( setSelectedKeyIdInfoView(null)} /> ) : selectedSessionId ? (
true} // Optionally: add session-specific row expansion state />
) : ( <>
setSearchTerm(e.target.value)} />
{showFilters && (
Where
{showColumnDropdown && (
{["Team ID", "Key Hash"].map((option) => ( ))}
)}
{ if (selectedFilter === "Team ID") { setTempTeamId(e.target.value); } else { setTempKeyHash(e.target.value); } }} />
)}
{quickSelectOpen && (
{[ { label: "Last 15 Minutes", value: 15, unit: "minutes" }, { label: "Last Hour", value: 1, unit: "hours" }, { label: "Last 4 Hours", value: 4, unit: "hours" }, { label: "Last 24 Hours", value: 24, unit: "hours" }, { label: "Last 7 Days", value: 7, unit: "days" }, ].map((option) => ( ))}
)}
{isCustomDate && (
{ setStartTime(e.target.value); setCurrentPage(1); }} className="px-3 py-2 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
to
{ setEndTime(e.target.value); setCurrentPage(1); }} className="px-3 py-2 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
)}
Showing{" "} {logs.isLoading ? "..." : logs.data ? (currentPage - 1) * pageSize + 1 : 0}{" "} -{" "} {logs.isLoading ? "..." : logs.data ? Math.min(currentPage * pageSize, logs.data.total) : 0}{" "} of{" "} {logs.isLoading ? "..." : logs.data ? logs.data.total : 0}{" "} results
Page {logs.isLoading ? "..." : currentPage} of{" "} {logs.isLoading ? "..." : logs.data ? logs.data.total_pages : 1}
true} onRowExpand={handleRowExpand} expandedRequestId={expandedRequestId} />
)}
); } export function RequestViewer({ row }: { row: Row }) { // Helper function to clean metadata by removing specific fields const formatData = (input: any) => { if (typeof input === "string") { try { return JSON.parse(input); } catch { return input; } } return input; }; // New helper function to get raw request const getRawRequest = () => { // First check if proxy_server_request exists in metadata if (row.original?.proxy_server_request) { return formatData(row.original.proxy_server_request); } // Fall back to messages if proxy_server_request is empty return formatData(row.original.messages); }; // Extract error information from metadata if available const hasError = row.original.metadata?.status === "failure"; const errorInfo = hasError ? row.original.metadata?.error_information : null; // Check if request/response data is missing const hasMessages = row.original.messages && (Array.isArray(row.original.messages) ? row.original.messages.length > 0 : Object.keys(row.original.messages).length > 0); const hasResponse = row.original.response && Object.keys(formatData(row.original.response)).length > 0; const missingData = !hasMessages && !hasResponse; // Format the response with error details if present const formattedResponse = () => { if (hasError && errorInfo) { return { error: { message: errorInfo.error_message || "An error occurred", type: errorInfo.error_class || "error", code: errorInfo.error_code || "unknown", param: null } }; } return formatData(row.original.response); }; return (
{/* Combined Info Card */}

Request Details

Request ID: {row.original.request_id}
Model: {row.original.model}
Model ID: {row.original.model_id}
Provider: {row.original.custom_llm_provider || "-"}
API Base: {row.original.api_base || "-"}
Start Time: {row.original.startTime}
Tokens: {row.original.total_tokens} ({row.original.prompt_tokens}+{row.original.completion_tokens})
Cost: ${Number(row.original.spend || 0).toFixed(6)}
Cache Hit: {row.original.cache_hit}
{row?.original?.requester_ip_address && (
IP Address: {row?.original?.requester_ip_address}
)}
Status: {(row.original.metadata?.status || "Success").toLowerCase() !== "failure" ? "Success" : "Failure"}
End Time: {row.original.endTime}
{/* Configuration Info Message - Show when data is missing */} {/* Request/Response Panel */}
{/* Request Side */}

Request

{JSON.stringify(getRawRequest(), null, 2)}
{/* Response Side */}

Response {hasError && ( • HTTP code {errorInfo?.error_code || 400} )}

{hasResponse ? (
{JSON.stringify(formattedResponse(), null, 2)}
) : (
Response data not available
)}
{/* Error Card - Only show for failures */} {hasError && errorInfo && } {/* Tags Card - Only show if there are tags */} {row.original.request_tags && Object.keys(row.original.request_tags).length > 0 && (

Request Tags

{Object.entries(row.original.request_tags).map(([key, value]) => ( {key}: {String(value)} ))}
)} {/* Metadata Card - Only show if there's metadata */} {row.original.metadata && Object.keys(row.original.metadata).length > 0 && (

Metadata

              {JSON.stringify(row.original.metadata, null, 2)}
            
)}
); }