mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): vector_store_management/vector_store_info to shadcn
- @tremor Card/Text/Title/Button/Badge + @heroicons ArrowLeftIcon + antd Tooltip/Button + @ant-design InfoCircleOutlined \u2192 shadcn Card/Button/Badge/Tooltip + lucide ArrowLeft/Info. - Tremor TabGroup/TabList family kept (whitelist). antd Form/Form. Item/Select2/AntInput/AntInput.TextArea preserved (parent antd Form context + validation rules + custom option renderer). - Refactored fetchVectorStoreDetails/fetchCredentials into use Callback; fixed unused activeTab state. Badge color='blue' \u2192 categorical bg-blue-100 class (file in .eslintrc overrides). Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
0a78ad67a3
commit
40855cb990
2 changed files with 192 additions and 86 deletions
|
|
@ -77,6 +77,7 @@
|
|||
"src/components/DeletedKeysPage/DeletedKeysTable/DeletedKeysTable.tsx",
|
||||
"src/components/AIHub/UsefulLinksManagement.tsx",
|
||||
"src/components/vector_store_management/VectorStoreTable.tsx",
|
||||
"src/components/vector_store_management/vector_store_info.tsx",
|
||||
"src/components/permissions/VectorStorePermissions.tsx",
|
||||
"src/components/permissions/AgentPermissions.tsx",
|
||||
"src/components/permissions/MCPServerPermissions.tsx",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,33 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Card, Text, Title, Button, Badge, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react";
|
||||
import { Form, Input, Select as Select2, Tooltip, Button as AntButton } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { ArrowLeftIcon } from "@heroicons/react/outline";
|
||||
import { vectorStoreInfoCall, vectorStoreUpdateCall, credentialListCall, CredentialItem } from "../networking";
|
||||
import React, { useCallback, useState, useEffect } from "react";
|
||||
// eslint-disable-next-line litellm-ui/no-banned-ui-imports
|
||||
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react";
|
||||
import {
|
||||
Form,
|
||||
Input as AntInput,
|
||||
Select as Select2,
|
||||
} from "antd";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { ArrowLeft, Info } from "lucide-react";
|
||||
import {
|
||||
vectorStoreInfoCall,
|
||||
vectorStoreUpdateCall,
|
||||
credentialListCall,
|
||||
CredentialItem,
|
||||
} from "../networking";
|
||||
import { VectorStore } from "./types";
|
||||
import { Providers, providerLogoMap, provider_map } from "../provider_info_helpers";
|
||||
import {
|
||||
Providers,
|
||||
providerLogoMap,
|
||||
provider_map,
|
||||
} from "../provider_info_helpers";
|
||||
import VectorStoreTester from "./VectorStoreTester";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
|
||||
|
|
@ -29,9 +51,8 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
const [isEditing, setIsEditing] = useState<boolean>(editVectorStore);
|
||||
const [metadataString, setMetadataString] = useState<string>("{}");
|
||||
const [credentials, setCredentials] = useState<CredentialItem[]>([]);
|
||||
const [activeTab, setActiveTab] = useState<string>(editVectorStore ? "details" : "details");
|
||||
|
||||
const fetchVectorStoreDetails = async () => {
|
||||
const fetchVectorStoreDetails = useCallback(async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
const response = await vectorStoreInfoCall(accessToken, vectorStoreId);
|
||||
|
|
@ -58,26 +79,28 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching vector store details:", error);
|
||||
NotificationsManager.fromBackend("Error fetching vector store details: " + error);
|
||||
NotificationsManager.fromBackend(
|
||||
"Error fetching vector store details: " + error,
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [accessToken, editVectorStore, form, vectorStoreId]);
|
||||
|
||||
const fetchCredentials = async () => {
|
||||
const fetchCredentials = useCallback(async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
const response = await credentialListCall(accessToken);
|
||||
console.log("List credentials response:", response);
|
||||
setCredentials(response.credentials || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching credentials:", error);
|
||||
}
|
||||
};
|
||||
}, [accessToken]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchVectorStoreDetails();
|
||||
fetchCredentials();
|
||||
}, [vectorStoreId, accessToken]);
|
||||
}, [fetchVectorStoreDetails, fetchCredentials]);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleSave = async (values: any) => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
|
|
@ -85,7 +108,8 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
let metadata = {};
|
||||
try {
|
||||
metadata = metadataString ? JSON.parse(metadataString) : {};
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (_e) {
|
||||
NotificationsManager.fromBackend("Invalid JSON in metadata field");
|
||||
return;
|
||||
}
|
||||
|
|
@ -116,13 +140,20 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
<div className="p-4 max-w-full">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<Button icon={ArrowLeftIcon} variant="light" className="mb-4" onClick={onClose}>
|
||||
<Button variant="ghost" className="mb-4" onClick={onClose}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
Back to Vector Stores
|
||||
</Button>
|
||||
<Title>Vector Store ID: {vectorStoreDetails.vector_store_id}</Title>
|
||||
<Text className="text-gray-500">{vectorStoreDetails.vector_store_description || "No description"}</Text>
|
||||
<h1 className="text-2xl font-semibold">
|
||||
Vector Store ID: {vectorStoreDetails.vector_store_id}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{vectorStoreDetails.vector_store_description || "No description"}
|
||||
</p>
|
||||
</div>
|
||||
{is_admin && !isEditing && <Button onClick={() => setIsEditing(true)}>Edit Vector Store</Button>}
|
||||
{is_admin && !isEditing && (
|
||||
<Button onClick={() => setIsEditing(true)}>Edit Vector Store</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<TabGroup>
|
||||
|
|
@ -137,33 +168,56 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
{isEditing ? (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Edit Vector Store</Title>
|
||||
<h2 className="text-lg font-semibold">Edit Vector Store</h2>
|
||||
</div>
|
||||
<Card>
|
||||
<Form form={form} onFinish={handleSave} layout="vertical" initialValues={vectorStoreDetails}>
|
||||
<Card className="p-4">
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleSave}
|
||||
layout="vertical"
|
||||
initialValues={vectorStoreDetails}
|
||||
>
|
||||
<Form.Item
|
||||
label="Vector Store ID"
|
||||
name="vector_store_id"
|
||||
rules={[{ required: true, message: "Please input a vector store ID" }]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please input a vector store ID",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
<AntInput disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Vector Store Name" name="vector_store_name">
|
||||
<Input />
|
||||
<Form.Item
|
||||
label="Vector Store Name"
|
||||
name="vector_store_name"
|
||||
>
|
||||
<AntInput />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Description" name="vector_store_description">
|
||||
<Input.TextArea rows={4} />
|
||||
<Form.Item
|
||||
label="Description"
|
||||
name="vector_store_description"
|
||||
>
|
||||
<AntInput.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Provider{" "}
|
||||
<Tooltip title="Select the provider for this vector store">
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="ml-1 h-3 w-3 inline text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Select the provider for this vector store
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</span>
|
||||
}
|
||||
name="custom_llm_provider"
|
||||
|
|
@ -174,22 +228,31 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
// Currently only showing Bedrock since it's the only supported provider
|
||||
if (providerEnum === "Bedrock") {
|
||||
return (
|
||||
<Select2.Option key={providerEnum} value={provider_map[providerEnum]}>
|
||||
<Select2.Option
|
||||
key={providerEnum}
|
||||
value={provider_map[providerEnum]}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={providerLogoMap[providerDisplayName]}
|
||||
alt={`${providerEnum} logo`}
|
||||
className="w-5 h-5"
|
||||
onError={(e) => {
|
||||
// Create a div with provider initial as fallback
|
||||
const target = e.target as HTMLImageElement;
|
||||
const target =
|
||||
e.target as HTMLImageElement;
|
||||
const parent = target.parentElement;
|
||||
if (parent) {
|
||||
const fallbackDiv = document.createElement("div");
|
||||
const fallbackDiv =
|
||||
document.createElement("div");
|
||||
fallbackDiv.className =
|
||||
"w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs";
|
||||
fallbackDiv.textContent = providerDisplayName.charAt(0);
|
||||
parent.replaceChild(fallbackDiv, target);
|
||||
"w-5 h-5 rounded-full bg-muted flex items-center justify-center text-xs";
|
||||
fallbackDiv.textContent =
|
||||
providerDisplayName.charAt(0);
|
||||
parent.replaceChild(
|
||||
fallbackDiv,
|
||||
target,
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
@ -203,20 +266,25 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
</Select2>
|
||||
</Form.Item>
|
||||
|
||||
{/* Credentials */}
|
||||
<div className="mb-4">
|
||||
<Text className="text-sm text-gray-500 mb-2">
|
||||
Either select existing credentials OR enter provider credentials below
|
||||
</Text>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
Either select existing credentials OR enter provider
|
||||
credentials below
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form.Item label="Existing Credentials" name="litellm_credential_name">
|
||||
<Form.Item
|
||||
label="Existing Credentials"
|
||||
name="litellm_credential_name"
|
||||
>
|
||||
<Select2
|
||||
showSearch
|
||||
placeholder="Select or search for existing credentials"
|
||||
optionFilterProp="children"
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
|
||||
(option?.label ?? "")
|
||||
.toLowerCase()
|
||||
.includes(input.toLowerCase())
|
||||
}
|
||||
options={[
|
||||
{ value: null, label: "None" },
|
||||
|
|
@ -230,22 +298,31 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
</Form.Item>
|
||||
|
||||
<div className="flex items-center my-4">
|
||||
<div className="flex-grow border-t border-gray-200"></div>
|
||||
<span className="px-4 text-gray-500 text-sm">OR</span>
|
||||
<div className="flex-grow border-t border-gray-200"></div>
|
||||
<div className="flex-grow border-t border-border"></div>
|
||||
<span className="px-4 text-muted-foreground text-sm">
|
||||
OR
|
||||
</span>
|
||||
<div className="flex-grow border-t border-border"></div>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Metadata{" "}
|
||||
<Tooltip title="JSON metadata for the vector store">
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="ml-1 h-3 w-3 inline text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
JSON metadata for the vector store
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Input.TextArea
|
||||
<AntInput.TextArea
|
||||
rows={4}
|
||||
value={metadataString}
|
||||
onChange={(e) => setMetadataString(e.target.value)}
|
||||
|
|
@ -254,10 +331,14 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
</Form.Item>
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<AntButton onClick={() => setIsEditing(false)}>Cancel</AntButton>
|
||||
<AntButton type="primary" htmlType="submit">
|
||||
Save Changes
|
||||
</AntButton>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setIsEditing(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Save Changes</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
|
|
@ -265,40 +346,50 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
) : (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Vector Store Details</Title>
|
||||
{is_admin && <Button onClick={() => setIsEditing(true)}>Edit Vector Store</Button>}
|
||||
<h2 className="text-lg font-semibold">
|
||||
Vector Store Details
|
||||
</h2>
|
||||
{is_admin && (
|
||||
<Button onClick={() => setIsEditing(true)}>
|
||||
Edit Vector Store
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Card>
|
||||
<Card className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Text className="font-medium">ID</Text>
|
||||
<Text>{vectorStoreDetails.vector_store_id}</Text>
|
||||
<p className="font-medium">ID</p>
|
||||
<p>{vectorStoreDetails.vector_store_id}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Name</Text>
|
||||
<Text>{vectorStoreDetails.vector_store_name || "-"}</Text>
|
||||
<p className="font-medium">Name</p>
|
||||
<p>{vectorStoreDetails.vector_store_name || "-"}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Description</Text>
|
||||
<Text>{vectorStoreDetails.vector_store_description || "-"}</Text>
|
||||
<p className="font-medium">Description</p>
|
||||
<p>
|
||||
{vectorStoreDetails.vector_store_description || "-"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Provider</Text>
|
||||
<p className="font-medium">Provider</p>
|
||||
<div className="flex items-center space-x-2 mt-1">
|
||||
{(() => {
|
||||
const provider = vectorStoreDetails.custom_llm_provider || "bedrock";
|
||||
const provider =
|
||||
vectorStoreDetails.custom_llm_provider || "bedrock";
|
||||
const { displayName, logo } = (() => {
|
||||
// Find the enum key by matching provider_map values
|
||||
const enumKey = Object.keys(provider_map).find(
|
||||
(key) => provider_map[key].toLowerCase() === provider.toLowerCase(),
|
||||
(key) =>
|
||||
provider_map[key].toLowerCase() ===
|
||||
provider.toLowerCase(),
|
||||
);
|
||||
|
||||
if (!enumKey) {
|
||||
return { displayName: provider, logo: "" };
|
||||
}
|
||||
|
||||
// Get the display name from Providers enum and logo from map
|
||||
const displayName = Providers[enumKey as keyof typeof Providers];
|
||||
const displayName =
|
||||
Providers[enumKey as keyof typeof Providers];
|
||||
const logo = providerLogoMap[displayName];
|
||||
|
||||
return { displayName, logo };
|
||||
|
|
@ -307,46 +398,60 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
return (
|
||||
<>
|
||||
{logo && (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={logo}
|
||||
alt={`${displayName} logo`}
|
||||
className="w-5 h-5"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
const target =
|
||||
e.target as HTMLImageElement;
|
||||
const parent = target.parentElement;
|
||||
if (parent) {
|
||||
const fallbackDiv = document.createElement("div");
|
||||
const fallbackDiv =
|
||||
document.createElement("div");
|
||||
fallbackDiv.className =
|
||||
"w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs";
|
||||
fallbackDiv.textContent = displayName.charAt(0);
|
||||
"w-5 h-5 rounded-full bg-muted flex items-center justify-center text-xs";
|
||||
fallbackDiv.textContent =
|
||||
displayName.charAt(0);
|
||||
parent.replaceChild(fallbackDiv, target);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Badge color="blue">{displayName}</Badge>
|
||||
<Badge className="bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300">
|
||||
{displayName}
|
||||
</Badge>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Metadata</Text>
|
||||
<div className="bg-gray-50 p-3 rounded mt-2 font-mono text-xs overflow-auto max-h-48">
|
||||
<p className="font-medium">Metadata</p>
|
||||
<div className="bg-muted p-3 rounded mt-2 font-mono text-xs overflow-auto max-h-48">
|
||||
<pre>{metadataString}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Created</Text>
|
||||
<Text>
|
||||
{vectorStoreDetails.created_at ? new Date(vectorStoreDetails.created_at).toLocaleString() : "-"}
|
||||
</Text>
|
||||
<p className="font-medium">Created</p>
|
||||
<p>
|
||||
{vectorStoreDetails.created_at
|
||||
? new Date(
|
||||
vectorStoreDetails.created_at,
|
||||
).toLocaleString()
|
||||
: "-"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Last Updated</Text>
|
||||
<Text>
|
||||
{vectorStoreDetails.updated_at ? new Date(vectorStoreDetails.updated_at).toLocaleString() : "-"}
|
||||
</Text>
|
||||
<p className="font-medium">Last Updated</p>
|
||||
<p>
|
||||
{vectorStoreDetails.updated_at
|
||||
? new Date(
|
||||
vectorStoreDetails.updated_at,
|
||||
).toLocaleString()
|
||||
: "-"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue