Display tag values

This commit is contained in:
David Corbitt
2023-08-15 02:32:05 -07:00
parent 9636fa033e
commit 3547c85c86
7 changed files with 70 additions and 58 deletions

View File

@@ -1,24 +1,16 @@
import { Button, HStack, Icon, Text } from "@chakra-ui/react";
import { BsPlus } from "react-icons/bs";
import { comparators } from "~/state/logFiltersSlice";
import { comparators, defaultFilterableFields } from "~/state/logFiltersSlice";
import { useAppStore } from "~/state/store";
import { useFilterableFields } from "~/utils/hooks";
const AddFilterButton = () => {
const filterableFields = useFilterableFields().data;
const addFilter = useAppStore((s) => s.logFilters.addFilter);
if (!filterableFields || !filterableFields.length || !comparators || !comparators.length)
return null;
return (
<HStack
as={Button}
variant="ghost"
onClick={() =>
addFilter({ field: filterableFields[0] as string, comparator: comparators[0] })
}
onClick={() => addFilter({ field: defaultFilterableFields[0], comparator: comparators[0] })}
spacing={0}
fontSize="sm"
>

View File

@@ -1,10 +1,10 @@
import { type LogFilter } from "~/state/logFiltersSlice";
import { defaultFilterableFields, type LogFilter } from "~/state/logFiltersSlice";
import { useAppStore } from "~/state/store";
import { useFilterableFields } from "~/utils/hooks";
import { useTagNames } from "~/utils/hooks";
import InputDropdown from "~/components/InputDropdown";
const SelectFieldDropdown = ({ filter, index }: { filter: LogFilter; index: number }) => {
const filterableFields = useFilterableFields().data;
const tagNames = useTagNames().data;
const updateFilter = useAppStore((s) => s.logFilters.updateFilter);
@@ -12,7 +12,7 @@ const SelectFieldDropdown = ({ filter, index }: { filter: LogFilter; index: numb
return (
<InputDropdown
options={filterableFields || []}
options={[...defaultFilterableFields, ...(tagNames || [])]}
selectedOption={field}
onSelect={(option) => updateFilter(index, { ...filter, field: option })}
/>

View File

@@ -21,7 +21,7 @@ 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 { useLoggedCalls, useTagNames } from "~/utils/hooks";
import { useMemo } from "react";
dayjs.extend(relativeTime);
@@ -37,6 +37,7 @@ export const TableHeader = ({ showCheckbox }: { showCheckbox?: boolean }) => {
if (!matchingLogIds || !matchingLogIds.length) return false;
return matchingLogIds.every((id) => selectedLogIds.has(id));
}, [selectedLogIds, matchingLogIds]);
const tagNames = useTagNames().data;
return (
<Thead>
<Tr>
@@ -58,6 +59,7 @@ export const TableHeader = ({ showCheckbox }: { showCheckbox?: boolean }) => {
)}
<Th>Sent At</Th>
<Th>Model</Th>
{tagNames?.map((tagName) => <Th key={tagName}>{tagName}</Th>)}
<Th isNumeric>Duration</Th>
<Th isNumeric>Input tokens</Th>
<Th isNumeric>Output tokens</Th>
@@ -82,19 +84,11 @@ export const TableRow = ({
const requestedAt = dayjs(loggedCall.requestedAt).format("MMMM D h:mm A");
const fullTime = dayjs(loggedCall.requestedAt).toString();
const durationCell = (
<Td isNumeric>
{loggedCall.cacheHit ? (
<Text color="gray.500">Cached</Text>
) : (
((loggedCall.modelResponse?.durationMs ?? 0) / 1000).toFixed(2) + "s"
)}
</Td>
);
const isChecked = useAppStore((s) => s.selectedLogs.selectedLogIds.has(loggedCall.id));
const toggleChecked = useAppStore((s) => s.selectedLogs.toggleSelectedLogId);
const tagNames = useTagNames().data;
return (
<>
<Tr
@@ -118,7 +112,7 @@ export const TableRow = ({
</Box>
</Tooltip>
</Td>
<Td width="100%">
<Td>
<HStack justifyContent="flex-start">
<Text
colorScheme="purple"
@@ -133,7 +127,14 @@ export const TableRow = ({
</Text>
</HStack>
</Td>
{durationCell}
{tagNames?.map((tagName) => <Td key={tagName}>{loggedCall.tags[tagName]}</Td>)}
<Td isNumeric>
{loggedCall.cacheHit ? (
<Text color="gray.500">Cached</Text>
) : (
((loggedCall.modelResponse?.durationMs ?? 0) / 1000).toFixed(2) + "s"
)}
</Td>
<Td isNumeric>{loggedCall.modelResponse?.inputTokens}</Td>
<Td isNumeric>{loggedCall.modelResponse?.outputTokens}</Td>
<Td sx={{ color: isError ? "red.500" : "green.500", fontWeight: "semibold" }} isNumeric>

View File

@@ -1,14 +1,12 @@
import { z } from "zod";
import { type Expression, type SqlBool } from "kysely";
import { sql } from "kysely";
import { type Expression, type SqlBool, sql } from "kysely";
import { jsonArrayFrom } from "kysely/helpers/postgres";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import { kysely, prisma } from "~/server/db";
import { comparators } from "~/state/logFiltersSlice";
import { comparators, defaultFilterableFields } from "~/state/logFiltersSlice";
import { requireCanViewProject } from "~/utils/accessControl";
const defaultFilterableFields = ["Request", "Response", "Model", "Status Code"];
// create comparator type based off of comparators
const comparatorToSqlValue = (comparator: (typeof comparators)[number], value: string) => {
switch (comparator) {
@@ -91,7 +89,10 @@ export const loggedCallsRouter = createTRPCRouter({
});
const tagFilters = input.filters.filter(
(filter) => !defaultFilterableFields.includes(filter.field),
(filter) =>
!defaultFilterableFields.includes(
filter.field as (typeof defaultFilterableFields)[number],
),
);
let updatedBaseQuery = baseQuery;
@@ -112,7 +113,7 @@ export const loggedCallsRouter = createTRPCRouter({
}
const rawCalls = await updatedBaseQuery
.select([
.select((eb) => [
"lc.id as id",
"lc.requestedAt as requestedAt",
"model",
@@ -127,28 +128,45 @@ export const loggedCallsRouter = createTRPCRouter({
"cost",
"statusCode",
"durationMs",
jsonArrayFrom(
eb
.selectFrom("LoggedCallTag")
.select(["name", "value"])
.whereRef("loggedCallId", "=", "lc.id"),
).as("tags"),
])
.orderBy("lc.requestedAt", "desc")
.limit(pageSize)
.offset((page - 1) * pageSize)
.execute();
const calls = rawCalls.map((rawCall) => ({
id: rawCall.id,
requestedAt: rawCall.requestedAt,
model: rawCall.model,
cacheHit: rawCall.cacheHit,
modelResponse: {
receivedAt: rawCall.receivedAt,
reqPayload: rawCall.reqPayload,
respPayload: rawCall.respPayload,
inputTokens: rawCall.inputTokens,
outputTokens: rawCall.outputTokens,
cost: rawCall.cost,
statusCode: rawCall.statusCode,
durationMs: rawCall.durationMs,
},
}));
const calls = rawCalls.map((rawCall) => {
const tagsObject = rawCall.tags.reduce(
(acc, tag) => {
acc[tag.name] = tag.value;
return acc;
},
{} as Record<string, string | null>,
);
return {
id: rawCall.id,
requestedAt: rawCall.requestedAt,
model: rawCall.model,
cacheHit: rawCall.cacheHit,
modelResponse: {
receivedAt: rawCall.receivedAt,
reqPayload: rawCall.reqPayload,
respPayload: rawCall.respPayload,
inputTokens: rawCall.inputTokens,
outputTokens: rawCall.outputTokens,
cost: rawCall.cost,
statusCode: rawCall.statusCode,
durationMs: rawCall.durationMs,
},
tags: tagsObject,
};
});
const matchingLogIds = await updatedBaseQuery.select(["lc.id"]).execute();
@@ -156,7 +174,7 @@ export const loggedCallsRouter = createTRPCRouter({
return { calls, count, matchingLogIds: matchingLogIds.map((log) => log.id) };
}),
getFilterableFields: protectedProcedure
getTagNames: protectedProcedure
.input(z.object({ projectId: z.string() }))
.query(async ({ input, ctx }) => {
await requireCanViewProject(input.projectId, ctx);
@@ -169,8 +187,11 @@ export const loggedCallsRouter = createTRPCRouter({
select: {
name: true,
},
orderBy: {
name: "asc",
},
});
return [...defaultFilterableFields, ...tags.map((tag) => tag.name)];
return tags.map((tag) => tag.name);
}),
});

View File

@@ -1,9 +1,9 @@
import { type SliceCreator } from "./store";
export const editorBackground = "#fafafa";
export const comparators = ["=", "!=", "CONTAINS"] as const;
export const defaultFilterableFields = ["Request", "Response", "Model", "Status Code"] as const;
export interface LogFilter {
field: string;
comparator: (typeof comparators)[number];

View File

@@ -1,7 +1,5 @@
import { type SliceCreator } from "./store";
export const editorBackground = "#fafafa";
export type SelectedLogsSlice = {
selectedLogIds: Set<string>;
toggleSelectedLogId: (id: string) => void;

View File

@@ -187,9 +187,9 @@ export const useLoggedCalls = () => {
);
};
export const useFilterableFields = () => {
export const useTagNames = () => {
const selectedProjectId = useAppStore((state) => state.selectedProjectId);
return api.loggedCalls.getFilterableFields.useQuery(
return api.loggedCalls.getTagNames.useQuery(
{ projectId: selectedProjectId ?? "" },
{ enabled: !!selectedProjectId },
);