From 9a26da90a47459d249030b6873ec99619161fdc9 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Thu, 13 Aug 2026 15:59:53 -0700 Subject: [PATCH] refactor(ui): migrate playground to shadcn Replaces antd and Tremor with shadcn primitives across the six route-owned, form-free playground components: the compare view and its panel, message input and unified selector, plus the realtime playground and the agent builder. Markup only, no behaviour change. The characterisation tests added in the previous commit are untouched here and stay green through the swap. Adds ui/slider.tsx via the shadcn CLI and retires the six antd no-restricted-imports suppressions the migration made obsolete. --- ui/litellm-dashboard/eslint-suppressions.json | 30 +- .../components/chat_ui/AgentBuilderView.tsx | 530 +++++++++--------- .../components/chat_ui/RealtimePlayground.tsx | 77 +-- .../components/compareUI/CompareUI.tsx | 76 ++- .../compareUI/components/ComparisonPanel.tsx | 75 ++- .../compareUI/components/MessageInput.tsx | 33 +- .../compareUI/components/UnifiedSelector.tsx | 66 ++- .../src/components/ui/slider.tsx | 41 ++ 8 files changed, 514 insertions(+), 414 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/ui/slider.tsx diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index d7f71a5840d..0c5bcc65702 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -1055,9 +1055,6 @@ "no-nested-ternary": { "count": 2 }, - "no-restricted-imports": { - "count": 1 - }, "react-hooks/set-state-in-effect": { "count": 5 } @@ -1098,9 +1095,6 @@ "no-nested-ternary": { "count": 2 }, - "no-restricted-imports": { - "count": 1 - }, "react-hooks/immutability": { "count": 2 }, @@ -1115,9 +1109,6 @@ "no-nested-ternary": { "count": 4 }, - "no-restricted-imports": { - "count": 1 - }, "react-hooks/set-state-in-effect": { "count": 1 } @@ -1125,9 +1116,6 @@ "src/app/(dashboard)/playground/components/compareUI/components/ComparisonPanel.tsx": { "local/no-complex-jsx-arrow": { "count": 2 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/(dashboard)/playground/components/compareUI/components/MessageDisplay.tsx": { @@ -1135,21 +1123,11 @@ "count": 1 } }, - "src/app/(dashboard)/playground/components/compareUI/components/MessageInput.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/playground/components/compareUI/components/ModelSelector.tsx": { "no-restricted-imports": { "count": 2 } }, - "src/app/(dashboard)/playground/components/compareUI/components/UnifiedSelector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.tsx": { "local/no-complex-jsx-arrow": { "count": 2 @@ -3582,6 +3560,14 @@ "count": 1 } }, + "src/components/ui/slider.tsx": { + "local/filename-pascal-case": { + "count": 1 + }, + "no-nested-ternary": { + "count": 1 + } + }, "src/components/ui/switch.tsx": { "local/filename-pascal-case": { "count": 1 diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AgentBuilderView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AgentBuilderView.tsx index d4333b95c62..44995707b10 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AgentBuilderView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AgentBuilderView.tsx @@ -1,16 +1,24 @@ "use client"; -import { - CommentOutlined, - DeleteOutlined, - ExperimentOutlined, - LinkOutlined, - PlusOutlined, - RobotOutlined, - SaveOutlined, -} from "@ant-design/icons"; -import { Button, Input, Modal, Select, Spin, Tabs } from "antd"; +import { Bot, FlaskConical, Link as LinkIcon, MessageSquare, Plus, Save, Trash2 } from "lucide-react"; import React, { useCallback, useEffect, useState } from "react"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Textarea } from "@/components/ui/textarea"; +import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; +import { MultiSelect } from "@/components/shared/MultiSelect"; +import { useVisitedTabs } from "@/hooks/useVisitedTabs"; import CodeBlock from "@/components/CodeBlock"; import NotificationsManager from "@/components/molecules/notifications_manager"; import { @@ -27,8 +35,6 @@ import { fetchAvailableModels, ModelGroup } from "@/components/llm_calls/fetch_m import ComplianceUI from "../complianceUI/ComplianceUI"; import ChatUI from "./ChatUI"; -const { TextArea } = Input; - export interface AgentBuilderViewProps { accessToken: string | null; token: string | null; @@ -45,6 +51,8 @@ export interface AgentBuilderViewProps { const NEW_AGENT_ID = "__new__"; +type AgentTab = "configure" | "chat" | "test" | "connect"; + function getConnectTabBaseUrl( proxySettings: AgentBuilderViewProps["proxySettings"], customProxyBaseUrl?: string, @@ -116,7 +124,7 @@ function ConnectTabContent({ Create a virtual key that can only call this agent. The key will be scoped to you (user_id) and restricted to the model {agentName}.

- {disabledPersonalKeyCreation && ( @@ -190,7 +198,12 @@ export default function AgentBuilderView({ const [modelGroups, setModelGroups] = useState([]); const [loadingAgents, setLoadingAgents] = useState(true); const [selectedId, setSelectedId] = useState(null); - const [activeTab, setActiveTab] = useState<"configure" | "chat" | "test" | "connect">("configure"); + const [activeTab, setActiveTab] = useState("configure"); + const { onTabChange, hasVisited } = useVisitedTabs("configure"); + const goToTab = (tab: AgentTab) => { + setActiveTab(tab); + onTabChange(tab); + }; const [creatingKey, setCreatingKey] = useState(false); const [createdKeyValue, setCreatedKeyValue] = useState(null); @@ -207,6 +220,7 @@ export default function AgentBuilderView({ const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(false); + const [confirmingDelete, setConfirmingDelete] = useState(false); const effectiveApiKey = apiKey || accessToken || ""; const selectedAgent = @@ -314,7 +328,7 @@ export default function AgentBuilderView({ setDraftTemperature(0.7); setDraftMaxTokens(4096); setDraftTools([]); - setActiveTab("configure"); + goToTab("configure"); }; const handleSaveAgent = async () => { @@ -344,7 +358,7 @@ export default function AgentBuilderView({ ? list.find((a) => getAgentModelId(a) === createdId) ?? list.find((a) => a.model_name === draftName.trim()) : list.find((a) => a.model_name === draftName.trim()); setSelectedId(created ? getAgentSelectionKey(created) : list[0] ? getAgentSelectionKey(list[0]) : null); - setActiveTab("chat"); + goToTab("chat"); } catch (e) { NotificationsManager.fromBackend("Failed to save agent"); } finally { @@ -411,27 +425,24 @@ export default function AgentBuilderView({ const handleDeleteAgent = () => { if (!selectedAgent || !selectedAgentModelId || !accessToken) return; - Modal.confirm({ - title: "Delete agent", - content: `Are you sure you want to delete "${selectedAgent.model_name}"? This cannot be undone.`, - okText: "Delete", - okType: "danger", - cancelText: "Cancel", - onOk: async () => { - setDeleting(true); - try { - await modelDeleteCall(accessToken, selectedAgentModelId); - NotificationsManager.success("Agent deleted"); - const list = await loadAgents(); - const remaining = list.filter((a) => getAgentModelId(a) !== selectedAgentModelId); - setSelectedId(remaining.length > 0 ? getAgentSelectionKey(remaining[0]) : null); - } catch (e) { - NotificationsManager.fromBackend("Failed to delete agent"); - } finally { - setDeleting(false); - } - }, - }); + setConfirmingDelete(true); + }; + + const handleConfirmDelete = async () => { + if (!selectedAgent || !selectedAgentModelId || !accessToken) return; + setDeleting(true); + try { + await modelDeleteCall(accessToken, selectedAgentModelId); + NotificationsManager.success("Agent deleted"); + const list = await loadAgents(); + const remaining = list.filter((a) => getAgentModelId(a) !== selectedAgentModelId); + setSelectedId(remaining.length > 0 ? getAgentSelectionKey(remaining[0]) : null); + } catch (e) { + NotificationsManager.fromBackend("Failed to delete agent"); + } finally { + setDeleting(false); + setConfirmingDelete(false); + } }; if (!accessToken || !userID || !userRole) { @@ -446,13 +457,8 @@ export default function AgentBuilderView({
Agent Builder {isNewAgent ? ( - ) : ( @@ -460,7 +466,7 @@ export default function AgentBuilderView({ )}
- + Agent Builder is experimental and may change or be removed without notice. We’d love your feedback—email us at{" "} @@ -477,12 +483,14 @@ export default function AgentBuilderView({
Agents -
{loadingAgents ? ( -
- +
+
) : ( <> @@ -509,7 +517,7 @@ export default function AgentBuilderView({ onClick={handleAddAgent} className="mb-1 w-full rounded-md border border-dashed border-gray-300 px-3 py-2 text-left text-sm text-gray-500 hover:border-blue-400 hover:bg-blue-50/50 hover:text-gray-700" > - New agent + New agent )} @@ -526,227 +534,231 @@ export default function AgentBuilderView({ {(selectedId !== null || isNewAgent) && ( <> setActiveTab(k as "configure" | "chat" | "test" | "connect")} - className="flex-1 overflow-hidden [&_.ant-tabs-content]:h-full [&_.ant-tabs-tabpane]:h-full [&_.ant-tabs-nav]:pl-4" - items={[ - { - key: "configure", - label: ( - - Configure - - ), - children: ( -
- {isNewAgent || selectedAgent ? ( -
- {!selectedAgentModelId && selectedAgent && ( -
- This agent cannot be updated or deleted here (missing model id). Manage it from Models - & Endpoints. -
- )} -
- - setDraftName(e.target.value)} - placeholder="My Agent" - /> -
-
- -