KQ-2095 · SWAP/SAVINGS
Swap Savings
This month's assistant spend by task and model with a what-if mode: as you move a share of a task's traffic to another model, the from-model's bar drops and the to-model's bar rises to their projected heights on springs.glide — chasing the slider so they arrive just behind your hand — over ghost outlines that hold today's heights, while the savings figure rolls on springs.snap beside a quality note that turns warn past your floor. Each share is a real slider whose thumb follows the finger 1:1 and lands on its step on snap with the release velocity; arrows step it, Page Up and Page Down move a quarter, Home and End jump, R resets every share and Flip reverses the pair. In a narrow container the chart turns horizontal.
save $408 · quality 91 → 88 · 1 change · applied none
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/swap-savings
Props
- models
- SwapModel[]
- default defaultSwapModels
- The models on the bill: { id, name, short?, inputPrice, outputPrice } per million tokens.
- tasks
- SwapTask[]
- default defaultSwapTasks
- What the traffic is for, in chart order: { id, label }.
- usage
- SwapUsage[]
- default defaultSwapUsage
- This month's tokens per task and model: { task, model, inputTokens, outputTokens }.
- quality
- Record<string, Record<string, number>>
- default defaultSwapQuality
- Quality per model and task, 0–100, from your evals.
- fromModel
- string
- default the first model
- The model traffic moves away from; Flip swaps the pair.
- toModel
- string
- default the second model
- The model traffic moves to.
- mix / defaultMix
- Record<string, number>
- default — / {}
- Share of each task's traffic moved, 0 to 1, controlled or initial.
- onMixChange
- (mix) => void
- default —
- Fires from the slider, key or Reset that changed a share, with the whole mix.
- onApplyMix
- (mix, projection) => void
- default —
- Apply mix was pressed.
- onReset
- () => void
- default —
- Reset (or R) put every share back to today's.
- onCompare
- (projection) => void
- default —
- Compare was pressed. Omit it and the button is not drawn.
- status
- "ready" | "loading" | "insufficient" | "error"
- default "ready"
- Usage is ready, loading, too thin to project from, or failed.
- daysOfData
- number
- default 30
- Days of traffic so far, for the not-enough-data state.
- daysNeeded
- number
- default 14
- Days a projection needs.
- onRetry
- () => void
- default —
- Try again was pressed after usage failed to load.
- step
- number
- default 0.05
- How far one arrow key moves a share.
- qualityFloor
- number
- default 3
- A drop in quality past this many points is flagged.
- title
- string
- default "October spend by model"
- The panel's heading.
- formatMoney
- (amount: number) => string
- default "$1,240", by hand
- How money is printed.
- ghostStyle
- "dashed" | "shade" | "cap"
- default "dashed"
- How today's heights are kept while the bars move: a dashed outline, a faint shade, or a cap line.
- barWidth
- number
- default 32
- Bar thickness in px; the gap between a group's bars widens for thin bars so their labels never collide.
- accent
- "cobalt" | "plum" | "mint" | "ink" | string
- default "cobalt"
- The to-model's bars, the sliders and the savings: a named pigment or any CSS colour.
- disabled
- boolean
- default false
- Read only: shares cannot move.
- className
- string
- default —
- Classes for the panel.
| Prop | Type | Default | Description |
|---|---|---|---|
| models | SwapModel[] | defaultSwapModels | The models on the bill: { id, name, short?, inputPrice, outputPrice } per million tokens. |
| tasks | SwapTask[] | defaultSwapTasks | What the traffic is for, in chart order: { id, label }. |
| usage | SwapUsage[] | defaultSwapUsage | This month's tokens per task and model: { task, model, inputTokens, outputTokens }. |
| quality | Record<string, Record<string, number>> | defaultSwapQuality | Quality per model and task, 0–100, from your evals. |
| fromModel | string | the first model | The model traffic moves away from; Flip swaps the pair. |
| toModel | string | the second model | The model traffic moves to. |
| mix / defaultMix | Record<string, number> | — / {} | Share of each task's traffic moved, 0 to 1, controlled or initial. |
| onMixChange | (mix) => void | — | Fires from the slider, key or Reset that changed a share, with the whole mix. |
| onApplyMix | (mix, projection) => void | — | Apply mix was pressed. |
| onReset | () => void | — | Reset (or R) put every share back to today's. |
| onCompare | (projection) => void | — | Compare was pressed. Omit it and the button is not drawn. |
| status | "ready" | "loading" | "insufficient" | "error" | "ready" | Usage is ready, loading, too thin to project from, or failed. |
| daysOfData | number | 30 | Days of traffic so far, for the not-enough-data state. |
| daysNeeded | number | 14 | Days a projection needs. |
| onRetry | () => void | — | Try again was pressed after usage failed to load. |
| step | number | 0.05 | How far one arrow key moves a share. |
| qualityFloor | number | 3 | A drop in quality past this many points is flagged. |
| title | string | "October spend by model" | The panel's heading. |
| formatMoney | (amount: number) => string | "$1,240", by hand | How money is printed. |
| ghostStyle | "dashed" | "shade" | "cap" | "dashed" | How today's heights are kept while the bars move: a dashed outline, a faint shade, or a cap line. |
| barWidth | number | 32 | Bar thickness in px; the gap between a group's bars widens for thin bars so their labels never collide. |
| accent | "cobalt" | "plum" | "mint" | "ink" | string | "cobalt" | The to-model's bars, the sliders and the savings: a named pigment or any CSS colour. |
| disabled | boolean | false | Read only: shares cannot move. |
| className | string | — | Classes for the panel. |