From 4808b7bd0da5d83df54ce5fdab0eb83fd75abbce Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 12:27:08 +0000 Subject: [PATCH] feat(ui): OpenAPIFormSection + OpenAPIQuickPicker to shadcn - OpenAPIFormSection: antd Tooltip + @ant-design InfoCircleOutlined \u2192 shadcn Tooltip + lucide Info. Preserves the antd Form.Item for spec_path since the parent form is antd-driven. - OpenAPIQuickPicker: antd Spin + inline gray palette \u2192 lucide Loader2 + semantic bg-primary/10 + border-primary for selected cards, bg-muted/hover styling for idle. Broken logo fallback avatar uses bg-muted + text-foreground. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- .../mcp_tools/OpenAPIFormSection.tsx | 55 +++++++++++-------- .../mcp_tools/OpenAPIQuickPicker.tsx | 32 ++++++----- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIFormSection.tsx b/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIFormSection.tsx index b25e1dcadd4..164a81d04e2 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIFormSection.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIFormSection.tsx @@ -1,28 +1,29 @@ import React, { useState } from "react"; -import { Form, Input, Tooltip } from "antd"; -import { InfoCircleOutlined } from "@ant-design/icons"; +import { Form, Input } from "antd"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Info } from "lucide-react"; import { FormInstance } from "antd/es/form"; import { AUTH_TYPE, OAUTH_FLOW } from "./types"; -import OpenAPIQuickPicker, { OpenAPIRegistryEntry, OpenAPIKeyTool } from "./OpenAPIQuickPicker"; +import OpenAPIQuickPicker, { + OpenAPIRegistryEntry, + OpenAPIKeyTool, +} from "./OpenAPIQuickPicker"; interface OpenAPIFormSectionProps { form: FormInstance; accessToken: string | null; - /** Called when a preset is selected so the parent can sync its formValues state. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any onValuesChange: (updates: Record) => void; - /** Called when key tools change (from registry preset selection). */ onKeyToolsChange?: (tools: OpenAPIKeyTool[]) => void; - /** Called when a preset is selected so the parent can set the logo URL from icon_url. */ onLogoUrlChange?: (url: string | undefined) => void; - /** Called when the OAuth docs URL changes (e.g. link to create a GitHub OAuth App). */ onOAuthDocsUrlChange?: (url: string | null) => void; } -/** - * Encapsulates all OpenAPI-specific form fields: - * - popular API quick-picker (logos) - * - spec URL input - */ const OpenAPIFormSection: React.FC = ({ form, accessToken, @@ -37,21 +38,18 @@ const OpenAPIFormSection: React.FC = ({ setSelectedPreset(entry.name); onKeyToolsChange?.(entry.key_tools ?? []); onLogoUrlChange?.(entry.icon_url || undefined); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const updates: Record = { spec_path: entry.spec_url, }; if (entry.oauth) { updates.auth_type = AUTH_TYPE.OAUTH2; - // OAuth2 registry entries always use the interactive (PKCE) flow — users - // authorize via their browser, not machine-to-machine client credentials. updates.oauth_flow_type = OAUTH_FLOW.INTERACTIVE; updates.authorization_url = entry.oauth.authorization_url; updates.token_url = entry.oauth.token_url; form.setFieldsValue(updates); onOAuthDocsUrlChange?.(entry.oauth.docs_url ?? null); } else { - // resetFields is required to visually clear Ant Design form fields — - // setFieldsValue with undefined silently skips undefined keys. form.resetFields(["auth_type", "authorization_url", "token_url"]); form.setFieldsValue(updates); onOAuthDocsUrlChange?.(null); @@ -69,22 +67,31 @@ const OpenAPIFormSection: React.FC = ({ + OpenAPI Spec URL - - - + + + + + + + URL to an OpenAPI specification (JSON or YAML). MCP tools + will be automatically generated from the API endpoints + defined in the spec. + + + } name="spec_path" - rules={[{ required: true, message: "Please enter an OpenAPI spec URL" }]} + rules={[ + { required: true, message: "Please enter an OpenAPI spec URL" }, + ]} > { - // Clear the preset selection when the user manually edits the spec URL - // so stale suggested tools from a previous preset don't persist. setSelectedPreset(null); onKeyToolsChange?.([]); onOAuthDocsUrlChange?.(null); diff --git a/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIQuickPicker.tsx b/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIQuickPicker.tsx index 2959ed25e57..ecd7ff8de2e 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIQuickPicker.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIQuickPicker.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; -import { Spin } from "antd"; +import { Loader2 } from "lucide-react"; +import { cn } from "@/lib/utils"; import { fetchOpenAPIRegistry } from "../networking"; export interface OpenAPIKeyTool { @@ -53,9 +54,11 @@ const OpenAPIQuickPicker: React.FC = ({ if (loading) { return (
- Popular APIs + + Popular APIs +
- +
); @@ -65,7 +68,9 @@ const OpenAPIQuickPicker: React.FC = ({ return (
- Popular APIs + + Popular APIs +
{apis.map((api) => { @@ -77,18 +82,19 @@ const OpenAPIQuickPicker: React.FC = ({ type="button" title={api.description} onClick={() => onSelect(api)} - className={`flex flex-col items-center gap-1.5 p-3 rounded-lg border transition-all cursor-pointer - ${ - isSelected - ? "border-blue-500 bg-blue-50 shadow-sm" - : "border-gray-200 hover:border-blue-300 hover:bg-gray-50" - }`} + className={cn( + "flex flex-col items-center gap-1.5 p-3 rounded-lg border transition-all cursor-pointer", + isSelected + ? "border-primary bg-primary/10 shadow-sm" + : "border-border hover:border-primary/50 hover:bg-muted", + )} > {imgFailed ? ( - + {api.title.charAt(0)} ) : ( + // eslint-disable-next-line @next/next/no-img-element {api.title} = ({ onError={() => handleImgError(api.name)} /> )} - + {api.title} @@ -104,7 +110,7 @@ const OpenAPIQuickPicker: React.FC = ({ })}
-

+

Select an API to pre-fill the spec URL and OAuth 2.0 settings, or enter your own spec URL below.