mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-29 03:44:06 +08:00
refactor(ui): centralize provider usage components
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
"@kilocode/kilo-ui": minor
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"./app-icon": "./src/components/app-icon.tsx",
|
||||
"./markdown": "./src/components/markdown.tsx",
|
||||
"./keybind": "./src/components/keybind.tsx",
|
||||
"./kilo-pass-meter": "./src/components/kilo-pass-meter.tsx",
|
||||
"./popover": "./src/components/popover.tsx",
|
||||
"./hover-card": "./src/components/hover-card.tsx",
|
||||
"./dropdown-menu": "./src/components/dropdown-menu.tsx",
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
padding: 8px;
|
||||
border: 1px solid var(--border-weak-base);
|
||||
|
||||
[data-slot="card-header"] {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
&[data-variant="error"] {
|
||||
padding: 12px;
|
||||
background-color: color-mix(in srgb, var(--surface-critical-strong) 10%, var(--surface-inset-base));
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
import { type ComponentProps, splitProps } from "solid-js"
|
||||
|
||||
export * from "@opencode-ai/ui/card"
|
||||
|
||||
export function CardHeader(props: ComponentProps<"div">) {
|
||||
const [local, rest] = splitProps(props, ["children", "class", "classList"])
|
||||
return (
|
||||
<div {...rest} data-slot="card-header" classList={{ ...local.classList, [local.class ?? ""]: !!local.class }}>
|
||||
{local.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
[data-component="kilo-pass-meter"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
|
||||
[data-slot="kilo-pass-meter-header"] {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
color: var(--text-base);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-header"] strong {
|
||||
text-align: right;
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-track"] {
|
||||
position: relative;
|
||||
height: 8px;
|
||||
overflow: visible;
|
||||
border-radius: 4px;
|
||||
background: var(--border-weak-base);
|
||||
}
|
||||
|
||||
:is(
|
||||
[data-slot="kilo-pass-meter-paid-background"],
|
||||
[data-slot="kilo-pass-meter-bonus-background"],
|
||||
[data-slot="kilo-pass-meter-paid-fill"],
|
||||
[data-slot="kilo-pass-meter-bonus-fill"]
|
||||
) {
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-paid-background"] {
|
||||
left: 0;
|
||||
background: color-mix(in srgb, var(--icon-warning-base) 18%, transparent);
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-bonus-background"] {
|
||||
background: color-mix(in srgb, var(--icon-success-base) 18%, transparent);
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-paid-fill"] {
|
||||
left: 0;
|
||||
border-radius: 4px 0 0 4px;
|
||||
background: var(--icon-warning-base);
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-bonus-fill"] {
|
||||
border-radius: 0 4px 4px 0;
|
||||
background: var(--icon-success-base);
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-boundary"] {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 2px;
|
||||
height: 5px;
|
||||
margin-left: -1px;
|
||||
background: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-amounts"] {
|
||||
position: relative;
|
||||
height: 15px;
|
||||
color: var(--icon-warning-base);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-amounts"] > span {
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-bonus-amount"] {
|
||||
right: 0;
|
||||
left: auto !important;
|
||||
color: var(--icon-success-base);
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-legend"],
|
||||
[data-slot="kilo-pass-meter-legend"] > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-legend"] {
|
||||
justify-content: space-between;
|
||||
color: var(--text-weak);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-legend"] > span {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-legend"] i {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-paid-dot"] {
|
||||
background: var(--icon-warning-base);
|
||||
}
|
||||
|
||||
[data-slot="kilo-pass-meter-bonus-dot"] {
|
||||
background: var(--icon-success-base);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { type ComponentProps, type JSX, splitProps } from "solid-js"
|
||||
|
||||
export interface KiloPassMeterProps extends Omit<ComponentProps<"div">, "children"> {
|
||||
used: number
|
||||
paid: number
|
||||
bonus: number
|
||||
label: JSX.Element
|
||||
paidLabel: JSX.Element
|
||||
bonusLabel: JSX.Element
|
||||
format: (value: number) => string
|
||||
}
|
||||
|
||||
export function KiloPassMeter(props: KiloPassMeterProps) {
|
||||
const [local, rest] = splitProps(props, [
|
||||
"used",
|
||||
"paid",
|
||||
"bonus",
|
||||
"label",
|
||||
"paidLabel",
|
||||
"bonusLabel",
|
||||
"format",
|
||||
"class",
|
||||
"classList",
|
||||
])
|
||||
const model = () => {
|
||||
const paid = Math.max(0, local.paid)
|
||||
const bonus = Math.max(0, local.bonus)
|
||||
const used = Math.max(0, local.used)
|
||||
const total = paid + bonus
|
||||
const boundary = total > 0 ? (paid / total) * 100 : 100
|
||||
const filled = total > 0 ? Math.min(100, (used / total) * 100) : 0
|
||||
return {
|
||||
paid,
|
||||
bonus,
|
||||
used,
|
||||
total,
|
||||
boundary,
|
||||
paidFill: Math.min(filled, boundary),
|
||||
bonusFill: Math.max(0, filled - boundary),
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
{...rest}
|
||||
data-component="kilo-pass-meter"
|
||||
role="meter"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={Math.max(model().total, 1)}
|
||||
aria-valuenow={Math.min(model().used, Math.max(model().total, 1))}
|
||||
aria-valuetext={`${local.format(model().used)} / ${local.format(model().total)}`}
|
||||
classList={{ ...local.classList, [local.class ?? ""]: !!local.class }}
|
||||
>
|
||||
<div data-slot="kilo-pass-meter-header">
|
||||
<span>{local.label}</span>
|
||||
<strong>
|
||||
{local.format(model().used)} / {local.format(model().total)}
|
||||
</strong>
|
||||
</div>
|
||||
<div data-slot="kilo-pass-meter-track" aria-hidden="true">
|
||||
<div data-slot="kilo-pass-meter-paid-background" style={{ width: `${model().boundary}%` }} />
|
||||
<div
|
||||
data-slot="kilo-pass-meter-bonus-background"
|
||||
hidden={model().bonus <= 0}
|
||||
style={{ left: `${model().boundary}%`, width: `${100 - model().boundary}%` }}
|
||||
/>
|
||||
<div data-slot="kilo-pass-meter-paid-fill" style={{ width: `${model().paidFill}%` }} />
|
||||
<div
|
||||
data-slot="kilo-pass-meter-bonus-fill"
|
||||
hidden={model().bonusFill <= 0}
|
||||
style={{ left: `${model().boundary}%`, width: `${model().bonusFill}%` }}
|
||||
/>
|
||||
<div
|
||||
data-slot="kilo-pass-meter-boundary"
|
||||
hidden={model().bonus <= 0}
|
||||
style={{ left: `${model().boundary}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div data-slot="kilo-pass-meter-amounts" aria-hidden="true">
|
||||
<span style={{ left: `${model().boundary}%` }}>{local.format(model().paid)}</span>
|
||||
<span data-slot="kilo-pass-meter-bonus-amount" hidden={model().bonus <= 0}>
|
||||
{local.format(model().bonus)}
|
||||
</span>
|
||||
</div>
|
||||
<div data-slot="kilo-pass-meter-legend">
|
||||
<span>
|
||||
<i data-slot="kilo-pass-meter-paid-dot" aria-hidden="true" />
|
||||
{local.paidLabel}
|
||||
</span>
|
||||
<span hidden={model().bonus <= 0}>
|
||||
<i data-slot="kilo-pass-meter-bonus-dot" aria-hidden="true" />
|
||||
{local.bonusLabel}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { Card } from "@opencode-ai/ui/card"
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "../components/card"
|
||||
import { Tag } from "../components/tag"
|
||||
|
||||
const meta: Meta<typeof Card> = {
|
||||
title: "Components/Card",
|
||||
@@ -33,6 +34,20 @@ export const Info: Story = {
|
||||
args: { variant: "info", children: "This is an info card" },
|
||||
}
|
||||
|
||||
export const WithHeader: Story = {
|
||||
render: () => (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div>
|
||||
<CardTitle icon={false}>MiniMax</CardTitle>
|
||||
<CardDescription>Token Plan Plus</CardDescription>
|
||||
</div>
|
||||
<Tag>Direct</Tag>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
),
|
||||
}
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", "flex-direction": "column", gap: "8px", width: "300px" }}>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { KiloPassMeter } from "../components/kilo-pass-meter"
|
||||
|
||||
const meta: Meta<typeof KiloPassMeter> = {
|
||||
title: "Components/Kilo Pass Meter",
|
||||
component: KiloPassMeter,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ padding: "16px", width: "320px" }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
parameters: { layout: "centered" },
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof KiloPassMeter>
|
||||
|
||||
const format = (value: number) => `$${value.toFixed(2)}`
|
||||
const render = (used: number, paid: number, bonus: number) => (
|
||||
<KiloPassMeter
|
||||
used={used}
|
||||
paid={paid}
|
||||
bonus={bonus}
|
||||
label="This month's usage"
|
||||
paidLabel="Paid"
|
||||
bonusLabel="Bonus"
|
||||
format={format}
|
||||
aria-label="Kilo Pass monthly usage"
|
||||
/>
|
||||
)
|
||||
|
||||
export const CurrentPlan: Story = { render: () => render(73.27, 199, 99.5) }
|
||||
export const UsingBonus: Story = { render: () => render(240, 199, 99.5) }
|
||||
export const PaidOnly: Story = { render: () => render(73.27, 199, 0) }
|
||||
export const Empty: Story = { render: () => render(0, 0, 0) }
|
||||
export const OverLimit: Story = { render: () => render(325, 199, 99.5) }
|
||||
@@ -22,6 +22,7 @@
|
||||
@import "../components/error-details.css";
|
||||
@import "../components/icon-button.css";
|
||||
@import "../components/inline-input.css";
|
||||
@import "../components/kilo-pass-meter.css";
|
||||
@import "../components/list.css";
|
||||
@import "../components/markdown.css";
|
||||
@import "../components/message-part.css";
|
||||
|
||||
@@ -2,7 +2,8 @@ import { Component, For, Show } from "solid-js"
|
||||
import type { KiloPassState, ProviderUsageData } from "../../types/messages"
|
||||
import type { ProviderUsageSnapshot } from "@kilocode/sdk/v2/client"
|
||||
import { Button } from "@kilocode/kilo-ui/button"
|
||||
import { Card, CardActions, CardDescription, CardTitle } from "@kilocode/kilo-ui/card"
|
||||
import { Card, CardActions, CardDescription, CardHeader, CardTitle } from "@kilocode/kilo-ui/card"
|
||||
import { KiloPassMeter } from "@kilocode/kilo-ui/kilo-pass-meter"
|
||||
import { Progress } from "@kilocode/kilo-ui/progress"
|
||||
import { Spinner } from "@kilocode/kilo-ui/spinner"
|
||||
import { Tag } from "@kilocode/kilo-ui/tag"
|
||||
@@ -55,17 +56,19 @@ const UsageCard: Component<{
|
||||
onOpen: (url: string) => void
|
||||
language: Language
|
||||
}> = (props) => (
|
||||
<Card class="provider-usage-card" variant={variant(props.item)}>
|
||||
<div class="provider-usage-heading">
|
||||
<Card variant={variant(props.item)}>
|
||||
<CardHeader>
|
||||
<div>
|
||||
<CardTitle icon={false}>{props.item.providerLabel}</CardTitle>
|
||||
<CardTitle icon={false} role="heading" aria-level={4}>
|
||||
{props.item.providerLabel}
|
||||
</CardTitle>
|
||||
<CardDescription>{props.item.planLabel}</CardDescription>
|
||||
</div>
|
||||
<Tag>{source(props.item, props.language)}</Tag>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<Show when={props.item.planState !== "active"}>
|
||||
<p class="provider-usage-meta">
|
||||
<CardDescription>
|
||||
{props.language.t(
|
||||
props.item.planState === "past_due"
|
||||
? "profile.usage.plan.pastDue"
|
||||
@@ -73,7 +76,7 @@ const UsageCard: Component<{
|
||||
? "profile.usage.plan.canceling"
|
||||
: "profile.usage.plan.unknown",
|
||||
)}
|
||||
</p>
|
||||
</CardDescription>
|
||||
</Show>
|
||||
|
||||
<Show when={props.item.fetchState !== "ready"}>
|
||||
@@ -88,25 +91,33 @@ const UsageCard: Component<{
|
||||
<For each={props.item.windows}>
|
||||
{(window) => {
|
||||
const progress = () => windowProgress(window)
|
||||
const value = () => formatWindowValue(window, labels(props.language))
|
||||
return (
|
||||
<div class="provider-usage-row">
|
||||
<div class="provider-usage-row-heading">
|
||||
<span>{window.label}</span>
|
||||
<strong>{formatWindowValue(window, labels(props.language))}</strong>
|
||||
</div>
|
||||
<Show when={progress() !== undefined}>
|
||||
<Progress
|
||||
value={progress()}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
aria-label={`${window.label}: ${formatWindowValue(window, labels(props.language))}`}
|
||||
/>
|
||||
showValueLabel
|
||||
getValueLabel={value}
|
||||
aria-label={`${window.label}: ${value()}`}
|
||||
aria-valuetext={value()}
|
||||
>
|
||||
{window.label}
|
||||
</Progress>
|
||||
</Show>
|
||||
<Show when={progress() === undefined}>
|
||||
<div class="provider-usage-summary">
|
||||
<span>{window.label}</span>
|
||||
<strong>{value()}</strong>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={window.resetAt}>
|
||||
{(reset) => (
|
||||
<span class="provider-usage-meta">
|
||||
<CardDescription>
|
||||
{props.language.t("profile.usage.reset", { date: new Date(reset()).toLocaleString() })}
|
||||
</span>
|
||||
</CardDescription>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
@@ -140,74 +151,7 @@ const UsageCard: Component<{
|
||||
</Card>
|
||||
)
|
||||
|
||||
const KiloPassUsage: Component<{ pass: KiloPassState }> = (props) => {
|
||||
const language = useLanguage()
|
||||
const model = () => {
|
||||
const paid = Math.max(0, props.pass.currentPeriodBaseCreditsUsd)
|
||||
const bonus = Math.max(0, props.pass.currentPeriodBonusCreditsUsd)
|
||||
const used = Math.max(0, props.pass.currentPeriodUsageUsd)
|
||||
const total = paid + bonus
|
||||
const paidWidth = total > 0 ? (paid / total) * 100 : 100
|
||||
const usedWidth = total > 0 ? Math.min(100, (used / total) * 100) : 0
|
||||
return {
|
||||
paid,
|
||||
bonus,
|
||||
used,
|
||||
total,
|
||||
paidWidth,
|
||||
paidFill: Math.min(usedWidth, paidWidth),
|
||||
bonusFill: Math.max(0, usedWidth - paidWidth),
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div class="provider-usage-row kilo-pass-usage">
|
||||
<div class="provider-usage-row-heading">
|
||||
<span>This month's usage</span>
|
||||
<strong>
|
||||
${model().used.toFixed(2)} / ${model().total.toFixed(2)}
|
||||
</strong>
|
||||
</div>
|
||||
<div
|
||||
class="kilo-pass-usage-track"
|
||||
role="progressbar"
|
||||
aria-label={`Kilo Pass: $${model().used.toFixed(2)} used of $${model().total.toFixed(2)}`}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={model().total}
|
||||
aria-valuenow={model().used}
|
||||
>
|
||||
<div class="kilo-pass-usage-paid-background" style={{ width: `${model().paidWidth}%` }} />
|
||||
<div
|
||||
class="kilo-pass-usage-bonus-background"
|
||||
hidden={model().bonus <= 0}
|
||||
style={{ left: `${model().paidWidth}%`, width: `${100 - model().paidWidth}%` }}
|
||||
/>
|
||||
<div class="kilo-pass-usage-paid-fill" style={{ width: `${model().paidFill}%` }} />
|
||||
<div
|
||||
class="kilo-pass-usage-bonus-fill"
|
||||
hidden={model().bonusFill <= 0}
|
||||
style={{ left: `${model().paidWidth}%`, width: `${model().bonusFill}%` }}
|
||||
/>
|
||||
<div class="kilo-pass-usage-boundary" hidden={model().bonus <= 0} style={{ left: `${model().paidWidth}%` }} />
|
||||
</div>
|
||||
<div class="kilo-pass-usage-amounts">
|
||||
<span style={{ left: `${model().paidWidth}%` }}>${model().paid.toFixed(2)}</span>
|
||||
<span class="kilo-pass-usage-bonus-amount" hidden={model().bonus <= 0}>
|
||||
${model().bonus.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="kilo-pass-usage-legend">
|
||||
<span>
|
||||
<i class="kilo-pass-usage-paid-dot" />
|
||||
Paid
|
||||
</span>
|
||||
<span hidden={model().bonus <= 0}>
|
||||
<i class="kilo-pass-usage-bonus-dot" />
|
||||
{language.t("profile.pass.bonus")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const money = (value: number) => `$${value.toFixed(2)}`
|
||||
|
||||
const KiloPassCard: Component<{
|
||||
pass?: KiloPassState | null
|
||||
@@ -226,14 +170,16 @@ const KiloPassCard: Component<{
|
||||
}).format(date)
|
||||
}
|
||||
return (
|
||||
<Card class="provider-usage-card">
|
||||
<div class="provider-usage-heading">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div>
|
||||
<CardTitle icon={false}>Kilo</CardTitle>
|
||||
<CardTitle icon={false} role="heading" aria-level={4}>
|
||||
Kilo
|
||||
</CardTitle>
|
||||
<CardDescription>Kilo Pass</CardDescription>
|
||||
</div>
|
||||
<Tag>Kilo Gateway</Tag>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<Show
|
||||
when={props.pass}
|
||||
fallback={
|
||||
@@ -246,10 +192,19 @@ const KiloPassCard: Component<{
|
||||
>
|
||||
{(pass) => (
|
||||
<div class="provider-usage-resources">
|
||||
<KiloPassUsage pass={pass()} />
|
||||
<KiloPassMeter
|
||||
used={pass().currentPeriodUsageUsd}
|
||||
paid={pass().currentPeriodBaseCreditsUsd}
|
||||
bonus={pass().currentPeriodBonusCreditsUsd}
|
||||
label="This month's usage"
|
||||
paidLabel="Paid"
|
||||
bonusLabel={props.language.t("profile.pass.bonus")}
|
||||
format={money}
|
||||
aria-label="Kilo Pass monthly usage"
|
||||
/>
|
||||
<Show when={renewal()}>
|
||||
{(date) => (
|
||||
<div class="provider-usage-row-heading">
|
||||
<div class="provider-usage-summary">
|
||||
<span>{props.language.t("profile.pass.renews")}</span>
|
||||
<strong>{date()}</strong>
|
||||
</div>
|
||||
@@ -292,13 +247,13 @@ export const ProviderUsageCards: Component<ProviderUsageCardsProps> = (props) =>
|
||||
<Show
|
||||
when={!props.loading && props.error}
|
||||
fallback={
|
||||
<div class="provider-usage-loading">
|
||||
<div class="provider-usage-loading" role="status" aria-label={language.t("profile.usage.title")}>
|
||||
<Spinner />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{(error) => (
|
||||
<Card variant="warning">
|
||||
<Card variant="warning" role="alert">
|
||||
<CardDescription>{error()}</CardDescription>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Provider Usage Cards */
|
||||
/* Provider Usage */
|
||||
.provider-usage-section {
|
||||
margin-top: 24px;
|
||||
padding-top: 18px;
|
||||
@@ -6,8 +6,7 @@
|
||||
}
|
||||
|
||||
.provider-usage-section-heading,
|
||||
.provider-usage-heading,
|
||||
.provider-usage-row-heading {
|
||||
.provider-usage-summary {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
@@ -20,18 +19,17 @@
|
||||
|
||||
.provider-usage-section-heading h3 {
|
||||
margin: 0;
|
||||
font-size: var(--kilo-font-size-14);
|
||||
font-weight: 650;
|
||||
color: var(--vscode-foreground);
|
||||
color: var(--text-base);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.provider-usage-section-heading p,
|
||||
.provider-usage-meta,
|
||||
.provider-usage-notice {
|
||||
margin: 3px 0 0;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: var(--kilo-font-size-11);
|
||||
line-height: 1.45;
|
||||
color: var(--text-weak);
|
||||
font-size: var(--font-size-small);
|
||||
line-height: var(--line-height-large);
|
||||
}
|
||||
|
||||
.provider-usage-list,
|
||||
@@ -41,129 +39,26 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.provider-usage-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.provider-usage-heading [data-slot="card-title"] {
|
||||
font-size: var(--kilo-font-size-13);
|
||||
}
|
||||
|
||||
.provider-usage-row {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.provider-usage-row-heading {
|
||||
.provider-usage-summary {
|
||||
align-items: baseline;
|
||||
color: var(--vscode-foreground);
|
||||
font-size: var(--kilo-font-size-12);
|
||||
color: var(--text-base);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.provider-usage-row-heading strong {
|
||||
.provider-usage-summary strong {
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.kilo-pass-usage-track {
|
||||
position: relative;
|
||||
height: 8px;
|
||||
overflow: visible;
|
||||
border-radius: 4px;
|
||||
background: var(--border-weak-base);
|
||||
}
|
||||
|
||||
.kilo-pass-usage-paid-background,
|
||||
.kilo-pass-usage-bonus-background,
|
||||
.kilo-pass-usage-paid-fill,
|
||||
.kilo-pass-usage-bonus-fill {
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
}
|
||||
|
||||
.kilo-pass-usage-paid-background {
|
||||
left: 0;
|
||||
background: color-mix(in srgb, var(--vscode-editorWarning-foreground, #cca700) 18%, transparent);
|
||||
}
|
||||
|
||||
.kilo-pass-usage-bonus-background {
|
||||
background: color-mix(in srgb, var(--vscode-testing-iconPassed, #73c991) 18%, transparent);
|
||||
}
|
||||
|
||||
.kilo-pass-usage-paid-fill {
|
||||
left: 0;
|
||||
border-radius: 4px 0 0 4px;
|
||||
background: var(--vscode-editorWarning-foreground, #cca700);
|
||||
}
|
||||
|
||||
.kilo-pass-usage-bonus-fill {
|
||||
border-radius: 0 4px 4px 0;
|
||||
background: var(--vscode-testing-iconPassed, #73c991);
|
||||
}
|
||||
|
||||
.kilo-pass-usage-boundary {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 2px;
|
||||
height: 5px;
|
||||
margin-left: -1px;
|
||||
background: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.kilo-pass-usage-amounts {
|
||||
position: relative;
|
||||
height: 15px;
|
||||
color: var(--vscode-editorWarning-foreground, #cca700);
|
||||
font-size: var(--kilo-font-size-11);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.kilo-pass-usage-amounts > span {
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.kilo-pass-usage-amounts .kilo-pass-usage-bonus-amount {
|
||||
right: 0;
|
||||
left: auto !important;
|
||||
color: var(--vscode-testing-iconPassed, #73c991);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.kilo-pass-usage-legend,
|
||||
.kilo-pass-usage-legend > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.kilo-pass-usage-legend {
|
||||
justify-content: space-between;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: var(--kilo-font-size-11);
|
||||
}
|
||||
|
||||
.kilo-pass-usage-legend > span {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.kilo-pass-usage-legend i {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.kilo-pass-usage-paid-dot {
|
||||
background: var(--vscode-editorWarning-foreground, #cca700);
|
||||
}
|
||||
|
||||
.kilo-pass-usage-bonus-dot {
|
||||
background: var(--vscode-testing-iconPassed, #73c991);
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.provider-usage-notice {
|
||||
padding-left: 8px;
|
||||
border-left: 2px solid var(--vscode-editorWarning-foreground);
|
||||
border-left: 2px solid var(--icon-warning-base);
|
||||
}
|
||||
|
||||
.provider-usage-loading {
|
||||
@@ -172,14 +67,8 @@
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.provider-usage-loading [data-component="spinner"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.provider-usage-section-heading,
|
||||
.provider-usage-heading {
|
||||
.provider-usage-section-heading {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user