mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
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 <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
2dae5d9372
commit
28e1cecc7e
3 changed files with 393 additions and 322 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="inline-block ml-1 h-3 w-3 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">{children}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
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<FormValues>({
|
||||
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<ModelGroup[]>([]);
|
||||
const [loadingModels, setLoadingModels] = useState(true);
|
||||
|
||||
// Test section state
|
||||
const [testQuery, setTestQuery] = useState("");
|
||||
const [testModel, setTestModel] = useState<string>("gpt-4o");
|
||||
const [testResult, setTestResult] = useState<TestResult | null>(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 (
|
||||
<div className="p-6 text-center text-gray-500">
|
||||
<div className="p-6 text-center text-muted-foreground">
|
||||
Please log in to configure semantic filter settings.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%" }}>
|
||||
<div className="w-full">
|
||||
{isLoading ? (
|
||||
<Skeleton active />
|
||||
<Skeleton className="h-32 w-full" />
|
||||
) : isError ? (
|
||||
<Alert
|
||||
type="error"
|
||||
message="Could not load MCP Semantic Filter settings"
|
||||
description={error instanceof Error ? error.message : undefined}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<Alert variant="destructive" className="mb-6">
|
||||
<AlertTitle>Could not load MCP Semantic Filter settings</AlertTitle>
|
||||
{error instanceof Error && (
|
||||
<AlertDescription>{error.message}</AlertDescription>
|
||||
)}
|
||||
</Alert>
|
||||
) : (
|
||||
<>
|
||||
<Alert
|
||||
type="info"
|
||||
message="Semantic Tool Filtering"
|
||||
description="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)."
|
||||
showIcon
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<Alert className="mb-6">
|
||||
<AlertTitle>Semantic Tool Filtering</AlertTitle>
|
||||
<AlertDescription>
|
||||
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).
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{saveSuccess && (
|
||||
<Alert
|
||||
type="success"
|
||||
message="Settings saved successfully"
|
||||
icon={<CheckCircleOutlined />}
|
||||
showIcon
|
||||
closable
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Alert className="mb-4 border-emerald-300 bg-emerald-50 text-emerald-900 dark:bg-emerald-950/30 dark:text-emerald-200">
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
<AlertTitle>Settings saved successfully</AlertTitle>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{updateError && (
|
||||
<Alert
|
||||
type="error"
|
||||
message="Could not update settings"
|
||||
description={
|
||||
updateError instanceof Error ? updateError.message : undefined
|
||||
}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Alert variant="destructive" className="mb-4">
|
||||
<AlertTitle>Could not update settings</AlertTitle>
|
||||
{updateError instanceof Error && (
|
||||
<AlertDescription>{updateError.message}</AlertDescription>
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Row gutter={24}>
|
||||
{/* Left Column - Settings */}
|
||||
<Col xs={24} lg={12}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
disabled={isUpdating}
|
||||
onValuesChange={() => {
|
||||
setIsDirty(true);
|
||||
}}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<FormProvider {...form}>
|
||||
<form
|
||||
onSubmit={handleSave}
|
||||
className="space-y-4"
|
||||
>
|
||||
<Card style={{ marginBottom: 16 }}>
|
||||
<Form.Item
|
||||
name="enabled"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Enable Semantic Filtering</Typography.Text>
|
||||
<Tooltip title="When enabled, only the most relevant MCP tools will be included in requests based on semantic similarity">
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={isUpdating} />
|
||||
</Form.Item>
|
||||
|
||||
<Typography.Text type="secondary" style={{ display: "block", marginTop: -16, marginBottom: 16 }}>
|
||||
<Card className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>
|
||||
<span className="font-semibold">
|
||||
Enable Semantic Filtering
|
||||
</span>
|
||||
<HelpTip>
|
||||
When enabled, only the most relevant MCP tools will be
|
||||
included in requests based on semantic similarity
|
||||
</HelpTip>
|
||||
</Label>
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="enabled"
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
{schema?.properties?.enabled?.description}
|
||||
</Typography.Text>
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card title="Configuration" style={{ marginBottom: 16 }}>
|
||||
<Form.Item
|
||||
name="embedding_model"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Embedding Model</Typography.Text>
|
||||
<Tooltip title="The model used to generate embeddings for semantic matching">
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
options={embeddingModels.map((model) => ({
|
||||
label: model.model_group,
|
||||
value: model.model_group,
|
||||
}))}
|
||||
placeholder={loadingModels ? "Loading models..." : "Select embedding model"}
|
||||
showSearch
|
||||
disabled={isUpdating || loadingModels}
|
||||
loading={loadingModels}
|
||||
notFoundContent={
|
||||
loadingModels ? "Loading..." : "No embedding models available"
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Card className="p-4">
|
||||
<h4 className="text-base font-semibold mb-4">
|
||||
Configuration
|
||||
</h4>
|
||||
|
||||
<Form.Item
|
||||
name="top_k"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Top K Results</Typography.Text>
|
||||
<Tooltip title="Maximum number of tools to return after filtering">
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={100}
|
||||
style={{ width: "100%" }}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
<span className="font-semibold">Embedding Model</span>
|
||||
<HelpTip>
|
||||
The model used to generate embeddings for semantic
|
||||
matching
|
||||
</HelpTip>
|
||||
</Label>
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="embedding_model"
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
value={field.value ?? ""}
|
||||
onValueChange={(v) => field.onChange(v)}
|
||||
disabled={isUpdating || loadingModels}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={
|
||||
loadingModels
|
||||
? "Loading models..."
|
||||
: "Select embedding model"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{embeddingModels.length === 0 ? (
|
||||
<div className="py-2 px-3 text-sm text-muted-foreground">
|
||||
{loadingModels
|
||||
? "Loading..."
|
||||
: "No embedding models available"}
|
||||
</div>
|
||||
) : (
|
||||
embeddingModels.map((model) => (
|
||||
<SelectItem
|
||||
key={model.model_group}
|
||||
value={model.model_group}
|
||||
>
|
||||
{model.model_group}
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
name="similarity_threshold"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Similarity Threshold</Typography.Text>
|
||||
<Tooltip title="Minimum similarity score (0-1) for a tool to be included">
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Slider
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
marks={{
|
||||
0: "0.0",
|
||||
0.3: "0.3",
|
||||
0.5: "0.5",
|
||||
0.7: "0.7",
|
||||
1: "1.0",
|
||||
}}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="top_k">
|
||||
<span className="font-semibold">Top K Results</span>
|
||||
<HelpTip>
|
||||
Maximum number of tools to return after filtering
|
||||
</HelpTip>
|
||||
</Label>
|
||||
<Input
|
||||
id="top_k"
|
||||
type="number"
|
||||
min={1}
|
||||
max={100}
|
||||
disabled={isUpdating}
|
||||
{...form.register("top_k", { valueAsNumber: true })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
<span className="font-semibold">
|
||||
Similarity Threshold
|
||||
</span>
|
||||
<HelpTip>
|
||||
Minimum similarity score (0-1) for a tool to be
|
||||
included
|
||||
</HelpTip>
|
||||
</Label>
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="similarity_threshold"
|
||||
render={({ field }) => (
|
||||
<div>
|
||||
<Slider
|
||||
value={[field.value]}
|
||||
onValueChange={([v]) => field.onChange(v)}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-muted-foreground mt-2">
|
||||
<span>0.0</span>
|
||||
<span>0.3</span>
|
||||
<span>0.5</span>
|
||||
<span>0.7</span>
|
||||
<span>1.0</span>
|
||||
</div>
|
||||
<div className="text-right text-sm text-muted-foreground mt-1">
|
||||
Current: {Number(field.value).toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={handleSave}
|
||||
loading={isUpdating}
|
||||
disabled={!isDirty}
|
||||
type="submit"
|
||||
disabled={!isDirty || isUpdating}
|
||||
>
|
||||
Save Settings
|
||||
<Save className="h-4 w-4" />
|
||||
{isUpdating ? "Saving…" : "Save Settings"}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Col>
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
||||
{/* Right Column - Test Configuration */}
|
||||
<Col xs={24} lg={12}>
|
||||
<MCPSemanticFilterTestPanel
|
||||
accessToken={accessToken}
|
||||
testQuery={testQuery}
|
||||
setTestQuery={setTestQuery}
|
||||
testModel={testModel}
|
||||
setTestModel={setTestModel}
|
||||
isTesting={isTesting}
|
||||
onTest={handleTest}
|
||||
filterEnabled={!!values.enabled}
|
||||
testResult={testResult}
|
||||
curlCommand={getCurlCommand(testModel, testQuery)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<MCPSemanticFilterTestPanel
|
||||
accessToken={accessToken}
|
||||
testQuery={testQuery}
|
||||
setTestQuery={setTestQuery}
|
||||
testModel={testModel}
|
||||
setTestModel={setTestModel}
|
||||
isTesting={isTesting}
|
||||
onTest={handleTest}
|
||||
filterEnabled={!!values.enabled}
|
||||
testResult={testResult}
|
||||
curlCommand={getCurlCommand(testModel, testQuery)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Card title="Test Configuration" style={{ marginBottom: 16 }}>
|
||||
<Tabs
|
||||
defaultActiveKey="test"
|
||||
items={[
|
||||
{
|
||||
key: "test",
|
||||
label: "Test",
|
||||
children: (
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||
<div>
|
||||
<Typography.Text strong style={{ display: "block", marginBottom: 8 }}>
|
||||
<PlayCircleOutlined /> Test Query
|
||||
</Typography.Text>
|
||||
<Input.TextArea
|
||||
placeholder="Enter a test query to see which tools would be selected..."
|
||||
value={testQuery}
|
||||
onChange={(e) => setTestQuery(e.target.value)}
|
||||
rows={4}
|
||||
disabled={isTesting}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ModelSelector
|
||||
accessToken={accessToken || ""}
|
||||
value={testModel}
|
||||
onChange={setTestModel}
|
||||
disabled={isTesting}
|
||||
showLabel={true}
|
||||
labelText="Select Model"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlayCircleOutlined />}
|
||||
onClick={onTest}
|
||||
loading={isTesting}
|
||||
disabled={!testQuery || !testModel || !filterEnabled}
|
||||
block
|
||||
>
|
||||
Test Filter
|
||||
</Button>
|
||||
|
||||
{!filterEnabled && (
|
||||
<Alert
|
||||
type="warning"
|
||||
message="Semantic filtering is disabled"
|
||||
description="Enable semantic filtering and save settings to test the filter."
|
||||
showIcon
|
||||
/>
|
||||
)}
|
||||
|
||||
{testResult && (
|
||||
<Card className="mb-4 p-6">
|
||||
<h4 className="text-base font-semibold mb-4">Test Configuration</h4>
|
||||
<Tabs defaultValue="test">
|
||||
<TabsList>
|
||||
<TabsTrigger value="test">Test</TabsTrigger>
|
||||
<TabsTrigger value="api">API Usage</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="test">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Typography.Title level={5}>Results</Typography.Title>
|
||||
<Alert
|
||||
type="success"
|
||||
message={`${testResult.selectedTools} tools selected`}
|
||||
description={`Filtered from ${testResult.totalTools} available tools`}
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
<Label className="flex items-center gap-1 mb-2">
|
||||
<PlayCircle className="h-4 w-4" />
|
||||
Test Query
|
||||
</Label>
|
||||
<Textarea
|
||||
placeholder="Enter a test query to see which tools would be selected..."
|
||||
value={testQuery}
|
||||
onChange={(e) => setTestQuery(e.target.value)}
|
||||
rows={4}
|
||||
disabled={isTesting}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Text strong style={{ display: "block", marginBottom: 8 }}>
|
||||
Selected Tools:
|
||||
</Typography.Text>
|
||||
<ul style={{ paddingLeft: 20, margin: 0 }}>
|
||||
{testResult.tools.map((tool, index) => (
|
||||
<li key={index} style={{ marginBottom: 4 }}>
|
||||
<Typography.Text>{tool}</Typography.Text>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "api",
|
||||
label: "API Usage",
|
||||
children: (
|
||||
|
||||
<div>
|
||||
<ModelSelector
|
||||
accessToken={accessToken || ""}
|
||||
value={testModel}
|
||||
onChange={setTestModel}
|
||||
disabled={isTesting}
|
||||
showLabel={true}
|
||||
labelText="Select Model"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={onTest}
|
||||
disabled={isTesting || !testQuery || !testModel || !filterEnabled}
|
||||
className="w-full"
|
||||
>
|
||||
<PlayCircle className="h-4 w-4" />
|
||||
{isTesting ? "Testing…" : "Test Filter"}
|
||||
</Button>
|
||||
|
||||
{!filterEnabled && (
|
||||
<Alert className="border-amber-300 bg-amber-50 text-amber-900 dark:bg-amber-950/30 dark:text-amber-200">
|
||||
<AlertTitle>Semantic filtering is disabled</AlertTitle>
|
||||
<AlertDescription>
|
||||
Enable semantic filtering and save settings to test the
|
||||
filter.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{testResult && (
|
||||
<div>
|
||||
<Space style={{ marginBottom: 8 }}>
|
||||
<CodeOutlined />
|
||||
<Typography.Text strong>API Usage</Typography.Text>
|
||||
</Space>
|
||||
<Typography.Text type="secondary" style={{ display: "block", marginBottom: 8 }}>
|
||||
Use this curl command to test the semantic filter with your current configuration.
|
||||
</Typography.Text>
|
||||
<Typography.Text strong style={{ display: "block", marginBottom: 8 }}>
|
||||
<h5 className="text-base font-semibold mb-3">Results</h5>
|
||||
<Alert className="mb-4 border-emerald-300 bg-emerald-50 text-emerald-900 dark:bg-emerald-950/30 dark:text-emerald-200">
|
||||
<AlertTitle>
|
||||
{testResult.selectedTools} tools selected
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
Filtered from {testResult.totalTools} available tools
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<div>
|
||||
<span className="font-semibold block mb-2">
|
||||
Selected Tools:
|
||||
</span>
|
||||
<ul className="pl-5 m-0 space-y-1">
|
||||
{testResult.tools.map((tool, index) => (
|
||||
<li key={index}>
|
||||
<span>{tool}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="api">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Code className="h-4 w-4" />
|
||||
<span className="font-semibold">API Usage</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
Use this curl command to test the semantic filter with your
|
||||
current configuration.
|
||||
</p>
|
||||
<span className="font-semibold block mb-2">
|
||||
Response headers to check:
|
||||
</Typography.Text>
|
||||
<ul style={{ paddingLeft: 20, margin: "0 0 12px 0" }}>
|
||||
</span>
|
||||
<ul className="pl-5 mb-3 space-y-2">
|
||||
<li>
|
||||
<Typography.Text>
|
||||
<span>
|
||||
x-litellm-semantic-filter: shows total tools → selected tools
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ display: "block" }}>
|
||||
</span>
|
||||
<span className="text-muted-foreground block text-xs">
|
||||
Example: 10→3
|
||||
</Typography.Text>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<Typography.Text>
|
||||
<span>
|
||||
x-litellm-semantic-filter-tools: CSV of selected tool names
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ display: "block" }}>
|
||||
</span>
|
||||
<span className="text-muted-foreground block text-xs">
|
||||
Example: wikipedia-fetch,github-search,slack-post
|
||||
</Typography.Text>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<pre
|
||||
style={{
|
||||
background: "#f5f5f5",
|
||||
padding: 12,
|
||||
borderRadius: 4,
|
||||
overflow: "auto",
|
||||
fontSize: 12,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
<pre className="bg-muted border border-border p-3 rounded-md overflow-auto text-xs m-0">
|
||||
{curlCommand}
|
||||
</pre>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue