Allow user to select logs

This commit is contained in:
David Corbitt
2023-08-12 04:07:58 -07:00
parent 89815e1f7f
commit 33751c12d2
5 changed files with 71 additions and 20 deletions

View File

@@ -10,7 +10,7 @@ export default function LoggedCallsTable() {
return ( return (
<Card width="100%" overflow="hidden"> <Card width="100%" overflow="hidden">
<Table> <Table>
<TableHeader /> <TableHeader showCheckbox />
<Tbody> <Tbody>
{loggedCalls?.calls.map((loggedCall) => { {loggedCalls?.calls.map((loggedCall) => {
return ( return (
@@ -25,6 +25,7 @@ export default function LoggedCallsTable() {
setExpandedRow(loggedCall.id); setExpandedRow(loggedCall.id);
} }
}} }}
showCheckbox
/> />
); );
})} })}

View File

@@ -12,6 +12,7 @@ import {
Button, Button,
ButtonGroup, ButtonGroup,
Text, Text,
Checkbox,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
@@ -19,32 +20,60 @@ import Link from "next/link";
import { type RouterOutputs } from "~/utils/api"; import { type RouterOutputs } from "~/utils/api";
import { FormattedJson } from "./FormattedJson"; import { FormattedJson } from "./FormattedJson";
import { useAppStore } from "~/state/store";
import { useLoggedCalls } from "~/utils/hooks";
import { useMemo } from "react";
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
type LoggedCall = RouterOutputs["loggedCalls"]["list"]["calls"][0]; type LoggedCall = RouterOutputs["loggedCalls"]["list"]["calls"][0];
export const TableHeader = () => ( export const TableHeader = ({ showCheckbox }: { showCheckbox?: boolean }) => {
<Thead> const matchingLogIds = useLoggedCalls().data?.matchingLogIds;
<Tr> const selectedLogIds = useAppStore((s) => s.selectedLogs.selectedLogIds);
<Th>Time</Th> const addAll = useAppStore((s) => s.selectedLogs.addSelectedLogIds);
<Th>Model</Th> const clearAll = useAppStore((s) => s.selectedLogs.clearSelectedLogIds);
<Th isNumeric>Duration</Th> const allSelected = useMemo(() => {
<Th isNumeric>Input tokens</Th> if (!matchingLogIds) return false;
<Th isNumeric>Output tokens</Th> return matchingLogIds.every((id) => selectedLogIds.has(id));
<Th isNumeric>Status</Th> }, [selectedLogIds, matchingLogIds]);
</Tr> return (
</Thead> <Thead>
); <Tr>
{showCheckbox && (
<Th>
<HStack w={8}>
<Checkbox
isChecked={allSelected}
onChange={() => {
allSelected ? clearAll() : addAll(matchingLogIds || []);
}}
/>
<Text>({selectedLogIds.size})</Text>
</HStack>
</Th>
)}
<Th>Time</Th>
<Th>Model</Th>
<Th isNumeric>Duration</Th>
<Th isNumeric>Input tokens</Th>
<Th isNumeric>Output tokens</Th>
<Th isNumeric>Status</Th>
</Tr>
</Thead>
);
};
export const TableRow = ({ export const TableRow = ({
loggedCall, loggedCall,
isExpanded, isExpanded,
onToggle, onToggle,
showCheckbox,
}: { }: {
loggedCall: LoggedCall; loggedCall: LoggedCall;
isExpanded: boolean; isExpanded: boolean;
onToggle: () => void; onToggle: () => void;
showCheckbox?: boolean;
}) => { }) => {
const isError = loggedCall.modelResponse?.statusCode !== 200; const isError = loggedCall.modelResponse?.statusCode !== 200;
const timeAgo = dayjs(loggedCall.requestedAt).fromNow(); const timeAgo = dayjs(loggedCall.requestedAt).fromNow();
@@ -60,16 +89,24 @@ export const TableRow = ({
</Td> </Td>
); );
const isChecked = useAppStore((s) => s.selectedLogs.selectedLogIds.has(loggedCall.id));
const toggleChecked = useAppStore((s) => s.selectedLogs.toggleSelectedLogId);
return ( return (
<> <>
<Tr <Tr
onClick={onToggle} onClick={onToggle}
key={loggedCall.id} key={loggedCall.id}
_hover={{ bgColor: "gray.100", cursor: "pointer" }} _hover={{ bgColor: "gray.50", cursor: "pointer" }}
sx={{ sx={{
"> td": { borderBottom: "none" }, "> td": { borderBottom: "none" },
}} }}
> >
{showCheckbox && (
<Td>
<Checkbox isChecked={isChecked} onChange={() => toggleChecked(loggedCall.id)} />
</Td>
)}
<Td> <Td>
<Tooltip label={fullTime} placement="top"> <Tooltip label={fullTime} placement="top">
<Box whiteSpace="nowrap" minW="120px"> <Box whiteSpace="nowrap" minW="120px">

View File

@@ -19,10 +19,15 @@ export const loggedCallsRouter = createTRPCRouter({
take: pageSize, take: pageSize,
}); });
const matchingLogs = await prisma.loggedCall.findMany({
where: { projectId },
select: { id: true },
});
const count = await prisma.loggedCall.count({ const count = await prisma.loggedCall.count({
where: { projectId }, where: { projectId },
}); });
return { count, calls }; return { calls, count, matchingLogIds: matchingLogs.map((log) => log.id) };
}), }),
}); });

