Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clear-model-usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---

Make session model usage easier to scan with collapsible summary rows and aligned steps and cost columns.
119 changes: 83 additions & 36 deletions packages/opencode/src/kilocode/plugins/sidebar-usage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@kilocode/plugin/tui"
import { createMemo, createResource, For, onCleanup, onMount, Show } from "solid-js"
import { createMemo, createResource, createSignal, For, onCleanup, onMount, Show } from "solid-js"
import { useLocal } from "@tui/context/local"
import * as Model from "@tui/util/model"
import { Locale } from "@/util/locale"
import { RoutedModelMeta } from "@/kilocode/cli/cmd/tui/routes/session/routed-model-meta"
import { fmtAttemptCost, fmtScore } from "@/kilocode/components/model-info-panel-utils"
import {
Expand All @@ -18,6 +19,8 @@ import {
const id = "internal:kilo-sidebar-usage"

function View(props: { api: TuiPluginApi; session_id: string }) {
const [modelsOpen, setModelsOpen] = createSignal(true)
const [expanded, setExpanded] = createSignal(new Set<string>())
const theme = () => props.api.theme.current
const local = useLocal()
const [result, { refetch }] = createResource(
Expand All @@ -44,6 +47,13 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
<text fg={theme().textMuted}>{props.value}</text>
</box>
)
const toggle = (key: string) =>
setExpanded((current) => {
const next = new Set(current)
if (next.has(key)) next.delete(key)
else next.add(key)
return next
})

onMount(() => {
const refresh = () => void refetch()
Expand Down Expand Up @@ -109,43 +119,80 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
<Show when={usage()}>
{(data) => (
<box>
<text fg={theme().text}>
<b>Models ({data().models.length})</b>
</text>
<Show when={data().models.length > 0} fallback={<text fg={theme().textMuted}>No model usage yet</text>}>
<box gap={1}>
<For each={groups()}>
{(group) => (
<box>
<text fg={theme().text}>
<b>{group.providerName}</b>
</text>
<box gap={1} paddingLeft={1}>
<For each={group.models}>
{(model) => (
<box>
<text fg={theme().text} wrapMode="char">
<b>{RoutedModelMeta.label(providers(), model)}</b>
</text>
<text fg={theme().textMuted} wrapMode="word">
Steps {formatCount(model.steps)} | Cost {formatCost(model.cost)}
</text>
<text fg={theme().textMuted} wrapMode="word">
In {formatCount(model.tokens.input)} | Out {formatCount(model.tokens.output)} | Reason{" "}
{formatCount(model.tokens.reasoning)}
</text>
<text fg={theme().textMuted} wrapMode="word">
Cache R {formatCount(model.tokens.cache.read)} | W{" "}
{formatCount(model.tokens.cache.write)} | Rate {formatRate(model.tokens)}
</text>
<box flexDirection="row" gap={1} onMouseDown={() => setModelsOpen((open) => !open)}>
<text fg={theme().text}>{modelsOpen() ? "▼" : "▶"}</text>
<text fg={theme().text}>
<b>Models ({data().models.length})</b>
</text>
</box>
<Show when={modelsOpen()}>
<Show when={data().models.length > 0} fallback={<text fg={theme().textMuted}>No model usage yet</text>}>
<box gap={1} paddingTop={1}>
<For each={groups()}>
{(group) => (
<box gap={1}>
<text fg={theme().text}>
<b>{group.providerName}</b>
</text>
<box>
<box flexDirection="row" gap={1}>
<box width={1} flexShrink={0} />
<text fg={theme().textMuted} flexGrow={1} minWidth={0} wrapMode="none">
Model
</text>
<box width={5} flexShrink={0} justifyContent="flex-end">
<text fg={theme().textMuted}>Steps</text>
</box>
<box width={9} flexShrink={0} justifyContent="flex-end">
<text fg={theme().textMuted}>Cost</text>
</box>
)}
</For>
</box>
<For each={group.models}>
{(model) => {
const key = `${props.session_id}/${model.providerID}/${model.modelID}`
return (
<box>
<box flexDirection="row" gap={1} onMouseDown={() => toggle(key)}>
<text fg={theme().text} flexShrink={0}>
{expanded().has(key) ? "▼" : "▶"}
</text>
<box flexGrow={1} minWidth={0} overflow="hidden">
<text fg={theme().text} wrapMode="none">
<b>
{Locale.truncate(
RoutedModelMeta.label(providers(), model) ?? model.modelID,
19,
)}
</b>
</text>
</box>
<box width={5} flexShrink={0} justifyContent="flex-end">
<text fg={theme().textMuted}>{formatCount(model.steps)}</text>
</box>
<box width={9} flexShrink={0} justifyContent="flex-end">
<text fg={theme().textMuted}>{formatCost(model.cost)}</text>
Comment on lines +172 to +173

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Widen the cost column before fixing its width

For models whose formatted cost is longer than 9 cells, this fixed-width box cannot display the value that formatCost returns. This happens for realistic values such as tiny nonzero costs (<$0.000001, 10 characters) or costs with six fractional digits at $10+, so the new compact row can clip or overflow the cost where the previous layout showed the full value; either reserve enough width or shorten the formatted value before putting it in the fixed column.

Useful? React with 👍 / 👎.

</box>
</box>
<Show when={expanded().has(key)}>
<box paddingLeft={2}>
<Row label="Input" value={formatCount(model.tokens.input)} />
<Row label="Output" value={formatCount(model.tokens.output)} />
<Row label="Reasoning" value={formatCount(model.tokens.reasoning)} />
<Row label="Cache read" value={formatCount(model.tokens.cache.read)} />
<Row label="Cache write" value={formatCount(model.tokens.cache.write)} />
<Row label="Cache rate" value={formatRate(model.tokens)} />
</box>
</Show>
</box>
)
}}
</For>
</box>
</box>
</box>
)}
</For>
</box>
)}
</For>
</box>
</Show>
</Show>
</box>
)}
Expand Down
Loading