diff --git a/web/components/templates/requestsV2/RequestsTable.tsx b/web/components/templates/requestsV2/RequestsTable.tsx new file mode 100644 index 0000000000..52df423e41 --- /dev/null +++ b/web/components/templates/requestsV2/RequestsTable.tsx @@ -0,0 +1,200 @@ +import React from "react"; +import { + useTable, + useSortBy, + usePagination, + Column, + UsePaginationInstanceProps, + UseSortByInstanceProps, + TableState, + TableInstance, +} from "react-table"; + +interface Request { + request_id: string; + created_at: string; + status: string; + user: string; + cost: string; + model: string | undefined; + request_text: string; + response_text: string; + prompt_tokens: number; +} + +interface RequestsTableProps { + requests: Request[]; +} + +const RequestsTable: React.FC = ({ requests }) => { + const data = React.useMemo(() => requests, [requests]); + + const columns: Column[] = React.useMemo( + () => [ + { + Header: "Created At", + accessor: "created_at", + }, + { + Header: "Status", + accessor: "status", + }, + { + Header: "User", + accessor: "user", + }, + { + Header: "Cost", + accessor: "cost", + }, + { + Header: "Model", + accessor: "model", + Cell: ({ value }: { value: string | undefined }) => + value || "Unsupported", + }, + { + Header: "Request", + accessor: "request_text", + }, + { + Header: "Response", + accessor: "response_text", + }, + { + Header: "Prompt Tokens", + accessor: "prompt_tokens", + }, + ], + [] + ); + + const tableInstance = useTable( + { + columns, + data, + initialState: { pageIndex: 0 } as Partial>, + }, + useSortBy, + usePagination + ) as TableInstance & + UsePaginationInstanceProps & + UseSortByInstanceProps; + + const { + getTableProps, + getTableBodyProps, + headerGroups, + prepareRow, + page, + canPreviousPage, + canNextPage, + pageOptions, + pageCount, + gotoPage, + nextPage, + previousPage, + setPageSize, + state: { pageIndex, pageSize }, + } = tableInstance; + + return ( +
+ + + {headerGroups.map((headerGroup, index) => ( + + {headerGroup.headers.map((column, columnIndex) => ( + + ))} + + ))} + + + {page.map((row, rowIndex) => { + prepareRow(row); + return ( + + {row.cells.map((cell, cellIndex) => { + return ( + + ); + })} + + ); + })} + +
+ {column.render("Header")} + + {column.isSorted + ? column.isSortedDesc + ? " 🔽" + : " 🔼" + : ""} + +
+ {cell.render("Cell")} +
+ +
+ {" "} + {" "} + {" "} + {" "} + + Page{" "} + + {pageIndex + 1} of {pageOptions.length} + {" "} + + + | Go to page:{" "} + { + const page = e.target.value ? Number(e.target.value) - 1 : 0; + gotoPage(page); + }} + style={{ width: "100px" }} + /> + {" "} + +
+ + +
+ ); +}; + +export default RequestsTable; diff --git a/web/components/templates/requestsV2/builder/requestBuilder.tsx b/web/components/templates/requestsV2/builder/requestBuilder.tsx index f8a9068c54..b2bdf72835 100644 --- a/web/components/templates/requestsV2/builder/requestBuilder.tsx +++ b/web/components/templates/requestsV2/builder/requestBuilder.tsx @@ -152,23 +152,21 @@ const builders: { const getModelFromPath = (path: string) => { const regex1 = /\/engines\/([^/]+)/; const regex2 = /models\/([^/:]+)/; + const regex3 = /\/v\d+\/([^/]+)/; - let match = path.match(regex1); + let match = path.match(regex1) || path.match(regex2) || path.match(regex3); - if (!match) { - match = path.match(regex2); - } - - if (match && match[1]) { - return match[1]; - } else { - return undefined; - } + return match && match[1] ? match[1] : undefined; }; const getRequestBuilder = (request: HeliconeRequest) => { let model = - request.request_model || getModelFromPath(request.target_url) || ""; + request.response_model || + request.model_override || + request.request_model || + getModelFromPath(request.target_url) || + ""; + console.log("Model extracted in getRequestBuilder:", model); const builderType = getBuilderType( model, request.provider, @@ -194,9 +192,26 @@ const isAssistantRequest = (request: HeliconeRequest) => { const getNormalizedRequest = (request: HeliconeRequest): NormalizedRequest => { try { - return getRequestBuilder(request).build(); + const normalizedRequest = getRequestBuilder(request).build(); + return { + ...normalizedRequest, + model: + normalizedRequest.model || + request.response_model || + request.model_override || + request.request_model || + "Unsupported", + }; } catch (error) { - return getRequestBuilder(request).build(); + console.error("Error in getNormalizedRequest:", error); + return { + ...getRequestBuilder(request).build(), + model: + request.response_model || + request.model_override || + request.request_model || + "Unsupported", + }; } }; diff --git a/web/package.json b/web/package.json index a573529a81..48d7cc271d 100644 --- a/web/package.json +++ b/web/package.json @@ -100,12 +100,14 @@ "react-resizable": "^3.0.5", "react-resizable-panels": "^2.1.4", "react-simple-code-editor": "^0.13.1", + "react-table": "^7.8.0", "stripe": "^16.11.0", "tailwind-merge": "^2.5.2", "vaul": "^1.0.0" }, "devDependencies": { "@graphql-eslint/eslint-plugin": "^3.17.0", + "@types/react-table": "^7.7.14", "@next/bundle-analyzer": "^14.2.13", "@parcel/watcher": "^2.3.0", "@tailwindcss/forms": "^0.5.3", diff --git a/web/services/hooks/requests.tsx b/web/services/hooks/requests.tsx index 024cdf9ea2..43fcdd68d4 100644 --- a/web/services/hooks/requests.tsx +++ b/web/services/hooks/requests.tsx @@ -120,29 +120,36 @@ const useGetRequestsWithBodies = ( if (!content) return request; const model = - request.model_override || request.response_model || + request.model_override || request.request_model || - content.response?.model || - content.request?.model || - content.response?.body?.model || + content?.response?.model || + content?.request?.model || + content?.response?.body?.model || getModelFromPath(request.target_url) || - ""; + "Unknown"; + + console.log('Extracted model:', model, 'for request:', request.request_id); let updatedRequest = { ...request, request_body: content.request, response_body: content.response, + model: model, }; if ( request.provider === "GOOGLE" && model.toLowerCase().includes("gemini") ) { - updatedRequest.llmSchema = mapGeminiPro( + const mappedRequest = mapGeminiPro( updatedRequest as HeliconeRequest, model ); + updatedRequest = { + ...mappedRequest, + model: model, // Ensure we keep our extracted model + }; } return updatedRequest; diff --git a/web/types/react-table.d.ts b/web/types/react-table.d.ts new file mode 100644 index 0000000000..cf5cd0faa4 --- /dev/null +++ b/web/types/react-table.d.ts @@ -0,0 +1,15 @@ +import { + UseTableInstanceProps, + UsePaginationInstanceProps, + UseSortByInstanceProps, + TableState as ReactTableState, +} from "react-table"; + +declare module "react-table" { + export interface TableInstance + extends UseTableInstanceProps, + UsePaginationInstanceProps, + UseSortByInstanceProps {} + + export type TableState = ReactTableState; +} diff --git a/web/yarn.lock b/web/yarn.lock index b76e146479..ff4dd13674 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -3917,6 +3917,13 @@ dependencies: "@types/react" "*" +"@types/react-table@^7.7.14": + version "7.7.20" + resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.20.tgz#2f68e70ca7a703ad8011a8da55c38482f0eb4314" + integrity sha512-ahMp4pmjVlnExxNwxyaDrFgmKxSbPwU23sGQw2gJK4EhCvnvmib2s/O/+y1dfV57dXOwpr2plfyBol+vEHbi2w== + dependencies: + "@types/react" "*" + "@types/react-transition-group@^4.4.10": version "4.4.10" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" @@ -7813,6 +7820,11 @@ react-style-singleton@^2.2.1: invariant "^2.2.4" tslib "^2.0.0" +react-table@^7.8.0: + version "7.8.0" + resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.8.0.tgz#07858c01c1718c09f7f1aed7034fcfd7bda907d2" + integrity sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA== + react-textarea-autosize@^8.5.3: version "8.5.3" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409"