diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index faaf3481a0b..9a666e504f0 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -4,11 +4,6 @@ "count": 1 } }, - "src/app/(dashboard)/access-groups/_components/AccessGroupsDetailsPage.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/access-groups/_components/AccessGroupsModal/AccessGroupBaseForm.tsx": { "no-restricted-imports": { "count": 2 @@ -24,11 +19,6 @@ "count": 1 } }, - "src/app/(dashboard)/access-groups/_components/AccessGroupsPage.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/admin-panel/_components/AdminPanel.tsx": { "no-restricted-imports": { "count": 2 @@ -2035,11 +2025,6 @@ "count": 1 } }, - "src/app/(dashboard)/vector-stores/_components/TestVectorStoreTab.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/vector-stores/_components/VectorStoreForm.tsx": { "no-nested-ternary": { "count": 2 @@ -2051,18 +2036,10 @@ "count": 1 } }, - "src/app/(dashboard)/vector-stores/_components/VectorStoreTester.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/vector-stores/_components/index.tsx": { "local/filename-pascal-case": { "count": 1 }, - "no-restricted-imports": { - "count": 1 - }, "react-hooks/set-state-in-effect": { "count": 1 } @@ -2998,23 +2975,10 @@ } }, "src/components/common_components/Filters/FilterInput.tsx": { - "no-restricted-imports": { - "count": 1 - }, "react-hooks/set-state-in-effect": { "count": 1 } }, - "src/components/common_components/Filters/FiltersButton.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/components/common_components/Filters/ResetFiltersButton.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/common_components/IconActionButton/BaseActionButton.tsx": { "no-restricted-imports": { "count": 1 @@ -3576,7 +3540,7 @@ "count": 1 }, "no-restricted-imports": { - "count": 3 + "count": 1 } }, "src/components/page_utils.test.ts": { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsDetailsPage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsDetailsPage.tsx index 72a89093bdb..9476a8d98af 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsDetailsPage.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsDetailsPage.tsx @@ -1,68 +1,63 @@ import { useAccessGroupDetails } from "@/app/(dashboard)/hooks/accessGroups/useAccessGroupDetails"; -import { - Button, - Card, - Col, - Descriptions, - Empty, - Flex, - Layout, - List, - Row, - Spin, - Tabs, - Tag, - theme, - Typography, -} from "antd"; import { ArrowLeftIcon, BotIcon, EditIcon, KeyIcon, LayersIcon, ServerIcon, UsersIcon } from "lucide-react"; import { useState } from "react"; import DefaultProxyAdminTag from "@/components/common_components/DefaultProxyAdminTag"; +import CopyButton from "@/components/shared/CopyButton"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; import { AccessGroupEditModal } from "./AccessGroupsModal/AccessGroupEditModal"; -const { Title, Text } = Typography; -const { Content } = Layout; - interface AccessGroupDetailProps { accessGroupId: string; onBack: () => void; } +const MAX_PREVIEW = 5; + +function ResourceList({ ids, emptyMessage }: { ids: string[]; emptyMessage: string }) { + if (ids.length === 0) { + return

{emptyMessage}

; + } + return ( +
+ {ids.map((id) => ( + + + {id} + + + ))} +
+ ); +} + export function AccessGroupDetail({ accessGroupId, onBack }: AccessGroupDetailProps) { const { data: accessGroup, isLoading } = useAccessGroupDetails(accessGroupId); - const { token } = theme.useToken(); const [isEditModalVisible, setIsEditModalVisible] = useState(false); const [showAllKeys, setShowAllKeys] = useState(false); const [showAllTeams, setShowAllTeams] = useState(false); - const MAX_PREVIEW = 5; - if (isLoading) { return ( - - - - - +
+
+ +
+
); } if (!accessGroup) { return ( - - +

Access group not found

+ ); } @@ -75,224 +70,159 @@ export function AccessGroupDetail({ accessGroupId, onBack }: AccessGroupDetailPr const displayedKeys = showAllKeys ? keyIds : keyIds.slice(0, MAX_PREVIEW); const displayedTeams = showAllTeams ? teamIds : teamIds.slice(0, MAX_PREVIEW); - const handleEdit = () => { - setIsEditModalVisible(true); - }; - - const tabItems = [ - { - key: "models", - label: ( - - - Models - {modelIds?.length} - - ), - children: - modelIds?.length > 0 ? ( - ( - - - {id} - - - )} - /> - ) : ( - - ), - }, - { - key: "mcp", - label: ( - - - MCP Servers - {mcpServerIds?.length} - - ), - children: - mcpServerIds?.length > 0 ? ( - ( - - - {id} - - - )} - /> - ) : ( - - ), - }, - { - key: "agents", - label: ( - - - Agents - {agentIds?.length} - - ), - children: - agentIds?.length > 0 ? ( - ( - - - {id} - - - )} - /> - ) : ( - - ), - }, - ]; - return ( - - {/* Header */} -
-
-
- - {accessGroup.access_group_name} - - - ID: {accessGroup.access_group_id} - +

{accessGroup.access_group_name}

+
+ ID: {accessGroup.access_group_id} + +
-
- {/* Group Details */} - - - - {accessGroup.description || "—"} - + + + Group Details + + +
+
Description
+
{accessGroup.description || "—"}
+
Created
+
{new Date(accessGroup.created_at).toLocaleString()} {accessGroup.created_by && ( - -  {"by"}  + <> + by - + )} - - +
+
Last Updated
+
{new Date(accessGroup.updated_at).toLocaleString()} {accessGroup.updated_by && ( - -  {"by"}  + <> + by - + )} - - - - - - {/* Attached Keys & Teams */} - - - - - Attached Keys - {keyIds?.length} - - } - extra={ - keyIds?.length > MAX_PREVIEW ? ( - - ) : null - } - > - {keyIds?.length > 0 ? ( - - {displayedKeys.map((id) => ( - - - {id.length > 20 ? `${id.slice(0, 10)}...${id.slice(-6)}` : id} - - - ))} - - ) : ( - - )} - - - - - - Attached Teams - {teamIds?.length} - - } - extra={ - teamIds?.length > MAX_PREVIEW ? ( - - ) : null - } - > - {teamIds?.length > 0 ? ( - - {displayedTeams.map((id) => ( - - - {id} - - - ))} - - ) : ( - - )} - - - - - {/* Resources Tabs */} - - +
+
+
+
+ +
+ + + + + Attached Keys + {keyIds.length} + + {keyIds.length > MAX_PREVIEW && ( + + + + )} + + + {keyIds.length > 0 ? ( +
+ {displayedKeys.map((id) => ( + + {id.length > 20 ? `${id.slice(0, 10)}...${id.slice(-6)}` : id} + + ))} +
+ ) : ( +

No keys attached

+ )} +
+
+ + + + + + Attached Teams + {teamIds.length} + + {teamIds.length > MAX_PREVIEW && ( + + + + )} + + + {teamIds.length > 0 ? ( +
+ {displayedTeams.map((id) => ( + + {id} + + ))} +
+ ) : ( +

No teams attached

+ )} +
+
+
+ + + + + + + + Models + {modelIds.length} + + + + MCP Servers + {mcpServerIds.length} + + + + Agents + {agentIds.length} + + + + + + + + + + + + + - {/* Edit Modal */} setIsEditModalVisible(false)} /> -
+ ); } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.tsx index 0de6596f57c..f37acb3d85a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.tsx @@ -1,10 +1,11 @@ import { AccessGroupResponse, useAccessGroups } from "@/app/(dashboard)/hooks/accessGroups/useAccessGroups"; import { useDeleteAccessGroup } from "@/app/(dashboard)/hooks/accessGroups/useDeleteAccessGroup"; -import { PlusOutlined } from "@ant-design/icons"; -import { Button, Flex, Input, Layout, Space, theme, Typography } from "antd"; -import { SearchIcon } from "lucide-react"; +import { Plus, SearchIcon, X } from "lucide-react"; import { useMemo, useState } from "react"; import DeleteResourceModal from "@/components/common_components/DeleteResourceModal"; +import { PageHeader } from "@/components/shared/PageHeader"; +import { Button } from "@/components/ui/button"; +import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group"; import { AccessGroupDetail } from "./AccessGroupsDetailsPage"; import { AccessGroupCreateModal } from "./AccessGroupsModal/AccessGroupCreateModal"; import { AccessGroupsTable } from "./AccessGroupsTable"; @@ -12,9 +13,6 @@ import { AccessGroup } from "./types"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { isProxyAdminRole } from "@/utils/roles"; -const { Title, Text } = Typography; -const { Content } = Layout; - function mapResponseToAccessGroup(r: AccessGroupResponse): AccessGroup { return { id: r.access_group_id, @@ -33,7 +31,6 @@ function mapResponseToAccessGroup(r: AccessGroupResponse): AccessGroup { } export function AccessGroupsPage() { - const { token } = theme.useToken(); const { userRole } = useAuthorized(); // Admin Viewer follows the read-parity rule: see access groups, no writes. const canModify = isProxyAdminRole(userRole ?? ""); @@ -62,31 +59,41 @@ export function AccessGroupsPage() { } return ( - - - - - Access Groups - - Manage resource permissions for your organization - - {canModify && ( - - )} - - - - } - placeholder="Search groups by name, ID, or description..." - style={{ maxWidth: 400 }} - value={searchText} - onChange={(e) => setSearchText(e.target.value)} - allowClear +
+
+ setIsCreateModalVisible(true)}> + + Create Access Group + + ) : undefined + } /> - +
+ +
+ + + + + setSearchText(e.target.value)} + /> + {searchText && ( + + setSearchText("")}> + + + + )} + +
- +
); } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx index 37eeaf4c2af..c1eda1be670 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx @@ -106,7 +106,7 @@ describe("OrganizationFilters", () => { org_alias: "test org", }; - render( + const { container } = render( { />, ); - const filtersButton = screen.getByRole("button", { name: /^filters$/i }); - const badgeWrapper = filtersButton.closest(".ant-badge"); - expect(badgeWrapper).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /^filters$/i })).toBeInTheDocument(); + expect(container.querySelector("sup")).toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/TestVectorStoreTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/TestVectorStoreTab.test.tsx index c1322dced60..1d7bce34e04 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/TestVectorStoreTab.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/TestVectorStoreTab.test.tsx @@ -1,4 +1,5 @@ -import { render, screen, fireEvent } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { describe, it, expect, vi } from "vitest"; import TestVectorStoreTab from "./TestVectorStoreTab"; import { VectorStore } from "@/components/vector_store_management/types"; @@ -60,31 +61,24 @@ describe("TestVectorStoreTab", () => { expect(screen.getByTestId("tester-access-token")).toHaveTextContent("test-token"); }); - it("should update VectorStoreTester when selecting different vector store", () => { + it("should update VectorStoreTester when selecting different vector store", async () => { + const user = userEvent.setup(); render(); - // Find the select component - const selectElement = screen.getByRole("combobox"); + await user.click(screen.getByRole("combobox")); + await user.click(await screen.findByText("Test Store 2")); - // Change selection - fireEvent.mouseDown(selectElement); - - // Wait for options to appear and click the second one - const option2 = screen.getByText("Test Store 2"); - fireEvent.click(option2); - - // Verify the tester component updated expect(screen.getByTestId("tester-vector-store-id")).toHaveTextContent("vs_456"); }); - it("should display vector store names in select options", () => { + it("should display vector store names in select options", async () => { + const user = userEvent.setup(); render(); - const selectElement = screen.getByRole("combobox"); - fireEvent.mouseDown(selectElement); + await user.click(screen.getByRole("combobox")); - // Use getAllByText since the selected value also shows the name - expect(screen.getAllByText("Test Store 1").length).toBeGreaterThan(0); + // The selected store's name may also render in the trigger, so only require at least one match. + expect((await screen.findAllByText("Test Store 1")).length).toBeGreaterThan(0); expect(screen.getByText("Test Store 2")).toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/TestVectorStoreTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/TestVectorStoreTab.tsx index 3156da0b413..fec2dc62e9c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/TestVectorStoreTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/TestVectorStoreTab.tsx @@ -1,24 +1,32 @@ import React, { useState } from "react"; -import { Card, Select, Typography } from "antd"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Combobox, + ComboboxContent, + ComboboxEmpty, + ComboboxInput, + ComboboxItem, + ComboboxList, +} from "@/components/ui/combobox"; import { VectorStoreTester } from "./VectorStoreTester"; import { VectorStore } from "@/components/vector_store_management/types"; -const { Text, Title } = Typography; - interface TestVectorStoreTabProps { accessToken: string | null; vectorStores: VectorStore[]; } +const storeLabel = (store: VectorStore) => store.vector_store_name || store.vector_store_id; + const TestVectorStoreTab: React.FC = ({ accessToken, vectorStores }) => { - const [selectedVectorStoreId, setSelectedVectorStoreId] = useState( - vectorStores.length > 0 ? vectorStores[0].vector_store_id : undefined, - ); + const [selectedVectorStore, setSelectedVectorStore] = useState(vectorStores[0] ?? null); if (!accessToken) { return ( - Access token is required to test vector stores. + +

Access token is required to test vector stores.

+
); } @@ -26,9 +34,11 @@ const TestVectorStoreTab: React.FC = ({ accessToken, ve if (vectorStores.length === 0) { return ( -
- No vector stores available. Create one first to test it. -
+ +
+

No vector stores available. Create one first to test it.

+
+
); } @@ -36,36 +46,41 @@ const TestVectorStoreTab: React.FC = ({ accessToken, ve return (
-
+
- Select Vector Store - Choose a vector store to test search queries against +
Select Vector Store
+

Choose a vector store to test search queries against

- -
+ + + No matching vector stores + + {(store: VectorStore) => ( + +
+ {storeLabel(store)} + {store.vector_store_name && ( + {store.vector_store_id} + )} +
+
+ )} +
+
+ +
- {selectedVectorStoreId && } + {selectedVectorStore && ( + + )}
); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/VectorStoreTester.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/VectorStoreTester.test.tsx new file mode 100644 index 00000000000..cbabcc6dca5 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/VectorStoreTester.test.tsx @@ -0,0 +1,156 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { vectorStoreSearchCall } from "@/components/networking"; + +import { VectorStoreTester } from "./VectorStoreTester"; + +vi.mock("@/components/networking", () => ({ + vectorStoreSearchCall: vi.fn(), +})); + +const mockWarning = vi.fn(); +vi.mock("@/components/molecules/message_manager", () => ({ + __esModule: true, + default: { warning: (...args: unknown[]) => mockWarning(...args) }, +})); + +const mockFromBackend = vi.fn(); +const mockSuccess = vi.fn(); +vi.mock("@/components/molecules/notifications_manager", () => ({ + __esModule: true, + default: { + fromBackend: (...args: unknown[]) => mockFromBackend(...args), + success: (...args: unknown[]) => mockSuccess(...args), + }, +})); + +const mockSearch = vi.mocked(vectorStoreSearchCall); + +const searchResponse = { + object: "vector_store.search_results.page", + search_query: "hello", + data: [ + { + score: 0.91234, + content: [{ text: "the quick brown fox", type: "text" }], + file_id: "file-1", + filename: "notes.txt", + attributes: { source: "manual" }, + }, + ], +}; + +const EMPTY_STATE = "Test your vector store by entering a search query below"; + +const renderTester = () => render(); + +const queryInput = () => screen.getByPlaceholderText(/enter your search query/i); +const searchButton = () => screen.getByRole("button", { name: /search/i }); + +describe("VectorStoreTester", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockSearch.mockResolvedValue(searchResponse); + }); + + it("shows the empty state before any search has run", () => { + renderTester(); + expect(screen.getByText(EMPTY_STATE)).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /clear history/i })).not.toBeInTheDocument(); + }); + + it("does not search until a non-blank query is entered", async () => { + const user = userEvent.setup(); + renderTester(); + + await user.click(searchButton()); + expect(mockSearch).not.toHaveBeenCalled(); + + await user.type(queryInput(), "hello"); + await user.click(searchButton()); + + await waitFor(() => expect(mockSearch).toHaveBeenCalledWith("sk-test", "vs_123", "hello")); + }); + + it("renders the returned result and clears the query input", async () => { + const user = userEvent.setup(); + renderTester(); + + await user.type(queryInput(), "hello"); + await user.click(searchButton()); + + expect(await screen.findByText("Result 1")).toBeInTheDocument(); + expect(screen.getByText("1 results")).toBeInTheDocument(); + expect(screen.getByText("Score: 0.9123")).toBeInTheDocument(); + expect(screen.queryByText(EMPTY_STATE)).not.toBeInTheDocument(); + await waitFor(() => expect(queryInput()).toHaveValue("")); + }); + + it("expands a result to reveal its content and metadata", async () => { + const user = userEvent.setup(); + renderTester(); + + await user.type(queryInput(), "hello"); + await user.click(searchButton()); + + expect(await screen.findByText("Result 1")).toBeInTheDocument(); + expect(screen.queryByText("the quick brown fox")).not.toBeInTheDocument(); + + await user.click(screen.getByText("Result 1")); + + expect(screen.getByText("the quick brown fox")).toBeInTheDocument(); + expect(screen.getByText("File ID:").parentElement).toHaveTextContent("file-1"); + expect(screen.getByText("Filename:").parentElement).toHaveTextContent("notes.txt"); + }); + + it("warns instead of searching when the query is only whitespace", async () => { + const user = userEvent.setup(); + renderTester(); + + await user.type(queryInput(), " "); + await user.type(queryInput(), "{Enter}"); + + expect(mockWarning).toHaveBeenCalledWith("Please enter a search query"); + expect(mockSearch).not.toHaveBeenCalled(); + }); + + it("submits on Enter but not on Shift+Enter", async () => { + const user = userEvent.setup(); + renderTester(); + + await user.type(queryInput(), "hello"); + await user.type(queryInput(), "{Shift>}{Enter}{/Shift}"); + expect(mockSearch).not.toHaveBeenCalled(); + + await user.type(queryInput(), "{Enter}"); + await waitFor(() => expect(mockSearch).toHaveBeenCalledTimes(1)); + }); + + it("reports a failed search and keeps the history empty", async () => { + const user = userEvent.setup(); + mockSearch.mockRejectedValue(new Error("boom")); + renderTester(); + + await user.type(queryInput(), "hello"); + await user.click(searchButton()); + + await waitFor(() => expect(mockFromBackend).toHaveBeenCalledWith("Failed to search vector store")); + expect(screen.getByText(EMPTY_STATE)).toBeInTheDocument(); + }); + + it("clears the search history", async () => { + const user = userEvent.setup(); + renderTester(); + + await user.type(queryInput(), "hello"); + await user.click(searchButton()); + expect(await screen.findByText("Result 1")).toBeInTheDocument(); + + await user.click(screen.getByRole("button", { name: /clear history/i })); + + expect(screen.queryByText("Result 1")).not.toBeInTheDocument(); + expect(screen.getByText(EMPTY_STATE)).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/VectorStoreTester.tsx b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/VectorStoreTester.tsx index fa4587b526c..015d58e8649 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/VectorStoreTester.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/VectorStoreTester.tsx @@ -1,12 +1,13 @@ import React, { useState } from "react"; -import { Button, Input, Card, Typography, Spin, Divider } from "antd"; import MessageManager from "@/components/molecules/message_manager"; -import { SendOutlined, DatabaseOutlined, LoadingOutlined, DownOutlined, RightOutlined } from "@ant-design/icons"; +import { ChevronDown, ChevronRight, Database, Send } from "lucide-react"; import { vectorStoreSearchCall } from "@/components/networking"; import NotificationsManager from "@/components/molecules/notifications_manager"; - -const { TextArea } = Input; -const { Text, Title } = Typography; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; +import { Textarea } from "@/components/ui/textarea"; +import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; interface VectorStoreContent { text: string; @@ -98,18 +99,16 @@ export const VectorStoreTester: React.FC = ({ vectorStor }; return ( - -
+ +
{/* Header */} -
+
- - - Test Vector Store - + +

Test Vector Store

{searchHistory.length > 0 && ( - )} @@ -118,9 +117,9 @@ export const VectorStoreTester: React.FC = ({ vectorStor {/* Results Area */}
{searchHistory.length === 0 ? ( -
- - Test your vector store by entering a search query below +
+ +

Test your vector store by entering a search query below

) : (
@@ -128,10 +127,10 @@ export const VectorStoreTester: React.FC = ({ vectorStor
{/* User Query */}
-
-
+
+
Query - {formatTimestamp(entry.timestamp)} + {formatTimestamp(entry.timestamp)}
{entry.query}
@@ -139,12 +138,12 @@ export const VectorStoreTester: React.FC = ({ vectorStor {/* Vector Store Response */}
-
-
- +
+
+ Vector Store Results {entry.response && ( - + {entry.response.data?.length || 0} results )} @@ -156,40 +155,42 @@ export const VectorStoreTester: React.FC = ({ vectorStor const isExpanded = expandedResults[`${index}-${resultIndex}`] || false; return ( -
+
{/* Clickable Header */}
toggleResultExpansion(index, resultIndex)} >
{isExpanded ? ( - + ) : ( - + )} - Result {resultIndex + 1} + Result {resultIndex + 1} {/* Show preview of content when collapsed */} {!isExpanded && result.content && result.content[0] && ( - + - {result.content[0].text.substring(0, 100)}... )}
- + Score: {result.score.toFixed(4)}
{/* Expandable Content */} {isExpanded && ( -
+
{/* Content */} {result.content && result.content.map((content, contentIndex) => (
-
Content ({content.type})
-
+
+ Content ({content.type}) +
+
{content.text}
@@ -197,23 +198,23 @@ export const VectorStoreTester: React.FC = ({ vectorStor {/* Metadata */} {(result.file_id || result.filename || result.attributes) && ( -
-
Metadata
+
+
Metadata
{result.file_id && ( -
+
File ID: {result.file_id}
)} {result.filename && ( -
+
Filename: {result.filename}
)} {result.attributes && Object.keys(result.attributes).length > 0 && ( -
- Attributes: -
+                                            
+ Attributes: +
                                                 {JSON.stringify(result.attributes, null, 2)}
                                               
@@ -228,45 +229,40 @@ export const VectorStoreTester: React.FC = ({ vectorStor })}
) : ( -
No results found
+
No results found
)}
- {index < searchHistory.length - 1 && } + {index < searchHistory.length - 1 && }
))}
)} {isLoading && ( -
- } /> +
+
)}
{/* Input Area */} -
+
-