Add dashboard page
This commit is contained in:
3
app/@types/nextjs-routes.d.ts
vendored
3
app/@types/nextjs-routes.d.ts
vendored
@@ -18,13 +18,14 @@ declare module "nextjs-routes" {
|
|||||||
| StaticRoute<"/api/openapi">
|
| StaticRoute<"/api/openapi">
|
||||||
| StaticRoute<"/api/sentry-example-api">
|
| StaticRoute<"/api/sentry-example-api">
|
||||||
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
|
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
|
||||||
|
| StaticRoute<"/dashboard">
|
||||||
| DynamicRoute<"/data/[id]", { "id": string }>
|
| DynamicRoute<"/data/[id]", { "id": string }>
|
||||||
| StaticRoute<"/data">
|
| StaticRoute<"/data">
|
||||||
| DynamicRoute<"/experiments/[id]", { "id": string }>
|
| DynamicRoute<"/experiments/[id]", { "id": string }>
|
||||||
| StaticRoute<"/experiments">
|
| StaticRoute<"/experiments">
|
||||||
| StaticRoute<"/">
|
| StaticRoute<"/">
|
||||||
| StaticRoute<"/logged-calls">
|
|
||||||
| StaticRoute<"/project/settings">
|
| StaticRoute<"/project/settings">
|
||||||
|
| StaticRoute<"/request-logs">
|
||||||
| StaticRoute<"/sentry-example-page">
|
| StaticRoute<"/sentry-example-page">
|
||||||
| StaticRoute<"/world-champs">
|
| StaticRoute<"/world-champs">
|
||||||
| StaticRoute<"/world-champs/signup">;
|
| StaticRoute<"/world-champs/signup">;
|
||||||
|
|||||||
@@ -57,11 +57,16 @@ export const ScenariosHeader = () => {
|
|||||||
<MenuButton
|
<MenuButton
|
||||||
as={IconButton}
|
as={IconButton}
|
||||||
mt={1}
|
mt={1}
|
||||||
|
ml={2}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
aria-label="Edit Scenarios"
|
aria-label="Edit Scenarios"
|
||||||
icon={<Icon as={loading ? Spinner : BsGear} />}
|
icon={<Icon as={loading ? Spinner : BsGear} />}
|
||||||
|
maxW={8}
|
||||||
|
minW={8}
|
||||||
|
minH={8}
|
||||||
|
maxH={8}
|
||||||
/>
|
/>
|
||||||
<MenuList fontSize="md" zIndex="dropdown" mt={-3}>
|
<MenuList fontSize="md" zIndex="dropdown" mt={-1}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon as={BsPlus} boxSize={6} mx="-5px" />}
|
icon={<Icon as={BsPlus} boxSize={6} mx="-5px" />}
|
||||||
onClick={() => onAddScenario(false)}
|
onClick={() => onAddScenario(false)}
|
||||||
|
|||||||
@@ -1,210 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
Card,
|
|
||||||
CardHeader,
|
|
||||||
Heading,
|
|
||||||
Table,
|
|
||||||
Tbody,
|
|
||||||
Td,
|
|
||||||
Th,
|
|
||||||
Thead,
|
|
||||||
Tr,
|
|
||||||
Tooltip,
|
|
||||||
Collapse,
|
|
||||||
HStack,
|
|
||||||
VStack,
|
|
||||||
IconButton,
|
|
||||||
useToast,
|
|
||||||
Icon,
|
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
|
||||||
import { ChevronUpIcon, ChevronDownIcon, CopyIcon } from "lucide-react";
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
import { type RouterOutputs } from "~/utils/api";
|
|
||||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
|
||||||
import { atelierCaveLight } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
|
||||||
import stringify from "json-stringify-pretty-compact";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useLoggedCalls } from "~/utils/hooks";
|
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
|
||||||
|
|
||||||
type LoggedCall = RouterOutputs["dashboard"]["loggedCalls"]["calls"][0];
|
|
||||||
|
|
||||||
const FormattedJson = ({ json }: { json: any }) => {
|
|
||||||
const jsonString = stringify(json, { maxLength: 40 });
|
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
const copyToClipboard = async (text: string) => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
toast({
|
|
||||||
title: "Copied to clipboard",
|
|
||||||
status: "success",
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
toast({
|
|
||||||
title: "Failed to copy to clipboard",
|
|
||||||
status: "error",
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box position="relative" fontSize="sm" borderRadius="md" overflow="hidden">
|
|
||||||
<SyntaxHighlighter
|
|
||||||
customStyle={{ overflowX: "unset" }}
|
|
||||||
language="json"
|
|
||||||
style={atelierCaveLight}
|
|
||||||
lineProps={{
|
|
||||||
style: { wordBreak: "break-all", whiteSpace: "pre-wrap" },
|
|
||||||
}}
|
|
||||||
wrapLines
|
|
||||||
>
|
|
||||||
{jsonString}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
<IconButton
|
|
||||||
aria-label="Copy"
|
|
||||||
icon={<CopyIcon />}
|
|
||||||
position="absolute"
|
|
||||||
top={1}
|
|
||||||
right={1}
|
|
||||||
size="xs"
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => void copyToClipboard(jsonString)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function TableRow({
|
|
||||||
loggedCall,
|
|
||||||
isExpanded,
|
|
||||||
onToggle,
|
|
||||||
}: {
|
|
||||||
loggedCall: LoggedCall;
|
|
||||||
isExpanded: boolean;
|
|
||||||
onToggle: () => void;
|
|
||||||
}) {
|
|
||||||
const isError = loggedCall.modelResponse?.statusCode !== 200;
|
|
||||||
const timeAgo = dayjs(loggedCall.requestedAt).fromNow();
|
|
||||||
const fullTime = dayjs(loggedCall.requestedAt).toString();
|
|
||||||
|
|
||||||
const model = useMemo(
|
|
||||||
() => loggedCall.tags.find((tag) => tag.name.startsWith("$model"))?.value,
|
|
||||||
[loggedCall.tags],
|
|
||||||
);
|
|
||||||
|
|
||||||
const durationCell = (
|
|
||||||
<Td isNumeric>
|
|
||||||
{loggedCall.cacheHit
|
|
||||||
? "Cache hit"
|
|
||||||
: ((loggedCall.modelResponse?.durationMs ?? 0) / 1000).toFixed(2) + "s"}
|
|
||||||
</Td>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Tr
|
|
||||||
onClick={onToggle}
|
|
||||||
key={loggedCall.id}
|
|
||||||
_hover={{ bgColor: "gray.100", cursor: "pointer" }}
|
|
||||||
sx={{
|
|
||||||
"> td": { borderBottom: "none" },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Td>
|
|
||||||
<Icon boxSize={6} as={isExpanded ? ChevronUpIcon : ChevronDownIcon} />
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Tooltip label={fullTime} placement="top">
|
|
||||||
<Box whiteSpace="nowrap" minW="120px">
|
|
||||||
{timeAgo}
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
</Td>
|
|
||||||
<Td width="100%">{model}</Td>
|
|
||||||
{durationCell}
|
|
||||||
<Td isNumeric>{loggedCall.modelResponse?.inputTokens}</Td>
|
|
||||||
<Td isNumeric>{loggedCall.modelResponse?.outputTokens}</Td>
|
|
||||||
<Td sx={{ color: isError ? "red.500" : "green.500", fontWeight: "semibold" }} isNumeric>
|
|
||||||
{loggedCall.modelResponse?.statusCode ?? "No response"}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
<Tr>
|
|
||||||
<Td colSpan={8} p={0}>
|
|
||||||
<Collapse in={isExpanded} unmountOnExit={true}>
|
|
||||||
<VStack p={4} align="stretch">
|
|
||||||
<HStack align="stretch">
|
|
||||||
<VStack flex={1} align="stretch">
|
|
||||||
<Heading size="sm">Input</Heading>
|
|
||||||
<FormattedJson json={loggedCall.modelResponse?.reqPayload} />
|
|
||||||
</VStack>
|
|
||||||
<VStack flex={1} align="stretch">
|
|
||||||
<Heading size="sm">Output</Heading>
|
|
||||||
<FormattedJson json={loggedCall.modelResponse?.respPayload} />
|
|
||||||
</VStack>
|
|
||||||
</HStack>
|
|
||||||
<ButtonGroup alignSelf="flex-end">
|
|
||||||
<Button as={Link} colorScheme="blue" href={{ pathname: "/experiments" }}>
|
|
||||||
Experiments
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</VStack>
|
|
||||||
</Collapse>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LoggedCallTable() {
|
|
||||||
const [expandedRow, setExpandedRow] = useState<string | null>(null);
|
|
||||||
const { data: loggedCalls } = useLoggedCalls();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card variant="outline" width="100%" overflow="hidden">
|
|
||||||
<CardHeader>
|
|
||||||
<Heading as="h3" size="sm">
|
|
||||||
Logged Calls
|
|
||||||
</Heading>
|
|
||||||
</CardHeader>
|
|
||||||
<Table>
|
|
||||||
<Thead>
|
|
||||||
<Tr>
|
|
||||||
<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>
|
|
||||||
<Tbody>
|
|
||||||
{loggedCalls?.calls.map((loggedCall) => {
|
|
||||||
return (
|
|
||||||
<TableRow
|
|
||||||
key={loggedCall.id}
|
|
||||||
loggedCall={loggedCall}
|
|
||||||
isExpanded={loggedCall.id === expandedRow}
|
|
||||||
onToggle={() => {
|
|
||||||
if (loggedCall.id === expandedRow) {
|
|
||||||
setExpandedRow(null);
|
|
||||||
} else {
|
|
||||||
setExpandedRow(loggedCall.id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
46
app/src/components/dashboard/LoggedCallsTable.tsx
Normal file
46
app/src/components/dashboard/LoggedCallsTable.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Card, CardHeader, Heading, Table, Tbody, HStack, Button, Text } from "@chakra-ui/react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useLoggedCalls } from "~/utils/hooks";
|
||||||
|
import { TableHeader, TableRow } from "../requestLogs/TableRow";
|
||||||
|
|
||||||
|
export default function LoggedCallsTable() {
|
||||||
|
const [expandedRow, setExpandedRow] = useState<string | null>(null);
|
||||||
|
const { data: loggedCalls } = useLoggedCalls();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card variant="outline" width="100%" overflow="hidden">
|
||||||
|
<CardHeader>
|
||||||
|
<HStack justifyContent="space-between">
|
||||||
|
<Heading as="h3" size="sm">
|
||||||
|
Logged Calls
|
||||||
|
</Heading>
|
||||||
|
<Button as={Link} href="/request-logs" variant="ghost" colorScheme="blue">
|
||||||
|
<Text>View All</Text>
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
</CardHeader>
|
||||||
|
<Table>
|
||||||
|
<TableHeader />
|
||||||
|
<Tbody>
|
||||||
|
{loggedCalls?.calls.map((loggedCall) => {
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={loggedCall.id}
|
||||||
|
loggedCall={loggedCall}
|
||||||
|
isExpanded={loggedCall.id === expandedRow}
|
||||||
|
onToggle={() => {
|
||||||
|
if (loggedCall.id === expandedRow) {
|
||||||
|
setExpandedRow(null);
|
||||||
|
} else {
|
||||||
|
setExpandedRow(loggedCall.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ import Head from "next/head";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { BsGearFill, BsGithub, BsPersonCircle } from "react-icons/bs";
|
import { BsGearFill, BsGithub, BsPersonCircle } from "react-icons/bs";
|
||||||
import { IoStatsChartOutline } from "react-icons/io5";
|
import { IoStatsChartOutline } from "react-icons/io5";
|
||||||
import { RiDatabase2Line, RiFlaskLine } from "react-icons/ri";
|
import { RiHome3Line, RiDatabase2Line, RiFlaskLine } from "react-icons/ri";
|
||||||
import { signIn, useSession } from "next-auth/react";
|
import { signIn, useSession } from "next-auth/react";
|
||||||
import UserMenu from "./UserMenu";
|
import UserMenu from "./UserMenu";
|
||||||
import { env } from "~/env.mjs";
|
import { env } from "~/env.mjs";
|
||||||
@@ -59,8 +59,17 @@ const NavSidebar = () => {
|
|||||||
<>
|
<>
|
||||||
<ProjectMenu />
|
<ProjectMenu />
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
{env.NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS && (
|
{env.NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS && (
|
||||||
<IconLink icon={IoStatsChartOutline} label="Logged Calls" href="/logged-calls" beta />
|
<>
|
||||||
|
<IconLink icon={RiHome3Line} label="Dashboard" href="/dashboard" beta />
|
||||||
|
<IconLink
|
||||||
|
icon={IoStatsChartOutline}
|
||||||
|
label="Request Logs"
|
||||||
|
href="/request-logs"
|
||||||
|
beta
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<IconLink icon={RiFlaskLine} label="Experiments" href="/experiments" />
|
<IconLink icon={RiFlaskLine} label="Experiments" href="/experiments" />
|
||||||
{env.NEXT_PUBLIC_SHOW_DATA && (
|
{env.NEXT_PUBLIC_SHOW_DATA && (
|
||||||
|
|||||||
55
app/src/components/requestLogs/FormattedJson.tsx
Normal file
55
app/src/components/requestLogs/FormattedJson.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Box, IconButton, useToast } from "@chakra-ui/react";
|
||||||
|
import { CopyIcon } from "lucide-react";
|
||||||
|
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||||
|
import { atelierCaveLight } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
||||||
|
import stringify from "json-stringify-pretty-compact";
|
||||||
|
|
||||||
|
const FormattedJson = ({ json }: { json: any }) => {
|
||||||
|
const jsonString = stringify(json, { maxLength: 40 });
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const copyToClipboard = async (text: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
toast({
|
||||||
|
title: "Copied to clipboard",
|
||||||
|
status: "success",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
toast({
|
||||||
|
title: "Failed to copy to clipboard",
|
||||||
|
status: "error",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box position="relative" fontSize="sm" borderRadius="md" overflow="hidden">
|
||||||
|
<SyntaxHighlighter
|
||||||
|
customStyle={{ overflowX: "unset" }}
|
||||||
|
language="json"
|
||||||
|
style={atelierCaveLight}
|
||||||
|
lineProps={{
|
||||||
|
style: { wordBreak: "break-all", whiteSpace: "pre-wrap" },
|
||||||
|
}}
|
||||||
|
wrapLines
|
||||||
|
>
|
||||||
|
{jsonString}
|
||||||
|
</SyntaxHighlighter>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Copy"
|
||||||
|
icon={<CopyIcon />}
|
||||||
|
position="absolute"
|
||||||
|
top={1}
|
||||||
|
right={1}
|
||||||
|
size="xs"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => void copyToClipboard(jsonString)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { FormattedJson };
|
||||||
35
app/src/components/requestLogs/LoggedCallsTable.tsx
Normal file
35
app/src/components/requestLogs/LoggedCallsTable.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Card, Table, Tbody } from "@chakra-ui/react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useLoggedCalls } from "~/utils/hooks";
|
||||||
|
import { TableHeader, TableRow } from "./TableRow";
|
||||||
|
|
||||||
|
export default function LoggedCallsTable() {
|
||||||
|
const [expandedRow, setExpandedRow] = useState<string | null>(null);
|
||||||
|
const { data: loggedCalls } = useLoggedCalls();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card variant="outline" width="100%" overflow="hidden">
|
||||||
|
<Table>
|
||||||
|
<TableHeader />
|
||||||
|
<Tbody>
|
||||||
|
{loggedCalls?.calls.map((loggedCall) => {
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={loggedCall.id}
|
||||||
|
loggedCall={loggedCall}
|
||||||
|
isExpanded={loggedCall.id === expandedRow}
|
||||||
|
onToggle={() => {
|
||||||
|
if (loggedCall.id === expandedRow) {
|
||||||
|
setExpandedRow(null);
|
||||||
|
} else {
|
||||||
|
setExpandedRow(loggedCall.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
128
app/src/components/requestLogs/TableRow.tsx
Normal file
128
app/src/components/requestLogs/TableRow.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Heading,
|
||||||
|
Td,
|
||||||
|
Tr,
|
||||||
|
Thead,
|
||||||
|
Th,
|
||||||
|
Tooltip,
|
||||||
|
Collapse,
|
||||||
|
HStack,
|
||||||
|
VStack,
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
Text,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { type RouterOutputs } from "~/utils/api";
|
||||||
|
import { FormattedJson } from "./FormattedJson";
|
||||||
|
|
||||||
|
type LoggedCall = RouterOutputs["dashboard"]["loggedCalls"]["calls"][0];
|
||||||
|
|
||||||
|
export const TableHeader = () => (
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<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 = ({
|
||||||
|
loggedCall,
|
||||||
|
isExpanded,
|
||||||
|
onToggle,
|
||||||
|
}: {
|
||||||
|
loggedCall: LoggedCall;
|
||||||
|
isExpanded: boolean;
|
||||||
|
onToggle: () => void;
|
||||||
|
}) => {
|
||||||
|
const isError = loggedCall.modelResponse?.statusCode !== 200;
|
||||||
|
const timeAgo = dayjs(loggedCall.requestedAt).fromNow();
|
||||||
|
const fullTime = dayjs(loggedCall.requestedAt).toString();
|
||||||
|
|
||||||
|
const model = useMemo(
|
||||||
|
() => loggedCall.tags.find((tag) => tag.name.startsWith("$model"))?.value,
|
||||||
|
[loggedCall.tags],
|
||||||
|
);
|
||||||
|
|
||||||
|
const durationCell = (
|
||||||
|
<Td isNumeric>
|
||||||
|
{loggedCall.cacheHit
|
||||||
|
? "Cache hit"
|
||||||
|
: ((loggedCall.modelResponse?.durationMs ?? 0) / 1000).toFixed(2) + "s"}
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tr
|
||||||
|
onClick={onToggle}
|
||||||
|
key={loggedCall.id}
|
||||||
|
_hover={{ bgColor: "gray.100", cursor: "pointer" }}
|
||||||
|
sx={{
|
||||||
|
"> td": { borderBottom: "none" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Td>
|
||||||
|
<Tooltip label={fullTime} placement="top">
|
||||||
|
<Box whiteSpace="nowrap" minW="120px">
|
||||||
|
{timeAgo}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</Td>
|
||||||
|
<Td width="100%">
|
||||||
|
<HStack justifyContent="flex-start">
|
||||||
|
<Text
|
||||||
|
colorScheme="purple"
|
||||||
|
color="purple.500"
|
||||||
|
borderColor="purple.500"
|
||||||
|
px={1}
|
||||||
|
borderRadius={4}
|
||||||
|
borderWidth={1}
|
||||||
|
fontSize="xs"
|
||||||
|
>
|
||||||
|
{model}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</Td>
|
||||||
|
{durationCell}
|
||||||
|
<Td isNumeric>{loggedCall.modelResponse?.inputTokens}</Td>
|
||||||
|
<Td isNumeric>{loggedCall.modelResponse?.outputTokens}</Td>
|
||||||
|
<Td sx={{ color: isError ? "red.500" : "green.500", fontWeight: "semibold" }} isNumeric>
|
||||||
|
{loggedCall.modelResponse?.statusCode ?? "No response"}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={8} p={0}>
|
||||||
|
<Collapse in={isExpanded} unmountOnExit={true}>
|
||||||
|
<VStack p={4} align="stretch">
|
||||||
|
<HStack align="stretch">
|
||||||
|
<VStack flex={1} align="stretch">
|
||||||
|
<Heading size="sm">Input</Heading>
|
||||||
|
<FormattedJson json={loggedCall.modelResponse?.reqPayload} />
|
||||||
|
</VStack>
|
||||||
|
<VStack flex={1} align="stretch">
|
||||||
|
<Heading size="sm">Output</Heading>
|
||||||
|
<FormattedJson json={loggedCall.modelResponse?.respPayload} />
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
<ButtonGroup alignSelf="flex-end">
|
||||||
|
<Button as={Link} colorScheme="blue" href={{ pathname: "/experiments" }}>
|
||||||
|
Experiments
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</VStack>
|
||||||
|
</Collapse>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -25,11 +25,10 @@ import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
|
|||||||
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
||||||
import { useSelectedProject } from "~/utils/hooks";
|
import { useSelectedProject } from "~/utils/hooks";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import LoggedCallTable from "~/components/dashboard/LoggedCallTable";
|
import LoggedCallsTable from "~/components/dashboard/LoggedCallsTable";
|
||||||
import UsageGraph from "~/components/dashboard/UsageGraph";
|
import UsageGraph from "~/components/dashboard/UsageGraph";
|
||||||
import LoggedCallsPaginator from "~/components/dashboard/LoggedCallsPaginator";
|
|
||||||
|
|
||||||
export default function LoggedCalls() {
|
export default function Dashboard() {
|
||||||
const { data: selectedProject } = useSelectedProject();
|
const { data: selectedProject } = useSelectedProject();
|
||||||
|
|
||||||
const stats = api.dashboard.stats.useQuery(
|
const stats = api.dashboard.stats.useQuery(
|
||||||
@@ -38,14 +37,14 @@ export default function LoggedCalls() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell title="Logged Calls" requireAuth>
|
<AppShell title="Dashboard" requireAuth>
|
||||||
<PageHeaderContainer>
|
<PageHeaderContainer>
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<ProjectBreadcrumbContents />
|
<ProjectBreadcrumbContents />
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
<BreadcrumbItem isCurrentPage>
|
<BreadcrumbItem isCurrentPage>
|
||||||
<Text>Logged Calls</Text>
|
<Text>Dashboard</Text>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
</PageHeaderContainer>
|
</PageHeaderContainer>
|
||||||
@@ -121,8 +120,7 @@ export default function LoggedCalls() {
|
|||||||
</Card>
|
</Card>
|
||||||
</VStack>
|
</VStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
<LoggedCallTable />
|
<LoggedCallsTable />
|
||||||
<LoggedCallsPaginator />
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</VStack>
|
</VStack>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
37
app/src/pages/request-logs/index.tsx
Normal file
37
app/src/pages/request-logs/index.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Text, VStack, Breadcrumb, BreadcrumbItem } from "@chakra-ui/react";
|
||||||
|
|
||||||
|
import AppShell from "~/components/nav/AppShell";
|
||||||
|
import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
|
||||||
|
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
||||||
|
import { useSelectedProject } from "~/utils/hooks";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
import LoggedCallTable from "~/components/requestLogs/LoggedCallsTable";
|
||||||
|
import LoggedCallsPaginator from "~/components/requestLogs/LoggedCallsPaginator";
|
||||||
|
|
||||||
|
export default function LoggedCalls() {
|
||||||
|
const { data: selectedProject } = useSelectedProject();
|
||||||
|
|
||||||
|
const stats = api.dashboard.stats.useQuery(
|
||||||
|
{ projectId: selectedProject?.id ?? "" },
|
||||||
|
{ enabled: !!selectedProject },
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppShell title="Request Logs" requireAuth>
|
||||||
|
<PageHeaderContainer>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<ProjectBreadcrumbContents />
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem isCurrentPage>
|
||||||
|
<Text>Request Logs</Text>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</PageHeaderContainer>
|
||||||
|
<VStack px={8} py={4} alignItems="flex-start" spacing={4}>
|
||||||
|
<LoggedCallTable />
|
||||||
|
<LoggedCallsPaginator />
|
||||||
|
</VStack>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user