/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { useEffect, useMemo, useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { formatTimestampToDate } from '@/lib/format' import { Dialog } from '@/components/dialog' import { getAffinityUsageCache } from './api' function formatRate(hit: number, total: number): string { if (!total || total <= 0) return '-' const r = (hit / total) * 100 if (!Number.isFinite(r)) return '-' return `${r.toFixed(2)}%` } interface Props { open: boolean onOpenChange: (open: boolean) => void target: { rule_name: string using_group: string key_hint: string key_fp: string } | null } export function CacheStatsDialog(props: Props) { const { t } = useTranslation() const [loading, setLoading] = useState(false) const [stats, setStats] = useState | null>(null) const seqRef = useRef(0) useEffect(() => { if (!props.open || !props.target?.rule_name || !props.target?.key_fp) { // eslint-disable-next-line react-hooks/set-state-in-effect setStats(null) return } const seq = ++seqRef.current setLoading(true) setStats(null) getAffinityUsageCache(props.target) .then((res) => { if (seq !== seqRef.current) return if (res.success) setStats((res.data as Record) || {}) else toast.error(res.message || t('Request failed')) }) .catch(() => { if (seq !== seqRef.current) return toast.error(t('Request failed')) }) .finally(() => { if (seq !== seqRef.current) return setLoading(false) }) }, [props.open, props.target, t]) const rows = useMemo(() => { if (!stats) return [] const s = stats const data: { key: string; value: string | number }[] = [] const hit = Number(s.hit || 0) const total = Number(s.total || 0) if (s.rule_name || props.target?.rule_name) data.push({ key: t('Rule'), value: (s.rule_name || props.target?.rule_name || '') as string, }) if (s.using_group || props.target?.using_group) data.push({ key: t('Group'), value: (s.using_group || props.target?.using_group || '') as string, }) if (props.target?.key_hint) data.push({ key: t('Key Summary'), value: props.target.key_hint }) if (s.key_fp || props.target?.key_fp) data.push({ key: t('Key Fingerprint'), value: (s.key_fp || props.target?.key_fp || '') as string, }) if (Number(s.window_seconds || 0) > 0) data.push({ key: t('TTL (seconds)'), value: s.window_seconds as number }) if (total > 0) data.push({ key: t('Hit Rate'), value: `${hit}/${total} (${formatRate(hit, total)})`, }) if (Number(s.last_seen_at || 0) > 0) data.push({ key: t('Last Seen'), value: formatTimestampToDate(s.last_seen_at as number | undefined), }) const promptTokens = Number(s.prompt_tokens || 0) const cachedTokens = Number(s.cached_tokens || 0) const completionTokens = Number(s.completion_tokens || 0) const totalTokens = Number(s.total_tokens || 0) if (promptTokens > 0) data.push({ key: 'Prompt tokens', value: promptTokens }) if (cachedTokens > 0) data.push({ key: 'Cached tokens', value: cachedTokens }) if (completionTokens > 0) data.push({ key: 'Completion tokens', value: completionTokens }) if (totalTokens > 0) data.push({ key: 'Total tokens', value: totalTokens }) return data }, [stats, props.target, t]) return (

{t( 'Hit criteria: If cached tokens exist in usage, it counts as a hit.' )}

{loading ? (
{t('Loading...')}
) : rows.length > 0 ? (
{rows.map((row) => (
{row.key} {row.value}
))}
) : (
{t('No data available')}
)}
) }