Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ui/litellm-dashboard/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2974,6 +2974,11 @@
"count": 1
}
},
"src/components/ui/button-group.tsx": {
"local/filename-pascal-case": {
"count": 1
}
},
"src/components/ui/button.tsx": {
"local/filename-pascal-case": {
"count": 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@
});

const [lastRefreshed, setLastRefreshed] = useState("");
const [healthCheckResponse, setHealthCheckResponse] = useState<any>("");

Check warning on line 83 in ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type

const { data: activity, refetch } = useCacheActivity({

Check warning on line 85 in ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Object literal with 4 properties passed inline as an argument; assign it to a named variable first
startDate: formatDateWithoutTZ(dateValue.from),
endDate: formatDateWithoutTZ(dateValue.to),
keyAliases: selectedApiKeys,
Expand All @@ -108,7 +108,7 @@
setHealthCheckResponse("");
const response = await cachingHealthCheckCall(accessToken !== null ? accessToken : "");
setHealthCheckResponse(response);
} catch (error: any) {

Check warning on line 111 in ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
console.error("Error running health check:", error);
let errorData;
if (error && error.message) {
Expand Down Expand Up @@ -190,7 +190,7 @@
Metrics&quot; on the Usage page or individual requests in the Logs page.
</p>

<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-3">
<div className="mt-4 grid grid-cols-1 items-center gap-4 md:grid-cols-3">
<Combobox
multiple
items={uniqueApiKeys}
Expand All @@ -207,7 +207,7 @@
))
}
</ComboboxValue>
<ComboboxChipsInput placeholder="Select Virtual Keys" className="border-0 bg-transparent" />
<ComboboxChipsInput placeholder="Select Virtual Keys" />
</ComboboxChips>
<ComboboxContent anchor={anchor1}>
<ComboboxEmpty>No virtual keys found</ComboboxEmpty>
Expand Down Expand Up @@ -237,7 +237,7 @@
))
}
</ComboboxValue>
<ComboboxChipsInput placeholder="Select Models" className="border-0 bg-transparent" />
<ComboboxChipsInput placeholder="Select Models" />
</ComboboxChips>
<ComboboxContent anchor={anchor2}>
<ComboboxEmpty>No models found</ComboboxEmpty>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const CostOptimizationView: React.FC<CostOptimizationViewProps> = ({ accessToken
</div>

<Tabs defaultValue="usage" onValueChange={handleTabChange}>
<TabsList variant="line" className="h-auto w-full justify-start rounded-none border-b p-0">
<TabsList variant="line" className="h-auto w-full justify-start rounded-none p-0">
<TabsTrigger value="usage" className="flex-none rounded-none px-4 py-2">
Overall
</TabsTrigger>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey) {

Check warning on line 64 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/GuardrailTestPanel.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Boolean expression combines 4 conditions; extract it into a named variable
e.preventDefault();
handleSubmit();
}
Expand Down Expand Up @@ -131,7 +131,7 @@
</div>

{/* Input Section */}
<div className="flex-1 overflow-auto space-y-4">
<div className="flex-1 space-y-4 overflow-auto px-1">
<div className="space-y-3">
<div>
<div className="mb-2 flex items-center justify-between">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@

useEffect(() => {
fetchGuardrails();
}, [accessToken]);

Check warning on line 64 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/GuardrailsPanel.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

React Hook useEffect has a missing dependency: 'fetchGuardrails'. Either include it or remove the dependency array

const handleAddGuardrail = () => {
if (selectedGuardrailId) {
Expand Down Expand Up @@ -126,7 +126,7 @@
return (
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
<Tabs defaultValue="guardrails">
<TabsList>
<TabsList variant="line">
{isAdmin && (
<>
<TabsTrigger value="garden" className="flex-none">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
mode?: string | string[];
default_on?: boolean;
custom_code?: string;
[key: string]: any;

Check warning on line 180 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/custom_code/CustomCodeModal.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
};
}

Expand All @@ -190,7 +190,7 @@
editData?: EditGuardrailData | null;
}

const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onSuccess, accessToken, editData }) => {

Check warning on line 193 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/custom_code/CustomCodeModal.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Arrow function has a complexity of 22. Maximum allowed is 20
const anchor = useComboboxAnchor();
const isEditMode = !!editData;
const [guardrailName, setGuardrailName] = useState("");
Expand Down Expand Up @@ -293,7 +293,7 @@
};

const [testInput, setTestInput] = useState(JSON.stringify(TEST_INPUT_EXAMPLES.pre_call.data, null, 2));
const [testResult, setTestResult] = useState<any>(null);

Check warning on line 296 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/custom_code/CustomCodeModal.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
const [copiedPrimitive, setCopiedPrimitive] = useState<string | null>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);

