editing scenarios is kinda working
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
"@trpc/server": "^10.26.0",
|
||||
"dayjs": "^1.11.8",
|
||||
"dotenv": "^16.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mantine-react-table": "1.0.0-beta.13",
|
||||
"next": "^13.4.2",
|
||||
"next-auth": "^4.22.1",
|
||||
@@ -42,6 +43,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/eslint": "^8.37.0",
|
||||
"@types/lodash": "^4.14.195",
|
||||
"@types/node": "^18.16.0",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
|
||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -65,6 +65,9 @@ dependencies:
|
||||
dotenv:
|
||||
specifier: ^16.3.1
|
||||
version: 16.3.1
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
mantine-react-table:
|
||||
specifier: 1.0.0-beta.13
|
||||
version: 1.0.0-beta.13(@emotion/react@11.11.1)(@mantine/core@6.0.14)(@mantine/dates@6.0.14)(@mantine/hooks@6.0.14)(@tabler/icons-react@2.22.0)(react-dom@18.2.0)(react@18.2.0)
|
||||
@@ -94,6 +97,9 @@ devDependencies:
|
||||
'@types/eslint':
|
||||
specifier: ^8.37.0
|
||||
version: 8.37.0
|
||||
'@types/lodash':
|
||||
specifier: ^4.14.195
|
||||
version: 4.14.195
|
||||
'@types/node':
|
||||
specifier: ^18.16.0
|
||||
version: 18.16.0
|
||||
@@ -1176,6 +1182,10 @@ packages:
|
||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
dev: true
|
||||
|
||||
/@types/lodash@4.14.195:
|
||||
resolution: {integrity: sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==}
|
||||
dev: true
|
||||
|
||||
/@types/node@18.16.0:
|
||||
resolution: {integrity: sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==}
|
||||
dev: true
|
||||
@@ -2860,6 +2870,10 @@ packages:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
|
||||
/lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: false
|
||||
|
||||
/loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
@@ -26,13 +26,12 @@ model PromptVariant {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
label String
|
||||
|
||||
uiId String @default(uuid()) @db.Uuid
|
||||
config Json
|
||||
|
||||
uiId String @default(uuid()) @db.Uuid
|
||||
visible Boolean @default(true)
|
||||
sortIndex Int @default(0)
|
||||
|
||||
config Json
|
||||
|
||||
experimentId String @db.Uuid
|
||||
experiment Experiment @relation(fields: [experimentId], references: [id])
|
||||
|
||||
@@ -46,11 +45,12 @@ model PromptVariant {
|
||||
model TestScenario {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
|
||||
variableValues Json
|
||||
|
||||
uiId String @default(uuid()) @db.Uuid
|
||||
visible Boolean @default(true)
|
||||
sortIndex Int @default(0)
|
||||
|
||||
variableValues Json
|
||||
|
||||
experimentId String @db.Uuid
|
||||
experiment Experiment @relation(fields: [experimentId], references: [id])
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ await prisma.templateVariable.createMany({
|
||||
data: [
|
||||
{
|
||||
experimentId,
|
||||
label: "input",
|
||||
label: "state",
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -83,7 +83,13 @@ await prisma.testScenario.createMany({
|
||||
{
|
||||
experimentId,
|
||||
variableValues: {
|
||||
state: "Georgia",
|
||||
state: "California",
|
||||
},
|
||||
},
|
||||
{
|
||||
experimentId,
|
||||
variableValues: {
|
||||
state: "Utah",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
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>
|
||||
<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