Spaces:
Paused
Paused
| import type { ColumnDef } from "@tanstack/react-table"; | |
| import { getCountryFromIP } from "./ip_lookup"; | |
| import moment from "moment"; | |
| import React from "react"; | |
| import { CountryCell } from "./country_cell"; | |
| import { getProviderLogoAndName } from "../provider_info_helpers"; | |
| import { Tooltip } from "antd"; | |
| import { TimeCell } from "./time_cell"; | |
| import { Button } from "@tremor/react"; | |
| // Helper to get the appropriate logo URL | |
| const getLogoUrl = ( | |
| row: LogEntry, | |
| provider: string | |
| ) => { | |
| // Check if mcp_tool_call_metadata exists and contains mcp_server_logo_url | |
| if (row.metadata?.mcp_tool_call_metadata?.mcp_server_logo_url) { | |
| return row.metadata.mcp_tool_call_metadata.mcp_server_logo_url; | |
| } | |
| // Fall back to default provider logo | |
| return provider ? getProviderLogoAndName(provider).logo : ''; | |
| }; | |
| export type LogEntry = { | |
| request_id: string; | |
| api_key: string; | |
| team_id: string; | |
| model: string; | |
| model_id: string; | |
| api_base?: string; | |
| call_type: string; | |
| spend: number; | |
| total_tokens: number; | |
| prompt_tokens: number; | |
| completion_tokens: number; | |
| startTime: string; | |
| endTime: string; | |
| user?: string; | |
| end_user?: string; | |
| custom_llm_provider?: string; | |
| metadata?: Record<string, any>; | |
| cache_hit: string; | |
| cache_key?: string; | |
| request_tags?: Record<string, any>; | |
| requester_ip_address?: string; | |
| messages: string | any[] | Record<string, any>; | |
| response: string | any[] | Record<string, any>; | |
| proxy_server_request?: string | any[] | Record<string, any>; | |
| session_id?: string; | |
| onKeyHashClick?: (keyHash: string) => void; | |
| onSessionClick?: (sessionId: string) => void; | |
| }; | |
| export const columns: ColumnDef<LogEntry>[] = [ | |
| { | |
| id: "expander", | |
| header: () => null, | |
| cell: ({ row }) => { | |
| // Convert the cell function to a React component to properly use hooks | |
| const ExpanderCell = () => { | |
| const [localExpanded, setLocalExpanded] = React.useState(row.getIsExpanded()); | |
| // Memoize the toggle handler to prevent unnecessary re-renders | |
| const toggleHandler = React.useCallback(() => { | |
| setLocalExpanded((prev) => !prev); | |
| row.getToggleExpandedHandler()(); | |
| }, [row]); | |
| return row.getCanExpand() ? ( | |
| <button | |
| onClick={toggleHandler} | |
| style={{ cursor: "pointer" }} | |
| aria-label={localExpanded ? "Collapse row" : "Expand row"} | |
| className="w-6 h-6 flex items-center justify-center focus:outline-none" | |
| > | |
| <svg | |
| className={`w-4 h-4 transform transition-transform duration-75 ${ | |
| localExpanded ? 'rotate-90' : '' | |
| }`} | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| xmlns="http://www.w3.org/2000/svg" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M9 5l7 7-7 7" | |
| /> | |
| </svg> | |
| </button> | |
| ) : ( | |
| <span className="w-6 h-6 flex items-center justify-center">●</span> | |
| ); | |
| }; | |
| // Return the component | |
| return <ExpanderCell />; | |
| }, | |
| }, | |
| { | |
| header: "Time", | |
| accessorKey: "startTime", | |
| cell: (info: any) => <TimeCell utcTime={info.getValue()} />, | |
| }, | |
| { | |
| header: "Status", | |
| accessorKey: "metadata.status", | |
| cell: (info: any) => { | |
| const status = info.getValue() || "Success"; | |
| const isSuccess = status.toLowerCase() !== "failure"; | |
| return ( | |
| <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> | |
| ); | |
| }, | |
| }, | |
| { | |
| header: "Session ID", | |
| accessorKey: "session_id", | |
| cell: (info: any) => { | |
| const value = String(info.getValue() || ""); | |
| const onSessionClick = info.row.original.onSessionClick; | |
| return ( | |
| <Tooltip title={String(info.getValue() || "")}> | |
| <Button | |
| size="xs" | |
| variant="light" | |
| className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal text-xs max-w-[15ch] truncate block" | |
| onClick={() => onSessionClick?.(value)} | |
| > | |
| {String(info.getValue() || "")} | |
| </Button> | |
| </Tooltip> | |
| ); | |
| }, | |
| }, | |
| { | |
| header: "Request ID", | |
| accessorKey: "request_id", | |
| cell: (info: any) => ( | |
| <Tooltip title={String(info.getValue() || "")}> | |
| <span className="font-mono text-xs max-w-[15ch] truncate block"> | |
| {String(info.getValue() || "")} | |
| </span> | |
| </Tooltip> | |
| ), | |
| }, | |
| { | |
| header: "Cost", | |
| accessorKey: "spend", | |
| cell: (info: any) => ( | |
| <span>${Number(info.getValue() || 0).toFixed(6)}</span> | |
| ), | |
| }, | |
| { | |
| header: "Country", | |
| accessorKey: "requester_ip_address", | |
| cell: (info: any) => <CountryCell ipAddress={info.getValue()} />, | |
| }, | |
| { | |
| header: "Team Name", | |
| accessorKey: "metadata.user_api_key_team_alias", | |
| cell: (info: any) => ( | |
| <Tooltip title={String(info.getValue() || "-")}> | |
| <span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span> | |
| </Tooltip> | |
| ), | |
| }, | |
| { | |
| header: "Key Hash", | |
| accessorKey: "metadata.user_api_key", | |
| cell: (info: any) => { | |
| const value = String(info.getValue() || "-"); | |
| const onKeyHashClick = info.row.original.onKeyHashClick; | |
| return ( | |
| <Tooltip title={value}> | |
| <span | |
| className="font-mono max-w-[15ch] truncate block cursor-pointer hover:text-blue-600" | |
| onClick={() => onKeyHashClick?.(value)} | |
| > | |
| {value} | |
| </span> | |
| </Tooltip> | |
| ); | |
| }, | |
| }, | |
| { | |
| header: "Key Name", | |
| accessorKey: "metadata.user_api_key_alias", | |
| cell: (info: any) => ( | |
| <Tooltip title={String(info.getValue() || "-")}> | |
| <span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span> | |
| </Tooltip> | |
| ), | |
| }, | |
| { | |
| header: "Model", | |
| accessorKey: "model", | |
| cell: (info: any) => { | |
| const row = info.row.original; | |
| const provider = row.custom_llm_provider; | |
| const modelName = String(info.getValue() || ""); | |
| return ( | |
| <div className="flex items-center space-x-2"> | |
| {provider && ( | |
| <img | |
| src={getLogoUrl(row, provider)} | |
| alt="" | |
| className="w-4 h-4" | |
| onError={(e) => { | |
| const target = e.target as HTMLImageElement; | |
| target.style.display = 'none'; | |
| }} | |
| /> | |
| )} | |
| <Tooltip title={modelName}> | |
| <span className="max-w-[15ch] truncate block"> | |
| {modelName} | |
| </span> | |
| </Tooltip> | |
| </div> | |
| ); | |
| }, | |
| }, | |
| { | |
| header: "Tokens", | |
| accessorKey: "total_tokens", | |
| cell: (info: any) => { | |
| const row = info.row.original; | |
| return ( | |
| <span className="text-sm"> | |
| {String(row.total_tokens || "0")} | |
| <span className="text-gray-400 text-xs ml-1"> | |
| ({String(row.prompt_tokens || "0")}+ | |
| {String(row.completion_tokens || "0")}) | |
| </span> | |
| </span> | |
| ); | |
| }, | |
| }, | |
| { | |
| header: "Internal User", | |
| accessorKey: "user", | |
| cell: (info: any) => ( | |
| <Tooltip title={String(info.getValue() || "-")}> | |
| <span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span> | |
| </Tooltip> | |
| ), | |
| }, | |
| { | |
| header: "End User", | |
| accessorKey: "end_user", | |
| cell: (info: any) => ( | |
| <Tooltip title={String(info.getValue() || "-")}> | |
| <span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span> | |
| </Tooltip> | |
| ), | |
| }, | |
| { | |
| header: "Tags", | |
| accessorKey: "request_tags", | |
| cell: (info: any) => { | |
| const tags = info.getValue(); | |
| if (!tags || Object.keys(tags).length === 0) return "-"; | |
| const tagEntries = Object.entries(tags); | |
| const firstTag = tagEntries[0]; | |
| const remainingTags = tagEntries.slice(1); | |
| return ( | |
| <div className="flex flex-wrap gap-1"> | |
| <Tooltip | |
| title={ | |
| <div className="flex flex-col gap-1"> | |
| {tagEntries.map(([key, value]) => ( | |
| <span key={key}> | |
| {key}: {String(value)} | |
| </span> | |
| ))} | |
| </div> | |
| } | |
| > | |
| <span className="px-2 py-1 bg-gray-100 rounded-full text-xs"> | |
| {firstTag[0]}: {String(firstTag[1])} | |
| {remainingTags.length > 0 && ` +${remainingTags.length}`} | |
| </span> | |
| </Tooltip> | |
| </div> | |
| ); | |
| }, | |
| }, | |
| ]; | |
| const formatMessage = (message: any): string => { | |
| if (!message) return "N/A"; | |
| if (typeof message === "string") return message; | |
| if (typeof message === "object") { | |
| // Handle the {text, type} object specifically | |
| if (message.text) return message.text; | |
| if (message.content) return message.content; | |
| return JSON.stringify(message); | |
| } | |
| return String(message); | |
| }; | |
| // Add this new component for displaying request/response with copy buttons | |
| export const RequestResponsePanel = ({ request, response }: { request: any; response: any }) => { | |
| const requestStr = typeof request === 'object' ? JSON.stringify(request, null, 2) : String(request || '{}'); | |
| const responseStr = typeof response === 'object' ? JSON.stringify(response, null, 2) : String(response || '{}'); | |
| const copyToClipboard = (text: string) => { | |
| navigator.clipboard.writeText(text); | |
| }; | |
| return ( | |
| <div className="grid grid-cols-2 gap-4 mt-4"> | |
| <div className="rounded-lg border border-gray-200 bg-gray-50"> | |
| <div className="flex justify-between items-center p-3 border-b border-gray-200"> | |
| <h3 className="text-sm font-medium">Request</h3> | |
| <button | |
| onClick={() => copyToClipboard(requestStr)} | |
| className="p-1 hover:bg-gray-200 rounded" | |
| title="Copy request" | |
| > | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> | |
| <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| <pre className="p-4 overflow-auto text-xs font-mono h-64 whitespace-pre-wrap break-words">{requestStr}</pre> | |
| </div> | |
| <div className="rounded-lg border border-gray-200 bg-gray-50"> | |
| <div className="flex justify-between items-center p-3 border-b border-gray-200"> | |
| <h3 className="text-sm font-medium">Response</h3> | |
| <button | |
| onClick={() => copyToClipboard(responseStr)} | |
| className="p-1 hover:bg-gray-200 rounded" | |
| title="Copy response" | |
| > | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> | |
| <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| <pre className="p-4 overflow-auto text-xs font-mono h-64 whitespace-pre-wrap break-words">{responseStr}</pre> | |
| </div> | |
| </div> | |
| ); | |
| }; | |