* testing agi-eval benchmark * Add scenario modal editor * Add initial values to ScenarioEditorModal * Add seedTwitterSentiment.ts --------- Co-authored-by: Kyle Corbitt <kyle@corbt.com>
227 lines
7.0 KiB
TypeScript
227 lines
7.0 KiB
TypeScript
import { useEffect, type DragEvent } from "react";
|
|
import { api } from "~/utils/api";
|
|
import { isEqual } from "lodash-es";
|
|
import { type Scenario } from "./types";
|
|
import { useExperiment, useExperimentAccess, useHandledAsyncCallback } from "~/utils/hooks";
|
|
import { useState } from "react";
|
|
|
|
import {
|
|
Box,
|
|
Button,
|
|
HStack,
|
|
Icon,
|
|
IconButton,
|
|
Spinner,
|
|
Stack,
|
|
Tooltip,
|
|
VStack,
|
|
Text,
|
|
} from "@chakra-ui/react";
|
|
import { cellPadding } from "../constants";
|
|
import { BsArrowsAngleExpand, BsX } from "react-icons/bs";
|
|
import { RiDraggable } from "react-icons/ri";
|
|
import { FloatingLabelInput } from "./FloatingLabelInput";
|
|
import { ScenarioEditorModal } from "./ScenarioEditorModal";
|
|
|
|
export default function ScenarioEditor({
|
|
scenario,
|
|
...props
|
|
}: {
|
|
scenario: Scenario;
|
|
hovered: boolean;
|
|
canHide: boolean;
|
|
}) {
|
|
const { canModify } = useExperimentAccess();
|
|
|
|
const savedValues = scenario.variableValues as Record<string, string>;
|
|
const utils = api.useContext();
|
|
const [isDragTarget, setIsDragTarget] = useState(false);
|
|
const [variableInputHovered, setVariableInputHovered] = useState(false);
|
|
|
|
const [values, setValues] = useState<Record<string, string>>(savedValues);
|
|
|
|
useEffect(() => {
|
|
if (savedValues) setValues(savedValues);
|
|
}, [savedValues]);
|
|
|
|
const experiment = useExperiment();
|
|
const vars = api.templateVars.list.useQuery({ experimentId: experiment.data?.id ?? "" });
|
|
|
|
const variableLabels = vars.data?.map((v) => v.label) ?? [];
|
|
|
|
const hasChanged = !isEqual(savedValues, values);
|
|
|
|
const mutation = api.scenarios.replaceWithValues.useMutation();
|
|
|
|
const [onSave] = useHandledAsyncCallback(async () => {
|
|
await mutation.mutateAsync({
|
|
id: scenario.id,
|
|
values,
|
|
});
|
|
await utils.scenarios.list.invalidate();
|
|
}, [mutation, values]);
|
|
|
|
const hideMutation = api.scenarios.hide.useMutation();
|
|
const [onHide, hidingInProgress] = useHandledAsyncCallback(async () => {
|
|
await hideMutation.mutateAsync({
|
|
id: scenario.id,
|
|
});
|
|
await utils.scenarios.list.invalidate();
|
|
await utils.promptVariants.stats.invalidate();
|
|
}, [hideMutation, scenario.id]);
|
|
|
|
const reorderMutation = api.scenarios.reorder.useMutation();
|
|
const [onReorder] = useHandledAsyncCallback(
|
|
async (e: DragEvent<HTMLDivElement>) => {
|
|
e.preventDefault();
|
|
setIsDragTarget(false);
|
|
const draggedId = e.dataTransfer.getData("text/plain");
|
|
const droppedId = scenario.id;
|
|
if (!draggedId || !droppedId || draggedId === droppedId) return;
|
|
await reorderMutation.mutateAsync({
|
|
draggedId,
|
|
droppedId,
|
|
});
|
|
await utils.scenarios.list.invalidate();
|
|
},
|
|
[reorderMutation, scenario.id],
|
|
);
|
|
|
|
const [scenarioEditorModalOpen, setScenarioEditorModalOpen] = useState(false);
|
|
|
|
return (
|
|
<>
|
|
<HStack
|
|
alignItems="flex-start"
|
|
px={cellPadding.x}
|
|
py={cellPadding.y}
|
|
spacing={0}
|
|
height="100%"
|
|
draggable={!variableInputHovered}
|
|
onDragStart={(e) => {
|
|
e.dataTransfer.setData("text/plain", scenario.id);
|
|
e.currentTarget.style.opacity = "0.4";
|
|
}}
|
|
onDragEnd={(e) => {
|
|
e.currentTarget.style.opacity = "1";
|
|
}}
|
|
onDragOver={(e) => {
|
|
e.preventDefault();
|
|
setIsDragTarget(true);
|
|
}}
|
|
onDragLeave={() => {
|
|
setIsDragTarget(false);
|
|
}}
|
|
onDrop={onReorder}
|
|
backgroundColor={isDragTarget ? "gray.100" : "transparent"}
|
|
>
|
|
{canModify && props.canHide && (
|
|
<Stack
|
|
alignSelf="flex-start"
|
|
opacity={props.hovered ? 1 : 0}
|
|
spacing={0}
|
|
ml={-cellPadding.x}
|
|
>
|
|
<Tooltip label="Hide scenario" hasArrow>
|
|
{/* for some reason the tooltip can't position itself properly relative to the icon without the wrapping box */}
|
|
<Button
|
|
variant="unstyled"
|
|
color="gray.400"
|
|
height="unset"
|
|
width="unset"
|
|
minW="unset"
|
|
onClick={onHide}
|
|
_hover={{
|
|
color: "gray.800",
|
|
cursor: "pointer",
|
|
}}
|
|
>
|
|
<Icon as={hidingInProgress ? Spinner : BsX} boxSize={hidingInProgress ? 4 : 6} />
|
|
</Button>
|
|
</Tooltip>
|
|
<Icon
|
|
as={RiDraggable}
|
|
boxSize={6}
|
|
color="gray.400"
|
|
_hover={{ color: "gray.800", cursor: "pointer" }}
|
|
/>
|
|
</Stack>
|
|
)}
|
|
|
|
{variableLabels.length === 0 ? (
|
|
<Box color="gray.500">
|
|
{vars.data ? "No scenario variables configured" : "Loading..."}
|
|
</Box>
|
|
) : (
|
|
<VStack spacing={4} flex={1} py={2}>
|
|
<HStack justifyContent="space-between" w="100%">
|
|
<Text color="gray.500">Scenario</Text>
|
|
<IconButton
|
|
className="fullscreen-toggle"
|
|
aria-label="Maximize"
|
|
icon={<BsArrowsAngleExpand />}
|
|
onClick={() => setScenarioEditorModalOpen(true)}
|
|
boxSize={6}
|
|
borderRadius={4}
|
|
p={1.5}
|
|
minW={0}
|
|
colorScheme="gray"
|
|
color="gray.500"
|
|
variant="ghost"
|
|
/>
|
|
</HStack>
|
|
{variableLabels.map((key) => {
|
|
const value = values[key] ?? "";
|
|
return (
|
|
<FloatingLabelInput
|
|
key={key}
|
|
label={key}
|
|
isDisabled={!canModify}
|
|
style={{ width: "100%" }}
|
|
maxHeight={32}
|
|
value={value}
|
|
onChange={(e) => {
|
|
setValues((prev) => ({ ...prev, [key]: e.target.value }));
|
|
}}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
e.preventDefault();
|
|
e.currentTarget.blur();
|
|
onSave();
|
|
}
|
|
}}
|
|
onMouseEnter={() => setVariableInputHovered(true)}
|
|
onMouseLeave={() => setVariableInputHovered(false)}
|
|
/>
|
|
);
|
|
})}
|
|
{hasChanged && (
|
|
<HStack justify="right">
|
|
<Button
|
|
size="sm"
|
|
onMouseDown={() => {
|
|
setValues(savedValues);
|
|
}}
|
|
colorScheme="gray"
|
|
>
|
|
Reset
|
|
</Button>
|
|
<Button size="sm" onMouseDown={onSave} colorScheme="blue">
|
|
Save
|
|
</Button>
|
|
</HStack>
|
|
)}
|
|
</VStack>
|
|
)}
|
|
</HStack>
|
|
{scenarioEditorModalOpen && (
|
|
<ScenarioEditorModal
|
|
scenarioId={scenario.id}
|
|
initialValues={savedValues}
|
|
onClose={() => setScenarioEditorModalOpen(false)}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|