diff --git a/package.json b/package.json index ca81000..94625b8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d15e772..eeab864 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0c8d8de..6cf0c5b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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]) diff --git a/prisma/seed.ts b/prisma/seed.ts index ebd1ad8..136ad4b 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -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", }, }, ], diff --git a/src/components/OutputsTable/EditableVariantLabel.tsx b/src/components/OutputsTable/EditableVariantLabel.tsx new file mode 100644 index 0000000..d288960 --- /dev/null +++ b/src/components/OutputsTable/EditableVariantLabel.tsx @@ -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(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 ( + + {props.variant.label} + + ); +} diff --git a/src/components/OutputsTable/ScenarioHeader.tsx b/src/components/OutputsTable/ScenarioHeader.tsx new file mode 100644 index 0000000..c273b93 --- /dev/null +++ b/src/components/OutputsTable/ScenarioHeader.tsx @@ -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; + const utils = api.useContext(); + + const [values, setValues] = useState>(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 ( + + {variableLabels.map((key) => { + return ( +