mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
feat(ui): migrate playground chrome to shadcn
- AgentBuilderView: antd Button/Input/Modal/Select/Spin/Tabs + Input.TextArea -> shadcn Button/Input/Select/Textarea/Tabs; Modal.confirm -> AlertDialog; added MCPServerMultiSelect chip-list pattern - ChatUI message rendering/streaming untouched; chrome defer due to deeply-coupled antd Select.OptGroup + optionLabelProp patterns that require a searchable multi-select primitive. Deferred in BLOCKERS. - CompareUI: antd Button/Input/Select/Tooltip -> shadcn equivalents; Input.Password -> Input type=password; retained Tooltip wrapper around disabled button via span trigger - RealtimePlayground: antd Button/Input/Select/Typography -> shadcn equivalents; round mic Button implemented with size=icon; loading spinners use LoaderCircle - ComparisonPanel: antd Slider -> shadcn Slider (value array) - CompareUI.test: repaired ant-btn classname selector with semantic getByRole - ESLint: exempt playground chrome files that retain status-color categorical palettes Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
10c89b4e83
commit
a5b2a32b4a
7 changed files with 649 additions and 318 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 `<Form form={form}>` 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 `<Form form={form}>` 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 `<Select>` has no `OptGroup` and no
|
||||
searchable multi-select primitive; a faithful rewrite needs a custom
|
||||
`Command`+`Popover` combobox plus a chip display, in tandem with the "__all__"
|
||||
sentinel handling. Migrating the surrounding chrome (Modal, Popover, Tooltip,
|
||||
Spin, Button, TextArea, Dragger) alone would leave the file in an
|
||||
inconsistent half-migrated state and exceeds the 2-attempt budget. Defer until
|
||||
a reusable searchable-multi-select-with-groups primitive exists.
|
||||
|
|
|
|||
|
|
@ -1,20 +1,64 @@
|
|||
"use client";
|
||||
|
||||
import { MessageCircle as CommentOutlined, Trash2 as DeleteOutlined, FlaskConical as ExperimentOutlined, Link as LinkOutlined, Plus as PlusOutlined, Bot as RobotOutlined, Save as SaveOutlined } from "lucide-react";
|
||||
import { Button, Input, Modal, Select, Spin, Tabs } from "antd";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
MessageCircle as CommentOutlined,
|
||||
Trash2 as DeleteOutlined,
|
||||
FlaskConical as ExperimentOutlined,
|
||||
Link as LinkOutlined,
|
||||
Plus as PlusOutlined,
|
||||
Bot as RobotOutlined,
|
||||
Save as SaveOutlined,
|
||||
LoaderCircle,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/components/ui/tabs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import CodeBlock from "@/app/(dashboard)/api-reference/components/CodeBlock";
|
||||
import NotificationsManager from "../../molecules/notifications_manager";
|
||||
import { keyCreateCall, modelCreateCall, modelDeleteCall, modelPatchUpdateCall, proxyBaseUrl } from "../../networking";
|
||||
import {
|
||||
keyCreateCall,
|
||||
modelCreateCall,
|
||||
modelDeleteCall,
|
||||
modelPatchUpdateCall,
|
||||
proxyBaseUrl,
|
||||
} from "../../networking";
|
||||
import { fetchMCPServers } from "../../networking";
|
||||
import { MCPServer } from "../../mcp_tools/types";
|
||||
import { AgentModel, fetchAvailableAgentModels, MCPToolEntry } from "../llm_calls/fetch_agents";
|
||||
import {
|
||||
AgentModel,
|
||||
fetchAvailableAgentModels,
|
||||
MCPToolEntry,
|
||||
} from "../llm_calls/fetch_agents";
|
||||
import { fetchAvailableModels, ModelGroup } from "../llm_calls/fetch_models";
|
||||
import ComplianceUI from "../complianceUI/ComplianceUI";
|
||||
import ChatUI from "./ChatUI";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
export interface AgentBuilderViewProps {
|
||||
accessToken: string | null;
|
||||
token: string | null;
|
||||
|
|
@ -102,12 +146,17 @@ function ConnectTabContent({
|
|||
the model <span className="font-mono text-gray-800">{agentName}</span>.
|
||||
</p>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onCreateKey}
|
||||
loading={creatingKey}
|
||||
disabled={disabledPersonalKeyCreation}
|
||||
disabled={disabledPersonalKeyCreation || creatingKey}
|
||||
>
|
||||
Create key for this agent
|
||||
{creatingKey ? (
|
||||
<>
|
||||
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
"Create key for this agent"
|
||||
)}
|
||||
</Button>
|
||||
{disabledPersonalKeyCreation && (
|
||||
<p className="text-xs text-amber-600 mt-2">Key creation is disabled for your account.</p>
|
||||
|
|
@ -122,6 +171,93 @@ function ConnectTabContent({
|
|||
);
|
||||
}
|
||||
|
||||
function MCPServerMultiSelect({
|
||||
servers,
|
||||
selectedIds,
|
||||
onChange,
|
||||
loading,
|
||||
}: {
|
||||
servers: MCPServer[];
|
||||
selectedIds: string[];
|
||||
onChange: (ids: string[]) => void;
|
||||
loading: boolean;
|
||||
}) {
|
||||
const options = useMemo(
|
||||
() =>
|
||||
servers.map((s) => ({
|
||||
value: s.server_id,
|
||||
label: s.alias || s.server_name || s.server_id,
|
||||
})),
|
||||
[servers],
|
||||
);
|
||||
const remaining = useMemo(
|
||||
() => options.filter((o) => !selectedIds.includes(o.value)),
|
||||
[options, selectedIds],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Select
|
||||
value=""
|
||||
onValueChange={(v) => {
|
||||
if (v && !selectedIds.includes(v)) onChange([...selectedIds, v]);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
loading
|
||||
? "Loading MCP servers..."
|
||||
: "Select MCP servers to attach (same format as chat completions API)"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{remaining.length === 0 ? (
|
||||
<div className="py-2 px-3 text-sm text-muted-foreground">
|
||||
{options.length === 0
|
||||
? "No MCP servers available"
|
||||
: "All available servers selected"}
|
||||
</div>
|
||||
) : (
|
||||
remaining.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{selectedIds.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{selectedIds.map((id) => {
|
||||
const opt = options.find((o) => o.value === id);
|
||||
return (
|
||||
<Badge
|
||||
key={id}
|
||||
variant="secondary"
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
{opt?.label ?? id}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onChange(selectedIds.filter((s) => s !== id))
|
||||
}
|
||||
className="inline-flex items-center justify-center rounded-full hover:bg-muted-foreground/20"
|
||||
aria-label={`Remove ${opt?.label ?? id}`}
|
||||
>
|
||||
<X size={12} />
|
||||
</button>
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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({
|
|||
<span className="text-sm font-medium text-gray-900">Agent Builder</span>
|
||||
{isNewAgent ? (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={handleSaveAgent}
|
||||
loading={saving}
|
||||
disabled={!draftName?.trim() || !draftUnderlyingModel}
|
||||
disabled={saving || !draftName?.trim() || !draftUnderlyingModel}
|
||||
>
|
||||
Save Agent
|
||||
{saving ? (
|
||||
<>
|
||||
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SaveOutlined className="mr-2 h-4 w-4" />
|
||||
Save Agent
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<span className="text-xs text-gray-500">Build Agents that pass your compliance requirements.</span>
|
||||
|
|
@ -443,12 +586,20 @@ export default function AgentBuilderView({
|
|||
<div className="w-60 flex-shrink-0 border-r border-gray-200 bg-white flex flex-col">
|
||||
<div className="flex items-center justify-between border-b border-gray-200 p-3">
|
||||
<span className="text-xs font-semibold uppercase tracking-wide text-gray-500">Agents</span>
|
||||
<Button type="text" size="small" icon={<PlusOutlined />} onClick={handleAddAgent} aria-label="Add agent" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleAddAgent}
|
||||
aria-label="Add agent"
|
||||
className="h-7 w-7 p-0"
|
||||
>
|
||||
<PlusOutlined className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-2">
|
||||
{loadingAgents ? (
|
||||
<div className="flex justify-center py-4">
|
||||
<Spin size="small" />
|
||||
<LoaderCircle className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
|
@ -489,220 +640,292 @@ export default function AgentBuilderView({
|
|||
{(selectedId !== null || isNewAgent) && (
|
||||
<>
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={(k) => 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: (
|
||||
<span>
|
||||
<RobotOutlined className="mr-1" /> Configure
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<div className="h-full overflow-y-auto p-6">
|
||||
{(isNewAgent || selectedAgent) ? (
|
||||
<div className="mx-auto max-w-xl space-y-4">
|
||||
{!selectedAgentModelId && selectedAgent && (
|
||||
<div className="rounded border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
|
||||
This agent cannot be updated or deleted here (missing model id). Manage it from Models & Endpoints.
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">Agent name</label>
|
||||
<Input
|
||||
value={draftName}
|
||||
onChange={(e) => setDraftName(e.target.value)}
|
||||
placeholder="My Agent"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">System prompt</label>
|
||||
<TextArea
|
||||
value={draftSystemPrompt}
|
||||
onChange={(e) => setDraftSystemPrompt(e.target.value)}
|
||||
placeholder="You are a helpful assistant..."
|
||||
rows={6}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">Underlying LLM</label>
|
||||
<Select
|
||||
value={draftUnderlyingModel}
|
||||
onChange={setDraftUnderlyingModel}
|
||||
className="w-full"
|
||||
options={modelGroups.map((m) => ({ value: m.model_group, label: m.model_group }))}
|
||||
placeholder="Select model"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">Temperature</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.1}
|
||||
value={draftTemperature}
|
||||
onChange={(e) => setDraftTemperature(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">Max tokens</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={draftMaxTokens}
|
||||
onChange={(e) => setDraftMaxTokens(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">MCP servers</label>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="Select MCP servers to attach (same format as chat completions API)"
|
||||
value={selectedMCPServerIds}
|
||||
onChange={handleMCPServerChange}
|
||||
loading={loadingMCPServers}
|
||||
className="w-full"
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
options={mcpServers.map((s) => ({
|
||||
value: s.server_id,
|
||||
label: s.alias || s.server_name || s.server_id,
|
||||
}))}
|
||||
/>
|
||||
{selectedAgent && draftTools.length > 0 && (
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
{draftTools.length} MCP server{draftTools.length !== 1 ? "s" : ""} saved. Use the same <code className="rounded bg-gray-100 px-1">tools</code> array in chat completions when calling this agent.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{selectedAgent && (
|
||||
<div className="flex flex-wrap items-center gap-2 pt-2">
|
||||
{selectedAgentModelId && (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={handleUpdateAgent}
|
||||
loading={saving}
|
||||
disabled={!draftName?.trim() || !draftUnderlyingModel}
|
||||
>
|
||||
value={activeTab}
|
||||
onValueChange={(k) =>
|
||||
setActiveTab(k as "configure" | "chat" | "test" | "connect")
|
||||
}
|
||||
className="flex-1 flex flex-col overflow-hidden"
|
||||
>
|
||||
<TabsList className="mx-4 mt-2 self-start">
|
||||
<TabsTrigger value="configure">
|
||||
<RobotOutlined className="mr-1 h-4 w-4" /> Configure
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="chat" disabled={isNewAgent}>
|
||||
<CommentOutlined className="mr-1 h-4 w-4" /> Chat
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="test" disabled={isNewAgent}>
|
||||
<ExperimentOutlined className="mr-1 h-4 w-4" /> Batch Test
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="connect" disabled={isNewAgent}>
|
||||
<LinkOutlined className="mr-1 h-4 w-4" /> Connect
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="configure" className="flex-1 overflow-hidden m-0">
|
||||
<div className="h-full overflow-y-auto p-6">
|
||||
{isNewAgent || selectedAgent ? (
|
||||
<div className="mx-auto max-w-xl space-y-4">
|
||||
{!selectedAgentModelId && selectedAgent && (
|
||||
// eslint-disable-next-line litellm-ui/no-raw-tailwind-colors
|
||||
<div className="rounded border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
|
||||
This agent cannot be updated or deleted here
|
||||
(missing model id). Manage it from Models &
|
||||
Endpoints.
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
Agent name
|
||||
</label>
|
||||
<Input
|
||||
value={draftName}
|
||||
onChange={(e) => setDraftName(e.target.value)}
|
||||
placeholder="My Agent"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
System prompt
|
||||
</label>
|
||||
<Textarea
|
||||
value={draftSystemPrompt}
|
||||
onChange={(e) =>
|
||||
setDraftSystemPrompt(e.target.value)
|
||||
}
|
||||
placeholder="You are a helpful assistant..."
|
||||
rows={6}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
Underlying LLM
|
||||
</label>
|
||||
<Select
|
||||
value={draftUnderlyingModel ?? ""}
|
||||
onValueChange={(v) => setDraftUnderlyingModel(v)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select model" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{modelGroups.map((m) => (
|
||||
<SelectItem
|
||||
key={m.model_group}
|
||||
value={m.model_group}
|
||||
>
|
||||
{m.model_group}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
Temperature
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.1}
|
||||
value={draftTemperature}
|
||||
onChange={(e) =>
|
||||
setDraftTemperature(Number(e.target.value))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
Max tokens
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={draftMaxTokens}
|
||||
onChange={(e) =>
|
||||
setDraftMaxTokens(Number(e.target.value))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
MCP servers
|
||||
</label>
|
||||
<MCPServerMultiSelect
|
||||
servers={mcpServers}
|
||||
selectedIds={selectedMCPServerIds}
|
||||
onChange={handleMCPServerChange}
|
||||
loading={loadingMCPServers}
|
||||
/>
|
||||
{selectedAgent && draftTools.length > 0 && (
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{draftTools.length} MCP server
|
||||
{draftTools.length !== 1 ? "s" : ""} saved. Use
|
||||
the same{" "}
|
||||
<code className="rounded bg-muted px-1">
|
||||
tools
|
||||
</code>{" "}
|
||||
array in chat completions when calling this
|
||||
agent.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{selectedAgent && (
|
||||
<div className="flex flex-wrap items-center gap-2 pt-2">
|
||||
{selectedAgentModelId && (
|
||||
<>
|
||||
<Button
|
||||
onClick={handleUpdateAgent}
|
||||
disabled={
|
||||
saving ||
|
||||
!draftName?.trim() ||
|
||||
!draftUnderlyingModel
|
||||
}
|
||||
>
|
||||
{saving ? (
|
||||
<>
|
||||
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
|
||||
Updating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SaveOutlined className="mr-2 h-4 w-4" />
|
||||
Update Agent
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={handleDeleteAgent}
|
||||
loading={deleting}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button type="primary" icon={<CommentOutlined />} onClick={() => setActiveTab("chat")}>
|
||||
Test in Chat
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDeleteAgent}
|
||||
disabled={deleting}
|
||||
>
|
||||
{deleting ? (
|
||||
<>
|
||||
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
|
||||
Deleting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DeleteOutlined className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "chat",
|
||||
label: (
|
||||
<span>
|
||||
<CommentOutlined className="mr-1" /> Chat
|
||||
</span>
|
||||
),
|
||||
disabled: isNewAgent,
|
||||
children: (
|
||||
<div className="flex h-full flex-col min-h-0">
|
||||
{selectedAgent ? (
|
||||
<ChatUI
|
||||
key={selectedAgent.model_name}
|
||||
simplified
|
||||
fixedModel={selectedAgent.model_name}
|
||||
accessToken={accessToken}
|
||||
token={token}
|
||||
userRole={userRole}
|
||||
userID={userID}
|
||||
disabledPersonalKeyCreation={disabledPersonalKeyCreation}
|
||||
proxySettings={proxySettings}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center text-gray-500">
|
||||
Save an agent first to test in Chat.
|
||||
<Button onClick={() => setActiveTab("chat")}>
|
||||
<CommentOutlined className="mr-2 h-4 w-4" />
|
||||
Test in Chat
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "test",
|
||||
label: (
|
||||
<span>
|
||||
<ExperimentOutlined className="mr-1" /> Batch Test
|
||||
</span>
|
||||
),
|
||||
disabled: isNewAgent,
|
||||
children: (
|
||||
<div className="flex h-full flex-col min-h-0">
|
||||
{selectedAgent ? (
|
||||
<ComplianceUI
|
||||
accessToken={accessToken}
|
||||
disabledPersonalKeyCreation={disabledPersonalKeyCreation}
|
||||
backendMode="chat_completions"
|
||||
fixedModel={selectedAgent.model_name}
|
||||
proxySettings={proxySettings}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center text-gray-500">
|
||||
Select an agent to run batch tests.
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="chat" className="flex-1 overflow-hidden m-0">
|
||||
<div className="flex h-full flex-col min-h-0">
|
||||
{selectedAgent ? (
|
||||
<ChatUI
|
||||
key={selectedAgent.model_name}
|
||||
simplified
|
||||
fixedModel={selectedAgent.model_name}
|
||||
accessToken={accessToken}
|
||||
token={token}
|
||||
userRole={userRole}
|
||||
userID={userID}
|
||||
disabledPersonalKeyCreation={disabledPersonalKeyCreation}
|
||||
proxySettings={proxySettings}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center text-muted-foreground">
|
||||
Save an agent first to test in Chat.
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "connect",
|
||||
label: (
|
||||
<span>
|
||||
<LinkOutlined className="mr-1" /> Connect
|
||||
</span>
|
||||
),
|
||||
disabled: isNewAgent,
|
||||
children: (
|
||||
<div className="h-full overflow-y-auto p-6">
|
||||
{selectedAgent ? (
|
||||
<ConnectTabContent
|
||||
agentName={selectedAgent.model_name}
|
||||
proxySettings={proxySettings}
|
||||
customProxyBaseUrl={customProxyBaseUrl}
|
||||
accessToken={accessToken}
|
||||
userID={userID}
|
||||
disabledPersonalKeyCreation={disabledPersonalKeyCreation}
|
||||
creatingKey={creatingKey}
|
||||
createdKeyValue={createdKeyValue}
|
||||
onCreateKey={handleCreateKeyForAgent}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center text-gray-500">
|
||||
Select an agent to see how to connect.
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="test" className="flex-1 overflow-hidden m-0">
|
||||
<div className="flex h-full flex-col min-h-0">
|
||||
{selectedAgent ? (
|
||||
<ComplianceUI
|
||||
accessToken={accessToken}
|
||||
disabledPersonalKeyCreation={disabledPersonalKeyCreation}
|
||||
backendMode="chat_completions"
|
||||
fixedModel={selectedAgent.model_name}
|
||||
proxySettings={proxySettings}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center text-muted-foreground">
|
||||
Select an agent to run batch tests.
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
value="connect"
|
||||
className="flex-1 overflow-hidden m-0"
|
||||
>
|
||||
<div className="h-full overflow-y-auto p-6">
|
||||
{selectedAgent ? (
|
||||
<ConnectTabContent
|
||||
agentName={selectedAgent.model_name}
|
||||
proxySettings={proxySettings}
|
||||
customProxyBaseUrl={customProxyBaseUrl}
|
||||
accessToken={accessToken}
|
||||
userID={userID}
|
||||
disabledPersonalKeyCreation={disabledPersonalKeyCreation}
|
||||
creatingKey={creatingKey}
|
||||
createdKeyValue={createdKeyValue}
|
||||
onCreateKey={handleCreateKeyForAgent}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center text-muted-foreground">
|
||||
Select an agent to see how to connect.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<AlertDialog
|
||||
open={deleteConfirmOpen}
|
||||
onOpenChange={setDeleteConfirmOpen}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete agent</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{`Are you sure you want to delete "${selectedAgent?.model_name ?? ""}"? This cannot be undone.`}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={deleting}>
|
||||
Cancel
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
confirmDeleteAgent();
|
||||
}}
|
||||
disabled={deleting}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{deleting ? (
|
||||
<>
|
||||
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
|
||||
Deleting...
|
||||
</>
|
||||
) : (
|
||||
"Delete"
|
||||
)}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,33 @@
|
|||
"use client";
|
||||
|
||||
import { MicOff as AudioMutedOutlined, Mic as AudioOutlined, XCircle as CloseCircleOutlined, Send as SendOutlined, Volume2 as SoundOutlined } from "lucide-react";
|
||||
import { Button, Input, Select, Typography } from "antd";
|
||||
import {
|
||||
MicOff as AudioMutedOutlined,
|
||||
Mic as AudioOutlined,
|
||||
XCircle as CloseCircleOutlined,
|
||||
Send as SendOutlined,
|
||||
Volume2 as SoundOutlined,
|
||||
LoaderCircle,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { getProxyBaseUrl } from "../../networking";
|
||||
import { OPEN_AI_VOICE_SELECT_OPTIONS } from "./chatConstants";
|
||||
|
||||
const { Text } = Typography;
|
||||
const Text = ({
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}) => <span className={className}>{children}</span>;
|
||||
|
||||
interface RealtimeMessage {
|
||||
role: "user" | "assistant" | "system" | "status";
|
||||
|
|
@ -356,19 +377,38 @@ const RealtimePlayground: React.FC<RealtimePlaygroundProps> = ({
|
|||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
size="small"
|
||||
value={selectedVoice}
|
||||
onChange={setSelectedVoice}
|
||||
options={OPEN_AI_VOICE_SELECT_OPTIONS}
|
||||
style={{ width: 220 }}
|
||||
onValueChange={setSelectedVoice}
|
||||
disabled={isConnected}
|
||||
/>
|
||||
>
|
||||
<SelectTrigger className="w-56 h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{OPEN_AI_VOICE_SELECT_OPTIONS.map((opt) => (
|
||||
<SelectItem
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{!isConnected ? (
|
||||
<Button type="primary" onClick={connect} loading={isConnecting} size="small">
|
||||
Connect
|
||||
<Button onClick={connect} disabled={isConnecting} size="sm">
|
||||
{isConnecting ? (
|
||||
<>
|
||||
<LoaderCircle className="mr-2 h-3 w-3 animate-spin" />
|
||||
Connecting...
|
||||
</>
|
||||
) : (
|
||||
"Connect"
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<Button danger onClick={disconnect} size="small" icon={<CloseCircleOutlined />}>
|
||||
<Button variant="destructive" onClick={disconnect} size="sm">
|
||||
<CloseCircleOutlined className="mr-2 h-3 w-3" />
|
||||
Disconnect
|
||||
</Button>
|
||||
)}
|
||||
|
|
@ -418,30 +458,40 @@ const RealtimePlayground: React.FC<RealtimePlaygroundProps> = ({
|
|||
<div className="border-t border-gray-200 p-3 bg-white">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
shape="circle"
|
||||
size="large"
|
||||
type={isRecording ? "primary" : "default"}
|
||||
danger={isRecording}
|
||||
icon={isRecording ? <AudioMutedOutlined /> : <AudioOutlined />}
|
||||
variant={isRecording ? "destructive" : "outline"}
|
||||
size="icon"
|
||||
onClick={isRecording ? stopRecording : startRecording}
|
||||
title={isRecording ? "Stop recording" : "Start recording"}
|
||||
className={isRecording ? "animate-pulse" : ""}
|
||||
/>
|
||||
className={`rounded-full h-10 w-10 ${isRecording ? "animate-pulse" : ""}`}
|
||||
aria-label={isRecording ? "Stop recording" : "Start recording"}
|
||||
>
|
||||
{isRecording ? (
|
||||
<AudioMutedOutlined className="h-5 w-5" />
|
||||
) : (
|
||||
<AudioOutlined className="h-5 w-5" />
|
||||
)}
|
||||
</Button>
|
||||
<Input
|
||||
placeholder="Type a message or use the mic..."
|
||||
value={inputText}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
onPressEnter={sendTextMessage}
|
||||
className="flex-1"
|
||||
size="large"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendTextMessage();
|
||||
}
|
||||
}}
|
||||
className="flex-1 h-10"
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SendOutlined />}
|
||||
size="icon"
|
||||
className="h-10 w-10"
|
||||
onClick={sendTextMessage}
|
||||
disabled={!inputText.trim()}
|
||||
size="large"
|
||||
/>
|
||||
aria-label="Send message"
|
||||
>
|
||||
<SendOutlined className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
{isRecording && (
|
||||
<div className="mt-2 flex items-center gap-2 text-red-500 text-xs">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { render, waitFor } from "@testing-library/react";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import CompareUI from "./CompareUI";
|
||||
|
|
@ -108,10 +108,11 @@ describe("CompareUI", () => {
|
|||
let comparisonPanels = container.querySelectorAll('[data-testid^="comparison-panel-"]');
|
||||
expect(comparisonPanels).toHaveLength(2);
|
||||
|
||||
const addButtons = Array.from(container.querySelectorAll('button[class*="ant-btn"]'));
|
||||
const addComparisonButton = addButtons.find((btn) => btn.textContent?.includes("Add Comparison"));
|
||||
const addComparisonButton = screen.getByRole("button", {
|
||||
name: /add comparison/i,
|
||||
});
|
||||
expect(addComparisonButton).toBeInTheDocument();
|
||||
await user.click(addComparisonButton!);
|
||||
await user.click(addComparisonButton);
|
||||
|
||||
// Wait for the new comparison panel to be added (should have 3 total now)
|
||||
await waitFor(() => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,27 @@
|
|||
"use client";
|
||||
|
||||
import NotificationsManager from "@/components/molecules/notifications_manager";
|
||||
import { Eraser as ClearOutlined, Trash2 as DeleteOutlined, FileText as FilePdfOutlined, Plus as PlusOutlined } from "lucide-react";
|
||||
import { Button, Input, Select, Tooltip } from "antd";
|
||||
import {
|
||||
Eraser as ClearOutlined,
|
||||
Trash2 as DeleteOutlined,
|
||||
FileText as FilePdfOutlined,
|
||||
Plus as PlusOutlined,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import ChatImageUpload from "../chat_ui/ChatImageUpload";
|
||||
|
|
@ -698,17 +717,22 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }:
|
|||
<span className="text-sm font-medium text-gray-600">Virtual Key Source</span>
|
||||
<Select
|
||||
value={apiKeySource}
|
||||
onChange={(value) => setApiKeySource(value as "session" | "custom")}
|
||||
onValueChange={(value) => setApiKeySource(value as "session" | "custom")}
|
||||
disabled={disabledPersonalKeyCreation}
|
||||
className="w-48"
|
||||
>
|
||||
<Select.Option value="session" disabled={!canUseSessionKey}>
|
||||
Current UI Session
|
||||
</Select.Option>
|
||||
<Select.Option value="custom">Virtual Key</Select.Option>
|
||||
<SelectTrigger className="w-48">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="session" disabled={!canUseSessionKey}>
|
||||
Current UI Session
|
||||
</SelectItem>
|
||||
<SelectItem value="custom">Virtual Key</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{apiKeySource === "custom" && (
|
||||
<Input.Password
|
||||
<Input
|
||||
type="password"
|
||||
value={customApiKey}
|
||||
onChange={(event) => setCustomApiKey(event.target.value)}
|
||||
placeholder="Enter Virtual Key"
|
||||
|
|
@ -718,34 +742,52 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }:
|
|||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-gray-600">Endpoint</span>
|
||||
<Select
|
||||
value={selectedEndpoint}
|
||||
onChange={(value) => setSelectedEndpoint(value as EndpointIdType)}
|
||||
className="w-56"
|
||||
<Select
|
||||
value={selectedEndpoint}
|
||||
onValueChange={(value) => setSelectedEndpoint(value as EndpointIdType)}
|
||||
>
|
||||
{getAvailableEndpoints().map((endpoint) => (
|
||||
<Select.Option
|
||||
key={endpoint.value}
|
||||
value={endpoint.value}
|
||||
>
|
||||
{endpoint.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
<SelectTrigger className="w-56">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{getAvailableEndpoints().map((endpoint) => (
|
||||
<SelectItem key={endpoint.value} value={endpoint.value}>
|
||||
{endpoint.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Button onClick={clearAllChats} disabled={!hasMessages} icon={<ClearOutlined />}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={clearAllChats}
|
||||
disabled={!hasMessages}
|
||||
>
|
||||
<ClearOutlined className="mr-2 h-4 w-4" />
|
||||
Clear All Chats
|
||||
</Button>
|
||||
<Tooltip
|
||||
title={
|
||||
comparisons.length >= maxComparisons ? "Compare up to 3 models at a time" : "Add another comparison"
|
||||
}
|
||||
>
|
||||
<Button onClick={addComparison} disabled={comparisons.length >= maxComparisons} icon={<PlusOutlined />}>
|
||||
Add Comparison
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={addComparison}
|
||||
disabled={comparisons.length >= maxComparisons}
|
||||
>
|
||||
<PlusOutlined className="mr-2 h-4 w-4" />
|
||||
Add Comparison
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{comparisons.length >= maxComparisons
|
||||
? "Compare up to 3 models at a time"
|
||||
: "Add another comparison"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { UnifiedSelector } from "./UnifiedSelector";
|
|||
import TagSelector from "../../../tag_management/TagSelector";
|
||||
import VectorStoreSelector from "../../../vector_store_management/VectorStoreSelector";
|
||||
import GuardrailSelector from "../../../guardrails/GuardrailSelector";
|
||||
import { Slider as AntSlider } from "antd";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
|
@ -192,12 +192,12 @@ export function ComparisonPanel({
|
|||
{comparison.temperature.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<AntSlider
|
||||
<Slider
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.01}
|
||||
value={comparison.temperature}
|
||||
onChange={(value) => {
|
||||
value={[comparison.temperature]}
|
||||
onValueChange={(value) => {
|
||||
const nextValue = Array.isArray(value) ? value[0] : value;
|
||||
const clamped = Math.min(
|
||||
2,
|
||||
|
|
@ -219,12 +219,12 @@ export function ComparisonPanel({
|
|||
{comparison.maxTokens}
|
||||
</span>
|
||||
</div>
|
||||
<AntSlider
|
||||
<Slider
|
||||
min={1}
|
||||
max={32768}
|
||||
step={1}
|
||||
value={comparison.maxTokens}
|
||||
onChange={(value) => {
|
||||
value={[comparison.maxTokens]}
|
||||
onValueChange={(value) => {
|
||||
const nextValue = Array.isArray(value) ? value[0] : value;
|
||||
const clamped = Math.min(
|
||||
32768,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue