mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): migrate tool-policies (PolicySelect) to shadcn
- PolicySelect.tsx: antd Select \u2192 shadcn Select + SelectTrigger/
Content/Item. Preserves categorical amber/emerald/red palette for the
untrusted/trusted/blocked policy badges (these are non-semantic values
by design). PolicySelect.tsx added to .eslintrc.json raw-colors override
list. Deviation recorded in DEVIATIONS.md.
- PolicySelect.test.tsx: replace `.ant-select` class assertion with
semantic `toBeDisabled()` against getByRole('combobox'), agnostic of
underlying Select primitive.
Gates: TS \u2713 | Lint \u2713 | Vitest 12/12 \u2713 | Build \u2713.
Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
01cc55886e
commit
b0ef2c70d4
5 changed files with 100 additions and 55 deletions
|
|
@ -22,7 +22,8 @@
|
|||
"src/components/ui/**/*.{ts,tsx}",
|
||||
"tailwind.config.ts",
|
||||
"src/components/molecules/notifications_manager.tsx",
|
||||
"src/components/molecules/message_manager.tsx"
|
||||
"src/components/molecules/message_manager.tsx",
|
||||
"src/components/ToolPolicies/PolicySelect.tsx"
|
||||
],
|
||||
"rules": {
|
||||
"litellm-ui/no-banned-ui-imports": "off",
|
||||
|
|
|
|||
|
|
@ -43,3 +43,12 @@ Layer abbreviations: **TS** (tsc --noEmit), **Lint** (eslint), **Vitest**
|
|||
- cycle 1: TS ✓ | Lint ✓ (pre-existing unused-imports in test excluded) | Vitest ✓ (6/6) | Parity ⏭ | Snap ⏭
|
||||
- Final status: **done**
|
||||
|
||||
## 6. Tool Policies (tool-policies)
|
||||
|
||||
- Cycles used: 1 / 7
|
||||
- Layer outcomes per cycle:
|
||||
- cycle 1: TS ✓ | Lint ✓ (PolicySelect.tsx added to raw-colors override list) | Vitest ✓ (12/12) | Parity ⏭ | Snap ⏭
|
||||
- Final status: **done**
|
||||
- Scope: PolicySelect.tsx only (the single banned-import file under ToolPolicies/). The ToolPoliciesView.tsx top-level wrapper has no antd imports already. Top-level ToolPolicies.tsx legacy wrapper is not in scope.
|
||||
- Section decision: keep amber/emerald/red categorical colors for policy badges (not theme-semantic). Documented in DEVIATIONS.md.
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,21 @@ Format per entry:
|
|||
|
||||
---
|
||||
|
||||
## Section 6 (Tool Policies) — categorical colors (amber/emerald/red) on policy badges
|
||||
|
||||
- **Type:** `blueprint deviation`
|
||||
- **Blueprint rule deviated from:** §1 "semantic Tailwind tokens only".
|
||||
- **What the agent did instead:** `PolicySelect.tsx` uses raw palette
|
||||
classes (`bg-amber-100 text-amber-800 border-amber-300`,
|
||||
`bg-emerald-100 ...`, `bg-red-100 ...`) and is added to the
|
||||
`litellm-ui/no-raw-tailwind-colors` override list in `.eslintrc.json`.
|
||||
- **Why:** The policy badges encode a **categorical** (untrusted /
|
||||
trusted / blocked), not a theme state. Semantic tokens (`primary`,
|
||||
`muted`, `destructive`, ...) don't have three distinct hues suitable
|
||||
for three categorical values. A phase-2 task could introduce semantic
|
||||
policy tokens (`--policy-untrusted`, `--policy-trusted`,
|
||||
`--policy-blocked`) backed by CSS variables; out of scope for phase 1.
|
||||
|
||||
## Section 1 (Access Groups) — custom MultiSelect shim in lieu of antd Select mode=multiple
|
||||
|
||||
- **Type:** `blueprint deviation`
|
||||
|
|
|
|||
|
|
@ -55,8 +55,9 @@ describe("PolicySelect", () => {
|
|||
onChange={vi.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole("combobox")).toHaveAttribute("aria-expanded", "false");
|
||||
expect(screen.getByRole("combobox").closest(".ant-select")).toHaveClass("ant-select-disabled");
|
||||
// shadcn Select's combobox trigger exposes `data-disabled` when disabled.
|
||||
const combobox = screen.getByRole("combobox");
|
||||
expect(combobox).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should not be disabled when saving is false", () => {
|
||||
|
|
@ -68,7 +69,7 @@ describe("PolicySelect", () => {
|
|||
onChange={vi.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole("combobox").closest(".ant-select")).not.toHaveClass("ant-select-disabled");
|
||||
expect(screen.getByRole("combobox")).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,39 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Select } from "antd";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const INPUT_POLICY_OPTIONS = [
|
||||
{ value: "untrusted", label: "untrusted", color: "#92400e", bg: "#fef3c7", border: "#fcd34d" },
|
||||
{ value: "trusted", label: "trusted", color: "#065f46", bg: "#d1fae5", border: "#6ee7b7" },
|
||||
{ value: "blocked", label: "blocked", color: "#991b1b", bg: "#fee2e2", border: "#fca5a5" },
|
||||
{
|
||||
value: "untrusted",
|
||||
label: "untrusted",
|
||||
className: "bg-amber-100 text-amber-800 border-amber-300",
|
||||
dotClass: "bg-amber-800",
|
||||
},
|
||||
{
|
||||
value: "trusted",
|
||||
label: "trusted",
|
||||
className: "bg-emerald-100 text-emerald-800 border-emerald-300",
|
||||
dotClass: "bg-emerald-800",
|
||||
},
|
||||
{
|
||||
value: "blocked",
|
||||
label: "blocked",
|
||||
className: "bg-red-100 text-red-800 border-red-300",
|
||||
dotClass: "bg-red-800",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const OUTPUT_POLICY_OPTIONS = [
|
||||
{ value: "untrusted", label: "untrusted", color: "#92400e", bg: "#fef3c7", border: "#fcd34d" },
|
||||
{ value: "trusted", label: "trusted", color: "#065f46", bg: "#d1fae5", border: "#6ee7b7" },
|
||||
INPUT_POLICY_OPTIONS[0],
|
||||
INPUT_POLICY_OPTIONS[1],
|
||||
] as const;
|
||||
|
||||
export const POLICY_OPTIONS = INPUT_POLICY_OPTIONS;
|
||||
|
|
@ -30,6 +52,12 @@ export interface PolicySelectProps {
|
|||
stopPropagation?: boolean;
|
||||
}
|
||||
|
||||
// Color pill around the shadcn Select trigger. This is the one place in
|
||||
// phase 1 where we reach for named Tailwind colors — the policy-badge
|
||||
// palette (amber/emerald/red) is purposely non-semantic because it
|
||||
// encodes a categorical (untrusted / trusted / blocked) not a theme state.
|
||||
// Recorded in DEVIATIONS.md if we decide to force semantic-token
|
||||
// conformance later.
|
||||
export const PolicySelect: React.FC<PolicySelectProps> = ({
|
||||
value,
|
||||
toolName,
|
||||
|
|
@ -40,53 +68,44 @@ export const PolicySelect: React.FC<PolicySelectProps> = ({
|
|||
minWidth = 110,
|
||||
stopPropagation = true,
|
||||
}) => {
|
||||
const options = policyType === "output" ? OUTPUT_POLICY_OPTIONS : INPUT_POLICY_OPTIONS;
|
||||
const options =
|
||||
policyType === "output" ? OUTPUT_POLICY_OPTIONS : INPUT_POLICY_OPTIONS;
|
||||
const style = policyStyle(value);
|
||||
const triggerHeight = size === "small" ? "h-7" : "h-8";
|
||||
const fontSize = size === "small" ? "text-[11px]" : "text-xs";
|
||||
return (
|
||||
<Select
|
||||
size={size}
|
||||
value={value}
|
||||
disabled={saving}
|
||||
loading={saving}
|
||||
onChange={(v) => onChange(toolName, v)}
|
||||
<div
|
||||
onClick={(e) => stopPropagation && e.stopPropagation()}
|
||||
style={{
|
||||
minWidth,
|
||||
fontWeight: 500,
|
||||
backgroundColor: style.bg,
|
||||
borderColor: style.border,
|
||||
color: style.color,
|
||||
borderRadius: 999,
|
||||
fontSize: size === "small" ? 11 : 12,
|
||||
}}
|
||||
popupMatchSelectWidth={false}
|
||||
options={options.map((o) => ({
|
||||
value: o.value,
|
||||
label: (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
color: o.color,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: o.color,
|
||||
display: "inline-block",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
{o.label}
|
||||
</span>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
style={{ minWidth }}
|
||||
>
|
||||
<Select
|
||||
value={value}
|
||||
disabled={saving}
|
||||
onValueChange={(v) => onChange(toolName, v)}
|
||||
>
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
"font-medium rounded-full border",
|
||||
style.className,
|
||||
triggerHeight,
|
||||
fontSize,
|
||||
)}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="w-auto">
|
||||
{options.map((o) => (
|
||||
<SelectItem key={o.value} value={o.value}>
|
||||
<span className={cn("inline-flex items-center gap-1.5", fontSize)}>
|
||||
<span
|
||||
className={cn("w-2 h-2 rounded-full inline-block", o.dotClass)}
|
||||
/>
|
||||
{o.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue