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:
Cursor Agent 2026-04-23 17:46:21 +00:00
parent 0a78ad67a3
commit 40855cb990
No known key found for this signature in database
2 changed files with 192 additions and 86 deletions

View file

@ -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",

View file

@ -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>