diff --git a/packages/opencode/src/kilocode/components/model-info-panel-utils.ts b/packages/opencode/src/kilocode/components/model-info-panel-utils.ts index 582db55650d..152c23a50e6 100644 --- a/packages/opencode/src/kilocode/components/model-info-panel-utils.ts +++ b/packages/opencode/src/kilocode/components/model-info-panel-utils.ts @@ -13,11 +13,11 @@ export function fmtPrice(n: number): string { return `$${n.toFixed(2)}/1M` } -export function fmtCachedPrice(cost: Cost): string { +export function fmtCachedPrice(cost: Cost): string | null { const read = cost.cache.read if (read > 0) return fmtPrice(read) if (cost.input === 0) return fmtPrice(0) - return "N/A" + return null } export function avgPrice(cost: Cost): number { @@ -31,3 +31,9 @@ export function fmtContext(n: number): string { if (n >= 1_000) return `${(n / 1_000).toFixed(n % 1_000 === 0 ? 0 : 1)}K` return String(n) } + +export function fmtDate(s: string): string { + const d = new Date(s) + if (isNaN(d.getTime())) return s + return d.toLocaleDateString(undefined, { year: "numeric", month: "short" }) +} diff --git a/packages/opencode/src/kilocode/components/model-info-panel.tsx b/packages/opencode/src/kilocode/components/model-info-panel.tsx index ceb9e860445..c228eb7e1f2 100644 --- a/packages/opencode/src/kilocode/components/model-info-panel.tsx +++ b/packages/opencode/src/kilocode/components/model-info-panel.tsx @@ -1,8 +1,9 @@ import { TextAttributes } from "@opentui/core" +import { useTerminalDimensions } from "@opentui/solid" +import { createMemo, Show } from "solid-js" import { useTheme } from "@tui/context/theme" import type { Model } from "@kilocode/sdk/v2" -import { avgPrice, fmtCachedPrice, fmtContext, fmtPrice } from "./model-info-panel-utils" -import { Show } from "solid-js" +import { avgPrice, fmtCachedPrice, fmtContext, fmtDate, fmtPrice } from "./model-info-panel-utils" interface Props { model: Model @@ -12,6 +13,40 @@ interface Props { export function ModelInfoPanel(props: Props) { const { theme } = useTheme() const m = () => props.model + const dimensions = useTerminalDimensions() + + const maxHeight = createMemo(() => Math.floor(dimensions().height / 2) - 3) + + const cost = () => m().cost + const cached = () => (cost() ? fmtCachedPrice(cost()) : null) + const avg = () => (cost() ? avgPrice(cost()) : undefined) + const caps = () => m().capabilities + const inputs = () => caps()?.input + const outputs = () => caps()?.output + const activeInputModalities = () => { + if (!inputs()) return [] as string[] + return Object.entries(inputs()) + .filter(([k, v]) => v && k !== "text") + .map(([k]) => k.charAt(0).toUpperCase() + k.slice(1)) + } + const activeOutputModalities = () => { + if (!outputs()) return [] as string[] + return Object.entries(outputs()) + .filter(([k, v]) => v && k !== "text") + .map(([k]) => k.charAt(0).toUpperCase() + k.slice(1)) + } + const inputLine = () => { + const mods = activeInputModalities() + return mods.length > 0 ? `In: ${mods.join(", ")}` : null + } + const outputLine = () => { + const mods = activeOutputModalities() + return mods.length > 0 ? `Out: ${mods.join(", ")}` : null + } + const desc = () => { + const d = m().options?.description + return typeof d === "string" && d.trim() ? d : null + } return ( - - - {m().name ?? m().id ?? "Model"} - - {props.provider ?? m().providerID ?? ""} - - + + + + {m().name ?? m().id ?? "Model"} + + {props.provider ?? m().providerID ?? ""} + - + Free - + - Input - {m() ? fmtPrice(m().cost.input) : "—"} - - - Output - {m() ? fmtPrice(m().cost.output) : "—"} - - - Cache Read - {m() ? fmtCachedPrice(m().cost) : "—"} - - - - Context Size - {m() ? fmtContext(m().limit.context) : "—"} - - - Average Cost - {m() ? fmtPrice(avgPrice(m().cost)) : "—"} + Family + {m().family!.charAt(0).toUpperCase() + m().family!.slice(1)} - + + + Released + {fmtDate(m().release_date)} + + + + + + Input + {m() ? fmtPrice(m().cost.input) : "—"} + + + Output + {m() ? fmtPrice(m().cost.output) : "—"} + + + + Cached + {cached()} + + + + + Avg Cost + {m() ? fmtPrice(avg()!) : "—"} + + + + Context + {m() ? fmtContext(m().limit.context) : "—"} + + + + + + + + Reasoning + Yes + + + + {(line) => ( + + Caps + {line().replace(/^In:\s*/, "")} + + )} + + + {(line) => ( + + Out + {line().replace(/^Out:\s*/, "")} + + )} + + + + + {" "} + + {desc()} + + + ) } diff --git a/packages/opencode/test/kilocode/model-info-panel-utils.test.ts b/packages/opencode/test/kilocode/model-info-panel-utils.test.ts index 07159e5bb22..8a07fcb2291 100644 --- a/packages/opencode/test/kilocode/model-info-panel-utils.test.ts +++ b/packages/opencode/test/kilocode/model-info-panel-utils.test.ts @@ -22,8 +22,8 @@ describe("model info panel price formatting", () => { expect(fmtCachedPrice({ input: 0, output: 0, cache: { read: 0, write: 0 } })).toBe("Free") }) - test("fmtCachedPrice returns N/A without cache read", () => { - expect(fmtCachedPrice({ input: 3, output: 15, cache: { read: 0, write: 0 } })).toBe("N/A") + test("fmtCachedPrice returns null without cache read", () => { + expect(fmtCachedPrice({ input: 3, output: 15, cache: { read: 0, write: 0 } })).toBeNull() }) test("avgPrice uses cache weighted formula when cache read exists", () => {