}
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
-
-
-
-
-
- |
- Time |
- Model |
- Duration |
- Input tokens |
- Output tokens |
- Status |
-
-
-
- {loggedCalls?.calls.map((loggedCall) => {
- return (
- {
- if (loggedCall.id === expandedRow) {
- setExpandedRow(null);
- } else {
- setExpandedRow(loggedCall.id);
- }
- }}
- />
- );
- })}
-
-
-
- );
-}
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
+
+
+
+
+
+
+
+
+ );
+}