View File

@@ -4,16 +4,13 @@ export const editorBackground = "#fafafa";
export type SelectedLogsSlice = { export type SelectedLogsSlice = {
selectedLogIds: Set<string>; selectedLogIds: Set<string>;
setSelectedLogIds: (ids: Set<string>) => void;
toggleSelectedLogId: (id: string) => void; toggleSelectedLogId: (id: string) => void;
addSelectedLogIds: (ids: string[]) => void;
clearSelectedLogIds: () => void;
}; };
export const createSelectedLogsSlice: SliceCreator<SelectedLogsSlice> = (set, get) => ({ export const createSelectedLogsSlice: SliceCreator<SelectedLogsSlice> = (set, get) => ({
selectedLogIds: new Set(), selectedLogIds: new Set(),
setSelectedLogIds: (ids: Set<string>) =>
set((state) => {
state.selectedLogs.selectedLogIds = ids;
}),
toggleSelectedLogId: (id: string) => toggleSelectedLogId: (id: string) =>
set((state) => { set((state) => {
if (state.selectedLogs.selectedLogIds.has(id)) { if (state.selectedLogs.selectedLogIds.has(id)) {
@@ -22,4 +19,12 @@ export const createSelectedLogsSlice: SliceCreator<SelectedLogsSlice> = (set, ge
state.selectedLogs.selectedLogIds.add(id); state.selectedLogs.selectedLogIds.add(id);
} }
}), }),
addSelectedLogIds: (ids: string[]) =>
set((state) => {
state.selectedLogs.selectedLogIds = new Set([...state.selectedLogs.selectedLogIds, ...ids]);
}),
clearSelectedLogIds: () =>
set((state) => {
state.selectedLogs.selectedLogIds = new Set();
}),
}); });

View File

@@ -1,5 +1,6 @@
import { type StateCreator, create } from "zustand"; import { type StateCreator, create } from "zustand";
import { immer } from "zustand/middleware/immer"; import { immer } from "zustand/middleware/immer";
import { enableMapSet } from "immer";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
import { createSelectors } from "./createSelectors"; import { createSelectors } from "./createSelectors";
import { import {
@@ -10,6 +11,8 @@ import { type APIClient } from "~/utils/api";
import { persistOptions, type stateToPersist } from "./persist"; import { persistOptions, type stateToPersist } from "./persist";
import { type SelectedLogsSlice, createSelectedLogsSlice } from "./selectedLogsSlice"; import { type SelectedLogsSlice, createSelectedLogsSlice } from "./selectedLogsSlice";
enableMapSet();
export type State = { export type State = {
drawerOpen: boolean; drawerOpen: boolean;
openDrawer: () => void; openDrawer: () => void;