mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): migrate virtual-keys (VirtualKeysTable + BudgetWindowsEditor) to shadcn
Scope: section-2 stress-test of the locked phase-1 blueprint.
- VirtualKeysTable.tsx (700+ lines):
- @tremor/react Table + TableHead/Body/Row/Cell \u2192 shadcn Table equivalents
- @tremor/react Badge / Icon / Text / Button \u2192 shadcn Badge + lucide icons +
Tailwind semantic tokens
- @heroicons/react ChevronDown/ChevronRight/ChevronUp/SwitchVertical \u2192 lucide
Chevron* + ChevronsUpDown
- @ant-design/icons InfoCircleOutlined / SyncOutlined \u2192 lucide Info / RefreshCw
- antd Popover / Tooltip / Button / Skeleton / Typography \u2192 shadcn equivalents
from @/components/ui/*. CopyableText helper replaces antd Typography.Text
copyable semantics (clipboard + sonner toast).
- Raw color classes (text-blue-500, bg-blue-50, text-gray-700, hover:bg-gray-50,
bg-white) replaced with semantic tokens (text-primary, bg-primary/10,
text-muted-foreground, hover:bg-muted, bg-background).
- BudgetWindowsEditor.tsx: antd Button/InputNumber/Select \u2192 shadcn
Button/Input/Select. Antd's InputNumber `prefix="$"` preserved as absolute-
positioned span adjacent to the input (shadcn Input has no prefix prop).
Blueprint stress-test result: NO new patterns required. Every antd /
@tremor / @heroicons / @ant-design/icons import encountered maps cleanly
to an entry already in BLUEPRINT.md. The blueprint is considered final
and is now permanently locked for the rest of phase 1.
Gates: TS \u2713 | Lint \u2713 | Vitest 44/44 \u2713 | Build \u2713. Playwright
parity + snapshot gates deferred per the DEVIATIONS.md environmental-gate
skip.
Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
f49d66daea
commit
b8d48fecca
3 changed files with 732 additions and 583 deletions
|
|
@ -23,5 +23,16 @@ Layer abbreviations: **TS** (tsc --noEmit), **Lint** (eslint), **Vitest**
|
|||
|
||||
## 2. Virtual Keys (api-keys)
|
||||
|
||||
_(pending)_
|
||||
- Cycles used: 1 / 7
|
||||
- Layer outcomes per cycle:
|
||||
- cycle 1: TS ✓ | Lint ✓ | Vitest ✓ (44/44) | Parity ⏭ | Snap ⏭
|
||||
- Final status: **done (scoped to VirtualKeysTable + BudgetWindowsEditor)**
|
||||
- Section scope: only the files directly under `src/components/VirtualKeysPage/` and
|
||||
`src/components/key_team_helpers/` that imported banned libs. Heavier key-related
|
||||
sub-components (e.g. `templates/key_info_view`, `organisms/create_key_button`,
|
||||
`KeyAliasSelect/*`, `DeletedKeysPage/*`) are outside this section's file set and
|
||||
will be picked up by either their own future sections or the final chrome sweep.
|
||||
- Blueprint stress-test outcome: no new patterns required. The existing blueprint
|
||||
(Table + @tanstack/react-table, Popover, Tooltip, Badge variants, Skeleton) covered
|
||||
every antd / @tremor / @heroicons import encountered.
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,14 @@
|
|||
import { Button, InputNumber, Select } from "antd";
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
export interface BudgetWindowEntry {
|
||||
budget_duration: string;
|
||||
|
|
@ -7,10 +16,10 @@ export interface BudgetWindowEntry {
|
|||
}
|
||||
|
||||
export const BUDGET_WINDOW_OPTIONS = [
|
||||
{ value: "1h", label: "Hourly", resetHint: "Resets every hour" },
|
||||
{ value: "24h", label: "Daily", resetHint: "Resets daily at midnight UTC" },
|
||||
{ value: "7d", label: "Weekly", resetHint: "Resets every Sunday at midnight UTC" },
|
||||
{ value: "30d", label: "Monthly", resetHint: "Resets on the 1st of every month at midnight UTC" },
|
||||
{ value: "1h", label: "Hourly", resetHint: "Resets every hour" },
|
||||
{ value: "24h", label: "Daily", resetHint: "Resets daily at midnight UTC" },
|
||||
{ value: "7d", label: "Weekly", resetHint: "Resets every Sunday at midnight UTC" },
|
||||
{ value: "30d", label: "Monthly", resetHint: "Resets on the 1st of every month at midnight UTC" },
|
||||
];
|
||||
|
||||
interface BudgetWindowsEditorProps {
|
||||
|
|
@ -27,7 +36,11 @@ export function BudgetWindowsEditor({ value, onChange }: BudgetWindowsEditorProp
|
|||
onChange(value.filter((_, i) => i !== idx));
|
||||
};
|
||||
|
||||
const updateWindow = (idx: number, field: keyof BudgetWindowEntry, fieldValue: string | number | null) => {
|
||||
const updateWindow = (
|
||||
idx: number,
|
||||
field: keyof BudgetWindowEntry,
|
||||
fieldValue: string | number | null,
|
||||
) => {
|
||||
const updated = value.map((w, i) => (i === idx ? { ...w, [field]: fieldValue } : w));
|
||||
onChange(updated);
|
||||
};
|
||||
|
|
@ -35,38 +48,57 @@ export function BudgetWindowsEditor({ value, onChange }: BudgetWindowsEditorProp
|
|||
return (
|
||||
<div>
|
||||
{value.map((window, idx) => {
|
||||
const hint = BUDGET_WINDOW_OPTIONS.find((o) => o.value === window.budget_duration)?.resetHint;
|
||||
const hint = BUDGET_WINDOW_OPTIONS.find(
|
||||
(o) => o.value === window.budget_duration,
|
||||
)?.resetHint;
|
||||
return (
|
||||
<div key={idx} style={{ marginBottom: 12 }}>
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||||
<div key={idx} className="mb-3">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Select
|
||||
value={window.budget_duration}
|
||||
onChange={(v) => updateWindow(idx, "budget_duration", v)}
|
||||
style={{ width: 130 }}
|
||||
options={BUDGET_WINDOW_OPTIONS.map((o) => ({ value: o.value, label: o.label }))}
|
||||
/>
|
||||
<InputNumber
|
||||
step={0.01}
|
||||
min={0}
|
||||
precision={2}
|
||||
value={window.max_budget ?? undefined}
|
||||
onChange={(v) => updateWindow(idx, "max_budget", v ?? null)}
|
||||
placeholder="Max spend ($)"
|
||||
style={{ width: 160 }}
|
||||
prefix="$"
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
size="small"
|
||||
onClick={() => removeWindow(idx)}
|
||||
style={{ padding: "0 4px" }}
|
||||
onValueChange={(v) => updateWindow(idx, "budget_duration", v)}
|
||||
>
|
||||
✕
|
||||
<SelectTrigger className="w-[130px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{BUDGET_WINDOW_OPTIONS.map((o) => (
|
||||
<SelectItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="relative w-[160px]">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground text-sm pointer-events-none">
|
||||
$
|
||||
</span>
|
||||
<Input
|
||||
type="number"
|
||||
step={0.01}
|
||||
min={0}
|
||||
value={window.max_budget ?? ""}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
updateWindow(idx, "max_budget", v === "" ? null : Number(v));
|
||||
}}
|
||||
placeholder="Max spend"
|
||||
className="pl-6"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => removeWindow(idx)}
|
||||
className="text-destructive hover:text-destructive"
|
||||
aria-label="Remove budget window"
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
{hint && (
|
||||
<div style={{ fontSize: 11, color: "#888", marginTop: 3, marginLeft: 2 }}>
|
||||
<div className="text-[11px] text-muted-foreground mt-1 ml-0.5">
|
||||
↻ {hint}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -74,8 +106,13 @@ export function BudgetWindowsEditor({ value, onChange }: BudgetWindowsEditorProp
|
|||
);
|
||||
})}
|
||||
<Button
|
||||
size="small"
|
||||
onClick={(e) => { e.preventDefault(); addWindow(); }}
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
addWindow();
|
||||
}}
|
||||
>
|
||||
+ Add Budget Window
|
||||
</Button>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue