mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
feat(ui): vector_store_management/VectorStoreTester to shadcn
- antd Button/Card/Divider/Input(TextArea)/Spin/Typography + antd icons (Send/Database/Loading/Down/Right) \u2192 shadcn Button/Card/ Textarea + lucide icons + native <hr>/<h4>. - Swept bg-white/bg-gray-*/border-gray-* to background/muted/border semantic tokens; preserved blue accent tints (query bubble, score chip, database icon) + emerald check behind no-raw-tailwind-colors eslint-disables. - Replaced antd Spin with lucide Loader2 (animate-spin). Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
d4e08f4ae2
commit
d90c70fed0
1 changed files with 150 additions and 74 deletions
|
|
@ -1,13 +1,18 @@
|
|||
import React, { useState } from "react";
|
||||
import { Button, Input, Card, Typography, Spin, Divider } from "antd";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import MessageManager from "@/components/molecules/message_manager";
|
||||
import { SendOutlined, DatabaseOutlined, LoadingOutlined, DownOutlined, RightOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Database,
|
||||
Loader2,
|
||||
Send,
|
||||
} from "lucide-react";
|
||||
import { vectorStoreSearchCall } from "../networking";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Text, Title } = Typography;
|
||||
|
||||
interface VectorStoreContent {
|
||||
text: string;
|
||||
type: string;
|
||||
|
|
@ -18,6 +23,7 @@ interface VectorStoreResult {
|
|||
content: VectorStoreContent[];
|
||||
file_id?: string;
|
||||
filename?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
attributes?: Record<string, any>;
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +39,10 @@ interface VectorStoreTesterProps {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
export const VectorStoreTester: React.FC<VectorStoreTesterProps> = ({ vectorStoreId, accessToken, className = "" }) => {
|
||||
export const VectorStoreTester: React.FC<VectorStoreTesterProps> = ({
|
||||
vectorStoreId,
|
||||
accessToken,
|
||||
}) => {
|
||||
const [query, setQuery] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [searchHistory, setSearchHistory] = useState<
|
||||
|
|
@ -43,7 +52,9 @@ export const VectorStoreTester: React.FC<VectorStoreTesterProps> = ({ vectorStor
|
|||
timestamp: number;
|
||||
}[]
|
||||
>([]);
|
||||
const [expandedResults, setExpandedResults] = useState<Record<string, boolean>>({});
|
||||
const [expandedResults, setExpandedResults] = useState<
|
||||
Record<string, boolean>
|
||||
>({});
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!query.trim()) {
|
||||
|
|
@ -54,7 +65,11 @@ export const VectorStoreTester: React.FC<VectorStoreTesterProps> = ({ vectorStor
|
|||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await vectorStoreSearchCall(accessToken, vectorStoreId, query);
|
||||
const response = await vectorStoreSearchCall(
|
||||
accessToken,
|
||||
vectorStoreId,
|
||||
query,
|
||||
);
|
||||
|
||||
const historyEntry = {
|
||||
query,
|
||||
|
|
@ -101,15 +116,21 @@ export const VectorStoreTester: React.FC<VectorStoreTesterProps> = ({ vectorStor
|
|||
<Card className="w-full rounded-xl shadow-md">
|
||||
<div className="flex flex-col h-[600px]">
|
||||
{/* Header */}
|
||||
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
|
||||
<div className="p-4 border-b border-border flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<DatabaseOutlined className="mr-2 text-blue-500" />
|
||||
<Title level={4} className="mb-0">
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<Database className="mr-2 text-blue-500 h-4 w-4" />
|
||||
<h4 className="text-base font-semibold m-0">
|
||||
Test Vector Store
|
||||
</Title>
|
||||
</h4>
|
||||
</div>
|
||||
{searchHistory.length > 0 && (
|
||||
<Button onClick={clearHistory} size="small">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={clearHistory}
|
||||
>
|
||||
Clear History
|
||||
</Button>
|
||||
)}
|
||||
|
|
@ -118,9 +139,11 @@ export const VectorStoreTester: React.FC<VectorStoreTesterProps> = ({ vectorStor
|
|||
{/* Results Area */}
|
||||
<div className="flex-1 overflow-auto p-4 pb-0">
|
||||
{searchHistory.length === 0 ? (
|
||||
<div className="h-full flex flex-col items-center justify-center text-gray-400">
|
||||
<DatabaseOutlined style={{ fontSize: "48px", marginBottom: "16px" }} />
|
||||
<Text>Test your vector store by entering a search query below</Text>
|
||||
<div className="h-full flex flex-col items-center justify-center text-muted-foreground">
|
||||
<Database className="h-12 w-12 mb-4" />
|
||||
<span>
|
||||
Test your vector store by entering a search query below
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
|
|
@ -128,10 +151,13 @@ export const VectorStoreTester: React.FC<VectorStoreTesterProps> = ({ vectorStor
|
|||
<div key={index} className="space-y-2">
|
||||
{/* User Query */}
|
||||
<div className="text-right">
|
||||
<div className="inline-block max-w-[80%] rounded-lg shadow-sm p-3 bg-blue-50 border border-blue-200">
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<div className="inline-block max-w-[80%] rounded-lg shadow-sm p-3 bg-blue-50 border border-blue-200 dark:bg-blue-950/30 dark:border-blue-900">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<strong className="text-sm">Query</strong>
|
||||
<span className="text-xs text-gray-500">{formatTimestamp(entry.timestamp)}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{formatTimestamp(entry.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-left">{entry.query}</div>
|
||||
</div>
|
||||
|
|
@ -139,85 +165,128 @@ export const VectorStoreTester: React.FC<VectorStoreTesterProps> = ({ vectorStor
|
|||
|
||||
{/* Vector Store Response */}
|
||||
<div className="text-left">
|
||||
<div className="inline-block max-w-[80%] rounded-lg shadow-sm p-3 bg-white border border-gray-200">
|
||||
<div className="inline-block max-w-[80%] rounded-lg shadow-sm p-3 bg-background border border-border">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<DatabaseOutlined className="text-green-500" />
|
||||
<strong className="text-sm">Vector Store Results</strong>
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<Database className="h-4 w-4 text-emerald-500" />
|
||||
<strong className="text-sm">
|
||||
Vector Store Results
|
||||
</strong>
|
||||
{entry.response && (
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-gray-100 text-gray-600">
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground">
|
||||
{entry.response.data?.length || 0} results
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{entry.response && entry.response.data && entry.response.data.length > 0 ? (
|
||||
{entry.response &&
|
||||
entry.response.data &&
|
||||
entry.response.data.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{entry.response.data.map((result, resultIndex) => {
|
||||
const isExpanded = expandedResults[`${index}-${resultIndex}`] || false;
|
||||
const isExpanded =
|
||||
expandedResults[`${index}-${resultIndex}`] ||
|
||||
false;
|
||||
|
||||
return (
|
||||
<div key={resultIndex} className="border rounded-lg overflow-hidden bg-gray-50">
|
||||
<div
|
||||
key={resultIndex}
|
||||
className="border border-border rounded-lg overflow-hidden bg-muted"
|
||||
>
|
||||
{/* Clickable Header */}
|
||||
<div
|
||||
className="flex justify-between items-center p-3 cursor-pointer hover:bg-gray-100 transition-colors"
|
||||
onClick={() => toggleResultExpansion(index, resultIndex)}
|
||||
className="flex justify-between items-center p-3 cursor-pointer hover:bg-muted/70 transition-colors"
|
||||
onClick={() =>
|
||||
toggleResultExpansion(index, resultIndex)
|
||||
}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{isExpanded ? (
|
||||
<DownOutlined className="text-gray-500 mr-2" />
|
||||
<ChevronDown className="h-4 w-4 text-muted-foreground mr-2" />
|
||||
) : (
|
||||
<RightOutlined className="text-gray-500 mr-2" />
|
||||
)}
|
||||
<span className="font-medium text-sm">Result {resultIndex + 1}</span>
|
||||
{/* Show preview of content when collapsed */}
|
||||
{!isExpanded && result.content && result.content[0] && (
|
||||
<span className="ml-2 text-xs text-gray-500 truncate max-w-md">
|
||||
- {result.content[0].text.substring(0, 100)}...
|
||||
</span>
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground mr-2" />
|
||||
)}
|
||||
<span className="font-medium text-sm">
|
||||
Result {resultIndex + 1}
|
||||
</span>
|
||||
{!isExpanded &&
|
||||
result.content &&
|
||||
result.content[0] && (
|
||||
<span className="ml-2 text-xs text-muted-foreground truncate max-w-md">
|
||||
-{" "}
|
||||
{result.content[0].text.substring(
|
||||
0,
|
||||
100,
|
||||
)}
|
||||
...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded dark:bg-blue-950/40 dark:text-blue-200">
|
||||
Score: {result.score.toFixed(4)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Expandable Content */}
|
||||
{isExpanded && (
|
||||
<div className="border-t bg-white p-3">
|
||||
{/* Content */}
|
||||
<div className="border-t border-border bg-background p-3">
|
||||
{result.content &&
|
||||
result.content.map((content, contentIndex) => (
|
||||
<div key={contentIndex} className="mb-3">
|
||||
<div className="text-xs text-gray-500 mb-1">Content ({content.type})</div>
|
||||
<div className="text-sm bg-gray-50 p-3 rounded border text-gray-800 max-h-40 overflow-y-auto">
|
||||
{content.text}
|
||||
result.content.map(
|
||||
(content, contentIndex) => (
|
||||
<div
|
||||
key={contentIndex}
|
||||
className="mb-3"
|
||||
>
|
||||
<div className="text-xs text-muted-foreground mb-1">
|
||||
Content ({content.type})
|
||||
</div>
|
||||
<div className="text-sm bg-muted p-3 rounded border border-border max-h-40 overflow-y-auto">
|
||||
{content.text}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
),
|
||||
)}
|
||||
|
||||
{/* Metadata */}
|
||||
{(result.file_id || result.filename || result.attributes) && (
|
||||
<div className="mt-3 pt-3 border-t border-gray-200">
|
||||
<div className="text-xs text-gray-500 mb-2 font-medium">Metadata</div>
|
||||
{(result.file_id ||
|
||||
result.filename ||
|
||||
result.attributes) && (
|
||||
<div className="mt-3 pt-3 border-t border-border">
|
||||
<div className="text-xs text-muted-foreground mb-2 font-medium">
|
||||
Metadata
|
||||
</div>
|
||||
<div className="space-y-2 text-xs">
|
||||
{result.file_id && (
|
||||
<div className="bg-gray-50 p-2 rounded">
|
||||
<span className="font-medium">File ID:</span> {result.file_id}
|
||||
<div className="bg-muted p-2 rounded">
|
||||
<span className="font-medium">
|
||||
File ID:
|
||||
</span>{" "}
|
||||
{result.file_id}
|
||||
</div>
|
||||
)}
|
||||
{result.filename && (
|
||||
<div className="bg-gray-50 p-2 rounded">
|
||||
<span className="font-medium">Filename:</span> {result.filename}
|
||||
</div>
|
||||
)}
|
||||
{result.attributes && Object.keys(result.attributes).length > 0 && (
|
||||
<div className="bg-gray-50 p-2 rounded">
|
||||
<span className="font-medium block mb-1">Attributes:</span>
|
||||
<pre className="text-xs bg-white p-2 rounded border overflow-x-auto">
|
||||
{JSON.stringify(result.attributes, null, 2)}
|
||||
</pre>
|
||||
<div className="bg-muted p-2 rounded">
|
||||
<span className="font-medium">
|
||||
Filename:
|
||||
</span>{" "}
|
||||
{result.filename}
|
||||
</div>
|
||||
)}
|
||||
{result.attributes &&
|
||||
Object.keys(result.attributes)
|
||||
.length > 0 && (
|
||||
<div className="bg-muted p-2 rounded">
|
||||
<span className="font-medium block mb-1">
|
||||
Attributes:
|
||||
</span>
|
||||
<pre className="text-xs bg-background p-2 rounded border border-border overflow-x-auto">
|
||||
{JSON.stringify(
|
||||
result.attributes,
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -228,12 +297,16 @@ export const VectorStoreTester: React.FC<VectorStoreTesterProps> = ({ vectorStor
|
|||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-gray-500 text-sm">No results found</div>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
No results found
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{index < searchHistory.length - 1 && <Divider />}
|
||||
{index < searchHistory.length - 1 && (
|
||||
<hr className="border-border" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -241,32 +314,35 @@ export const VectorStoreTester: React.FC<VectorStoreTesterProps> = ({ vectorStor
|
|||
|
||||
{isLoading && (
|
||||
<div className="flex justify-center items-center my-4">
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />
|
||||
<Loader2 className="h-6 w-6 animate-spin text-primary" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Input Area */}
|
||||
<div className="p-4 border-t border-gray-200 bg-white">
|
||||
<div className="flex items-end space-x-2">
|
||||
<div className="p-4 border-t border-border bg-background">
|
||||
<div className="flex items-end gap-2">
|
||||
<div className="flex-1">
|
||||
<TextArea
|
||||
<Textarea
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Enter your search query... (Shift+Enter for new line)"
|
||||
disabled={isLoading}
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
style={{ resize: "none" }}
|
||||
rows={1}
|
||||
className="resize-none"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
type="button"
|
||||
onClick={handleSearch}
|
||||
disabled={isLoading || !query.trim()}
|
||||
icon={<SendOutlined />}
|
||||
loading={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Send className="h-4 w-4" />
|
||||
)}
|
||||
Search
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue