The admin dashboard gives the operator visibility into AI chat usage and costs without needing to query the database directly. It is scoped to the "Ask AI" feature and provides operational awareness: how many questions are being asked, what it's costing, and who the heaviest users are.
The dashboard loads four data sets from the aiChatLogs table:
(anonymous).Access is gated: isAdmin(locals.session) must return true or a 403 is thrown.
72rem, 1.5rem padding.6.25rem wide), a proportional fill bar, and a numeric value. Bars scale relative to the max value in that dataset. Questions bar uses --color-cross-ref; cost bar uses --color-jesus-words (gold).aria-expanded for accessibility. Expanded detail row shows question and answer in a pre-wrap text block with metadata badges below.$0.0000 (4 decimal places for small values, 2 for ≥$1), durations as Xms or X.Xs, timestamps as DD Mon HH:MM.Route: apps/web/src/routes/(app)/admin/+page.svelte + +page.server.ts
Server load (+page.server.ts):
isAdmin(locals.session) from $lib/server/admin.aiChatLogs (imported from @bibleweb/db):count, sum, avg aggregate for summary.substr(..., 1, 10) for daily stats, filtered to last 30 days.limit(50) ordered by desc(aiChatLogs.id) for recent conversations.desc(count(...)), limit(10) for top users.sum/avg results cast to Number() (Drizzle returns them as strings from SQLite).Client component (+page.svelte):
expandedRows is a $state<Set<number>>.(value / max) * 100% with a minimum 2px fill for visibility.$derived values for maxDailyQuestions and maxDailyCost guard against division-by-zero with fallback minimums.Dependencies:
@bibleweb/db — aiChatLogs Drizzle table schema$lib/server/admin — isAdmin() helperdrizzle-orm — count, sum, avg, sql, descFully implemented. Data queries, access guard, summary cards, bar charts, and expandable conversation table are all working. Dashboard is scoped only to AI chat logs; expanding it to cover other metrics (notes, users, feedback) would require additional queries and sections.