editing scenarios is kinda working
This commit is contained in:
27
src/components/OutputsTable/EditableVariantLabel.tsx
Normal file
27
src/components/OutputsTable/EditableVariantLabel.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useRef } from "react";
|
||||
import { Title } from "@mantine/core";
|
||||
import { type PromptVariant } from "./types";
|
||||
import { api } from "~/utils/api";
|
||||
import { useHandledAsyncCallback } from "~/utils/hooks";
|
||||
|
||||
export default function EditableVariantLabel(props: { variant: PromptVariant }) {
|
||||
const labelRef = useRef<HTMLHeadingElement | null>(null);
|
||||
|
||||
const mutation = api.promptVariants.update.useMutation();
|
||||
|
||||
const [onBlur] = useHandledAsyncCallback(async () => {
|
||||
const newLabel = labelRef.current?.innerText;
|
||||
if (newLabel && newLabel !== props.variant.label) {
|
||||
await mutation.mutateAsync({
|
||||
id: props.variant.id,
|
||||
updates: { label: newLabel },
|
||||
});
|
||||
}
|
||||
}, [mutation, props.variant.id, props.variant.label]);
|
||||
|
||||
return (
|
||||
<Title order={4} ref={labelRef} contentEditable suppressContentEditableWarning onBlur={onBlur}>
|
||||
{props.variant.label}
|
||||
</Title>
|
||||
);
|
||||
}
|
||||
65
src/components/OutputsTable/ScenarioHeader.tsx
Normal file
65
src/components/OutputsTable/ScenarioHeader.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { api } from "~/utils/api";
|
||||
import { isEqual } from "lodash";
|
||||
import { PromptVariant, Scenario } from "./types";
|
||||
import { Badge, Button, Group, Stack, TextInput, Textarea, Tooltip } from "@mantine/core";
|
||||
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function ScenarioHeader({ scenario }: { scenario: Scenario }) {
|
||||
const savedValues = scenario.variableValues as Record<string, string>;
|
||||
const utils = api.useContext();
|
||||
|
||||
const [values, setValues] = useState<Record<string, string>>(savedValues);
|
||||
|
||||
const experiment = useExperiment();
|
||||
|
||||
const variableLabels = experiment.data?.TemplateVariable.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]);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
{variableLabels.map((key) => {
|
||||
return (
|
||||
<Textarea
|
||||
key={key}
|
||||
label={key}
|
||||
value={values[key] ?? ""}
|
||||
onChange={(e) => {
|
||||
setValues((prev) => ({ ...prev, [key]: e.target.value }));
|
||||
}}
|
||||
autosize
|
||||
rows={1}
|
||||
maxRows={20}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{hasChanged && (
|
||||
<Group spacing={4} position="right">
|
||||
<Button
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setValues(savedValues);
|
||||
}}
|
||||
color="gray"
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button size="xs" onClick={onSave}>
|
||||
Save
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Box, Button, Group, Stack, Title } from "@mantine/core";
|
||||
import { Box, Button, Group, Stack, Title, Tooltip } from "@mantine/core";
|
||||
import { useMonaco } from "@monaco-editor/react";
|
||||
import { useRef, useEffect, useState, useCallback } from "react";
|
||||
import { set } from "zod";
|
||||
import { useHandledAsyncCallback } from "~/utils/hooks";
|
||||
import { useHandledAsyncCallback, useModifierKeyLabel } from "~/utils/hooks";
|
||||
|
||||
let isThemeDefined = false;
|
||||
|
||||
@@ -16,6 +16,8 @@ export default function VariantConfigEditor(props: {
|
||||
const [isChanged, setIsChanged] = useState(false);
|
||||
const savedConfigRef = useRef(props.savedConfig);
|
||||
|
||||
const modifierKey = useModifierKeyLabel();
|
||||
|
||||
const checkForChanges = useCallback(() => {
|
||||
if (!editorRef.current) return;
|
||||
const currentConfig = editorRef.current.getValue();
|
||||
@@ -104,9 +106,11 @@ export default function VariantConfigEditor(props: {
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button size="xs" onClick={onSave}>
|
||||
Save
|
||||
</Button>
|
||||
<Tooltip label={`${modifierKey} + Enter`} withArrow>
|
||||
<Button size="xs" onClick={onSave}>
|
||||
Save
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { api } from "~/utils/api";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { type JSONSerializable } from "~/server/types";
|
||||
import VariantConfigEditor from "./VariantConfigEditor";
|
||||
import EditableVariantLabel from "./EditableVariantLabel";
|
||||
|
||||
export default function VariantHeader({ variant }: { variant: PromptVariant }) {
|
||||
const replaceWithConfig = api.promptVariants.replaceWithConfig.useMutation();
|
||||
@@ -39,15 +40,14 @@ export default function VariantHeader({ variant }: { variant: PromptVariant }) {
|
||||
});
|
||||
|
||||
await utils.promptVariants.list.invalidate();
|
||||
|
||||
// TODO: invalidate the variants query
|
||||
},
|
||||
[variant.id, replaceWithConfig, utils.promptVariants.list]
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack w="100%">
|
||||
<Title order={4}>{variant.label}</Title>
|
||||
// title="" to hide the title text that mantine-react-table likes to add
|
||||
<Stack w="100%" title="">
|
||||
<EditableVariantLabel variant={variant} />
|
||||
<VariantConfigEditor savedConfig={JSON.stringify(variant.config, null, 2)} onSave={onSave} />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { RouterOutputs, api } from "~/utils/api";
|
||||
import { PromptVariant } from "./types";
|
||||
import VariantHeader from "./VariantHeader";
|
||||
import OutputCell from "./OutputCell";
|
||||
import ScenarioHeader from "./ScenarioHeader";
|
||||
|
||||
type CellData = {
|
||||
variant: PromptVariant;
|
||||
@@ -15,11 +16,6 @@ type TableRow = {
|
||||
} & Record<string, CellData>;
|
||||
|
||||
export default function OutputsTable({ experimentId }: { experimentId: string | undefined }) {
|
||||
const experiment = api.experiments.get.useQuery(
|
||||
{ id: experimentId as string },
|
||||
{ enabled: !!experimentId }
|
||||
);
|
||||
|
||||
const variants = api.promptVariants.list.useQuery(
|
||||
{ experimentId: experimentId as string },
|
||||
{ enabled: !!experimentId }
|
||||
@@ -37,9 +33,7 @@ export default function OutputsTable({ experimentId }: { experimentId: string |
|
||||
header: "Scenario",
|
||||
enableColumnDragging: false,
|
||||
size: 200,
|
||||
Cell: ({ row }) => {
|
||||
return <div>{JSON.stringify(row.original.scenario.variableValues)}</div>;
|
||||
},
|
||||
Cell: ({ row }) => <ScenarioHeader scenario={row.original.scenario} />,
|
||||
},
|
||||
...(variants.data?.map(
|
||||
(variant): MRT_ColumnDef<TableRow> => ({
|
||||
@@ -91,7 +85,6 @@ export default function OutputsTable({ experimentId }: { experimentId: string |
|
||||
"& .mantine-TableHeadCell-Content": {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
// display: "flex",
|
||||
|
||||
"& .mantine-TableHeadCell-Content-Actions": {
|
||||
alignSelf: "flex-start",
|
||||
|
||||
@@ -8,6 +8,17 @@ export const experimentsRouter = createTRPCRouter({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
include: {
|
||||
TemplateVariable: {
|
||||
orderBy: {
|
||||
createdAt: "asc",
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
label: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, publicProcedure, protectedProcedure } from "~/server/api/trpc";
|
||||
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
||||
import { prisma } from "~/server/db";
|
||||
import { JSONSerializable, OpenAIChatConfig } from "~/server/types";
|
||||
import { OpenAIChatConfig } from "~/server/types";
|
||||
|
||||
export const promptVariantsRouter = createTRPCRouter({
|
||||
list: publicProcedure.input(z.object({ experimentId: z.string() })).query(async ({ input }) => {
|
||||
@@ -16,6 +16,34 @@ export const promptVariantsRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
|
||||
update: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
updates: z.object({
|
||||
label: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const existing = await prisma.promptVariant.findUnique({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
throw new Error(`Prompt Variant with id ${input.id} does not exist`);
|
||||
}
|
||||
|
||||
return await prisma.promptVariant.update({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
data: input.updates,
|
||||
});
|
||||
}),
|
||||
|
||||
replaceWithConfig: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
@@ -41,14 +69,6 @@ export const promptVariantsRouter = createTRPCRouter({
|
||||
throw new Error(`Prompt Variant with id ${input.id} does not exist`);
|
||||
}
|
||||
|
||||
console.log("new config", {
|
||||
experimentId: existing.experimentId,
|
||||
label: existing.label,
|
||||
sortIndex: existing.sortIndex,
|
||||
uiId: existing.uiId,
|
||||
config: parsedConfig,
|
||||
});
|
||||
|
||||
// Create a duplicate with only the config changed
|
||||
const newVariant = await prisma.promptVariant.create({
|
||||
data: {
|
||||
|
||||
@@ -7,7 +7,51 @@ export const scenariosRouter = createTRPCRouter({
|
||||
return await prisma.testScenario.findMany({
|
||||
where: {
|
||||
experimentId: input.experimentId,
|
||||
visible: true,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
replaceWithValues: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
values: z.record(z.string()),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const existing = await prisma.testScenario.findUnique({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
throw new Error(`Scenario with id ${input.id} does not exist`);
|
||||
}
|
||||
|
||||
const newScenario = await prisma.testScenario.create({
|
||||
data: {
|
||||
experimentId: existing.experimentId,
|
||||
sortIndex: existing.sortIndex,
|
||||
variableValues: input.values,
|
||||
uiId: existing.uiId,
|
||||
},
|
||||
});
|
||||
|
||||
// Hide the old scenario
|
||||
await prisma.testScenario.updateMany({
|
||||
where: {
|
||||
uiId: existing.uiId,
|
||||
id: {
|
||||
not: newScenario.id,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
visible: false,
|
||||
},
|
||||
});
|
||||
|
||||
return newScenario;
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user