feat: enhance dashboard, log detail & costs UX - #1623
Conversation
- Add cached tokens and cached input cost to activity API - Add GET /logs/:id endpoint for individual log retrieval - Add dedicated log detail page (/activity/[logId]) - Add Input Tokens, Cached Tokens, Most Used Model cards - Break cost chart into input/output/cached input lines - Clarify inference costs as reference (not deducted) - Separate provider pricing from billed costs in log cards - Prompt top-up dialog when playground credits are zero - Make TopUpCreditsDialog controllable (open/onOpenChange) - Fix discount price alignment in models list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WalkthroughAdds cachedTokens and cachedInputCost to daily activity data; introduces GET /logs/{id} API and a server+client log detail page; updates dashboard and recent-activity flows to surface cached metrics and provider pricing; makes TopUpCreditsDialog controllable and integrates it into the chat UI. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Browser as Client (LogDetailClient)
participant Server as NextJS Server (LogDetailPage)
participant API as API Server
participant DB as Database
User->>Browser: Navigate to /dashboard/{orgId}/{projectId}/activity/{logId}
Browser->>Server: Request page
Server->>API: GET /logs/{id}
API->>API: Validate auth & org access
API->>DB: Query log by id
DB-->>API: Return log record
API-->>Server: Return { log }
Server-->>Browser: Render LogDetailClient with initialData
Browser->>Browser: Render sections (Request, Cost, Tokens, Messages, etc.)
Browser-->>User: Display log details
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/api/src/routes/logs.ts (1)
33-103:⚠️ Potential issue | 🟠 MajorAdd missing fields to
logSchemathat are used by the log detail page and defined in the database schema.The schema is missing
cachedTokens,cachedInputCost,timeToFirstToken,dataStorageCost,usedModelMapping, and other fields from the DB schema (seepackages/db/src/schema.ts). The log-detail client actively uses these fields—for example,cachedInputCostis displayed in the cost breakdown, andcachedTokensdetermines the cache status. Without these fields in the schema, Zod validation could strip them from responses, breaking the UI.At minimum, add:
responseSize: z.number(), +cachedInputCost: z.number().nullable(), +cachedTokens: z.string().nullable(), +timeToFirstToken: z.number().nullable(), +dataStorageCost: z.string().nullable(), +usedModelMapping: z.string().nullable(), content: z.string().nullable(),Consider adding remaining fields (
discount,serviceFee,pricingTier,traceId,effort,webSearchCost, etc.) for completeness.apps/ui/src/components/dashboard/log-card.tsx (1)
755-761:⚠️ Potential issue | 🟡 MinorSame
as anyusage forresponseFormatas inlog-detail-client.tsx.This is the same coding guidelines violation flagged in the detail client. Apply the same type-guard approach here for consistency.
As per coding guidelines,
**/*.{ts,tsx}: "Never useanyoras anytype assertions in TypeScript code unless absolutely necessary."
🤖 Fix all issues with AI agents
In `@apps/api/src/routes/logs.ts`:
- Around line 105-127: The OpenAPI spec for the getById route (created via
createRoute) only declares 200 and 404 but the handler can throw
HTTPException(401) and HTTPException(403); update the responses object for
getById to include 401 and 403 entries (with appropriate descriptions and, if
your project uses a shared error schema, reference that schema under
"application/json") so the generated docs include those auth/permission error
cases and match the handler behavior.
- Around line 138-140: The query using db.query.log.findFirst currently uses the
shorthand where: { id } but the repository prefers explicit operators; update
the where clause in the log retrieval to use the imported eq operator (e.g.,
where: { id: { eq: id } }) so it matches the style used elsewhere in logs.ts and
authorization.ts.
In `@apps/playground/src/components/playground/chat-page-client.tsx`:
- Around line 623-626: The credit check inside handleUserMessage currently
returns early but does not prevent handlePromptSubmit from clearing the input
(setText("")) or calling sendMessage, so the LLM still receives the prompt and
the input is lost; modify the flow so the credit guard runs before clearing
input/sending: either (A) change onUserMessage/handleUserMessage to return a
boolean (e.g., true=continue, false=abort) and update handlePromptSubmit to
await onUserMessage and only call setText("") and sendMessage(...) when the
returned value is true, or (B) move the credits check out of handleUserMessage
into handlePromptSubmit and perform it there before setText/sendMessage; ensure
handleUserMessage returns an appropriate value when using option A and that
setShowTopUp(true) still runs when aborting.
In
`@apps/ui/src/app/dashboard/`[orgId]/[projectId]/activity/[logId]/log-detail-client.tsx:
- Around line 599-607: The JSX uses (log.responseFormat as any).type; replace
this unsafe assertion by introducing and using a proper type guard such as
isResponseFormat(value): value is { type?: string } (or your existing
ResponseFormat interface) and then render the Field value by checking
log.responseFormat with that guard (e.g., isResponseFormat(log.responseFormat) ?
log.responseFormat.type || "-" : "-"). Update the component around the Field
render to call the guard instead of using as any so the code conforms to the
no-any rule while preserving the same fallback logic.
In `@apps/ui/src/components/dashboard/dashboard-client.tsx`:
- Around line 158-181: The code computes the top model by aggregated cost
(modelCostMap built from activityData → modelBreakdown) but exposes it as
mostUsedModel/mostUsedProvider which is misleading; either change the metric
calculation to rank by request count (use the count/request field in each
modelBreakdown entry and sum it into a map) or rename the outputs to reflect
cost ranking (e.g., topModelByCost/topProviderByCost) and update all usages and
the UI label accordingly; update the anonymous IIFE return keys
(mostUsedModel/mostUsedProvider) and any components that consume them (and the
display card title) to match the chosen semantic change so behavior and label
are consistent.
In `@apps/ui/src/components/dashboard/log-card.tsx`:
- Around line 200-219: The nested ternary inside the cost display is redundant:
replace the expression "{log.cost ? `$${log.cost.toFixed(6)}` : log.cached ?
"$0" : "$0"}" with a single fallback value so it reads "{log.cost ?
`$${log.cost.toFixed(6)}` : "$0"}"; update the JSX in the component that renders
the cost (the block using Tooltip, TooltipTrigger and the "log" variable) and
remove the unnecessary "log.cached ? "$0" : "$0"" branch, keeping any use of
log.cached only if you intend different behavior.
🧹 Nitpick comments (8)
apps/playground/src/components/playground/chat-ui.tsx (1)
133-134:errorprop is accepted but never used in the component's JSX.The error alert UI was removed, but
errorremains declared inChatUIProps(Line 133) and destructured at Line 407. Since no JSX references it anymore, this is dead code. Consider removing it from the interface and the destructuring to avoid confusion — callers likechat-page-client.tsxstill passerror={error}to no effect.♻️ Suggested cleanup
In the
ChatUIPropsinterface:- error?: string | null;In the destructured props:
- error = null,Also applies to: 407-407
apps/playground/src/components/credits/top-up-credits-dialog.tsx (1)
90-96: Minor:handleClosesetTimeout can race with a quick reopen in controlled mode.When the parent controls
open, callingsetOpen(false)at Line 91 and then scheduling thestep/loadingreset at Line 92–95 creates a window where re-opening the dialog within 300ms would have its state reset underneath it. This is very unlikely in practice with a payment dialog, so flagging as a nit.A safer alternative is to reset state when
opentransitions fromfalse → true:♻️ Optional: reset on open instead of delayed reset on close
+ useEffect(() => { + if (open) { + setStep("amount"); + setLoading(false); + } + }, [open]); + const handleClose = () => { setOpen(false); - setTimeout(() => { - setStep("amount"); - setLoading(false); - }, 300); };Also applies to: 106-108
apps/ui/src/components/dashboard/recent-activity-card.tsx (1)
83-88: The abbreviation "ref." may be unclear to users.Consider using a more descriptive label like "inference (reference)" or adding a tooltip. "ref." is ambiguous — users might not understand it means "reference pricing" without additional context.
apps/ui/src/types/activity.ts (1)
40-65:ActivitTduplicatesDailyActivityinline — consider reusing the interface.This is a pre-existing pattern, but now that both types need to stay in sync (with
cachedTokens,cachedInputCost, etc.), consider definingActivitTin terms ofDailyActivity:export type ActivitT = { activity: DailyActivity[] } | undefined;This eliminates the risk of the two definitions drifting apart.
apps/ui/src/components/dashboard/overview.tsx (1)
41-57: Tooltip color mapping relies on a catch-all fallback.The ternary chain maps
inputCost→ blue,outputCost→ amber, and falls through to green for anything else. If a fourth line is added later, it would incorrectly render green. Consider using a lookup map for maintainability.♻️ Optional: use a color map for clarity
+const COST_LINE_COLORS: Record<string, string> = { + inputCost: "#3b82f6", + outputCost: "#f59e0b", + cachedInputCost: "#10b981", +}; + {payload.map((entry) => ( <p key={entry.dataKey} className="text-sm"> <span className="inline-block w-2 h-2 rounded-full mr-1.5" style={{ - backgroundColor: - entry.dataKey === "inputCost" - ? "#3b82f6" - : entry.dataKey === "outputCost" - ? "#f59e0b" - : "#10b981", + backgroundColor: COST_LINE_COLORS[entry.dataKey ?? ""] ?? "#888", }} />This would also deduplicate the color strings shared between the tooltip and the
<Line>elements below.apps/ui/src/components/dashboard/dashboard-client.tsx (1)
422-442: Long subtitle string could overflow on narrow screens.The subtitle concatenates multiple cost segments into one line. On smaller cards this will wrap aggressively and may be hard to scan. The
MetricCardrenders this as a single<p>withtext-xs. Consider truncating or showing fewer details inline (e.g., just "Not deducted from your balance") and revealing the full breakdown on hover or in the chart tooltip.apps/ui/src/app/dashboard/[orgId]/[projectId]/activity/[logId]/log-detail-client.tsx (2)
46-61:navigator.clipboard.writeTextcan reject — consider adding error handling.In non-secure contexts (HTTP) or when the document lacks focus, this API throws. A
.catch(() => {})or try-catch would prevent an unhandled promise rejection.🛡️ Proposed fix
onClick={() => { - navigator.clipboard.writeText(value); - setCopied(true); - setTimeout(() => setCopied(false), 1500); + navigator.clipboard.writeText(value).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }).catch(() => { + // Silently fail — clipboard API may not be available + }); }}
204-208:as Logtype assertion after date conversion — acceptable but slightly fragile.The spread + date conversion produces a shape that may not exactly match
Log(e.g., if the API response omits optional fields or serializes differently). This works for now but could silently mask mismatches if the API schema evolves.
| const log = await db.query.log.findFirst({ | ||
| where: { id }, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for findFirst and findMany with their where clauses
rg -n 'findFirst|findMany' --type=ts -A5 -g '!node_modules/**' | head -150Repository: theopenco/llmgateway
Length of output: 8556
🏁 Script executed:
#!/bin/bash
# Check the exact code in logs.ts around lines 138-140
sed -n '130,150p' apps/api/src/routes/logs.ts
# Search for any findFirst/findMany with bare value where clauses (pattern: where: { [a-zA-Z_]: [^{] )
echo "---"
echo "Searching for any bare value where clauses in findFirst/findMany:"
rg 'findFirst|findMany' --type=ts -A3 | rg -B2 'where:\s*\{\s*[a-zA-Z_]+\s*\}' || echo "No bare value where clauses found"Repository: theopenco/llmgateway
Length of output: 759
🏁 Script executed:
#!/bin/bash
# Search for the logSchema definition in logs.ts
rg -n 'logSchema' apps/api/src/routes/logs.ts -B5 -A15 | head -50
# Also check the database schema for log table
echo "---"
echo "Checking database schema for log table:"
find . -path ./node_modules -prune -o -name "*.ts" -type f -exec rg -l "export const log" {} \; | grep -i schemaRepository: theopenco/llmgateway
Length of output: 1507
🏁 Script executed:
#!/bin/bash
# Check the database schema for log table
rg -n "export const log" packages/db/src/schema.ts -A100 | head -120
# Also verify if there are any instances where bare value where clause works
echo "---"
echo "Checking if Drizzle ORM version supports bare value where syntax:"
cat packages/db/package.json | rg -A2 'drizzle-orm'Repository: theopenco/llmgateway
Length of output: 3462
🏁 Script executed:
#!/bin/bash
# Check if the bare value where syntax is valid in Drizzle 1.0.0-beta.1
# Search for any documentation or examples in the codebase about where clause syntax
rg -n 'where:' packages/db/src -B2 -A2 | head -80
# Also check if there's a types file that might show the expected where clause structure
echo "---"
echo "Looking for Drizzle type definitions or examples:"
find . -path ./node_modules -prune -o -name "*.ts" -type f -exec rg -l "type.*where" {} \; | head -5Repository: theopenco/llmgateway
Length of output: 1274
🏁 Script executed:
#!/bin/bash
# Check if the logs.ts file has any tests or if this endpoint is tested
find . -path ./node_modules -prune -o -name "*.spec.ts" -o -name "*.test.ts" | xargs rg -l "logs" 2>/dev/null | head -5
# Check the GET /logs/:id endpoint more thoroughly
echo "---"
sed -n '100,160p' apps/api/src/routes/logs.ts
# Verify Drizzle 1.0.0-beta.1 where clause syntax by checking their examples
echo "---"
echo "Checking if bare value syntax appears anywhere else in codebase:"
rg 'findFirst|findMany' --type=ts -A2 | rg -B2 'where:\s*{\s*[a-zA-Z_]+\s*:' | head -30Repository: theopenco/llmgateway
Length of output: 1690
🏁 Script executed:
#!/bin/bash
# Fix the regex and search for where clauses more carefully
echo "Looking for all where clauses in findFirst/findMany calls:"
rg 'findFirst|findMany' --type=ts -A3 | grep -A2 'where:' | head -60
# Check if there are any tests for the logs.ts endpoint
echo "---"
echo "Checking logs.spec.ts for getById test:"
rg -n 'getById|/logs.*id' apps/api/src/routes/logs.ts.spec.ts 2>/dev/null || echo "No logs.spec.ts file found for routes"
# Check if there's a test file for the API routes
ls -la apps/api/src/routes/*.spec.ts 2>/dev/null || echo "No spec files in routes directory"Repository: theopenco/llmgateway
Length of output: 2806
🏁 Script executed:
#!/bin/bash
# Check the logs.spec.ts file for tests on the getById endpoint
rg -n 'getById|/:id|findFirst' apps/api/src/routes/logs.spec.ts -B3 -A5 | head -80
# Also check the exact line where the issue occurs to see context
echo "---"
echo "Checking the findFirst call with bare value syntax in logs.ts:"
sed -n '135,145p' apps/api/src/routes/logs.tsRepository: theopenco/llmgateway
Length of output: 316
🏁 Script executed:
#!/bin/bash
# Check the beacon.ts and stripe.ts files to see if they're using a different syntax
echo "Checking beacon.ts where clause:"
rg -n 'where.*type.*self-host' apps/api/src/lib/beacon.ts -B2 -A2
echo "---"
echo "Checking stripe.ts where clauses:"
rg -n 'where.*id:.*organizationId' apps/api/src/stripe.ts -B2 -A2
echo "---"
echo "Checking if these are different query methods (not findFirst/findMany):"
rg -n 'where:' apps/api/src/stripe.ts -B5 | head -40Repository: theopenco/llmgateway
Length of output: 1413
🏁 Script executed:
#!/bin/bash
# Get the full where clauses from stripe.ts
rg -n 'findFirst' apps/api/src/stripe.ts -A5 | head -100Repository: theopenco/llmgateway
Length of output: 2032
🏁 Script executed:
#!/bin/bash
# Check authorization.ts to see the pattern mentioned in the review
rg -n 'getActiveUserOrganizationIds' apps/api/src -A15 | head -50
echo "---"
echo "Check if there's a pattern in what files use bare values vs explicit operators:"
echo "Bare values (stripe.ts lines 27, 116, 141):"
sed -n '25,30p;114,119p;139,144p' apps/api/src/stripe.ts
echo "---"
echo "Files using explicit operators:"
rg -n 'findFirst|findMany' apps/api/src/lib/beacon.ts -A3 | head -20Repository: theopenco/llmgateway
Length of output: 3264
🏁 Script executed:
#!/bin/bash
# Check if there are ANY instances of the shorthand { id } syntax vs { id: id }
echo "Searching for shorthand syntax like { id } or { userId }:"
rg -n 'where:\s*{\s*[a-zA-Z_]+\s*}' apps/api/src --type=ts
echo "---"
echo "Checking if all bare values use the full form { field: value }:"
rg -n 'where:' apps/api/src/stripe.ts -A1 | grep -v 'eq:' | head -30Repository: theopenco/llmgateway
Length of output: 736
🏁 Script executed:
#!/bin/bash
# Check the context around stripe.ts line 1041
sed -n '1035,1050p' apps/api/src/stripe.tsRepository: theopenco/llmgateway
Length of output: 454
🏁 Script executed:
#!/bin/bash
# Check if stripe.ts tests pass or if there are runtime errors
echo "Looking for tests of the findMany with bare value syntax in stripe.ts:"
rg -n 'stripe' apps/api/src/routes --type=ts -l
echo "---"
echo "Check stripe.ts to see if it's a working, tested file:"
head -5 apps/api/src/stripe.ts
echo "---"
echo "Summary: Count usage patterns in the codebase"
echo "Pattern 1: { field: { eq: value } } - explicit operator:"
rg 'where:' apps/api/src --type=ts -A2 | grep -c 'eq:'
echo "Pattern 2: { field: value } - bare value/shorthand (not wrapped in eq):"
rg 'where:' apps/api/src --type=ts -A2 | grep -E 'field|organizationId|userId|id' | grep -v 'eq:' | grep -v '{' | wc -l
echo "---"
echo "Checking beacon.ts where clause pattern more carefully:"
sed -n '78,85p' apps/api/src/lib/beacon.tsRepository: theopenco/llmgateway
Length of output: 961
🏁 Script executed:
#!/bin/bash
# Verify that stripe.ts with bare value syntax is production code and not just tests
echo "Check git log to see if stripe.ts is actively used:"
git log --oneline apps/api/src/stripe.ts 2>/dev/null | head -5 || echo "Git not available"
echo "---"
echo "Check if stripe.ts is imported and used in production routes:"
rg 'from.*stripe' apps/api/src/routes --type=ts
echo "---"
echo "Verify Drizzle documentation comment in the codebase about object syntax:"
rg -n 'object query\|relational query API' packages/db/src -B2 -A2 | head -30Repository: theopenco/llmgateway
Length of output: 639
🏁 Script executed:
#!/bin/bash
# Final verification: Check if logs.ts imports `eq` but doesn't use it in the findFirst call
echo "logs.ts imports (lines 7-20):"
sed -n '7,20p' apps/api/src/routes/logs.ts
echo "---"
echo "Does logs.ts use eq elsewhere in the file?"
rg -n '\beq\(' apps/api/src/routes/logs.ts | head -10Repository: theopenco/llmgateway
Length of output: 850
Align the where clause with the predominant codebase pattern for consistency.
While the bare value syntax where: { id } is functional and used in other files (e.g., stripe.ts), most database queries in this codebase use explicit operators like { id: { eq: id } }. For consistency with files like authorization.ts and the rest of logs.ts itself (which imports eq and uses it throughout), consider adopting the explicit operator form:
Suggested change
const log = await db.query.log.findFirst({
- where: { id },
+ where: { id: { eq: id } },
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const log = await db.query.log.findFirst({ | |
| where: { id }, | |
| }); | |
| const log = await db.query.log.findFirst({ | |
| where: { id: { eq: id } }, | |
| }); |
🤖 Prompt for AI Agents
In `@apps/api/src/routes/logs.ts` around lines 138 - 140, The query using
db.query.log.findFirst currently uses the shorthand where: { id } but the
repository prefers explicit operators; update the where clause in the log
retrieval to use the imported eq operator (e.g., where: { id: { eq: id } }) so
it matches the style used elsewhere in logs.ts and authorization.ts.
| <Field | ||
| label="Response Format" | ||
| value={ | ||
| log.responseFormat | ||
| ? typeof log.responseFormat === "object" | ||
| ? (log.responseFormat as any).type || "-" | ||
| : "-" | ||
| : "-" | ||
| } |
There was a problem hiding this comment.
Avoid as any for responseFormat — violates coding guidelines.
Line 604 uses (log.responseFormat as any).type. Since responseFormat is a JSON column, prefer a type guard or a narrower assertion.
🔧 Suggested fix
<Field
label="Response Format"
value={
log.responseFormat
- ? typeof log.responseFormat === "object"
- ? (log.responseFormat as any).type || "-"
+ ? typeof log.responseFormat === "object" &&
+ log.responseFormat !== null &&
+ "type" in log.responseFormat
+ ? String(
+ (log.responseFormat as Record<string, unknown>).type,
+ ) || "-"
: "-"
: "-"
}
/>As per coding guidelines, **/*.{ts,tsx}: "Never use any or as any type assertions in TypeScript code unless absolutely necessary."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Field | |
| label="Response Format" | |
| value={ | |
| log.responseFormat | |
| ? typeof log.responseFormat === "object" | |
| ? (log.responseFormat as any).type || "-" | |
| : "-" | |
| : "-" | |
| } | |
| <Field | |
| label="Response Format" | |
| value={ | |
| log.responseFormat | |
| ? typeof log.responseFormat === "object" && | |
| log.responseFormat !== null && | |
| "type" in log.responseFormat | |
| ? String( | |
| (log.responseFormat as Record<string, unknown>).type, | |
| ) || "-" | |
| : "-" | |
| : "-" | |
| } |
🤖 Prompt for AI Agents
In
`@apps/ui/src/app/dashboard/`[orgId]/[projectId]/activity/[logId]/log-detail-client.tsx
around lines 599 - 607, The JSX uses (log.responseFormat as any).type; replace
this unsafe assertion by introducing and using a proper type guard such as
isResponseFormat(value): value is { type?: string } (or your existing
ResponseFormat interface) and then render the Field value by checking
log.responseFormat with that guard (e.g., isResponseFormat(log.responseFormat) ?
log.responseFormat.type || "-" : "-"). Update the component around the Field
render to call the guard instead of using as any so the code conforms to the
no-any rule while preserving the same fallback logic.
## Summary Fixed a potential issue with Map iteration in the dashboard cost calculation logic by explicitly converting the Map to an array before iterating. ## Changes - Wrapped `modelCostMap` with `Array.from()` when iterating in the for...of loop to ensure proper iteration behavior across different JavaScript environments ## Details The change ensures that the Map iteration is more explicit and compatible with various runtime environments. Using `Array.from()` converts the Map entries to an array before iteration, which can prevent potential issues with certain TypeScript/JavaScript configurations or bundlers that may not properly handle direct Map iteration in for...of loops. https://claude.ai/code/session_01WXgGhPNd1LjfxF9CiS8Aqy Co-authored-by: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/ui/src/components/dashboard/dashboard-client.tsx`:
- Around line 162-167: The code stores provider only on first encounter in
modelCostMap (modelCostMap.set(m.id, { cost: m.cost, provider: m.provider }))
which leads to stale provider attribution when a model appears with different
providers; modify the update logic inside the loop where modelCostMap is updated
(the block referencing modelCostMap, existing, m.id, m.provider) to either (A)
overwrite existing.provider with m.provider when you add to existing.cost (i.e.,
existing.cost += m.cost; existing.provider = m.provider;) so the latest provider
is shown, or (B) change the map value shape to track per-provider costs (e.g.,
store a provider->cost map for each m.id and aggregate/display the provider with
highest cost) depending on intended semantics — implement one of these fixes and
ensure all code reading provider from modelCostMap handles the new shape if you
choose option B.
Summary
GET /logs/:idendpoint and a dedicated log detail page (/activity/[logId])TopUpCreditsDialogexternally controllable viaopen/onOpenChangepropsTest plan
cachedTokensandcachedInputCostfieldsGET /logs/:idreturns a single log with proper auth checks/activity/[logId]and confirm the log detail page renders correctly🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements