prompt reordering works

This commit is contained in:
Kyle Corbitt
2023-06-26 10:17:24 -07:00
parent e6fdd2d5c5
commit eea9d8191e
5 changed files with 158 additions and 60 deletions

View File

@@ -18,24 +18,21 @@ export default function NewVariantButton() {
}, [mutation]); }, [mutation]);
return ( return (
<Tooltip label="Add Prompt Variant" placement="right"> <Button
<Button w="100%"
w="100%" borderRadius={0}
borderRadius={0} alignItems="center"
alignItems="center" justifyContent="center"
justifyContent="center" fontWeight="normal"
fontWeight="normal" bgColor="transparent"
bgColor="transparent" _hover={{ bgColor: "gray.100" }}
_hover={{ bgColor: "gray.100" }} px={cellPadding.x}
px={cellPadding.x} onClick={onClick}
// py={cellPadding.y} height="unset"
onClick={onClick} minH={headerMinHeight}
height="unset" >
minH={headerMinHeight} <BsPlus size={24} />
> New Variant
<BsPlus size={24} /> </Button>
New Variant
</Button>
</Tooltip>
); );
} }

View File

@@ -94,11 +94,8 @@ export default function VariantConfigEditor(props: { variant: PromptVariant }) {
wordWrap: "on", wordWrap: "on",
folding: false, folding: false,
scrollbar: { scrollbar: {
vertical: "hidden",
alwaysConsumeMouseWheel: false, alwaysConsumeMouseWheel: false,
verticalScrollbarSize: 0, verticalScrollbarSize: 0,
// Don't let you scroll to an empty line
}, },
wordWrapBreakAfterCharacters: "", wordWrapBreakAfterCharacters: "",
wordWrapBreakBeforeCharacters: "", wordWrapBreakBeforeCharacters: "",
@@ -145,7 +142,7 @@ export default function VariantConfigEditor(props: { variant: PromptVariant }) {
<Box w="100%" pos="relative"> <Box w="100%" pos="relative">
<div id={editorId} style={{ height: "300px", width: "100%" }}></div> <div id={editorId} style={{ height: "300px", width: "100%" }}></div>
{isChanged && ( {isChanged && (
<HStack pos="absolute" bottom={0} right={0}> <HStack pos="absolute" bottom={2} right={2}>
<Button <Button
colorScheme="gray" colorScheme="gray"
size="sm" size="sm"

View File

@@ -1,25 +1,26 @@
import { useRef } from "react"; import { useRef, useState } from "react";
import { type PromptVariant } from "./types"; import { type PromptVariant } from "./types";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useHandledAsyncCallback } from "~/utils/hooks"; import { useHandledAsyncCallback } from "~/utils/hooks";
import { Button, HStack, Heading, Tooltip } from "@chakra-ui/react"; import { Button, HStack, Input, Icon, Tooltip } from "@chakra-ui/react"; // Changed here
import { BsX } from "react-icons/bs"; import { BsX } from "react-icons/bs";
import { RiDraggable } from "react-icons/ri";
import { cellPadding, headerMinHeight } from "../constants"; import { cellPadding, headerMinHeight } from "../constants";
export default function VariantHeader(props: { variant: PromptVariant }) { export default function VariantHeader(props: { variant: PromptVariant }) {
const utils = api.useContext(); const utils = api.useContext();
const [isDragTarget, setIsDragTarget] = useState(false);
const [label, setLabel] = useState(props.variant.label);
const labelRef = useRef<HTMLHeadingElement | null>(null);
const updateMutation = api.promptVariants.update.useMutation(); const updateMutation = api.promptVariants.update.useMutation();
const [onSaveLabel] = useHandledAsyncCallback(async () => { const [onSaveLabel] = useHandledAsyncCallback(async () => {
const newLabel = labelRef.current?.innerText; if (label && label !== props.variant.label) {
if (newLabel && newLabel !== props.variant.label) {
await updateMutation.mutateAsync({ await updateMutation.mutateAsync({
id: props.variant.id, id: props.variant.id,
updates: { label: newLabel }, updates: { label: label },
}); });
} }
}, [updateMutation, props.variant.id, props.variant.label]); }, [updateMutation, props.variant.id, props.variant.label, label]);
const hideMutation = api.promptVariants.hide.useMutation(); const hideMutation = api.promptVariants.hide.useMutation();
const [onHide] = useHandledAsyncCallback(async () => { const [onHide] = useHandledAsyncCallback(async () => {
@@ -29,36 +30,69 @@ export default function VariantHeader(props: { variant: PromptVariant }) {
await utils.promptVariants.list.invalidate(); await utils.promptVariants.list.invalidate();
}, [hideMutation, props.variant.id]); }, [hideMutation, props.variant.id]);
const reorderMutation = api.promptVariants.reorder.useMutation();
const [onReorder] = useHandledAsyncCallback(
async (e: DragEvent) => {
e.preventDefault();
console.log("onDrop");
const draggedId = e.dataTransfer.getData("text/plain");
const droppedId = props.variant.id;
if (!draggedId || !droppedId || draggedId === droppedId) return;
await reorderMutation.mutateAsync({
draggedId,
droppedId,
});
await utils.promptVariants.list.invalidate();
},
[reorderMutation, props.variant.id]
);
return ( return (
<HStack spacing={4} alignItems="center" minH={headerMinHeight}> <HStack
<Heading spacing={4}
fontWeight="bold" alignItems="center"
size="md" minH={headerMinHeight}
ref={labelRef} draggable
contentEditable onDragStart={(e) => {
suppressContentEditableWarning e.dataTransfer.setData("text/plain", props.variant.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"}
>
<Icon
as={RiDraggable}
boxSize={6}
color="gray.400"
_hover={{ color: "gray.800", cursor: "pointer" }}
/>
<Input // Changed to Input
size="sm"
value={label}
onChange={(e) => setLabel(e.target.value)}
onBlur={onSaveLabel}
borderWidth={1} borderWidth={1}
borderColor="transparent" borderColor="transparent"
fontWeight="bold"
fontSize={16}
_hover={{ borderColor: "gray.300" }} _hover={{ borderColor: "gray.300" }}
_focus={{ borderColor: "blue.500", outline: "none" }} _focus={{ borderColor: "blue.500", outline: "none" }}
onBlur={onSaveLabel}
flex={1} flex={1}
px={cellPadding.x} px={cellPadding.x}
// py={cellPadding.y} />
>
{props.variant.label}
</Heading>
<Tooltip label="Hide Variant" hasArrow> <Tooltip label="Hide Variant" hasArrow>
<Button <Button variant="ghost" colorScheme="gray" size="sm" onClick={onHide} borderRadius={0}>
variant="ghost" <Icon as={BsX} boxSize={6} />
colorScheme="gray"
size="sm"
onClick={onHide}
borderRadius={0}
// px={cellPadding.x}
// py={cellPadding.y}
>
<BsX size={24} />
</Button> </Button>
</Tooltip> </Tooltip>
</HStack> </HStack>

View File

@@ -1,13 +1,14 @@
import { RouterOutputs, api } from "~/utils/api"; import { Box, Grid, GridItem, Heading, type SystemStyleObject } from "@chakra-ui/react";
import { Scenario, type PromptVariant } from "./types";
import OutputCell from "./OutputCell";
import ScenarioEditor from "./ScenarioEditor";
import React, { useState } from "react"; import React, { useState } from "react";
import { Box, Grid, GridItem, Heading, SystemStyleObject } from "@chakra-ui/react"; import { api } from "~/utils/api";
import NewScenarioButton from "./NewScenarioButton"; import NewScenarioButton from "./NewScenarioButton";
import NewVariantButton from "./NewVariantButton"; import NewVariantButton from "./NewVariantButton";
import VariantHeader from "./VariantHeader"; import OutputCell from "./OutputCell";
import ScenarioEditor from "./ScenarioEditor";
import VariantConfigEditor from "./VariantConfigEditor"; import VariantConfigEditor from "./VariantConfigEditor";
import VariantHeader from "./VariantHeader";
import type { Scenario, PromptVariant } from "./types";
import { cellPadding } from "../constants";
const stickyHeaderStyle: SystemStyleObject = { const stickyHeaderStyle: SystemStyleObject = {
position: "sticky", position: "sticky",
@@ -76,8 +77,8 @@ export default function OutputsTable({ experimentId }: { experimentId: string |
}} }}
> >
<GridItem display="flex" alignItems="flex-end" rowSpan={2}> <GridItem display="flex" alignItems="flex-end" rowSpan={2}>
<Box sx={stickyHeaderStyle} flex={1}> <Box sx={stickyHeaderStyle} flex={1} px={cellPadding.x} py={cellPadding.y}>
<Heading size="md" fontWeight="bold"> <Heading size="sm" fontWeight="bold">
Scenario Scenario
</Heading> </Heading>
</Box> </Box>

View File

@@ -143,4 +143,73 @@ export const promptVariantsRouter = createTRPCRouter({
return newVariant; return newVariant;
}), }),
reorder: publicProcedure
.input(
z.object({
draggedId: z.string(),
droppedId: z.string(),
})
)
.mutation(async ({ input }) => {
const dragged = await prisma.promptVariant.findUnique({
where: {
id: input.draggedId,
},
});
const dropped = await prisma.promptVariant.findUnique({
where: {
id: input.droppedId,
},
});
if (!dragged || !dropped || dragged.experimentId !== dropped.experimentId) {
throw new Error(
`Prompt Variant with id ${input.draggedId} or ${input.droppedId} does not exist`
);
}
const visibleItems = await prisma.promptVariant.findMany({
where: {
experimentId: dragged.experimentId,
visible: true,
},
orderBy: {
sortIndex: "asc",
},
});
// Remove the dragged item from its current position
const orderedItems = visibleItems.filter((item) => item.id !== dragged.id);
// Find the index of the dragged item and the dropped item
const dragIndex = visibleItems.findIndex((item) => item.id === dragged.id);
const dropIndex = orderedItems.findIndex((item) => item.id === dropped.id);
// Determine the new index for the dragged item
let newIndex;
if (dragIndex < dropIndex) {
newIndex = dropIndex + 1; // Insert after the dropped item
} else {
newIndex = dropIndex; // Insert before the dropped item
}
// Insert the dragged item at the new position
orderedItems.splice(newIndex, 0, dragged);
// Now, we need to update all the items with their new sortIndex
await prisma.$transaction(
orderedItems.map((item, index) => {
return prisma.promptVariant.update({
where: {
id: item.id,
},
data: {
sortIndex: index,
},
});
})
);
}),
}); });