import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { useRequestLogs, usageKeys } from "@/lib/query/usage"; import { useQueryClient } from "@tanstack/react-query"; import type { LogFilters } from "@/types/usage"; import { ChevronLeft, ChevronRight, RefreshCw, Search, X } from "lucide-react"; export function RequestLogTable() { const { t, i18n } = useTranslation(); const queryClient = useQueryClient(); // 默认时间范围:过去24小时 const getDefaultFilters = (): LogFilters => { const now = Math.floor(Date.now() / 1000); const oneDayAgo = now - 24 * 60 * 60; return { startDate: oneDayAgo, endDate: now }; }; const [filters, setFilters] = useState(getDefaultFilters); const [tempFilters, setTempFilters] = useState(getDefaultFilters); const [page, setPage] = useState(0); const pageSize = 20; const { data: result, isLoading } = useRequestLogs(filters, page, pageSize); const logs = result?.data ?? []; const total = result?.total ?? 0; const totalPages = Math.ceil(total / pageSize); const handleSearch = () => { setFilters(tempFilters); setPage(0); }; const handleReset = () => { const defaults = getDefaultFilters(); setTempFilters(defaults); setFilters(defaults); setPage(0); }; const handleRefresh = () => { queryClient.invalidateQueries({ queryKey: usageKeys.logs(filters, page, pageSize), }); }; const dateLocale = i18n.language === "zh" ? "zh-CN" : i18n.language === "ja" ? "ja-JP" : "en-US"; return (
{/* 筛选栏 */}
setTempFilters({ ...tempFilters, providerName: e.target.value || undefined, }) } />
setTempFilters({ ...tempFilters, model: e.target.value || undefined, }) } />
{t("usage.timeRange")}: setTempFilters({ ...tempFilters, startDate: e.target.value ? Math.floor(new Date(e.target.value).getTime() / 1000) : undefined, }) } /> - setTempFilters({ ...tempFilters, endDate: e.target.value ? Math.floor(new Date(e.target.value).getTime() / 1000) : undefined, }) } />
{isLoading ? (
) : ( <>
{t("usage.time")} {t("usage.provider")} {t("usage.billingModel")} {t("usage.inputTokens")} {t("usage.outputTokens")} {t("usage.cacheReadTokens")} {t("usage.cacheCreationTokens")} {t("usage.totalCost")} {t("usage.timingInfo")} {t("usage.status")} {logs.length === 0 ? ( {t("usage.noData")} ) : ( logs.map((log) => ( {new Date(log.createdAt * 1000).toLocaleString( dateLocale, )} {log.providerName || t("usage.unknownProvider")} {log.model} {log.inputTokens.toLocaleString()} {log.outputTokens.toLocaleString()} {log.cacheReadTokens.toLocaleString()} {log.cacheCreationTokens.toLocaleString()} ${parseFloat(log.totalCostUsd).toFixed(6)}
{(() => { const durationSec = (log.durationMs ?? log.latencyMs) / 1000; const durationColor = durationSec <= 5 ? "bg-green-100 text-green-800" : durationSec <= 120 ? "bg-orange-100 text-orange-800" : "bg-red-200 text-red-900"; return ( {Math.round(durationSec)}s ); })()} {log.isStreaming && log.firstTokenMs != null && (() => { const firstSec = log.firstTokenMs / 1000; const firstColor = firstSec <= 5 ? "bg-green-100 text-green-800" : firstSec <= 120 ? "bg-orange-100 text-orange-800" : "bg-red-200 text-red-900"; return ( {firstSec.toFixed(1)}s ); })()} {log.isStreaming ? t("usage.stream") : t("usage.nonStream")}
= 200 && log.statusCode < 300 ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800" }`} > {log.statusCode}
)) )}
{/* 分页控件 */} {total > 0 && (
{t("usage.totalRecords", { total })}
{/* 页码按钮 */} {(() => { const pages: (number | string)[] = []; if (totalPages <= 7) { for (let i = 0; i < totalPages; i++) pages.push(i); } else { pages.push(0); if (page > 2) pages.push("..."); for ( let i = Math.max(1, page - 1); i <= Math.min(totalPages - 2, page + 1); i++ ) { pages.push(i); } if (page < totalPages - 3) pages.push("..."); pages.push(totalPages - 1); } return pages.map((p, idx) => typeof p === "string" ? ( ... ) : ( ), ); })()}
)} )}
); }