feat(ui): admin-only AI usage viewer + pricing/retention editor#115
Merged
Conversation
Add a client-reachable, admin-gated way to READ the AI usage log for the C2 UI. The usage database is created lazily and previously only its pricing + retention were client-reachable; the rows/totals had no route. - SDK: `aiUsage` route constant, `getAiUsage()` client method, and the `AiUsageRow`/`AiUsageTotals`/`AiUsageResponse` types (raw `p_*` property ids resolved to named fields so the client never depends on the schema). - Server: `AiUsageLog.report()` projects the DB into recent rows (newest first, capped) + aggregate totals + the retention window, WITHOUT seeding (a workspace that never used AI reports `exists:false`, no phantom page). - `GET /api/ai/usage` behind `requireInstanceAdmin` (same gate as pricing). - LocalDataClient stub for the no-hosted-engine in-app mode. Tests: viewer/guest 403; owner+admin get rows+totals+retention; a read on a fresh workspace reports exists:false and creates no database. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG
C2 admin-facing surface for the C1 backend, mounted in the Admin ("Backups &
data") settings tab and RENDERING NOTHING for a non-admin — the whole surface
is self-gated on the effective instance role AND re-gated by a 403 from the
admin-only endpoints (the MembersSettings hide-not-break pattern), so a viewer
or guest sees none of it.
- AiUsageSettings.tsx: a recent-usage table + totals summary (graceful empty
state when the usage DB doesn't exist yet), an editable per-provider/model
$/Mtok pricing table (numeric-sanitised; persists only the models that differ
from the shipped default, carrying cache prices through), and a retention-days
control (setAiUsageRetention).
- Wired into AdminSettings; en i18n strings.
Tests (aiUsageSettings.test.tsx): admin sees viewer+editor+retention; a viewer
renders nothing and never probes the admin endpoints; a 403 guest hides;
editing a price persists as a minimal override and round-trips; retention calls
setAiUsageRetention; empty state renders without a table.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG
- pricing inputs show the shipped default as a placeholder; clearing a
field drops the override so the model falls back to its default
- reword the pricing hint to match ("Clear a price to fall back to its default")
- keep the model id on one line in the usage table (whitespace-nowrap)
- format the total-cost summary card to 2dp; per-row cost stays 4dp
- add a "Showing N of M calls" hint when totals exceed the shown rows;
clarify the AiUsageTotals + buildOverride comments (totals span ALL rows)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
C1 added the AI usage-attribution backend, but there was no way for an admin to see usage or edit the pricing/retention that drive it. Board: C2 · Admin-only AI usage viewer + pricing/retention editor UI — the final slice of the Epic.
Solution
An admin-only surface in the Admin settings tab, plus the read-path C1 didn't expose:
GET /api/ai/usage(requireInstanceAdmin) viaAiUsageLog.report()— read-only, never seeds (a fresh workspace reports{exists:false}).setAiPricing), preserving cache-tier prices.setAiUsageRetention(updates the usage DB'sautoExpiry.days).youRolefast-path + 403-from-endpoint hide, MembersSettings pattern). Viewers/guests see no AI section.Key files:
packages/ui/src/components/settings/AiUsageSettings.tsx(new) +AdminSettings.tsxmount +i18n/messages/en.ts;packages/server/src/ai/routes.ts(GET /api/ai/usage) +ai/usage.ts(report());packages/sdk/src/{ai,routes,client,index}.ts;localClient.tsstub. New deps: none.Before / After
Test procedure
pnpm verify→ green (ui 948, server 645 unit + e2e).aiUsageSettings.test.tsx(9): admin shows all; viewer hidden without probing; guest-403 hide-not-break; price override round-trip; default placeholder + clear-to-reset; retention; empty state; total 2dp vs per-row 4dp; row-cap hint.aiUsage.test.ts(28):GET /api/ai/usage403 for viewer/guest; no-seed-on-read; rows newest-first + totals over EVERY row + row cap; no key/property-id leak.Operational notes
requireInstanceAdmin+ the C1restrictedusage DB). Read-only route — doesn't weaken any C1 write-lockout/visibility invariant.mainf6fbc55.Verify: green — sdk 171 · ui 948 · server 645 unit + e2e (251 / 40). Gates cleared: design ✅ · code ✅ · security ✅.
🤖 Generated with Claude Code
https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG