diff --git a/app/@types/nextjs-routes.d.ts b/app/@types/nextjs-routes.d.ts index 6253ce2..fcf99ba 100644 --- a/app/@types/nextjs-routes.d.ts +++ b/app/@types/nextjs-routes.d.ts @@ -18,13 +18,14 @@ declare module "nextjs-routes" { | StaticRoute<"/api/openapi"> | StaticRoute<"/api/sentry-example-api"> | DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }> + | StaticRoute<"/dashboard"> | DynamicRoute<"/data/[id]", { "id": string }> | StaticRoute<"/data"> | DynamicRoute<"/experiments/[id]", { "id": string }> | StaticRoute<"/experiments"> | StaticRoute<"/"> - | StaticRoute<"/logged-calls"> | StaticRoute<"/project/settings"> + | StaticRoute<"/request-logs"> | StaticRoute<"/sentry-example-page"> | StaticRoute<"/world-champs"> | StaticRoute<"/world-champs/signup">; diff --git a/app/src/components/OutputsTable/ScenariosHeader.tsx b/app/src/components/OutputsTable/ScenariosHeader.tsx index a785f50..3e470c4 100644 --- a/app/src/components/OutputsTable/ScenariosHeader.tsx +++ b/app/src/components/OutputsTable/ScenariosHeader.tsx @@ -57,11 +57,16 @@ export const ScenariosHeader = () => { } + maxW={8} + minW={8} + minH={8} + maxH={8} /> - + } onClick={() => onAddScenario(false)} diff --git a/app/src/components/dashboard/LoggedCallTable.tsx b/app/src/components/dashboard/LoggedCallTable.tsx deleted file mode 100644 index cde7d11..0000000 --- a/app/src/components/dashboard/LoggedCallTable.tsx +++ /dev/null @@ -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 ( - - - {jsonString} - - } - position="absolute" - top={1} - right={1} - size="xs" - variant="ghost" - onClick={() => void copyToClipboard(jsonString)} - /> - - ); -}; - -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 = ( - - {loggedCall.cacheHit - ? "Cache hit" - : ((loggedCall.modelResponse?.durationMs ?? 0) / 1000).toFixed(2) + "s"} - - ); - - return ( - <> - td": { borderBottom: "none" }, - }} - > - - - - - - - {timeAgo} - - - - {model} - {durationCell} - {loggedCall.modelResponse?.inputTokens} - {loggedCall.modelResponse?.outputTokens} - - {loggedCall.modelResponse?.statusCode ?? "No response"} - - - - - - - - - Input - - - - Output - - - - - - - - - - - - ); -} - -export default function LoggedCallTable() { - const [expandedRow, setExpandedRow] = useState(null); - const { data: loggedCalls } = useLoggedCalls(); - - return ( - - - - Logged Calls - - - - - - - - - - - - - - - {loggedCalls?.calls.map((loggedCall) => { - return ( - { - if (loggedCall.id === expandedRow) { - setExpandedRow(null); - } else { - setExpandedRow(loggedCall.id); - } - }} - /> - ); - })} - -
- TimeModelDurationInput tokensOutput tokensStatus
-
- ); -} diff --git a/app/src/components/dashboard/LoggedCallsTable.tsx b/app/src/components/dashboard/LoggedCallsTable.tsx new file mode 100644 index 0000000..cb20d6d --- /dev/null +++ b/app/src/components/dashboard/LoggedCallsTable.tsx @@ -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(null); + const { data: loggedCalls } = useLoggedCalls(); + + return ( + + + + + Logged Calls + + + + + + + + {loggedCalls?.calls.map((loggedCall) => { + return ( + { + if (loggedCall.id === expandedRow) { + setExpandedRow(null); + } else { + setExpandedRow(loggedCall.id); + } + }} + /> + ); + })} + +
+
+ ); +} diff --git a/app/src/components/nav/AppShell.tsx b/app/src/components/nav/AppShell.tsx index 14385d5..dbcfcc2 100644 --- a/app/src/components/nav/AppShell.tsx +++ b/app/src/components/nav/AppShell.tsx @@ -14,7 +14,7 @@ import Head from "next/head"; import Link from "next/link"; import { BsGearFill, BsGithub, BsPersonCircle } from "react-icons/bs"; 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 UserMenu from "./UserMenu"; import { env } from "~/env.mjs"; @@ -59,8 +59,17 @@ const NavSidebar = () => { <> + {env.NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS && ( - + <> + + + )} {env.NEXT_PUBLIC_SHOW_DATA && ( diff --git a/app/src/components/requestLogs/FormattedJson.tsx b/app/src/components/requestLogs/FormattedJson.tsx new file mode 100644 index 0000000..ed21bac --- /dev/null +++ b/app/src/components/requestLogs/FormattedJson.tsx @@ -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 ( + + + {jsonString} + + } + position="absolute" + top={1} + right={1} + size="xs" + variant="ghost" + onClick={() => void copyToClipboard(jsonString)} + /> + + ); +}; + +export { FormattedJson }; diff --git a/app/src/components/dashboard/LoggedCallsPaginator.tsx b/app/src/components/requestLogs/LoggedCallsPaginator.tsx similarity index 100% rename from app/src/components/dashboard/LoggedCallsPaginator.tsx rename to app/src/components/requestLogs/LoggedCallsPaginator.tsx diff --git a/app/src/components/requestLogs/LoggedCallsTable.tsx b/app/src/components/requestLogs/LoggedCallsTable.tsx new file mode 100644 index 0000000..8bd2133 --- /dev/null +++ b/app/src/components/requestLogs/LoggedCallsTable.tsx @@ -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(null); + const { data: loggedCalls } = useLoggedCalls(); + + return ( + + + + + {loggedCalls?.calls.map((loggedCall) => { + return ( + { + if (loggedCall.id === expandedRow) { + setExpandedRow(null); + } else { + setExpandedRow(loggedCall.id); + } + }} + /> + ); + })} + +
+
+ ); +} diff --git a/app/src/components/requestLogs/TableRow.tsx b/app/src/components/requestLogs/TableRow.tsx new file mode 100644 index 0000000..00930a6 --- /dev/null +++ b/app/src/components/requestLogs/TableRow.tsx @@ -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 = () => ( + + + Time + Model + Duration + Input tokens + Output tokens + Status + + +); + +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 = ( + + {loggedCall.cacheHit + ? "Cache hit" + : ((loggedCall.modelResponse?.durationMs ?? 0) / 1000).toFixed(2) + "s"} + + ); + + return ( + <> + td": { borderBottom: "none" }, + }} + > + + + + {timeAgo} + + + + + + + {model} + + + + {durationCell} + {loggedCall.modelResponse?.inputTokens} + {loggedCall.modelResponse?.outputTokens} + + {loggedCall.modelResponse?.statusCode ?? "No response"} + + + + + + + + + Input + + + + Output + + + + + + + + + + + + ); +}; diff --git a/app/src/pages/logged-calls/index.tsx b/app/src/pages/dashboard/index.tsx similarity index 92% rename from app/src/pages/logged-calls/index.tsx rename to app/src/pages/dashboard/index.tsx index 71a2475..1e9d296 100644 --- a/app/src/pages/logged-calls/index.tsx +++ b/app/src/pages/dashboard/index.tsx @@ -25,11 +25,10 @@ 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/dashboard/LoggedCallTable"; +import LoggedCallsTable from "~/components/dashboard/LoggedCallsTable"; 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 stats = api.dashboard.stats.useQuery( @@ -38,14 +37,14 @@ export default function LoggedCalls() { ); return ( - + - Logged Calls + Dashboard @@ -121,8 +120,7 @@ export default function LoggedCalls() { - - + diff --git a/app/src/pages/request-logs/index.tsx b/app/src/pages/request-logs/index.tsx new file mode 100644 index 0000000..b0ff12d --- /dev/null +++ b/app/src/pages/request-logs/index.tsx @@ -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 ( + + + + + + + + Request Logs + + + + + + + + + ); +}