mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-24 05:41:40 +00:00
feat(dashboard): hide token/cost analytics behind config flag (default off) (#25438)
The Analytics page and the token/cost surfaces on the Models page show local debug estimates only. They count input+output (and a bar viz adds cache_read+reasoning, missing cache_write entirely) from successful main-agent responses that returned a usable usage block. Excluded silently: - All auxiliary calls — context compression, title generation, vision, session search, web extract, smart approvals, MCP routing, plugin LLM access (13 production call sites bypass update_token_counts) - Provider-side retries, fallback attempts - Any call whose usage block didn't come back - cache_write_tokens (column exists in sessions table but not returned by /api/analytics/models) Real-world impact: a user on Kimi K2.6 saw 150K local vs 27M on the OpenRouter side over the same window. Precise-looking numbers next to provider billing create false confidence and support load. This change adds dashboard.show_token_analytics (default False) to gate: - The Analytics nav item (hidden from sidebar when off) - The Analytics page (renders an explanation card instead of charts) - Token bars, totals, cost figures, avg/api_calls on the Models page The Models page keeps capability metadata (context window, vision, tools, reasoning), the use-as-main/aux menu, sessions count, and last-used timestamps when the flag is off. Set dashboard.show_token_analytics: true in config.yaml to opt back in to the local debug estimate. Fixing the underlying accounting (issue #23270) is a separate, larger workstream. Refs: #23270, #21705
This commit is contained in:
parent
e90508103c
commit
f7ad2f1115
4 changed files with 249 additions and 100 deletions
|
|
@ -397,10 +397,26 @@ export default function AnalyticsPage() {
|
|||
const [data, setData] = useState<AnalyticsResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
// Gated on `dashboard.show_token_analytics` (default off). When off the
|
||||
// page renders an explanation card instead of fetching analytics — the
|
||||
// local token counts exclude auxiliary calls and provider retries, so
|
||||
// they diverge from provider billing in ways that mislead users.
|
||||
const [showTokens, setShowTokens] = useState<boolean | null>(null);
|
||||
const { t } = useI18n();
|
||||
const { setAfterTitle, setEnd } = usePageHeader();
|
||||
|
||||
useEffect(() => {
|
||||
api
|
||||
.getConfig()
|
||||
.then((cfg) => {
|
||||
const dash = (cfg?.dashboard ?? {}) as { show_token_analytics?: unknown };
|
||||
setShowTokens(dash.show_token_analytics === true);
|
||||
})
|
||||
.catch(() => setShowTokens(false));
|
||||
}, []);
|
||||
|
||||
const load = useCallback(() => {
|
||||
if (!showTokens) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
api
|
||||
|
|
@ -408,7 +424,7 @@ export default function AnalyticsPage() {
|
|||
.then(setData)
|
||||
.catch((err) => setError(String(err)))
|
||||
.finally(() => setLoading(false));
|
||||
}, [days]);
|
||||
}, [days, showTokens]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const periodLabel =
|
||||
|
|
@ -422,37 +438,39 @@ export default function AnalyticsPage() {
|
|||
</span>,
|
||||
);
|
||||
setEnd(
|
||||
<div className="flex w-full min-w-0 flex-wrap items-center justify-end gap-2 sm:gap-2">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
{PERIODS.map((p) => (
|
||||
<Button
|
||||
key={p.label}
|
||||
type="button"
|
||||
size="sm"
|
||||
outlined={days !== p.days}
|
||||
onClick={() => setDays(p.days)}
|
||||
>
|
||||
{p.label}
|
||||
</Button>
|
||||
))}
|
||||
showTokens === false ? null : (
|
||||
<div className="flex w-full min-w-0 flex-wrap items-center justify-end gap-2 sm:gap-2">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
{PERIODS.map((p) => (
|
||||
<Button
|
||||
key={p.label}
|
||||
type="button"
|
||||
size="sm"
|
||||
outlined={days !== p.days}
|
||||
onClick={() => setDays(p.days)}
|
||||
>
|
||||
{p.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
outlined
|
||||
onClick={load}
|
||||
disabled={loading}
|
||||
prefix={loading ? <Spinner /> : <RefreshCw />}
|
||||
>
|
||||
{t.common.refresh}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
outlined
|
||||
onClick={load}
|
||||
disabled={loading}
|
||||
prefix={loading ? <Spinner /> : <RefreshCw />}
|
||||
>
|
||||
{t.common.refresh}
|
||||
</Button>
|
||||
</div>,
|
||||
),
|
||||
);
|
||||
return () => {
|
||||
setAfterTitle(null);
|
||||
setEnd(null);
|
||||
};
|
||||
}, [days, loading, load, setAfterTitle, setEnd, t.common.refresh]);
|
||||
}, [days, loading, load, setAfterTitle, setEnd, t.common.refresh, showTokens]);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
|
|
@ -461,13 +479,51 @@ export default function AnalyticsPage() {
|
|||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<PluginSlot name="analytics:top" />
|
||||
{loading && !data && (
|
||||
|
||||
{showTokens === false && (
|
||||
<Card>
|
||||
<CardContent className="py-12">
|
||||
<div className="mx-auto flex max-w-2xl flex-col gap-3 text-sm text-muted-foreground">
|
||||
<h2 className="font-display text-base tracking-wider uppercase text-foreground">
|
||||
Token analytics hidden
|
||||
</h2>
|
||||
<p>
|
||||
The token, cost, and per-day analytics on this page are a
|
||||
local debug estimate. They only count successful main-agent
|
||||
responses with a usable <span className="font-mono">usage</span>{" "}
|
||||
block, and silently exclude auxiliary calls (context
|
||||
compression, title generation, vision, session search, web
|
||||
extract, smart approvals, MCP routing, plugin LLM access)
|
||||
plus provider-side retries and fallback attempts. Cache
|
||||
writes are missing entirely.
|
||||
</p>
|
||||
<p>
|
||||
On models with heavy auxiliary traffic (Kimi K2.6, MiniMax
|
||||
M2.7) the local total can be 10x–100x lower than what your
|
||||
provider bills. Hiding these numbers is safer than letting
|
||||
them look authoritative.
|
||||
</p>
|
||||
<p>
|
||||
Check your provider dashboard (OpenRouter, Anthropic, etc.)
|
||||
for actual usage and billing. To re-enable the local debug
|
||||
estimate anyway, set{" "}
|
||||
<span className="font-mono">
|
||||
dashboard.show_token_analytics: true
|
||||
</span>{" "}
|
||||
in <a href="/config" className="underline">Config</a>.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{showTokens && loading && !data && (
|
||||
<div className="flex items-center justify-center py-24">
|
||||
<Spinner className="text-2xl text-primary" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
{showTokens && error && (
|
||||
<Card>
|
||||
<CardContent className="py-6">
|
||||
<p className="text-sm text-destructive text-center">{error}</p>
|
||||
|
|
@ -475,7 +531,7 @@ export default function AnalyticsPage() {
|
|||
</Card>
|
||||
)}
|
||||
|
||||
{data && (
|
||||
{showTokens && data && (
|
||||
<>
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
<Card>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue