Skip to main content
Glama

@arizeai/phoenix-mcp

Official
by Arize-ai
PromptsTable.tsx8.74 kB
import { startTransition, useCallback, useEffect, useMemo, useRef, } from "react"; import { graphql, usePaginationFragment } from "react-relay"; import { useNavigate } from "react-router"; import { ColumnDef, flexRender, getCoreRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table"; import { css } from "@emotion/react"; import { Flex, Icon, Icons, Link, LinkButton, Token, } from "@phoenix/components"; import { StopPropagation } from "@phoenix/components/StopPropagation"; import { TextCell } from "@phoenix/components/table"; import { selectableTableCSS } from "@phoenix/components/table/styles"; import { TimestampCell } from "@phoenix/components/table/TimestampCell"; import { usePromptsFilterContext } from "@phoenix/pages/prompts/PromptsFilterProvider"; import { PromptsTable_prompts$key } from "./__generated__/PromptsTable_prompts.graphql"; import { PromptsTablePromptsQuery } from "./__generated__/PromptsTablePromptsQuery.graphql"; import { PromptActionMenu } from "./PromptActionMenu"; import { PromptsEmpty } from "./PromptsEmpty"; const PAGE_SIZE = 100; type PromptsTableProps = { query: PromptsTable_prompts$key; }; export function PromptsTable(props: PromptsTableProps) { const { filter, selectedPromptLabelIds } = usePromptsFilterContext(); const navigate = useNavigate(); //we need a reference to the scrolling element for logic down below const tableContainerRef = useRef<HTMLDivElement>(null); const queryArgs = useMemo( () => ({ filter: filter.trim() ? { value: filter, col: "name" as const } : null, labelIds: selectedPromptLabelIds.length > 0 ? selectedPromptLabelIds : null, }), [filter, selectedPromptLabelIds] ); const { data, loadNext, hasNext, isLoadingNext, refetch } = usePaginationFragment<PromptsTablePromptsQuery, PromptsTable_prompts$key>( graphql` fragment PromptsTable_prompts on Query @refetchable(queryName: "PromptsTablePromptsQuery") @argumentDefinitions( after: { type: "String", defaultValue: null } first: { type: "Int", defaultValue: 100 } filter: { type: "PromptFilter", defaultValue: null } labelIds: { type: "[ID!]", defaultValue: null } ) { prompts( first: $first after: $after filter: $filter labelIds: $labelIds ) @connection(key: "PromptsTable_prompts") { edges { prompt: node { id name description version { createdAt } labels { id name color } } } } } `, props.query ); // Refetch when searchFilter changes useEffect(() => { startTransition(() => { refetch(queryArgs, { fetchPolicy: "store-and-network", }); }); }, [refetch, queryArgs]); const tableData = useMemo( () => data.prompts.edges.map((edge) => { return { lastUpdatedAt: edge.prompt.version.createdAt, ...edge.prompt, }; }), [data] ); const fetchMoreOnBottomReached = useCallback( (containerRefElement?: HTMLDivElement | null) => { if (containerRefElement) { const { scrollHeight, scrollTop, clientHeight } = containerRefElement; //once the user has scrolled within 300px of the bottom of the table, fetch more data if there is any if ( scrollHeight - scrollTop - clientHeight < 300 && !isLoadingNext && hasNext ) { loadNext(PAGE_SIZE, { UNSTABLE_extraVariables: queryArgs }); } } }, [hasNext, isLoadingNext, loadNext, queryArgs] ); type TableRow = (typeof tableData)[number]; const columns = useMemo(() => { const cols: ColumnDef<TableRow>[] = [ { header: "name", accessorKey: "name", cell: ({ row }) => { return <Link to={`${row.original.id}`}>{row.original.name}</Link>; }, }, { header: "labels", accessorKey: "labels", cell: ({ row }) => { return ( <ul css={css` display: flex; flex-direction: row; gap: var(--ac-global-dimension-size-100); `} > {row.original.labels.map((label) => ( <Token key={label.id} color={label.color}> {label.name} </Token> ))} </ul> ); }, }, { header: "description", accessorKey: "description", cell: TextCell, }, { header: "last updated", accessorKey: "lastUpdatedAt", cell: TimestampCell, }, { id: "actions", header: "", size: 5, accessorKey: "id", cell: ({ row }) => { return ( <Flex direction="row" gap="size-100" justifyContent="end" width="100%" > <StopPropagation> <LinkButton leadingVisual={<Icon svg={<Icons.PlayCircleOutline />} />} size="S" aria-label="Open in playground" to={`${row.original.id}/playground`} > Playground </LinkButton> </StopPropagation> <PromptActionMenu promptId={row.original.id} onDeleted={() => { refetch(queryArgs, { fetchPolicy: "network-only" }); }} /> </Flex> ); }, }, ]; return cols; }, [refetch, queryArgs]); const table = useReactTable({ columns, data: tableData, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), }); const rows = table.getRowModel().rows; const isEmpty = rows.length === 0; if (isEmpty) { return <PromptsEmpty />; } return ( <div css={css` flex: 1 1 auto; overflow: auto; `} onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)} ref={tableContainerRef} > <table css={selectableTableCSS}> <thead> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th colSpan={header.colSpan} key={header.id}> {header.isPlaceholder ? null : ( <div {...{ className: header.column.getCanSort() ? "sort" : "", ["aria-role"]: header.column.getCanSort() ? "button" : null, onClick: header.column.getToggleSortingHandler(), style: { textAlign: header.column.columnDef.meta?.textAlign, }, }} > {flexRender( header.column.columnDef.header, header.getContext() )} {header.column.getIsSorted() ? ( <Icon className="sort-icon" svg={ header.column.getIsSorted() === "asc" ? ( <Icons.ArrowUpFilled /> ) : ( <Icons.ArrowDownFilled /> ) } /> ) : null} </div> )} </th> ))} </tr> ))} </thead> <tbody> {rows.map((row) => { return ( <tr key={row.id} onClick={() => { navigate(`${row.original.id}`); }} > {row.getVisibleCells().map((cell) => ( <td key={cell.id} align={cell.column.columnDef.meta?.textAlign} > {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ); })} </tbody> </table> </div> ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Arize-ai/phoenix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server