Display tag values
This commit is contained in:
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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 })}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { type SliceCreator } from "./store";
|
||||
|
||||
export const editorBackground = "#fafafa";
|
||||
|
||||
export type SelectedLogsSlice = {
|
||||
selectedLogIds: Set<string>;
|
||||
toggleSelectedLogId: (id: string) => void;
|
||||
|
||||
@@ -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 },
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user