From 28e1cecc7e8dace50b901c8318c9e5567d194f8d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 09:43:57 +0000 Subject: [PATCH] feat(ui): migrate MCPSemanticFilterSettings + TestPanel to shadcn - MCPSemanticFilterTestPanel: antd Alert/Button/Card/Input/Space/Tabs/ Typography + @ant-design/icons CodeOutlined/PlayCircleOutlined \u2192 shadcn Alert/Button/Card/Label/Textarea/Tabs + lucide Code/PlayCircle. Inline disabled/selected-tools/api-usage panels. Added to .eslintrc.json overrides for the amber/emerald categorical Alert palette. - MCPSemanticFilterSettings: antd Form/InputNumber/Slider/Select/Switch/ Row/Col/Tooltip/Typography + @ant-design/icons QuestionCircle/ CheckCircle/Save \u2192 shadcn Card/Input/Select/Slider/Switch/Alert/ Tooltip + react-hook-form (form.reset preserves 'dirty' semantics; form.formState.isDirty gates the Save button). Shadcn Slider returns an array; adapter passes [value] in, extracts [v] out. Tick labels rendered as a flex row beneath the track. Gates: TS \u2713 | Vitest 33/33 \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/.eslintrc.json | 3 +- .../MCPSemanticFilterSettings.tsx | 479 ++++++++++-------- .../MCPSemanticFilterTestPanel.tsx | 233 ++++----- 3 files changed, 393 insertions(+), 322 deletions(-) diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index 0b5b1e1f97a..aea804d8c6a 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -34,7 +34,8 @@ "src/components/SCIM.tsx", "src/components/logging_settings_view.tsx", "src/components/model_group_alias_settings.tsx", - "src/components/cache_dashboard.tsx" + "src/components/cache_dashboard.tsx", + "src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx" ], "rules": { "litellm-ui/no-banned-ui-imports": "off", diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.tsx index d24c14a8e86..4b396d26cdc 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.tsx @@ -5,51 +5,96 @@ import { useUpdateMCPSemanticFilterSettings } from "@/app/(dashboard)/hooks/mcpS import NotificationManager from "@/components/molecules/notifications_manager"; import { Alert, - Button, - Card, - Col, - Form, - InputNumber, - Row, + AlertDescription, + AlertTitle, +} from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Select, - Skeleton, - Slider, - Space, - Switch, - Typography, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Slider } from "@/components/ui/slider"; +import { Switch } from "@/components/ui/switch"; +import { Tooltip, -} from "antd"; -import { QuestionCircleOutlined, CheckCircleOutlined, SaveOutlined } from "@ant-design/icons"; -import { useEffect, useState } from "react"; -import { fetchAvailableModels, ModelGroup } from "@/components/playground/llm_calls/fetch_models"; + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { CheckCircle, HelpCircle, Save } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; +import { Controller, FormProvider, useForm } from "react-hook-form"; +import { + fetchAvailableModels, + ModelGroup, +} from "@/components/playground/llm_calls/fetch_models"; import MCPSemanticFilterTestPanel from "./MCPSemanticFilterTestPanel"; -import { getCurlCommand, runSemanticFilterTest, TestResult } from "./semanticFilterTestUtils"; +import { + getCurlCommand, + runSemanticFilterTest, + TestResult, +} from "./semanticFilterTestUtils"; interface MCPSemanticFilterSettingsProps { accessToken: string | null; } -export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFilterSettingsProps) { +interface FormValues { + enabled: boolean; + embedding_model: string; + top_k: number; + similarity_threshold: number; +} + +function HelpTip({ children }: { children: React.ReactNode }) { + return ( + + + + + + {children} + + + ); +} + +export default function MCPSemanticFilterSettings({ + accessToken, +}: MCPSemanticFilterSettingsProps) { const { data, isLoading, isError, error } = useMCPSemanticFilterSettings(); const { mutate: updateSettings, isPending: isUpdating, error: updateError, } = useUpdateMCPSemanticFilterSettings(accessToken || ""); - const [form] = Form.useForm(); + const form = useForm({ + defaultValues: { + enabled: false, + embedding_model: "text-embedding-3-small", + top_k: 10, + similarity_threshold: 0.3, + }, + mode: "onSubmit", + }); const [saveSuccess, setSaveSuccess] = useState(false); - const [isDirty, setIsDirty] = useState(false); const [embeddingModels, setEmbeddingModels] = useState([]); const [loadingModels, setLoadingModels] = useState(true); - // Test section state const [testQuery, setTestQuery] = useState(""); const [testModel, setTestModel] = useState("gpt-4o"); const [testResult, setTestResult] = useState(null); const [isTesting, setIsTesting] = useState(false); const schema = data?.field_schema; - const values = data?.values ?? {}; + const values = useMemo(() => data?.values ?? {}, [data?.values]); useEffect(() => { const loadEmbeddingModels = async () => { @@ -57,56 +102,48 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi try { setLoadingModels(true); const models = await fetchAvailableModels(accessToken); - const embeddingOnly = models.filter((model) => model.mode === "embedding"); - setEmbeddingModels(embeddingOnly); + setEmbeddingModels(models.filter((m) => m.mode === "embedding")); } catch (error) { console.error("Error fetching embedding models:", error); } finally { setLoadingModels(false); } }; - loadEmbeddingModels(); }, [accessToken]); useEffect(() => { if (values) { - form.setFieldsValue({ + form.reset({ enabled: values.enabled ?? false, embedding_model: values.embedding_model ?? "text-embedding-3-small", top_k: values.top_k ?? 10, similarity_threshold: values.similarity_threshold ?? 0.3, }); - setIsDirty(false); } - }, [values, form]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [values]); - const handleSave = async () => { - try { - const formValues = await form.validateFields(); - updateSettings(formValues, { - onSuccess: () => { - setIsDirty(false); - setSaveSuccess(true); - setTimeout(() => setSaveSuccess(false), 3000); - NotificationManager.success( - "Settings updated successfully. Changes will be applied across all pods within 10 seconds." - ); - }, - onError: (error) => { - NotificationManager.fromBackend(error); - }, - }); - } catch (error) { - console.error("Form validation failed:", error); - } - }; + const isDirty = form.formState.isDirty; + + const handleSave = form.handleSubmit((formValues) => { + updateSettings(formValues, { + onSuccess: () => { + form.reset(formValues); // clears dirty state + setSaveSuccess(true); + setTimeout(() => setSaveSuccess(false), 3000); + NotificationManager.success( + "Settings updated successfully. Changes will be applied across all pods within 10 seconds.", + ); + }, + onError: (err) => { + NotificationManager.fromBackend(err); + }, + }); + }); const handleTest = async () => { - if (!accessToken) { - return; - } - + if (!accessToken) return; await runSemanticFilterTest({ accessToken, testModel, @@ -118,190 +155,222 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi if (!accessToken) { return ( -
+
Please log in to configure semantic filter settings.
); } return ( -
+
{isLoading ? ( - + ) : isError ? ( - + + Could not load MCP Semantic Filter settings + {error instanceof Error && ( + {error.message} + )} + ) : ( <> - + + Semantic Tool Filtering + + Filter MCP tools semantically based on query relevance. This + reduces context window size and improves tool selection accuracy. + Click 'Save Settings' to apply changes across all pods + (takes effect within 10 seconds). + + {saveSuccess && ( - } - showIcon - closable - style={{ marginBottom: 16 }} - /> + + + Settings saved successfully + )} {updateError && ( - + + Could not update settings + {updateError instanceof Error && ( + {updateError.message} + )} + )} - - {/* Left Column - Settings */} - -
{ - setIsDirty(true); - }} +
+ + - - - Enable Semantic Filtering - - - - - } - valuePropName="checked" - > - - - - + +
+ + ( + + )} + /> +
+

