"use client"; import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import Card from "./Card"; import RequestLoggerDetail from "./RequestLoggerDetail"; import { PROTOCOL_COLORS, PROVIDER_COLORS, getHttpStatusStyle as getStatusStyle, } from "@/shared/constants/colors"; import { formatTime, formatDuration, maskSegment, maskAccount, formatApiKeyLabel, } from "@/shared/utils/formatting"; // Quick filter categories - status-based only (providers are dynamic from data) const STATUS_FILTERS = [ { key: "all", label: "All" }, { key: "error", label: "Errors", icon: "error" }, { key: "ok", label: "Success", icon: "check_circle" }, { key: "combo", label: "Combo", icon: "hub" }, ]; // Column definitions for visibility toggles const COLUMNS = [ { key: "status", label: "Status" }, { key: "model", label: "Model" }, { key: "provider", label: "Provider" }, { key: "protocol", label: "Protocol" }, { key: "account", label: "Account" }, { key: "apiKey", label: "API Key" }, { key: "combo", label: "Combo" }, { key: "tokens", label: "Tokens" }, { key: "duration", label: "Duration" }, { key: "time", label: "Time" }, ]; const DEFAULT_VISIBLE = Object.fromEntries(COLUMNS.map((c) => [c.key, true])); function getLogTotalTokens(log) { return (log?.tokens?.in || 0) + (log?.tokens?.out || 0); } export default function RequestLoggerV2() { const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [recording, setRecording] = useState(true); const [search, setSearch] = useState(""); const [activeFilter, setActiveFilter] = useState("all"); const [selectedModel, setSelectedModel] = useState(""); const [selectedAccount, setSelectedAccount] = useState(""); const [selectedProvider, setSelectedProvider] = useState(""); const [selectedApiKey, setSelectedApiKey] = useState(""); const [sortBy, setSortBy] = useState("newest"); const [selectedLog, setSelectedLog] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [detailData, setDetailData] = useState(null); const intervalRef = useRef(null); const hasLoadedRef = useRef(false); // Column visibility with localStorage persistence const [visibleColumns, setVisibleColumns] = useState(() => { if (typeof window === "undefined") return DEFAULT_VISIBLE; try { const saved = localStorage.getItem("loggerVisibleColumns"); return saved ? { ...DEFAULT_VISIBLE, ...JSON.parse(saved) } : DEFAULT_VISIBLE; } catch { return DEFAULT_VISIBLE; } }); const toggleColumn = useCallback((key) => { setVisibleColumns((prev) => { const next = { ...prev, [key]: !prev[key] }; try { localStorage.setItem("loggerVisibleColumns", JSON.stringify(next)); } catch {} return next; }); }, []); const fetchLogs = useCallback( async (showLoading = false) => { if (showLoading) setLoading(true); try { const params = new URLSearchParams(); if (search) params.set("search", search); if (activeFilter === "error") params.set("status", "error"); if (activeFilter === "ok") params.set("status", "ok"); if (activeFilter === "combo") params.set("combo", "1"); if (selectedModel) params.set("model", selectedModel); if (selectedProvider) params.set("provider", selectedProvider); if (selectedAccount) params.set("account", selectedAccount); if (selectedApiKey) params.set("apiKey", selectedApiKey); params.set("limit", "300"); const res = await fetch(`/api/usage/call-logs?${params}`); if (res.ok) { const data = await res.json(); setLogs(data); } } catch (error) { console.error("Failed to fetch call logs:", error); } finally { if (showLoading) setLoading(false); } }, [search, activeFilter, selectedModel, selectedAccount, selectedProvider, selectedApiKey] ); useEffect(() => { const showLoading = !hasLoadedRef.current; hasLoadedRef.current = true; fetchLogs(showLoading); }, [fetchLogs]); // Auto-refresh useEffect(() => { if (intervalRef.current) clearInterval(intervalRef.current); if (recording) { intervalRef.current = setInterval(() => fetchLogs(false), 3000); } return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; }, [recording, fetchLogs]); const filteredLogs = useMemo(() => { if (activeFilter === "combo") return logs.filter((l) => l.comboName); return logs; }, [activeFilter, logs]); const sortedLogs = useMemo(() => { const arr = [...filteredLogs]; arr.sort((a, b) => { switch (sortBy) { case "oldest": return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(); case "tokens_desc": return getLogTotalTokens(b) - getLogTotalTokens(a); case "tokens_asc": return getLogTotalTokens(a) - getLogTotalTokens(b); case "duration_desc": return (b.duration || 0) - (a.duration || 0); case "duration_asc": return (a.duration || 0) - (b.duration || 0); case "status_desc": return (b.status || 0) - (a.status || 0); case "status_asc": return (a.status || 0) - (b.status || 0); case "model_asc": return (a.model || "").localeCompare(b.model || ""); case "model_desc": return (b.model || "").localeCompare(a.model || ""); case "newest": default: return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(); } }); return arr; }, [filteredLogs, sortBy]); // Fetch log detail const openDetail = async (logEntry) => { setSelectedLog(logEntry); setDetailLoading(true); setDetailData(null); try { const res = await fetch(`/api/usage/call-logs/${logEntry.id}`); if (res.ok) { const data = await res.json(); setDetailData(data); } } catch (error) { console.error("Failed to fetch log detail:", error); } finally { setDetailLoading(false); } }; const closeDetail = () => { setSelectedLog(null); setDetailData(null); }; // Copy to clipboard const copyToClipboard = async (text) => { try { await navigator.clipboard.writeText(text); return true; } catch { // Fallback for non-HTTPS or older browsers try { const textarea = document.createElement("textarea"); textarea.value = text; textarea.style.position = "fixed"; textarea.style.left = "-9999px"; document.body.appendChild(textarea); textarea.select(); document.execCommand("copy"); document.body.removeChild(textarea); return true; } catch { return false; } } }; // Unique accounts and providers for dropdowns const uniqueAccounts = [...new Set(logs.map((l) => l.account).filter((a) => a && a !== "-"))]; const uniqueModels = [...new Set(logs.map((l) => l.model).filter(Boolean))].sort(); const uniqueProviders = [ ...new Set(logs.map((l) => l.provider).filter((p) => p && p !== "-")), ].sort(); const uniqueApiKeys = [ ...new Set(logs.map((l) => l.apiKeyId || l.apiKeyName).filter(Boolean)), ].sort(); // Stats const totalCount = filteredLogs.length; const okCount = filteredLogs.filter((l) => l.status >= 200 && l.status < 300).length; const errorCount = filteredLogs.filter((l) => l.status >= 400).length; const comboCount = logs.filter((l) => l.comboName).length; const apiKeyCount = uniqueApiKeys.length; return (
| Status | )} {visibleColumns.model && (Model | )} {visibleColumns.provider && (Provider | )} {visibleColumns.protocol && (Protocol | )} {visibleColumns.account && (Account | )} {visibleColumns.apiKey && (API Key | )} {visibleColumns.combo && (Combo | )} {visibleColumns.tokens && (Tokens | )} {visibleColumns.duration && (Duration | )} {visibleColumns.time && (Time | )}
|---|---|---|---|---|---|---|---|---|---|
| {log.status || "..."} | )} {visibleColumns.model && ({log.model} | )} {visibleColumns.provider && ({providerColor.label} | )} {visibleColumns.protocol && ({protocol.label} | )} {visibleColumns.account && ({maskAccount(log.account)} | )} {visibleColumns.apiKey && ({formatApiKeyLabel(log.apiKeyName, log.apiKeyId)} | )} {visibleColumns.combo && ({log.comboName ? ( {log.comboName} ) : ( — )} | )} {visibleColumns.tokens && (I:{" "} {log.tokens?.in?.toLocaleString() || 0} | O:{" "} {log.tokens?.out?.toLocaleString() || 0} | )} {visibleColumns.duration && ({formatDuration(log.duration)} | )} {visibleColumns.time && ({formatTime(log.timestamp)} | )}
{`{DATA_DIR}/call_logs/`} with 7-day
rotation.