diff --git a/src/components/OutputsTable/AddVariantButton.tsx b/src/components/OutputsTable/AddVariantButton.tsx
new file mode 100644
index 0000000..460b47e
--- /dev/null
+++ b/src/components/OutputsTable/AddVariantButton.tsx
@@ -0,0 +1,48 @@
+import { Box, Flex, Icon, Spinner } from "@chakra-ui/react";
+import { BsPlus } from "react-icons/bs";
+import { api } from "~/utils/api";
+import { useExperiment, useExperimentAccess, useHandledAsyncCallback } from "~/utils/hooks";
+import { cellPadding } from "../constants";
+import { ActionButton } from "./ScenariosHeader";
+
+export default function AddVariantButton() {
+ const experiment = useExperiment();
+ const mutation = api.promptVariants.create.useMutation();
+ const utils = api.useContext();
+
+ const [onClick, loading] = useHandledAsyncCallback(async () => {
+ if (!experiment.data) return;
+ await mutation.mutateAsync({
+ experimentId: experiment.data.id,
+ });
+ await utils.promptVariants.list.invalidate();
+ }, [mutation]);
+
+ const { canModify } = useExperimentAccess();
+ if (!canModify) return ;
+
+ return (
+
+ }
+ >
+ Add Variant
+
+ {/* */}
+
+ );
+}
diff --git a/src/components/OutputsTable/NewScenarioButton.tsx b/src/components/OutputsTable/NewScenarioButton.tsx
deleted file mode 100644
index 5542338..0000000
--- a/src/components/OutputsTable/NewScenarioButton.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { Button, type ButtonProps, HStack, Spinner, Icon } from "@chakra-ui/react";
-import { BsPlus } from "react-icons/bs";
-import { api } from "~/utils/api";
-import { useExperiment, useExperimentAccess, useHandledAsyncCallback } from "~/utils/hooks";
-
-// Extracted Button styling into reusable component
-const StyledButton = ({ children, onClick }: ButtonProps) => (
-
-);
-
-export default function NewScenarioButton() {
- const { canModify } = useExperimentAccess();
-
- const experiment = useExperiment();
- const mutation = api.scenarios.create.useMutation();
- const utils = api.useContext();
-
- const [onClick] = useHandledAsyncCallback(async () => {
- if (!experiment.data) return;
- await mutation.mutateAsync({
- experimentId: experiment.data.id,
- });
- await utils.scenarios.list.invalidate();
- }, [mutation]);
-
- const [onAutogenerate, autogenerating] = useHandledAsyncCallback(async () => {
- if (!experiment.data) return;
- await mutation.mutateAsync({
- experimentId: experiment.data.id,
- autogenerate: true,
- });
- await utils.scenarios.list.invalidate();
- }, [mutation]);
-
- if (!canModify) return null;
-
- return (
-
-
-
- Add Scenario
-
-
-
- Autogenerate Scenario
-
-
- );
-}
diff --git a/src/components/OutputsTable/NewVariantButton.tsx b/src/components/OutputsTable/NewVariantButton.tsx
deleted file mode 100644
index f2ddfdd..0000000
--- a/src/components/OutputsTable/NewVariantButton.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Box, Button, Icon, Spinner, Text } from "@chakra-ui/react";
-import { BsPlus } from "react-icons/bs";
-import { api } from "~/utils/api";
-import { useExperiment, useExperimentAccess, useHandledAsyncCallback } from "~/utils/hooks";
-import { cellPadding, headerMinHeight } from "../constants";
-
-export default function NewVariantButton() {
- const experiment = useExperiment();
- const mutation = api.promptVariants.create.useMutation();
- const utils = api.useContext();
-
- const [onClick, loading] = useHandledAsyncCallback(async () => {
- if (!experiment.data) return;
- await mutation.mutateAsync({
- experimentId: experiment.data.id,
- });
- await utils.promptVariants.list.invalidate();
- }, [mutation]);
-
- const { canModify } = useExperimentAccess();
- if (!canModify) return ;
-
- return (
-
- );
-}
diff --git a/src/components/OutputsTable/ScenarioRow.tsx b/src/components/OutputsTable/ScenarioRow.tsx
index 4431318..d9544ce 100644
--- a/src/components/OutputsTable/ScenarioRow.tsx
+++ b/src/components/OutputsTable/ScenarioRow.tsx
@@ -4,11 +4,13 @@ import { cellPadding } from "../constants";
import OutputCell from "./OutputCell/OutputCell";
import ScenarioEditor from "./ScenarioEditor";
import type { PromptVariant, Scenario } from "./types";
+import { borders } from "./styles";
const ScenarioRow = (props: {
scenario: Scenario;
variants: PromptVariant[];
canHide: boolean;
+ rowStart: number;
}) => {
const [isHovered, setIsHovered] = useState(false);
@@ -21,15 +23,21 @@ const ScenarioRow = (props: {
onMouseLeave={() => setIsHovered(false)}
sx={isHovered ? highlightStyle : undefined}
borderLeftWidth={1}
+ {...borders}
+ rowStart={props.rowStart}
+ colStart={1}
>
- {props.variants.map((variant) => (
+ {props.variants.map((variant, i) => (
setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
sx={isHovered ? highlightStyle : undefined}
+ rowStart={props.rowStart}
+ colStart={i + 2}
+ {...borders}
>
diff --git a/src/components/OutputsTable/ScenariosHeader.tsx b/src/components/OutputsTable/ScenariosHeader.tsx
index c9a79e7..f3ccdc6 100644
--- a/src/components/OutputsTable/ScenariosHeader.tsx
+++ b/src/components/OutputsTable/ScenariosHeader.tsx
@@ -1,52 +1,73 @@
-import { Button, GridItem, HStack, Heading } from "@chakra-ui/react";
+import {
+ Button,
+ type ButtonProps,
+ HStack,
+ Text,
+ Icon,
+ Menu,
+ MenuButton,
+ MenuList,
+ MenuItem,
+ IconButton,
+ Spinner,
+} from "@chakra-ui/react";
import { cellPadding } from "../constants";
-import { useElementDimensions, useExperimentAccess } from "~/utils/hooks";
-import { stickyHeaderStyle } from "./styles";
-import { BsPencil } from "react-icons/bs";
+import { useExperiment, useExperimentAccess, useHandledAsyncCallback } from "~/utils/hooks";
+import { BsGear, BsPencil, BsPlus, BsStars } from "react-icons/bs";
import { useAppStore } from "~/state/store";
+import { api } from "~/utils/api";
-export const ScenariosHeader = ({
- headerRows,
- numScenarios,
-}: {
- headerRows: number;
- numScenarios: number;
-}) => {
+export const ActionButton = (props: ButtonProps) => (
+
+);
+
+export const ScenariosHeader = (props: { numScenarios: number }) => {
const openDrawer = useAppStore((s) => s.openDrawer);
const { canModify } = useExperimentAccess();
- const [ref, dimensions] = useElementDimensions();
- const topValue = dimensions ? `-${dimensions.height - 24}px` : "-455px";
+ const experiment = useExperiment();
+ const createScenarioMutation = api.scenarios.create.useMutation();
+ const utils = api.useContext();
+
+ const [onAddScenario, loading] = useHandledAsyncCallback(
+ async (autogenerate: boolean) => {
+ if (!experiment.data) return;
+ await createScenarioMutation.mutateAsync({
+ experimentId: experiment.data.id,
+ autogenerate,
+ });
+ await utils.scenarios.list.invalidate();
+ },
+ [createScenarioMutation],
+ );
return (
-
-
-
- Scenarios ({numScenarios})
-
- {canModify && (
- }
- onClick={openDrawer}
- >
- Edit Vars
-
- )}
-
-
+
+
+ Scenarios ({props.numScenarios})
+
+ {canModify && (
+
+ )}
+
);
};
diff --git a/src/components/OutputsTable/index.tsx b/src/components/OutputsTable/index.tsx
index 00bd471..4b6fbb9 100644
--- a/src/components/OutputsTable/index.tsx
+++ b/src/components/OutputsTable/index.tsx
@@ -1,13 +1,12 @@
-import { Grid, GridItem } from "@chakra-ui/react";
+import { Grid, GridItem, type GridItemProps } from "@chakra-ui/react";
import { api } from "~/utils/api";
-import NewScenarioButton from "./NewScenarioButton";
-import NewVariantButton from "./NewVariantButton";
+import AddVariantButton from "./AddVariantButton";
import ScenarioRow from "./ScenarioRow";
import VariantEditor from "./VariantEditor";
import VariantHeader from "../VariantHeader/VariantHeader";
import VariantStats from "./VariantStats";
import { ScenariosHeader } from "./ScenariosHeader";
-import { stickyHeaderStyle } from "./styles";
+import { borders } from "./styles";
export default function OutputsTable({ experimentId }: { experimentId: string | undefined }) {
const variants = api.promptVariants.list.useQuery(
@@ -22,61 +21,76 @@ export default function OutputsTable({ experimentId }: { experimentId: string |
if (!variants.data || !scenarios.data) return null;
- const allCols = variants.data.length + 1;
- const headerRows = 3;
+ const allCols = variants.data.length + 2;
+ const variantHeaderRows = 3;
+ const scenarioHeaderRows = 1;
+ const allRows = variantHeaderRows + scenarioHeaderRows + scenarios.data.length;
return (
*": {
borderColor: "gray.300",
- borderBottomWidth: 1,
- borderRightWidth: 1,
},
}}
fontSize="sm"
>
-
-
- {variants.data.map((variant) => (
- 1} />
- ))}
- *" selector on Grid
- style={{ borderRightWidth: 0, borderBottomWidth: 0 }}
- h={8}
- sx={stickyHeaderStyle}
- >
-
+
+
- {variants.data.map((variant) => (
-
-
-
- ))}
- {variants.data.map((variant) => (
-
-
-
- ))}
- {scenarios.data.map((scenario) => (
+ {variants.data.map((variant, i) => {
+ const sharedProps: GridItemProps = {
+ ...borders,
+ colStart: i + 2,
+ borderLeftWidth: i === 0 ? 1 : 0,
+ };
+ return (
+ <>
+ 1}
+ rowStart={1}
+ {...sharedProps}
+ />
+
+
+
+
+
+
+ >
+ );
+ })}
+
+
+
+
+
+ {scenarios.data.map((scenario, i) => (
1}
/>
))}
-
-
-
+
+ {/* Add some extra padding on the right, because when the table is too wide to fit in the viewport `pr` on the Grid isn't respected. */}
+
);
}
diff --git a/src/components/OutputsTable/styles.ts b/src/components/OutputsTable/styles.ts
index fbb6e67..234a22d 100644
--- a/src/components/OutputsTable/styles.ts
+++ b/src/components/OutputsTable/styles.ts
@@ -1,4 +1,4 @@
-import { type SystemStyleObject } from "@chakra-ui/react";
+import { type GridItemProps, type SystemStyleObject } from "@chakra-ui/react";
export const stickyHeaderStyle: SystemStyleObject = {
position: "sticky",
@@ -6,3 +6,8 @@ export const stickyHeaderStyle: SystemStyleObject = {
backgroundColor: "#fff",
zIndex: 10,
};
+
+export const borders: GridItemProps = {
+ borderRightWidth: 1,
+ borderBottomWidth: 1,
+};
diff --git a/src/components/VariantHeader/VariantHeader.tsx b/src/components/VariantHeader/VariantHeader.tsx
index 3c83316..a0e8482 100644
--- a/src/components/VariantHeader/VariantHeader.tsx
+++ b/src/components/VariantHeader/VariantHeader.tsx
@@ -3,28 +3,34 @@ import { type PromptVariant } from "../OutputsTable/types";
import { api } from "~/utils/api";
import { RiDraggable } from "react-icons/ri";
import { useExperimentAccess, useHandledAsyncCallback } from "~/utils/hooks";
-import { HStack, Icon, Text, GridItem } from "@chakra-ui/react"; // Changed here
+import { HStack, Icon, Text, GridItem, type GridItemProps } from "@chakra-ui/react"; // Changed here
import { cellPadding, headerMinHeight } from "../constants";
import AutoResizeTextArea from "../AutoResizeTextArea";
import { stickyHeaderStyle } from "../OutputsTable/styles";
import VariantHeaderMenuButton from "./VariantHeaderMenuButton";
-export default function VariantHeader(props: { variant: PromptVariant; canHide: boolean }) {
+export default function VariantHeader(
+ allProps: {
+ variant: PromptVariant;
+ canHide: boolean;
+ } & GridItemProps,
+) {
+ const { variant, canHide, ...gridItemProps } = allProps;
const { canModify } = useExperimentAccess();
const utils = api.useContext();
const [isDragTarget, setIsDragTarget] = useState(false);
const [isInputHovered, setIsInputHovered] = useState(false);
- const [label, setLabel] = useState(props.variant.label);
+ const [label, setLabel] = useState(variant.label);
const updateMutation = api.promptVariants.update.useMutation();
const [onSaveLabel] = useHandledAsyncCallback(async () => {
- if (label && label !== props.variant.label) {
+ if (label && label !== variant.label) {
await updateMutation.mutateAsync({
- id: props.variant.id,
+ id: variant.id,
updates: { label: label },
});
}
- }, [updateMutation, props.variant.id, props.variant.label, label]);
+ }, [updateMutation, variant.id, variant.label, label]);
const reorderMutation = api.promptVariants.reorder.useMutation();
const [onReorder] = useHandledAsyncCallback(
@@ -32,7 +38,7 @@ export default function VariantHeader(props: { variant: PromptVariant; canHide:
e.preventDefault();
setIsDragTarget(false);
const draggedId = e.dataTransfer.getData("text/plain");
- const droppedId = props.variant.id;
+ const droppedId = variant.id;
if (!draggedId || !droppedId || draggedId === droppedId) return;
await reorderMutation.mutateAsync({
draggedId,
@@ -40,16 +46,16 @@ export default function VariantHeader(props: { variant: PromptVariant; canHide:
});
await utils.promptVariants.list.invalidate();
},
- [reorderMutation, props.variant.id],
+ [reorderMutation, variant.id],
);
const [menuOpen, setMenuOpen] = useState(false);
if (!canModify) {
return (
-
+
- {props.variant.label}
+ {variant.label}
);
@@ -64,6 +70,7 @@ export default function VariantHeader(props: { variant: PromptVariant; canHide:
zIndex: menuOpen ? "dropdown" : stickyHeaderStyle.zIndex,
}}
borderTopWidth={1}
+ {...gridItemProps}
>
{
- e.dataTransfer.setData("text/plain", props.variant.id);
+ e.dataTransfer.setData("text/plain", variant.id);
e.currentTarget.style.opacity = "0.4";
}}
onDragEnd={(e) => {
@@ -112,8 +119,8 @@ export default function VariantHeader(props: { variant: PromptVariant; canHide:
onMouseLeave={() => setIsInputHovered(false)}
/>
diff --git a/src/server/api/routers/scenarios.router.ts b/src/server/api/routers/scenarios.router.ts
index 91f1852..c8cdd4d 100644
--- a/src/server/api/routers/scenarios.router.ts
+++ b/src/server/api/routers/scenarios.router.ts
@@ -34,22 +34,21 @@ export const scenariosRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
await requireCanModifyExperiment(input.experimentId, ctx);
- const maxSortIndex =
- (
- await prisma.testScenario.aggregate({
- where: {
- experimentId: input.experimentId,
- },
- _max: {
- sortIndex: true,
- },
- })
- )._max.sortIndex ?? 0;
+ await prisma.testScenario.updateMany({
+ where: {
+ experimentId: input.experimentId,
+ },
+ data: {
+ sortIndex: {
+ increment: 1,
+ },
+ },
+ });
const createNewScenarioAction = prisma.testScenario.create({
data: {
experimentId: input.experimentId,
- sortIndex: maxSortIndex + 1,
+ sortIndex: 0,
variableValues: input.autogenerate
? await autogenerateScenarioValues(input.experimentId)
: {},