mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge branch 'main' into docs/cleanup-questions-readme
This commit is contained in:
@@ -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" })
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<box
|
||||
@@ -24,42 +59,94 @@ export function ModelInfoPanel(props: Props) {
|
||||
gap={1}
|
||||
flexShrink={0}
|
||||
>
|
||||
<box>
|
||||
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
||||
{m().name ?? m().id ?? "Model"}
|
||||
</text>
|
||||
<text fg={theme.textMuted}>{props.provider ?? m().providerID ?? ""}</text>
|
||||
</box>
|
||||
<box>
|
||||
<scrollbox
|
||||
maxHeight={maxHeight()}
|
||||
paddingRight={1}
|
||||
>
|
||||
<box>
|
||||
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
||||
{m().name ?? m().id ?? "Model"}
|
||||
</text>
|
||||
<text fg={theme.textMuted}>{props.provider ?? m().providerID ?? ""}</text>
|
||||
</box>
|
||||
<Show when={m().isFree}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<box>
|
||||
<text fg={theme.text}>Free</text>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={!m().isFree}>
|
||||
<Show when={m().family}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Input</text>
|
||||
<text fg={theme.text}>{m() ? fmtPrice(m().cost.input) : "—"}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Output</text>
|
||||
<text fg={theme.text}>{m() ? fmtPrice(m().cost.output) : "—"}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Cache Read</text>
|
||||
<text fg={theme.text}>{m() ? fmtCachedPrice(m().cost) : "—"}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="space-between"></box>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Context Size</text>
|
||||
<text fg={theme.text}>{m() ? fmtContext(m().limit.context) : "—"}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Average Cost</text>
|
||||
<text fg={theme.text}>{m() ? fmtPrice(avgPrice(m().cost)) : "—"}</text>
|
||||
<text fg={theme.textMuted}>Family</text>
|
||||
<text fg={theme.text}>{m().family!.charAt(0).toUpperCase() + m().family!.slice(1)}</text>
|
||||
</box>
|
||||
</Show>
|
||||
</box>
|
||||
<Show when={m().release_date}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Released</text>
|
||||
<text fg={theme.text}>{fmtDate(m().release_date)}</text>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={!m().isFree}>
|
||||
<box>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Input</text>
|
||||
<text fg={theme.text}>{m() ? fmtPrice(m().cost.input) : "—"}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Output</text>
|
||||
<text fg={theme.text}>{m() ? fmtPrice(m().cost.output) : "—"}</text>
|
||||
</box>
|
||||
<Show when={cached()}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Cached</text>
|
||||
<text fg={theme.text}>{cached()}</text>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={avg() !== undefined}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Avg Cost</text>
|
||||
<text fg={theme.text}>{m() ? fmtPrice(avg()!) : "—"}</text>
|
||||
</box>
|
||||
</Show>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Context</text>
|
||||
<text fg={theme.text}>{m() ? fmtContext(m().limit.context) : "—"}</text>
|
||||
</box>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={caps()?.reasoning || inputLine() || outputLine()}>
|
||||
<box flexDirection="column">
|
||||
<Show when={caps()?.reasoning}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Reasoning</text>
|
||||
<text fg={theme.text}>Yes</text>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={inputLine()}>
|
||||
{(line) => (
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Caps</text>
|
||||
<text fg={theme.text}>{line().replace(/^In:\s*/, "")}</text>
|
||||
</box>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={outputLine()}>
|
||||
{(line) => (
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>Out</text>
|
||||
<text fg={theme.text}>{line().replace(/^Out:\s*/, "")}</text>
|
||||
</box>
|
||||
)}
|
||||
</Show>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={desc()}>
|
||||
<text fg={theme.textMuted}>{" "}</text>
|
||||
<text fg={theme.textMuted} width={23}>
|
||||
{desc()}
|
||||
</text>
|
||||
</Show>
|
||||
</scrollbox>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user