feat(agent-office): add TokenTab with usage stats and cache hit rate
This commit is contained in:
86
src/pages/agent-office/components/TokenTab.jsx
Normal file
86
src/pages/agent-office/components/TokenTab.jsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// src/pages/agent-office/components/TokenTab.jsx
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { getAgentTokenUsage } from '../../../api';
|
||||||
|
|
||||||
|
export default function TokenTab({ agentId }) {
|
||||||
|
const [usage, setUsage] = useState(null);
|
||||||
|
const [days, setDays] = useState(1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
getAgentTokenUsage(agentId, days).then(data => {
|
||||||
|
if (!cancelled) setUsage(data);
|
||||||
|
});
|
||||||
|
return () => { cancelled = true; };
|
||||||
|
}, [agentId, days]);
|
||||||
|
|
||||||
|
if (!usage) return <div className="ao-empty">Loading...</div>;
|
||||||
|
|
||||||
|
const inputTokens = usage.input_tokens || 0;
|
||||||
|
const outputTokens = usage.output_tokens || 0;
|
||||||
|
const cacheRead = usage.cache_read || 0;
|
||||||
|
const cacheWrite = usage.cache_write || 0;
|
||||||
|
const total = inputTokens + outputTokens;
|
||||||
|
const cacheHitRate = inputTokens > 0 ? Math.round((cacheRead / inputTokens) * 100) : 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ao-token-tab">
|
||||||
|
<div className="ao-token-period">
|
||||||
|
{[1, 7, 30].map(d => (
|
||||||
|
<button
|
||||||
|
key={d}
|
||||||
|
className={`ao-btn-period ${days === d ? 'active' : ''}`}
|
||||||
|
onClick={() => setDays(d)}
|
||||||
|
>
|
||||||
|
{d === 1 ? 'Today' : d === 7 ? '7 Days' : '30 Days'}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ao-token-grid">
|
||||||
|
<div className="ao-token-card">
|
||||||
|
<div className="ao-token-label">Input Tokens</div>
|
||||||
|
<div className="ao-token-value">{inputTokens.toLocaleString()}</div>
|
||||||
|
</div>
|
||||||
|
<div className="ao-token-card">
|
||||||
|
<div className="ao-token-label">Output Tokens</div>
|
||||||
|
<div className="ao-token-value">{outputTokens.toLocaleString()}</div>
|
||||||
|
</div>
|
||||||
|
<div className="ao-token-card">
|
||||||
|
<div className="ao-token-label">Total</div>
|
||||||
|
<div className="ao-token-value">{total.toLocaleString()}</div>
|
||||||
|
</div>
|
||||||
|
<div className="ao-token-card">
|
||||||
|
<div className="ao-token-label">Cache Hit Rate</div>
|
||||||
|
<div className="ao-token-value">{cacheHitRate}%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Simple bar chart */}
|
||||||
|
<div className="ao-token-bar">
|
||||||
|
<div className="ao-token-bar-label">Input vs Output</div>
|
||||||
|
<div className="ao-token-bar-track">
|
||||||
|
<div
|
||||||
|
className="ao-token-bar-fill input"
|
||||||
|
style={{ width: total > 0 ? `${(inputTokens / total) * 100}%` : '0%' }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="ao-token-bar-fill output"
|
||||||
|
style={{ width: total > 0 ? `${(outputTokens / total) * 100}%` : '0%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ao-token-bar-legend">
|
||||||
|
<span><span className="dot input" />Input</span>
|
||||||
|
<span><span className="dot output" />Output</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{cacheRead > 0 && (
|
||||||
|
<div className="ao-token-detail">
|
||||||
|
<span>Cache Read: {cacheRead.toLocaleString()}</span>
|
||||||
|
<span>Cache Write: {cacheWrite.toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user