Expand Down Expand Up @@ -380,7 +380,7 @@
try {
if (isEditMode && editData) {
// Update existing guardrail
const updateData: any = {

Check warning on line 383 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/custom_code/CustomCodeModal.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
litellm_params: {
custom_code: code,
},
Expand Down Expand Up @@ -465,7 +465,7 @@
? "response"
: "request";

const response = await testCustomCodeGuardrail(accessToken, {

Check warning on line 468 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/custom_code/CustomCodeModal.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Object literal with 4 properties passed inline as an argument; assign it to a named variable first
custom_code: code,
test_input: parsedInput,
input_type: testInputType,
Expand Down Expand Up @@ -532,10 +532,7 @@
{option.label}
</ComboboxChip>
))}
<ComboboxChipsInput
className="border-0 bg-transparent"
placeholder={mode.length === 0 ? "Select modes" : undefined}
/>
<ComboboxChipsInput placeholder={mode.length === 0 ? "Select modes" : undefined} />
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>No matching modes</ComboboxEmpty>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export const CategoryFilter: React.FC<CategoryFilterProps> = ({ categories, sele
</ComboboxChip>
))}
<ComboboxChipsInput
className="border-0 bg-transparent"
placeholder={selectedCategories.length === 0 ? "Select categories to filter by" : undefined}
/>
</ComboboxChips>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export default function ModelsAndEndpointsPage() {
) : (
<Tabs value={activeKey} onValueChange={setActiveKey}>
<div className="flex min-w-0 flex-nowrap items-center gap-3 border-b">
<div className="-mb-1.5 min-w-0 flex-1 overflow-x-auto pb-1.5">
<div className="no-scrollbar scroll-fade-e -mb-1.5 min-w-0 flex-1 overflow-x-auto pb-1.5">
<TabsList variant="line" className="w-max justify-start">
{visibleSlugs.map((slug) => {
const key = slug || BASE_TAB_KEY;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
))
}
</ComboboxValue>
<ComboboxChipsInput placeholder="Select tags" className="border-0 bg-transparent" />
<ComboboxChipsInput placeholder="Select tags" />
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>No tags found</ComboboxEmpty>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,12 @@ export function ChatComposer({
return (
<div className={cn("relative flex w-full flex-col gap-3", className)}>
{showSuggestions && suggestions.length > 0 && (
<div
className="flex w-full gap-2 overflow-x-auto pb-1 sm:grid sm:grid-cols-2 sm:overflow-visible"
data-testid="chat-suggested-actions"
>
<div className="flex w-full flex-col gap-1.5" data-testid="chat-suggested-actions">
{suggestions.map((suggestion) => (
<button
key={suggestion}
type="button"
className="min-w-[200px] shrink-0 rounded-xl border border-border/50 bg-card/30 px-4 py-3 text-left text-[12px] leading-relaxed text-muted-foreground transition-all duration-200 hover:-translate-y-0.5 hover:bg-card/60 hover:text-foreground sm:min-w-0 sm:whitespace-normal sm:p-4 sm:text-[13px]"
className="w-full truncate rounded-lg border border-border/50 bg-card/30 px-3 py-1.5 text-left text-[12px] leading-snug text-muted-foreground transition-colors hover:bg-card/60 hover:text-foreground"
onClick={() => onSuggestionSelect?.(suggestion)}
>
{suggestion}
Expand Down
13 changes: 5 additions & 8 deletions ui/litellm-dashboard/src/app/(dashboard)/playground/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,17 @@ export default function PlaygroundPage() {
return (
<div className="flex h-full min-h-0 w-full min-w-0 flex-col overflow-hidden">
<Tabs defaultValue="chat" className="flex min-h-0 min-w-0 flex-1 flex-col gap-0 overflow-hidden">
<TabsList
variant="line"
className="mb-0 h-auto w-full shrink-0 justify-start overflow-x-auto rounded-none border-b border-border bg-transparent p-0"
>
<TabsTrigger value="chat" className="flex-none rounded-none px-4 py-2">
<TabsList variant="line" className="w-full shrink-0 justify-start overflow-x-auto pb-1">
<TabsTrigger value="chat" className="flex-none">
Chat
</TabsTrigger>
<TabsTrigger value="compare" className="flex-none rounded-none px-4 py-2">
<TabsTrigger value="compare" className="flex-none">
Compare
</TabsTrigger>
<TabsTrigger value="compliance" className="flex-none rounded-none px-4 py-2">
<TabsTrigger value="compliance" className="flex-none">
Compliance
</TabsTrigger>
<TabsTrigger value="agent-builder" className="flex-none rounded-none px-4 py-2">
<TabsTrigger value="agent-builder" className="flex-none">
Agent Builder (Experimental)
</TabsTrigger>
</TabsList>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,18 +675,15 @@ const EntityUsage: React.FC<EntityUsageProps> = ({
</AlertDescription>
</Alert>
)}
{entityType === "team" && (
<div className="mb-4">
<p className="mb-2 text-sm text-foreground">Filter by team</p>
<TeamMultiSelect value={selectedTags} onChange={setSelectedTags} />
</div>
)}
<UsageExportHeader
dateValue={dateValue}
entityType={entityType}
spendData={spendData}
showFilters={entityType !== "team" && entityList !== null && entityList.length > 0}
filterLabel={getFilterLabel(entityType)}
filterSlot={
entityType === "team" ? <TeamMultiSelect value={selectedTags} onChange={setSelectedTags} /> : undefined
}
filterLabel={entityType === "team" ? "Filter by team" : getFilterLabel(entityType)}
filterPlaceholder={getFilterPlaceholder(entityType)}
selectedFilters={selectedTags}
onFiltersChange={setSelectedTags}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ const VectorStoreManagement: React.FC<VectorStoreProps> = ({ accessToken, userID
</p>

<Tabs defaultValue="create" onValueChange={onTabChange}>
<TabsList variant="line" className="mb-6 h-auto w-full justify-start rounded-none border-b p-0">
<TabsList variant="line" className="mb-6 h-auto w-full justify-start rounded-none p-0">
<TabsTrigger value="create" className="flex-none rounded-none px-4 py-2">
Create Vector Store
</TabsTrigger>
Expand Down
72 changes: 72 additions & 0 deletions ui/litellm-dashboard/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,54 @@
}
}

/* Verbatim end-edge subset of the shadcn scroll-fade utility (shadcn@4.17.0 dist/tailwind.css),
vendored so the CLI package is not a build dependency. Re-copy from upstream to update. */
@property --scroll-fade-e {
Comment on lines +33 to +35

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Stylesheet adds maintenance commentary

These vendoring and update notes duplicate implementation history in source comments, increasing the chance that the explanation drifts from the CSS; the same pattern also appears in the new forms-plugin override

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

syntax: "<length-percentage>";
inherits: false;
initial-value: 0px;
}
@property --scroll-fade-mask {
syntax: "*";
inherits: false;
}

@theme inline {
@keyframes scroll-fade-reveal-e {
from {
--scroll-fade-e: var(--_scroll-fade-size-e, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
}
to {
--scroll-fade-e: 0px;
}
}
}

@utility scroll-fade-e {
--_scroll-fade-size-e: var(--scroll-fade-e-size, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
--scroll-fade-mask: linear-gradient(to right, #000 0, #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
&:where([dir="rtl"], [dir="rtl"] *) {
--scroll-fade-mask: linear-gradient(to left, #000 0, #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
}
-webkit-mask-image: var(--scroll-fade-mask);
mask-image: var(--scroll-fade-mask);
-webkit-mask-composite: source-in;
mask-composite: intersect;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;

@supports (animation-timeline: scroll()) {
animation: scroll-fade-reveal-e 1ms ease-in-out;
animation-timeline: scroll(self inline);
animation-range: calc(100% - var(--scroll-fade-reveal, calc(var(--spacing) * 24))) 100%;
animation-fill-mode: both;
}

@supports not (animation-timeline: scroll()) {
--scroll-fade-e: var(--_scroll-fade-size-e);
}
}

:root {
--radius: 0.5rem;
--background: oklch(1 0 0);
Expand Down Expand Up @@ -211,6 +259,30 @@
outline-color: color-mix(in oklab, var(--color-ring) 50%, transparent);
}

/* @tailwindcss/forms paints a 1px blue-600 ring via box-shadow on :focus, which `outline-none`
cannot cancel. Components that declare their own focus ring override this from the utilities
layer; the ones that do not inherit a blue rectangle. `:not([disabled])` is here only to
outweigh the plugin's own (0,1,1) selector, which Tailwind emits after this block. Delete
along with the plugin once no route renders a Tremor form control. */
:is(input, textarea, select):focus:not([disabled]) {
--tw-ring-color: initial;
--tw-ring-shadow: 0 0 #0000;
border-color: var(--color-border);
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

/* Same plugin, resting state: it also gives every bare input a border, a white fill, 0.5rem/0.75rem
padding and 1rem text. shadcn primitives style themselves and only break where a base class omits
those, which today is `combobox-chip-input` alone (audited against every input-rendering file in
components/ui). These values restore Tailwind's own preflight, and `[data-slot]` outweighs the
plugin's zero-specificity `input:where(:not([type]))`. Delete along with the plugin. */
[data-slot="combobox-chip-input"] {
border-width: 0;
background-color: transparent;
padding: 0;
font: inherit;
letter-spacing: inherit;
}

button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface UsageExportHeaderProps {
onFiltersChange?: (filters: string[]) => void;
filterOptions?: Array<{ label: string; value: string }>;
filterMode?: "multiple" | "single";
filterSlot?: React.ReactNode;
customTitle?: string;
compactLayout?: boolean;
teams?: Team[];
Expand All @@ -48,14 +49,15 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
onFiltersChange,
filterOptions = [],
filterMode = "multiple",
filterSlot,
customTitle,
compactLayout = false,
teams = [],
}) => {
const anchor = useComboboxAnchor();
const [isExportModalOpen, setIsExportModalOpen] = useState(false);

const hasFilters = showFilters && filterOptions.length > 0;
const hasFilters = filterSlot != null || (showFilters && filterOptions.length > 0);
const optionValues = filterOptions.map((option) => option.value);
const labelOf = (value: string) => filterOptions.find((option) => option.value === value)?.label ?? value;

Expand All @@ -72,6 +74,46 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
</ComboboxContent>
);

const builtInFilter =
filterMode === "single" ? (
<Combobox
items={optionValues}
value={selectedFilters[0] ?? null}
onValueChange={(next: string | null) => onFiltersChange?.(next ? [next] : [])}
itemToStringLabel={labelOf}
>
<ComboboxInput
className="w-full"
placeholder={filterPlaceholder}
aria-label={filterPlaceholder}
showClear={selectedFilters.length > 0}
/>
{filterList}
</Combobox>
) : (
<Combobox
multiple
items={optionValues}
value={selectedFilters}
onValueChange={(next: string[]) => onFiltersChange?.(next)}
>
<ComboboxChips render={<div ref={anchor} />} className="w-full">
<ComboboxValue>
{(selected: string[]) =>
selected.map((value) => (
<ComboboxChip key={value} aria-label={labelOf(value)}>
{labelOf(value)}
</ComboboxChip>
))
}
</ComboboxValue>
<ComboboxChipsInput placeholder={filterPlaceholder} aria-label={filterPlaceholder} />
{selectedFilters.length > 0 && <ComboboxClear aria-label={`Clear ${filterLabel ?? "filters"}`} />}
</ComboboxChips>
{filterList}
</Combobox>
);

return (
<>
<div className="mb-4">
Expand All @@ -84,48 +126,7 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
{hasFilters && (
<div>
{filterLabel && <label className="text-sm font-medium text-gray-700 block mb-2">{filterLabel}</label>}
{filterMode === "single" ? (
<Combobox
items={optionValues}
value={selectedFilters[0] ?? null}
onValueChange={(next: string | null) => onFiltersChange?.(next ? [next] : [])}
itemToStringLabel={labelOf}
>
<ComboboxInput
className="w-full"
placeholder={filterPlaceholder}
aria-label={filterPlaceholder}
showClear={selectedFilters.length > 0}
/>
{filterList}
</Combobox>
) : (
<Combobox
multiple
items={optionValues}
value={selectedFilters}
onValueChange={(next: string[]) => onFiltersChange?.(next)}
>
<ComboboxChips render={<div ref={anchor} />} className="w-full">
<ComboboxValue>
{(selected: string[]) =>
selected.map((value) => (
<ComboboxChip key={value} aria-label={labelOf(value)}>
{labelOf(value)}
</ComboboxChip>
))
}
</ComboboxValue>
<ComboboxChipsInput
className="border-0 bg-transparent"
placeholder={filterPlaceholder}
aria-label={filterPlaceholder}
/>
{selectedFilters.length > 0 && <ComboboxClear aria-label={`Clear ${filterLabel ?? "filters"}`} />}
</ComboboxChips>
{filterList}
</Combobox>
)}
{filterSlot ?? builtInFilter}
</div>
)}

Expand Down
Loading
Loading