diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index 3fc9318498c..2a73b0e31b3 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -149,6 +149,9 @@ "src/components/policies/impact_preview_alert.tsx", "src/components/policies/impact_popover.tsx", "src/components/policies/ai_suggestion_modal.tsx", + "src/components/playground/chat_ui/AgentBuilderView.tsx", + "src/components/playground/chat_ui/RealtimePlayground.tsx", + "src/components/playground/compareUI/CompareUI.tsx", "src/components/DeletedKeysPage/DeletedKeysPage.tsx", "src/components/DeletedTeamsPage/DeletedTeamsPage.tsx", "src/components/molecules/models/columns.tsx", diff --git a/ui/litellm-dashboard/docs/BLOCKERS.md b/ui/litellm-dashboard/docs/BLOCKERS.md index 43fadfab125..498377d08c7 100644 --- a/ui/litellm-dashboard/docs/BLOCKERS.md +++ b/ui/litellm-dashboard/docs/BLOCKERS.md @@ -106,9 +106,21 @@ stay on antd for phase 1 and will be addressed in a targeted follow-up. prop for `setFieldsValue`/`resetFields`. Tightly coupled to the parent antd Form. Defer with parents. - `src/components/mcp_tools/MCPPermissionManagement.tsx`: uses - `Form.useFormInstance()` + `Form.List` for dynamic static-headers plus - several `Form.Item` fields (`allow_all_keys`, `available_on_public_internet`, - `mcp_access_groups`, `extra_headers`). Directly writes to the parent's - antd Form via `form.setFieldValue`. `MCPPermissionManagement.test.tsx` - wraps it in an antd `
` harness. Cannot migrate - independently of the parent forms. Defer with parents. + `Form.useFormInstance()` + `Form.List` for dynamic static-headers plus + several `Form.Item` fields (`allow_all_keys`, `available_on_public_internet`, + `mcp_access_groups`, `extra_headers`). Directly writes to the parent's + antd Form via `form.setFieldValue`. `MCPPermissionManagement.test.tsx` + wraps it in an antd `` harness. Cannot migrate + independently of the parent forms. Defer with parents. +- `src/components/playground/chat_ui/ChatUI.tsx` (2239 LoC): chrome-only + migration blocked by deeply-coupled antd `Select.OptGroup` + `optionLabelProp` + + `maxTagCount="responsive"` + custom `filterOption` on the MCP servers + multi-select (with "__all__" sentinel, toolset/server option groups, and per- + option custom JSX rendering). The direct-tool / arg selectors inside the MCP + tool picker use the same pattern. shadcn ` { + if (v && !selectedIds.includes(v)) onChange([...selectedIds, v]); + }} + > + + + + + {remaining.length === 0 ? ( +
+ {options.length === 0 + ? "No MCP servers available" + : "All available servers selected"} +
+ ) : ( + remaining.map((opt) => ( + + {opt.label} + + )) + )} +
+ + {selectedIds.length > 0 && ( +
+ {selectedIds.map((id) => { + const opt = options.find((o) => o.value === id); + return ( + + {opt?.label ?? id} + + + ); + })} +
+ )} + + ); +} + function getAgentModelId(agent: AgentModel): string | null { const info = agent.model_info as { id?: string } | null | undefined; return info?.id ?? null; @@ -189,6 +325,7 @@ export default function AgentBuilderView({ const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(false); + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const effectiveApiKey = apiKey || accessToken || ""; const selectedAgent = selectedId === NEW_AGENT_ID ? null : agentModels.find((a) => a.model_name === selectedId) ?? null; @@ -376,27 +513,26 @@ 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"); - await loadAgents(); - const remaining = agentModels.filter((a) => a.model_name !== selectedAgent.model_name); - setSelectedId(remaining.length > 0 ? remaining[0].model_name : null); - } catch (e) { - NotificationsManager.fromBackend("Failed to delete agent"); - } finally { - setDeleting(false); - } - }, - }); + setDeleteConfirmOpen(true); + }; + + const confirmDeleteAgent = async () => { + if (!selectedAgent || !selectedAgentModelId || !accessToken) return; + setDeleting(true); + try { + await modelDeleteCall(accessToken, selectedAgentModelId); + NotificationsManager.success("Agent deleted"); + await loadAgents(); + const remaining = agentModels.filter( + (a) => a.model_name !== selectedAgent.model_name, + ); + setSelectedId(remaining.length > 0 ? remaining[0].model_name : null); + } catch (e) { + NotificationsManager.fromBackend("Failed to delete agent"); + } finally { + setDeleting(false); + setDeleteConfirmOpen(false); + } }; if (!accessToken || !userID || !userRole) { @@ -414,13 +550,20 @@ export default function AgentBuilderView({ Agent Builder {isNewAgent ? ( ) : ( Build Agents that pass your compliance requirements. @@ -443,12 +586,20 @@ export default function AgentBuilderView({
Agents -
{loadingAgents ? (
- +
) : ( <> @@ -489,220 +640,292 @@ 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" - /> -
-
- -