{schema?.properties?.enabled?.description} - +

- - - Embedding Model - - - - - } - > - field.onChange(v)} + disabled={isUpdating || loadingModels} + > + + + + + {embeddingModels.length === 0 ? ( +
+ {loadingModels + ? "Loading..." + : "No embedding models available"} +
+ ) : ( + embeddingModels.map((model) => ( + + {model.model_group} + + )) + )} +
+ + )} + /> +
- - Similarity Threshold - - - - - } - > - - +
+ + +
+ +
+ + ( +
+ field.onChange(v)} + min={0} + max={1} + step={0.05} + disabled={isUpdating} + /> +
+ 0.0 + 0.3 + 0.5 + 0.7 + 1.0 +
+
+ Current: {Number(field.value).toFixed(2)} +
+
+ )} + /> +
+
-
+
- - + + - {/* Right Column - Test Configuration */} - - - - + +
)}
diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx index 084de740014..beb03bcfa8d 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx @@ -1,5 +1,19 @@ -import { CodeOutlined, PlayCircleOutlined } from "@ant-design/icons"; -import { Alert, Button, Card, Input, Space, Tabs, Typography } from "antd"; +import { + Alert, + AlertDescription, + AlertTitle, +} from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs"; +import { Code, PlayCircle } from "lucide-react"; import ModelSelector from "@/components/common_components/ModelSelector"; import { TestResult } from "./semanticFilterTestUtils"; @@ -29,136 +43,123 @@ export default function MCPSemanticFilterTestPanel({ curlCommand, }: MCPSemanticFilterTestPanelProps) { return ( - - -
- - Test Query - - setTestQuery(e.target.value)} - rows={4} - disabled={isTesting} - /> -
- -
- -
- - - - {!filterEnabled && ( - - )} - - {testResult && ( + +

Test Configuration

+ + + Test + API Usage + + +
- Results - + + Test Query + +