diff --git a/app/src/components/requestLogs/LoggedCallsTable.tsx b/app/src/components/requestLogs/LoggedCallsTable.tsx index 31c3ce1..55692e9 100644 --- a/app/src/components/requestLogs/LoggedCallsTable.tsx +++ b/app/src/components/requestLogs/LoggedCallsTable.tsx @@ -10,7 +10,7 @@ export default function LoggedCallsTable() { return ( - + {loggedCalls?.calls.map((loggedCall) => { return ( @@ -25,6 +25,7 @@ export default function LoggedCallsTable() { setExpandedRow(loggedCall.id); } }} + showCheckbox /> ); })} diff --git a/app/src/components/requestLogs/TableRow.tsx b/app/src/components/requestLogs/TableRow.tsx index ef0b9d5..a5f9731 100644 --- a/app/src/components/requestLogs/TableRow.tsx +++ b/app/src/components/requestLogs/TableRow.tsx @@ -12,6 +12,7 @@ import { Button, ButtonGroup, Text, + Checkbox, } from "@chakra-ui/react"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; @@ -19,32 +20,60 @@ import Link from "next/link"; import { type RouterOutputs } from "~/utils/api"; import { FormattedJson } from "./FormattedJson"; +import { useAppStore } from "~/state/store"; +import { useLoggedCalls } from "~/utils/hooks"; +import { useMemo } from "react"; dayjs.extend(relativeTime); type LoggedCall = RouterOutputs["loggedCalls"]["list"]["calls"][0]; -export const TableHeader = () => ( - - - - - - - - - - -); +export const TableHeader = ({ showCheckbox }: { showCheckbox?: boolean }) => { + const matchingLogIds = useLoggedCalls().data?.matchingLogIds; + const selectedLogIds = useAppStore((s) => s.selectedLogs.selectedLogIds); + const addAll = useAppStore((s) => s.selectedLogs.addSelectedLogIds); + const clearAll = useAppStore((s) => s.selectedLogs.clearSelectedLogIds); + const allSelected = useMemo(() => { + if (!matchingLogIds) return false; + return matchingLogIds.every((id) => selectedLogIds.has(id)); + }, [selectedLogIds, matchingLogIds]); + return ( + + + {showCheckbox && ( + + )} + + + + + + + + + ); +}; export const TableRow = ({ loggedCall, isExpanded, onToggle, + showCheckbox, }: { loggedCall: LoggedCall; isExpanded: boolean; onToggle: () => void; + showCheckbox?: boolean; }) => { const isError = loggedCall.modelResponse?.statusCode !== 200; const timeAgo = dayjs(loggedCall.requestedAt).fromNow(); @@ -60,16 +89,24 @@ export const TableRow = ({ ); + const isChecked = useAppStore((s) => s.selectedLogs.selectedLogIds.has(loggedCall.id)); + const toggleChecked = useAppStore((s) => s.selectedLogs.toggleSelectedLogId); + return ( <> td": { borderBottom: "none" }, }} > + {showCheckbox && ( + + )}
TimeModelDurationInput tokensOutput tokensStatus
+ + { + allSelected ? clearAll() : addAll(matchingLogIds || []); + }} + /> + ({selectedLogIds.size}) + + TimeModelDurationInput tokensOutput tokensStatus
+ toggleChecked(loggedCall.id)} /> + diff --git a/app/src/server/api/routers/loggedCalls.router.ts b/app/src/server/api/routers/loggedCalls.router.ts index dc8d661..f8f0602 100644 --- a/app/src/server/api/routers/loggedCalls.router.ts +++ b/app/src/server/api/routers/loggedCalls.router.ts @@ -19,10 +19,15 @@ export const loggedCallsRouter = createTRPCRouter({ take: pageSize, }); + const matchingLogs = await prisma.loggedCall.findMany({ + where: { projectId }, + select: { id: true }, + }); + const count = await prisma.loggedCall.count({ where: { projectId }, }); - return { count, calls }; + return { calls, count, matchingLogIds: matchingLogs.map((log) => log.id) }; }), }); diff --git a/app/src/state/selectedLogsSlice.ts b/app/src/state/selectedLogsSlice.ts index ddd407a..43ca247 100644 --- a/app/src/state/selectedLogsSlice.ts +++ b/app/src/state/selectedLogsSlice.ts @@ -4,16 +4,13 @@ export const editorBackground = "#fafafa"; export type SelectedLogsSlice = { selectedLogIds: Set; - setSelectedLogIds: (ids: Set) => void; toggleSelectedLogId: (id: string) => void; + addSelectedLogIds: (ids: string[]) => void; + clearSelectedLogIds: () => void; }; export const createSelectedLogsSlice: SliceCreator = (set, get) => ({ selectedLogIds: new Set(), - setSelectedLogIds: (ids: Set) => - set((state) => { - state.selectedLogs.selectedLogIds = ids; - }), toggleSelectedLogId: (id: string) => set((state) => { if (state.selectedLogs.selectedLogIds.has(id)) { @@ -22,4 +19,12 @@ export const createSelectedLogsSlice: SliceCreator = (set, ge 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(); + }), }); diff --git a/app/src/state/store.ts b/app/src/state/store.ts index 9fc3bd7..5f115c1 100644 --- a/app/src/state/store.ts +++ b/app/src/state/store.ts @@ -1,5 +1,6 @@ import { type StateCreator, create } from "zustand"; import { immer } from "zustand/middleware/immer"; +import { enableMapSet } from "immer"; import { persist } from "zustand/middleware"; import { createSelectors } from "./createSelectors"; import { @@ -10,6 +11,8 @@ import { type APIClient } from "~/utils/api"; import { persistOptions, type stateToPersist } from "./persist"; import { type SelectedLogsSlice, createSelectedLogsSlice } from "./selectedLogsSlice"; +enableMapSet(); + export type State = { drawerOpen: boolean; openDrawer: () => void;