Continue polling VariantStats while LLM retrieval in progress, minor UI fixes (#54)
* Prevent zoom in on iOS * Expand function return code background to fill cell * Keep OutputStats on far right of cells * Continue polling prompt stats while cells are retrieving from LLM * Add comment to _document.tsx * Fix prettier
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { type PromptVariant, type Scenario } from "../types";
|
import { type PromptVariant, type Scenario } from "../types";
|
||||||
import { Spinner, Text, Box, Center, Flex, VStack } from "@chakra-ui/react";
|
import { Spinner, Text, Center, VStack } from "@chakra-ui/react";
|
||||||
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
|
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
|
||||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||||
import { docco } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
import { docco } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
||||||
@@ -55,7 +55,10 @@ export default function OutputCell({
|
|||||||
const fetchingOutput = queryLoading || refetchingOutput;
|
const fetchingOutput = queryLoading || refetchingOutput;
|
||||||
|
|
||||||
const awaitingOutput =
|
const awaitingOutput =
|
||||||
!cell || cell.retrievalStatus === "PENDING" || cell.retrievalStatus === "IN_PROGRESS";
|
!cell ||
|
||||||
|
cell.retrievalStatus === "PENDING" ||
|
||||||
|
cell.retrievalStatus === "IN_PROGRESS" ||
|
||||||
|
refetchingOutput;
|
||||||
useEffect(() => setRefetchInterval(awaitingOutput ? 1000 : 0), [awaitingOutput]);
|
useEffect(() => setRefetchInterval(awaitingOutput ? 1000 : 0), [awaitingOutput]);
|
||||||
|
|
||||||
const modelOutput = cell?.modelOutput;
|
const modelOutput = cell?.modelOutput;
|
||||||
@@ -95,11 +98,18 @@ export default function OutputCell({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box fontSize="xs" width="100%" flexWrap="wrap" overflowX="auto">
|
<VStack
|
||||||
<VStack w="full" spacing={0}>
|
w="100%"
|
||||||
|
h="100%"
|
||||||
|
fontSize="xs"
|
||||||
|
flexWrap="wrap"
|
||||||
|
overflowX="auto"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<VStack w="full" flex={1} spacing={0}>
|
||||||
<CellOptions refetchingOutput={refetchingOutput} refetchOutput={hardRefetch} />
|
<CellOptions refetchingOutput={refetchingOutput} refetchOutput={hardRefetch} />
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
customStyle={{ overflowX: "unset" }}
|
customStyle={{ overflowX: "unset", width: "100%", flex: 1 }}
|
||||||
language="json"
|
language="json"
|
||||||
style={docco}
|
style={docco}
|
||||||
lineProps={{
|
lineProps={{
|
||||||
@@ -117,7 +127,7 @@ export default function OutputCell({
|
|||||||
</SyntaxHighlighter>
|
</SyntaxHighlighter>
|
||||||
</VStack>
|
</VStack>
|
||||||
<OutputStats model={variant.model} modelOutput={modelOutput} scenario={scenario} />
|
<OutputStats model={variant.model} modelOutput={modelOutput} scenario={scenario} />
|
||||||
</Box>
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +135,7 @@ export default function OutputCell({
|
|||||||
message?.content ?? streamedContent ?? JSON.stringify(modelOutput?.output);
|
message?.content ?? streamedContent ?? JSON.stringify(modelOutput?.output);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex w="100%" h="100%" direction="column" justifyContent="space-between" whiteSpace="pre-wrap">
|
<VStack w="100%" h="100%" justifyContent="space-between" whiteSpace="pre-wrap">
|
||||||
<VStack w="full" alignItems="flex-start" spacing={0}>
|
<VStack w="full" alignItems="flex-start" spacing={0}>
|
||||||
<CellOptions refetchingOutput={refetchingOutput} refetchOutput={hardRefetch} />
|
<CellOptions refetchingOutput={refetchingOutput} refetchOutput={hardRefetch} />
|
||||||
<Text>{contentToDisplay}</Text>
|
<Text>{contentToDisplay}</Text>
|
||||||
@@ -133,6 +143,6 @@ export default function OutputCell({
|
|||||||
{modelOutput && (
|
{modelOutput && (
|
||||||
<OutputStats model={variant.model} modelOutput={modelOutput} scenario={scenario} />
|
<OutputStats model={variant.model} modelOutput={modelOutput} scenario={scenario} />
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const OutputStats = ({
|
|||||||
const cost = promptCost + completionCost;
|
const cost = promptCost + completionCost;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack align="center" color="gray.500" fontSize="2xs" mt={{ base: 0, md: 1 }}>
|
<HStack w="full" align="center" color="gray.500" fontSize="2xs" mt={{ base: 0, md: 1 }}>
|
||||||
<HStack flex={1}>
|
<HStack flex={1}>
|
||||||
{modelOutput.outputEvaluation.map((evaluation) => {
|
{modelOutput.outputEvaluation.map((evaluation) => {
|
||||||
const passed = evaluation.result > 0.5;
|
const passed = evaluation.result > 0.5;
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { HStack, Icon, Text, useToken } from "@chakra-ui/react";
|
import { HStack, Icon, Skeleton, Text, useToken } from "@chakra-ui/react";
|
||||||
import { type PromptVariant } from "./types";
|
import { type PromptVariant } from "./types";
|
||||||
import { cellPadding } from "../constants";
|
import { cellPadding } from "../constants";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { BsCurrencyDollar } from "react-icons/bs";
|
import { BsCurrencyDollar } from "react-icons/bs";
|
||||||
import { CostTooltip } from "../tooltip/CostTooltip";
|
import { CostTooltip } from "../tooltip/CostTooltip";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function VariantStats(props: { variant: PromptVariant }) {
|
export default function VariantStats(props: { variant: PromptVariant }) {
|
||||||
|
const [refetchInterval, setRefetchInterval] = useState(0);
|
||||||
const { data } = api.promptVariants.stats.useQuery(
|
const { data } = api.promptVariants.stats.useQuery(
|
||||||
{
|
{
|
||||||
variantId: props.variant.id,
|
variantId: props.variant.id,
|
||||||
@@ -19,10 +21,18 @@ export default function VariantStats(props: { variant: PromptVariant }) {
|
|||||||
completionTokens: 0,
|
completionTokens: 0,
|
||||||
scenarioCount: 0,
|
scenarioCount: 0,
|
||||||
outputCount: 0,
|
outputCount: 0,
|
||||||
|
awaitingRetrievals: false,
|
||||||
},
|
},
|
||||||
|
refetchInterval,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Poll every two seconds while we are waiting for LLM retrievals to finish
|
||||||
|
useEffect(
|
||||||
|
() => setRefetchInterval(data.awaitingRetrievals ? 2000 : 0),
|
||||||
|
[data.awaitingRetrievals],
|
||||||
|
);
|
||||||
|
|
||||||
const [passColor, neutralColor, failColor] = useToken("colors", [
|
const [passColor, neutralColor, failColor] = useToken("colors", [
|
||||||
"green.500",
|
"green.500",
|
||||||
"gray.500",
|
"gray.500",
|
||||||
@@ -33,16 +43,20 @@ export default function VariantStats(props: { variant: PromptVariant }) {
|
|||||||
|
|
||||||
const showNumFinished = data.scenarioCount > 0 && data.scenarioCount !== data.outputCount;
|
const showNumFinished = data.scenarioCount > 0 && data.scenarioCount !== data.outputCount;
|
||||||
|
|
||||||
if (!(data.evalResults.length > 0) && !data.overallCost) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack justifyContent="space-between" alignItems="center" mx="2" fontSize="xs">
|
<HStack
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
mx="2"
|
||||||
|
fontSize="xs"
|
||||||
|
py={cellPadding.y}
|
||||||
|
>
|
||||||
{showNumFinished && (
|
{showNumFinished && (
|
||||||
<Text>
|
<Text>
|
||||||
{data.outputCount} / {data.scenarioCount}
|
{data.outputCount} / {data.scenarioCount}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<HStack px={cellPadding.x} py={cellPadding.y}>
|
<HStack px={cellPadding.x}>
|
||||||
{data.evalResults.map((result) => {
|
{data.evalResults.map((result) => {
|
||||||
const passedFrac = result.passCount / result.totalCount;
|
const passedFrac = result.passCount / result.totalCount;
|
||||||
return (
|
return (
|
||||||
@@ -55,17 +69,19 @@ export default function VariantStats(props: { variant: PromptVariant }) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</HStack>
|
</HStack>
|
||||||
{data.overallCost && (
|
{data.overallCost && !data.awaitingRetrievals ? (
|
||||||
<CostTooltip
|
<CostTooltip
|
||||||
promptTokens={data.promptTokens}
|
promptTokens={data.promptTokens}
|
||||||
completionTokens={data.completionTokens}
|
completionTokens={data.completionTokens}
|
||||||
cost={data.overallCost}
|
cost={data.overallCost}
|
||||||
>
|
>
|
||||||
<HStack spacing={0} align="center" color="gray.500" my="2">
|
<HStack spacing={0} align="center" color="gray.500">
|
||||||
<Icon as={BsCurrencyDollar} />
|
<Icon as={BsCurrencyDollar} />
|
||||||
<Text mr={1}>{data.overallCost.toFixed(3)}</Text>
|
<Text mr={1}>{data.overallCost.toFixed(3)}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</CostTooltip>
|
</CostTooltip>
|
||||||
|
) : (
|
||||||
|
<Skeleton height={4} width={12} mr={1} />
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export const CostTooltip = ({
|
|||||||
color="gray.800"
|
color="gray.800"
|
||||||
bgColor="gray.50"
|
bgColor="gray.50"
|
||||||
borderWidth={1}
|
borderWidth={1}
|
||||||
py={2}
|
|
||||||
hasArrow
|
hasArrow
|
||||||
shouldWrapChildren
|
shouldWrapChildren
|
||||||
label={
|
label={
|
||||||
|
|||||||
23
src/pages/_document.tsx
Normal file
23
src/pages/_document.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import Document, { Html, Head, Main, NextScript } from "next/document";
|
||||||
|
|
||||||
|
class MyDocument extends Document {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head>
|
||||||
|
{/* Prevent automatic zoom-in on iPhone when focusing on text input */}
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyDocument;
|
||||||
@@ -109,7 +109,26 @@ export const promptVariantsRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const overallCost = overallPromptCost + overallCompletionCost;
|
const overallCost = overallPromptCost + overallCompletionCost;
|
||||||
|
|
||||||
return { evalResults, promptTokens, completionTokens, overallCost, scenarioCount, outputCount };
|
const awaitingRetrievals = !!(await prisma.scenarioVariantCell.findFirst({
|
||||||
|
where: {
|
||||||
|
promptVariantId: input.variantId,
|
||||||
|
testScenario: { visible: true },
|
||||||
|
// Check if is PENDING or IN_PROGRESS
|
||||||
|
retrievalStatus: {
|
||||||
|
in: ["PENDING", "IN_PROGRESS"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
evalResults,
|
||||||
|
promptTokens,
|
||||||
|
completionTokens,
|
||||||
|
overallCost,
|
||||||
|
scenarioCount,
|
||||||
|
outputCount,
|
||||||
|
awaitingRetrievals,
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
create: publicProcedure
|
create: publicProcedure
|
||||||
|
|||||||
Reference in New Issue
Block a user