- Miuix day/night dual theme CSS variables - themeStore: light default, follows prefers-color-scheme - Header: sun/moon toggle button - All components migrated to CSS variables - Cyrene avatar image replaces 'C' logo everywhere - Favicon added
85 lines
3.7 KiB
TypeScript
85 lines
3.7 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { getToken } from '../../api/client'
|
|
|
|
export default function LLMCallsPage() {
|
|
const [calls, setCalls] = useState<any[]>([])
|
|
const [stats, setStats] = useState<any>({})
|
|
|
|
useEffect(() => {
|
|
fetchLLM()
|
|
const interval = setInterval(fetchLLM, 5000)
|
|
return () => clearInterval(interval)
|
|
}, [])
|
|
|
|
const fetchLLM = async () => {
|
|
try {
|
|
const token = getToken()
|
|
const res = await fetch('/api/v1/admin/llm-calls', { headers: { Authorization: `Bearer ${token}` } })
|
|
const data = await res.json()
|
|
setCalls(data.calls || [])
|
|
setStats(data.stats || {})
|
|
} catch {}
|
|
}
|
|
|
|
return (
|
|
<div className="p-6 space-y-4">
|
|
<h2 className="text-lg font-semibold">LLM 调用日志</h2>
|
|
|
|
{/* Stats */}
|
|
<div className="grid grid-cols-4 gap-3">
|
|
<MiniStat label="总调用" value={stats.total_calls || 0} />
|
|
<MiniStat label="成功率" value={stats.total_calls ? `${Math.round(stats.success_calls/stats.total_calls*100)}%` : '-'} />
|
|
<MiniStat label="平均耗时" value={stats.avg_duration_ms ? `${stats.avg_duration_ms}ms` : '-'} />
|
|
<MiniStat label="总 Token" value={stats.total_tokens || 0} />
|
|
</div>
|
|
|
|
{/* Calls table */}
|
|
<div className="bg-[var(--mx-surface)] rounded-[18px] border border-[var(--mx-outline)] overflow-hidden">
|
|
{calls.length === 0 ? (
|
|
<div className="p-8 text-center text-[var(--mx-text-dim)] text-sm">暂无 LLM 调用记录</div>
|
|
) : (
|
|
<table className="w-full text-sm">
|
|
<thead>
|
|
<tr className="border-b border-[var(--mx-divider)] text-[var(--mx-text-dim)] text-xs">
|
|
<th className="text-left px-4 py-2 font-normal">时间</th>
|
|
<th className="text-left px-4 py-2 font-normal">模型</th>
|
|
<th className="text-left px-4 py-2 font-normal">用途</th>
|
|
<th className="text-right px-4 py-2 font-normal">耗时</th>
|
|
<th className="text-right px-4 py-2 font-normal">Token</th>
|
|
<th className="text-center px-4 py-2 font-normal">状态</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{calls.map((c, i) => (
|
|
<tr key={c.id || i} className="border-b border-[var(--mx-divider)]/50">
|
|
<td className="px-4 py-2.5 text-[var(--mx-text-dim)] text-xs font-mono">{c.time?.slice(11,19) || '-'}</td>
|
|
<td className="px-4 py-2.5 text-[var(--mx-text)]">{c.model || '-'}</td>
|
|
<td className="px-4 py-2.5 text-[var(--mx-text-dim)] text-xs">
|
|
<span className="px-1.5 py-0.5 rounded bg-[var(--mx-badge-blue)]/50 text-[var(--mx-tertiary-text)]">{c.purpose || '-'}</span>
|
|
</td>
|
|
<td className="px-4 py-2.5 text-[var(--mx-text-dim)] text-xs text-right">{c.duration_ms}ms</td>
|
|
<td className="px-4 py-2.5 text-[var(--mx-text-dim)] text-xs text-right">{(c.prompt_tokens||0)+(c.completion_tokens||0)}</td>
|
|
<td className="px-4 py-2.5 text-center">
|
|
<span className={`text-xs ${c.success ? 'text-[var(--mx-tertiary-text)]' : 'text-[var(--mx-error)]'}`}>
|
|
{c.success ? '✓' : '✗'}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function MiniStat({ label, value }: { label:string; value:string|number }) {
|
|
return (
|
|
<div className="bg-[var(--mx-surface)] border border-[var(--mx-outline)] rounded-[18px] px-4 py-3">
|
|
<p className="text-xs text-[var(--mx-text-dim)] mb-1">{label}</p>
|
|
<p className="text-lg font-bold text-[var(--mx-text)]">{value}</p>
|
|
</div>
|
|
)
|
|
}
|