diff --git a/open-sse/handlers/chatCore.js b/open-sse/handlers/chatCore.js index 38cf1490..21255998 100644 --- a/open-sse/handlers/chatCore.js +++ b/open-sse/handlers/chatCore.js @@ -538,6 +538,7 @@ export async function handleChatCore({ provider, model, connectionId, + apiKey, latency: { ttft: 0, total: Date.now() - requestStartTime }, tokens: { prompt_tokens: 0, completion_tokens: 0 }, request: extractRequestConfig(body, stream), @@ -676,6 +677,7 @@ export async function handleChatCore({ provider, model, connectionId, + apiKey, latency: { ttft: 0, total: Date.now() - requestStartTime }, tokens: { prompt_tokens: 0, completion_tokens: 0 }, request: extractRequestConfig(body, stream), diff --git a/open-sse/handlers/chatCore/nonStreamingHandler.js b/open-sse/handlers/chatCore/nonStreamingHandler.js index f93058fd..d1f4a845 100644 --- a/open-sse/handlers/chatCore/nonStreamingHandler.js +++ b/open-sse/handlers/chatCore/nonStreamingHandler.js @@ -286,7 +286,7 @@ export async function handleNonStreamingResponse({ providerResponse, provider, m const totalLatency = Date.now() - requestStartTime; saveRequestDetail(buildRequestDetail({ - provider, model, connectionId, + provider, model, connectionId, apiKey, latency: { ttft: totalLatency, total: totalLatency }, tokens: usage || { prompt_tokens: 0, completion_tokens: 0 }, request: extractRequestConfig(body, stream), diff --git a/open-sse/handlers/chatCore/requestDetail.js b/open-sse/handlers/chatCore/requestDetail.js index b3d24115..de1b3cad 100644 --- a/open-sse/handlers/chatCore/requestDetail.js +++ b/open-sse/handlers/chatCore/requestDetail.js @@ -57,11 +57,21 @@ export function extractUsageFromResponse(responseBody) { return null; } +// Mask API keys before they reach the requestDetails data blob / DB column. +// Only the prefix is kept — enough to distinguish keys without leaking them. +export function maskApiKey(key) { + if (!key || typeof key !== "string") return undefined; + const trimmed = key.trim(); + if (trimmed.length <= 8) return trimmed.charAt(0) + "***"; + return trimmed.slice(0, 8) + "***"; +} + export function buildRequestDetail(base, overrides = {}) { return { provider: base.provider || "unknown", model: base.model || "unknown", connectionId: base.connectionId || undefined, + apiKey: maskApiKey(base.apiKey), timestamp: new Date().toISOString(), latency: base.latency || { ttft: 0, total: 0 }, tokens: base.tokens || { prompt_tokens: 0, completion_tokens: 0 }, diff --git a/open-sse/handlers/chatCore/streamingHandler.js b/open-sse/handlers/chatCore/streamingHandler.js index 15fdfd2f..d37582bd 100644 --- a/open-sse/handlers/chatCore/streamingHandler.js +++ b/open-sse/handlers/chatCore/streamingHandler.js @@ -230,6 +230,7 @@ export async function handleStreamingResponse({ provider, model, connectionId, + apiKey, latency: { ttft: 0, total: Date.now() - requestStartTime }, tokens: { prompt_tokens: 0, completion_tokens: 0 }, request: extractRequestConfig(body, stream), @@ -293,6 +294,7 @@ export function buildOnStreamComplete({ provider, model, connectionId, + apiKey, latency, tokens: usage || { prompt_tokens: 0, completion_tokens: 0 }, request: extractRequestConfig(body, stream), diff --git a/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js b/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js index af8eb736..d3637df5 100644 --- a/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js +++ b/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js @@ -114,9 +114,19 @@ export default function RequestDetailsTab() { const [providerNameCache, setProviderNameCache] = useState(null); const [filters, setFilters] = useState({ provider: "", + model: "", + status: "", + connectionId: "", + apiKey: "", startDate: "", endDate: "" }); + const [filterOptions, setFilterOptions] = useState({ + models: [], + apiKeys: [], + statuses: [], + connections: [] + }); const fetchProviders = useCallback(async () => { try { @@ -131,6 +141,21 @@ export default function RequestDetailsTab() { } }, []); + const fetchFilterOptions = useCallback(async () => { + try { + const res = await fetch("/api/usage/filters"); + const data = await res.json(); + setFilterOptions({ + models: data.models || [], + apiKeys: data.apiKeys || [], + statuses: data.statuses || [], + connections: data.connections || [] + }); + } catch (error) { + console.error("Failed to fetch filter options:", error); + } + }, []); + const fetchDetails = useCallback(async () => { setLoading(true); try { @@ -139,6 +164,10 @@ export default function RequestDetailsTab() { pageSize: pagination.pageSize.toString() }); if (filters.provider) params.append("provider", filters.provider); + if (filters.model) params.append("model", filters.model); + if (filters.status) params.append("status", filters.status); + if (filters.connectionId) params.append("connectionId", filters.connectionId); + if (filters.apiKey) params.append("apiKey", filters.apiKey); if (filters.startDate) params.append("startDate", filters.startDate); if (filters.endDate) params.append("endDate", filters.endDate); @@ -156,7 +185,8 @@ export default function RequestDetailsTab() { useEffect(() => { fetchProviders(); - }, [fetchProviders]); + fetchFilterOptions(); + }, [fetchProviders, fetchFilterOptions]); useEffect(() => { fetchDetails(); @@ -176,7 +206,7 @@ export default function RequestDetailsTab() { }; const handleClearFilters = () => { - setFilters({ provider: "", startDate: "", endDate: "" }); + setFilters({ provider: "", model: "", status: "", connectionId: "", apiKey: "", startDate: "", endDate: "" }); }; return ( @@ -204,6 +234,98 @@ export default function RequestDetailsTab() { ))} + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
@@ -238,7 +360,7 @@ export default function RequestDetailsTab() {