diff --git a/apps/evalite-ui/app/components/ui/line-chart.tsx b/apps/evalite-ui/app/components/ui/line-chart.tsx index 7cf63ba..729a6c3 100644 --- a/apps/evalite-ui/app/components/ui/line-chart.tsx +++ b/apps/evalite-ui/app/components/ui/line-chart.tsx @@ -1,8 +1,8 @@ "use client"; -import { formatDistance } from "date-fns"; -import { Area, AreaChart, XAxis } from "recharts"; +import { Area, AreaChart } from "recharts"; +import { Fragment } from "react/jsx-runtime"; import { type ChartConfig, ChartContainer, @@ -20,6 +20,7 @@ const chartConfig = { export function MyLineChart(props: { data: { date: string; score: number }[]; + onDotClick: (props: { date: string }) => void; }) { return ( - value.slice(0, 3)} - /> { + const onClick = () => { + props.onDotClick({ date: payload.date }); + }; + return ( + + + + + + ); + }} /> diff --git a/apps/evalite-ui/app/components/ui/live-date.tsx b/apps/evalite-ui/app/components/ui/live-date.tsx index cb46584..4830cb0 100644 --- a/apps/evalite-ui/app/components/ui/live-date.tsx +++ b/apps/evalite-ui/app/components/ui/live-date.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; const ONE_MINUTE = 60_000; -export const LiveDate = (props: { date: string }) => { +export const LiveDate = (props: { date: string; className?: string }) => { const [, setNow] = useState(new Date()); useEffect(() => { const interval = setInterval(() => { @@ -12,6 +12,8 @@ export const LiveDate = (props: { date: string }) => { return () => clearInterval(interval); }, []); return ( - {formatDistance(props.date, new Date(), { addSuffix: true })} + + {formatDistance(props.date, new Date(), { addSuffix: true })} + ); }; diff --git a/apps/evalite-ui/app/routes/eval.$name.trace.$index.tsx b/apps/evalite-ui/app/routes/eval.$name.trace.$index.tsx index 7f35aca..685fc84 100644 --- a/apps/evalite-ui/app/routes/eval.$name.trace.$index.tsx +++ b/apps/evalite-ui/app/routes/eval.$name.trace.$index.tsx @@ -18,6 +18,7 @@ import { BreadcrumbList, } from "~/components/ui/breadcrumb"; import { Button } from "~/components/ui/button"; +import { LiveDate } from "~/components/ui/live-date"; import { Separator } from "~/components/ui/separator"; import { SidebarContent, SidebarHeader } from "~/components/ui/sidebar"; import { cn } from "~/lib/utils"; @@ -45,26 +46,34 @@ const MainBodySection = ({ ); export const clientLoader = async (args: ClientLoaderFunctionArgs) => { + const searchParams = new URLSearchParams(args.request.url.split("?")[1]); const data = await getResult({ evalName: args.params.name!, resultIndex: args.params.index!, + evalTimestamp: searchParams.get("timestamp"), }); - return { data, name: args.params.name!, resultIndex: args.params.index! }; + return { + data, + name: args.params.name!, + resultIndex: args.params.index!, + timestamp: searchParams.get("timestamp"), + }; }; export default function Page() { const { - data: { result, prevResult, filepath, evalStatus }, + data: { result, prevResult, evaluation }, name, resultIndex, + timestamp, } = useLoaderData(); const serverState = useContext(TestServerStateContext); const isRunning = serverState.state.type === "running" && - serverState.state.filepaths.has(filepath); + serverState.state.filepaths.has(evaluation.filepath); const [searchParams] = useSearchParams(); @@ -95,7 +104,11 @@ export default function Page() {
@@ -108,11 +121,15 @@ export default function Page() { isRunning={isRunning} score={result.score} state={getScoreState(result.score, prevResult?.score)} - evalStatus={evalStatus} + evalStatus={evaluation.status} /> {formatTime(result.duration)} + + + + {wholeEvalUsage && ( <> @@ -224,7 +241,7 @@ export default function Page() { (prevScore) => prevScore.name === score.name )?.score )} - evalStatus={evalStatus} + evalStatus={evaluation.status} /> {score.metadata && ( diff --git a/apps/evalite-ui/app/routes/eval.$name.tsx b/apps/evalite-ui/app/routes/eval.$name.tsx index 84bc6e5..9256566 100644 --- a/apps/evalite-ui/app/routes/eval.$name.tsx +++ b/apps/evalite-ui/app/routes/eval.$name.tsx @@ -2,9 +2,11 @@ import { getEvalByName } from "@evalite/core/sdk"; import { average, sum } from "@evalite/core/utils"; import type { MetaFunction } from "@remix-run/node"; import { + Link, NavLink, Outlet, useLoaderData, + useSearchParams, type ClientLoaderFunctionArgs, } from "@remix-run/react"; import { XCircleIcon } from "lucide-react"; @@ -12,6 +14,7 @@ import React, { useContext } from "react"; import { DisplayInput } from "~/components/display-input"; import { InnerPageLayout } from "~/components/page-layout"; import { getScoreState, Score } from "~/components/score"; +import { Button } from "~/components/ui/button"; import { MyLineChart } from "~/components/ui/line-chart"; import { LiveDate } from "~/components/ui/live-date"; import { Separator } from "~/components/ui/separator"; @@ -34,7 +37,11 @@ export const meta: MetaFunction = (args) => { }; export const clientLoader = async (args: ClientLoaderFunctionArgs) => { - const result = await getEvalByName(args.params.name!); + const searchParams = new URLSearchParams(args.request.url.split("?")[1]); + const result = await getEvalByName( + args.params.name!, + searchParams.get("timestamp") + ); return { ...result, @@ -64,6 +71,10 @@ export default function Page() { ? average(prevEvaluation.results, (r) => average(r.scores, (s) => s.score)) : undefined; + const [search, setSearch] = useSearchParams(); + + const timestamp = search.get("timestamp"); + return ( <> {formatTime(evaluation.duration)} - - - +
+ + {timestamp && ( + <> + + View Latest + + + )} +
@@ -114,7 +136,14 @@ export default function Page() {

History

- {history.length > 1 && } + {history.length > 1 && ( + { + setSearch({ timestamp: date }); + }} + /> + )} )}

Results

@@ -139,7 +168,7 @@ export default function Page() { const Wrapper = (props: { children: React.ReactNode }) => ( { return cn("block h-full p-4", isActive && "active"); diff --git a/packages/evalite-core/src/db.ts b/packages/evalite-core/src/db.ts index e5bf7c0..2a9dcf6 100644 --- a/packages/evalite-core/src/db.ts +++ b/packages/evalite-core/src/db.ts @@ -359,7 +359,7 @@ export const getMostRecentRun = ( return run; }; -export const getPreviousEvalRun = ( +export const getPreviousEval = ( db: BetterSqlite3.Database, name: string, startTime: string @@ -441,20 +441,25 @@ export const jsonParseFields = ( return objToReturn; }; -export const getMostRecentEvalByName = ( +/** + * Defaults to most recent if timestamp not passed + */ +export const getEvalByName = ( db: BetterSqlite3.Database, - name: string + name: string, + timestamp?: string ) => { return db - .prepare<{ name: string }, Db.Eval>( + .prepare<{ name: string; timestamp?: string }, Db.Eval>( ` SELECT * FROM evals WHERE name = @name + ${timestamp ? "AND created_at = @timestamp" : ""} ORDER BY created_at DESC LIMIT 1 ` ) - .get({ name }); + .get({ name, timestamp }); }; export const getHistoricalEvalsWithScoresByName = ( diff --git a/packages/evalite-core/src/sdk.ts b/packages/evalite-core/src/sdk.ts index 55aa5e6..01cc170 100644 --- a/packages/evalite-core/src/sdk.ts +++ b/packages/evalite-core/src/sdk.ts @@ -35,25 +35,30 @@ export type GetEvalByNameResult = { }; export const getEvalByName = async ( - name: string + name: string, + timestamp: string | null | undefined ): Promise => { - const res = await fetch(`${BASE_URL}/api/eval?name=${name}`); + const params = new URLSearchParams({ name, timestamp: timestamp || "" }); + const res = await fetch(`${BASE_URL}/api/eval?${params.toString()}`); return res.json() as any; }; export type GetResultResult = { result: Db.Result & { traces: Db.Trace[]; score: number; scores: Db.Score[] }; prevResult: (Db.Result & { score: number; scores: Db.Score[] }) | undefined; - filepath: string; - evalStatus: "success" | "fail"; + evaluation: Db.Eval; }; export const getResult = async (opts: { evalName: string; + evalTimestamp: string | null | undefined; resultIndex: string; }): Promise => { - const res = await fetch( - `${BASE_URL}/api/eval/result?name=${opts.evalName}&index=${opts.resultIndex}` - ); + const params = new URLSearchParams({ + name: opts.evalName, + index: opts.resultIndex, + timestamp: opts.evalTimestamp || "", + }); + const res = await fetch(`${BASE_URL}/api/eval/result?${params.toString()}`); return res.json() as any; }; diff --git a/packages/evalite-core/src/server.ts b/packages/evalite-core/src/server.ts index 367329f..c22ecfb 100644 --- a/packages/evalite-core/src/server.ts +++ b/packages/evalite-core/src/server.ts @@ -7,9 +7,9 @@ import { getEvals, getEvalsAverageScores, getHistoricalEvalsWithScoresByName, - getMostRecentEvalByName, + getEvalByName, getMostRecentRun, - getPreviousEvalRun, + getPreviousEval, getResults, getScores, getTraces, @@ -99,7 +99,7 @@ export const createServer = (opts: { db: SQLiteDatabase }) => { ) ).map((e) => ({ ...e, - prevEval: getPreviousEvalRun(opts.db, e.name, e.created_at), + prevEval: getPreviousEval(opts.db, e.name, e.created_at), })); const evalsAverageScores = getEvalsAverageScores( @@ -196,13 +196,13 @@ export const createServer = (opts: { db: SQLiteDatabase }) => { handler: async (req, res) => { const name = req.query.name; - const evaluation = getMostRecentEvalByName(opts.db, name); + const evaluation = getEvalByName(opts.db, name, req.query.timestamp); if (!evaluation) { return res.code(404).send(); } - const prevEvaluation = getPreviousEvalRun( + const prevEvaluation = getPreviousEval( opts.db, name, evaluation.created_at @@ -253,6 +253,7 @@ export const createServer = (opts: { db: SQLiteDatabase }) => { Querystring: { name: string; index: string; + timestamp?: string; }; Reply: GetResultResult; }>({ @@ -264,17 +265,23 @@ export const createServer = (opts: { db: SQLiteDatabase }) => { properties: { name: { type: "string" }, index: { type: "string" }, + timestamp: { type: "string" }, }, + required: ["name", "index"], }, }, handler: async (req, res) => { - const evaluation = getMostRecentEvalByName(opts.db, req.query.name); + const evaluation = getEvalByName( + opts.db, + req.query.name, + req.query.timestamp + ); if (!evaluation) { return res.code(404).send(); } - const prevEvaluation = getPreviousEvalRun( + const prevEvaluation = getPreviousEval( opts.db, req.query.name, evaluation.created_at @@ -338,8 +345,7 @@ export const createServer = (opts: { db: SQLiteDatabase }) => { return res.code(200).send({ result, prevResult, - filepath: evaluation.filepath, - evalStatus: evaluation.status, + evaluation, }); }, });