lots of changes on making new experiments work

This commit is contained in:
Kyle Corbitt
2023-06-26 18:03:26 -07:00
parent dbc61b8672
commit 0646fc0ba3
17 changed files with 544 additions and 188 deletions

176
@types/nextjs-routes.d.ts vendored Normal file
View File

@@ -0,0 +1,176 @@
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
// This file will be automatically regenerated when your Next.js server is running.
// nextjs-routes version: 2.0.1
/* eslint-disable */
// prettier-ignore
declare module "nextjs-routes" {
import type {
GetServerSidePropsContext as NextGetServerSidePropsContext,
GetServerSidePropsResult as NextGetServerSidePropsResult
} from "next";
export type Route =
| DynamicRoute<"/api/auth/[...nextauth]", { "nextauth": string[] }>
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
| DynamicRoute<"/experiments/[id]", { "id": string }>;
interface StaticRoute<Pathname> {
pathname: Pathname;
query?: Query | undefined;
hash?: string | null | undefined;
}
interface DynamicRoute<Pathname, Parameters> {
pathname: Pathname;
query: Parameters & Query;
hash?: string | null | undefined;
}
interface Query {
[key: string]: string | string[] | undefined;
};
export type RoutedQuery<P extends Route["pathname"]> = Extract<
Route,
{ pathname: P }
>["query"];
export type Locale =
| "en";
/**
* A typesafe utility function for generating paths in your application.
*
* route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
*/
export declare function route(r: Route): string;
/**
* Nearly identical to GetServerSidePropsContext from next, but further narrows
* types based on nextjs-route's route data.
*/
export type GetServerSidePropsContext<
Pathname extends Route["pathname"] = Route["pathname"],
Preview extends NextGetServerSidePropsContext["previewData"] = NextGetServerSidePropsContext["previewData"]
> = Omit<NextGetServerSidePropsContext, 'params' | 'query' | 'defaultLocale' | 'locale' | 'locales'> & {
params: Extract<Route, { pathname: Pathname }>["query"];
query: Query;
defaultLocale: "en";
locale: Locale;
locales: [
"en"
];
};
/**
* Nearly identical to GetServerSideProps from next, but further narrows
* types based on nextjs-route's route data.
*/
export type GetServerSideProps<
Props extends { [key: string]: any } = { [key: string]: any },
Pathname extends Route["pathname"] = Route["pathname"],
Preview extends NextGetServerSideProps["previewData"] = NextGetServerSideProps["previewData"]
> = (
context: GetServerSidePropsContext<Pathname, Preview>
) => Promise<NextGetServerSidePropsResult<Props>>
}
// prettier-ignore
declare module "next/link" {
import type { Route } from "nextjs-routes";
import type { LinkProps as NextLinkProps } from "next/dist/client/link";
import type {
AnchorHTMLAttributes,
DetailedReactHTMLElement,
MouseEventHandler,
PropsWithChildren,
} from "react";
export * from "next/dist/client/link";
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
export interface LinkProps
extends Omit<NextLinkProps, "href" | "locale">,
AnchorHTMLAttributes<HTMLAnchorElement> {
href: Route | StaticRoute | Omit<Route, "pathname">
locale?: Locale | false;
}
type LinkReactElement = DetailedReactHTMLElement<
{
onMouseEnter?: MouseEventHandler<Element> | undefined;
onClick: MouseEventHandler;
href?: string | undefined;
ref?: any;
},
HTMLElement
>;
declare function Link(props: PropsWithChildren<LinkProps>): LinkReactElement;
export default Link;
}
// prettier-ignore
declare module "next/router" {
import type { Locale, Route, RoutedQuery } from "nextjs-routes";
import type { NextRouter as Router } from "next/dist/client/router";
export * from "next/dist/client/router";
export { default } from "next/dist/client/router";
type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
locale?: Locale | false;
}
type PathnameAndQuery<Pathname> = Required<
Pick<Extract<Route, { pathname: Pathname }>, "pathname" | "query">
>;
type AutomaticStaticOptimizedQuery<PaQ> = Omit<PaQ, "query"> & {
query: Partial<PaQ["query"]>;
};
type BaseRouter<PaQ> =
| ({ isReady: false } & AutomaticStaticOptimizedQuery<PaQ>)
| ({ isReady: true } & PaQ);
export type NextRouter<P extends Route["pathname"] = Route["pathname"]> =
BaseRouter<PathnameAndQuery<P>> &
Omit<
Router,
| "defaultLocale"
| "domainLocales"
| "isReady"
| "locale"
| "locales"
| "pathname"
| "push"
| "query"
| "replace"
| "route"
> & {
defaultLocale: "en";
domainLocales?: undefined;
locale: Locale;
locales: [
"en"
];
push(
url: Route | StaticRoute | Omit<Route, "pathname">,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
replace(
url: Route | StaticRoute | Omit<Route, "pathname">,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
route: P;
};
export function useRouter<P extends Route["pathname"]>(): NextRouter<P>;
}

View File

@@ -1,3 +1,5 @@
import nextRoutes from "nextjs-routes/config";
/** /**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds. * for Docker builds.
@@ -20,4 +22,4 @@ const config = {
}, },
}; };
export default config; export default nextRoutes()(config);

View File

@@ -33,6 +33,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"next": "^13.4.2", "next": "^13.4.2",
"next-auth": "^4.22.1", "next-auth": "^4.22.1",
"nextjs-routes": "^2.0.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-icons": "^4.10.1", "react-icons": "^4.10.1",

68
pnpm-lock.yaml generated
View File

@@ -68,6 +68,9 @@ dependencies:
next-auth: next-auth:
specifier: ^4.22.1 specifier: ^4.22.1
version: 4.22.1(next@13.4.2)(react-dom@18.2.0)(react@18.2.0) version: 4.22.1(next@13.4.2)(react-dom@18.2.0)(react@18.2.0)
nextjs-routes:
specifier: ^2.0.1
version: 2.0.1(next@13.4.2)
react: react:
specifier: 18.2.0 specifier: 18.2.0
version: 18.2.0 version: 18.2.0
@@ -2256,6 +2259,14 @@ packages:
color-convert: 2.0.1 color-convert: 2.0.1
dev: true dev: true
/anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
dev: false
/argparse@2.0.1: /argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true dev: true
@@ -2364,6 +2375,11 @@ packages:
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
dev: true dev: true
/binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: false
/bplist-parser@0.2.0: /bplist-parser@0.2.0:
resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==}
engines: {node: '>= 5.10.0'} engines: {node: '>= 5.10.0'}
@@ -2383,7 +2399,6 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
fill-range: 7.0.1 fill-range: 7.0.1
dev: true
/buffer-from@0.1.2: /buffer-from@0.1.2:
resolution: {integrity: sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==} resolution: {integrity: sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==}
@@ -2438,6 +2453,21 @@ packages:
supports-color: 7.2.0 supports-color: 7.2.0
dev: true dev: true
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.3
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
dev: false
/client-only@0.0.1: /client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false dev: false
@@ -3117,7 +3147,6 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
dev: true
/find-root@1.1.0: /find-root@1.1.0:
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
@@ -3246,7 +3275,6 @@ packages:
engines: {node: '>= 6'} engines: {node: '>= 6'}
dependencies: dependencies:
is-glob: 4.0.3 is-glob: 4.0.3
dev: true
/glob-parent@6.0.2: /glob-parent@6.0.2:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
@@ -3466,6 +3494,13 @@ packages:
has-bigints: 1.0.2 has-bigints: 1.0.2
dev: true dev: true
/is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.2.0
dev: false
/is-boolean-object@1.1.2: /is-boolean-object@1.1.2:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -3506,14 +3541,12 @@ packages:
/is-extglob@2.1.1: /is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true
/is-glob@4.0.3: /is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dependencies: dependencies:
is-extglob: 2.1.1 is-extglob: 2.1.1
dev: true
/is-inside-container@1.0.0: /is-inside-container@1.0.0:
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
@@ -3538,7 +3571,6 @@ packages:
/is-number@7.0.0: /is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'} engines: {node: '>=0.12.0'}
dev: true
/is-path-inside@3.0.3: /is-path-inside@3.0.3:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
@@ -3867,6 +3899,21 @@ packages:
- babel-plugin-macros - babel-plugin-macros
dev: false dev: false
/nextjs-routes@2.0.1(next@13.4.2):
resolution: {integrity: sha512-pBGRm6uR44zwUjWWYn6+gwz08BhBbqUYlIzsbNHAh1TWohHYKWFaa2YVsj8BxEo726MZYg87OJPnHpaaY1ia0w==}
hasBin: true
peerDependencies:
next: '*'
dependencies:
chokidar: 3.5.3
next: 13.4.2(react-dom@18.2.0)(react@18.2.0)
dev: false
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: false
/npm-run-path@4.0.1: /npm-run-path@4.0.1:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -4083,7 +4130,6 @@ packages:
/picomatch@2.3.1: /picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
dev: true
/postcss@8.4.14: /postcss@8.4.14:
resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==} resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==}
@@ -4305,6 +4351,13 @@ packages:
util-deprecate: 1.0.2 util-deprecate: 1.0.2
dev: false dev: false
/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
dev: false
/regenerator-runtime@0.13.11: /regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
@@ -4636,7 +4689,6 @@ packages:
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
dev: true
/toggle-selection@1.0.6: /toggle-selection@1.0.6:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}

View File

@@ -15,6 +15,8 @@ model Experiment {
id String @id @default(uuid()) @db.Uuid id String @id @default(uuid()) @db.Uuid
label String label String
sortIndex Int @default(0)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
TemplateVariable TemplateVariable[] TemplateVariable TemplateVariable[]
@@ -33,7 +35,7 @@ model PromptVariant {
sortIndex Int @default(0) sortIndex Int @default(0)
experimentId String @db.Uuid experimentId String @db.Uuid
experiment Experiment @relation(fields: [experimentId], references: [id]) experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -52,7 +54,7 @@ model TestScenario {
sortIndex Int @default(0) sortIndex Int @default(0)
experimentId String @db.Uuid experimentId String @db.Uuid
experiment Experiment @relation(fields: [experimentId], references: [id]) experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -65,7 +67,7 @@ model TemplateVariable {
label String label String
experimentId String @db.Uuid experimentId String @db.Uuid
experiment Experiment @relation(fields: [experimentId], references: [id]) experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -78,10 +80,10 @@ model ModelOutput {
output Json output Json
promptVariantId String @db.Uuid promptVariantId String @db.Uuid
promptVariant PromptVariant @relation(fields: [promptVariantId], references: [id]) promptVariant PromptVariant @relation(fields: [promptVariantId], references: [id], onDelete: Cascade)
testScenarioId String @db.Uuid testScenarioId String @db.Uuid
testScenario TestScenario @relation(fields: [testScenarioId], references: [id]) testScenario TestScenario @relation(fields: [testScenarioId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt

View File

@@ -1,14 +1,18 @@
import { prisma } from "~/server/db"; import { prisma } from "~/server/db";
const experimentId = "11111111-1111-1111-1111-111111111111"; const experimentId = "11111111-1111-1111-1111-111111111111";
const experiment = await prisma.experiment.upsert({
// Delete the existing experiment
await prisma.experiment.delete({
where: { where: {
id: experimentId, id: experimentId,
}, },
update: {}, });
create: {
const experiment = await prisma.experiment.create({
data: {
id: experimentId, id: experimentId,
label: "Quick Start", label: "Country Capitals Example",
}, },
}); });
@@ -34,7 +38,7 @@ const resp = await prisma.promptVariant.createMany({
sortIndex: 0, sortIndex: 0,
config: { config: {
model: "gpt-3.5-turbo", model: "gpt-3.5-turbo",
messages: [{ role: "user", content: "What is the capitol of {{state}}?" }], messages: [{ role: "user", content: "What is the capital of {{country}}?" }],
temperature: 0, temperature: 0,
}, },
}, },
@@ -44,7 +48,12 @@ const resp = await prisma.promptVariant.createMany({
sortIndex: 1, sortIndex: 1,
config: { config: {
model: "gpt-3.5-turbo", model: "gpt-3.5-turbo",
messages: [{ role: "user", content: "What is the capitol of the US state {{state}}?" }], messages: [
{
role: "user",
content: "What is the capital of {{country}}? Only return the city name.",
},
],
temperature: 0, temperature: 0,
}, },
}, },
@@ -61,7 +70,7 @@ await prisma.templateVariable.createMany({
data: [ data: [
{ {
experimentId, experimentId,
label: "state", label: "country",
}, },
], ],
}); });
@@ -78,21 +87,21 @@ await prisma.testScenario.createMany({
experimentId, experimentId,
sortIndex: 0, sortIndex: 0,
variableValues: { variableValues: {
state: "Washington", country: "USA",
}, },
}, },
{ {
experimentId, experimentId,
sortIndex: 1, sortIndex: 1,
variableValues: { variableValues: {
state: "California", country: "Spain",
}, },
}, },
{ {
experimentId, experimentId,
sortIndex: 2, sortIndex: 2,
variableValues: { variableValues: {
state: "Utah", country: "Chile",
}, },
}, },
], ],

View File

@@ -2,7 +2,6 @@ import { api } from "~/utils/api";
import { PromptVariant, Scenario } from "./types"; import { PromptVariant, Scenario } from "./types";
import { Center, Spinner, Text } from "@chakra-ui/react"; import { Center, Spinner, Text } from "@chakra-ui/react";
import { useExperiment } from "~/utils/hooks"; import { useExperiment } from "~/utils/hooks";
import { JSONSerializable } from "~/server/types";
import { cellPadding } from "../constants"; import { cellPadding } from "../constants";
const CellShell = ({ children }: { children: React.ReactNode }) => ( const CellShell = ({ children }: { children: React.ReactNode }) => (
@@ -19,12 +18,11 @@ export default function OutputCell({
variant: PromptVariant; variant: PromptVariant;
}) { }) {
const experiment = useExperiment(); const experiment = useExperiment();
const vars = api.templateVars.list.useQuery({ experimentId: experiment.data?.id ?? "" }).data;
const experimentVariables = experiment.data?.TemplateVariable.map((v) => v.label) ?? [];
const scenarioVariables = scenario.variableValues as Record<string, string>; const scenarioVariables = scenario.variableValues as Record<string, string>;
const templateHasVariables = const templateHasVariables =
experimentVariables.length === 0 || vars?.length === 0 || vars?.some((v) => scenarioVariables[v.label] !== undefined);
experimentVariables.some((v) => scenarioVariables[v] !== undefined);
let disabledReason: string | null = null; let disabledReason: string | null = null;
@@ -41,6 +39,8 @@ export default function OutputCell({
{ enabled: disabledReason === null } { enabled: disabledReason === null }
); );
if (!vars) return;
if (disabledReason) if (disabledReason)
return ( return (
<CellShell> <CellShell>

View File

@@ -25,8 +25,9 @@ export default function ScenarioEditor({
const [values, setValues] = useState<Record<string, string>>(savedValues); const [values, setValues] = useState<Record<string, string>>(savedValues);
const experiment = useExperiment(); const experiment = useExperiment();
const vars = api.templateVars.list.useQuery({ experimentId: experiment.data?.id ?? "" }).data;
const variableLabels = experiment.data?.TemplateVariable.map((v) => v.label) ?? []; const variableLabels = vars?.map((v) => v.label) ?? [];
const hasChanged = !isEqual(savedValues, values); const hasChanged = !isEqual(savedValues, values);
@@ -113,58 +114,69 @@ export default function ScenarioEditor({
_hover={{ color: "gray.800", cursor: "pointer" }} _hover={{ color: "gray.800", cursor: "pointer" }}
/> />
</Stack> </Stack>
<Stack> {variableLabels.length === 0 ? (
{variableLabels.map((key) => { <Box color="gray.500">No scenario variables configured</Box>
const value = values[key] ?? ""; ) : (
const layoutDirection = value.length > 20 ? "column" : "row"; <Stack>
return ( {variableLabels.map((key) => {
<Flex const value = values[key] ?? "";
key={key} const layoutDirection = value.length > 20 ? "column" : "row";
direction={layoutDirection} return (
alignItems={layoutDirection === "column" ? "flex-start" : "center"} <Flex
flexWrap="wrap" key={key}
> direction={layoutDirection}
<Box bgColor="blue.100" color="blue.600" px={2} fontSize="xs" fontWeight="bold"> alignItems={layoutDirection === "column" ? "flex-start" : "center"}
{key} flexWrap="wrap"
</Box> >
<Textarea <Box bgColor="blue.100" color="blue.600" px={2} fontSize="xs" fontWeight="bold">
px={2} {key}
py={1} </Box>
placeholder="empty" <Textarea
value={value} px={2}
onChange={(e) => { py={1}
setValues((prev) => ({ ...prev, [key]: e.target.value })); placeholder="empty"
borderRadius="sm"
value={value}
onChange={(e) => {
setValues((prev) => ({ ...prev, [key]: e.target.value }));
}}
onKeyDown={(e) => {
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
onSave();
}
}}
resize="none"
overflow="hidden"
minRows={1}
minH="unset"
as={ResizeTextarea}
flex={layoutDirection === "row" ? 1 : undefined}
borderColor={hasChanged ? "blue.300" : "transparent"}
_hover={{ borderColor: "gray.300" }}
_focus={{ borderColor: "blue.500", outline: "none", bg: "white" }}
/>
</Flex>
);
})}
{hasChanged && (
<HStack justify="right">
<Button
size="sm"
onMouseDown={() => {
setValues(savedValues);
}} }}
resize="none" colorScheme="gray"
overflow="hidden" >
minRows={1} Reset
minH="unset" </Button>
as={ResizeTextarea} <Button size="sm" onMouseDown={onSave} colorScheme="blue">
flex={layoutDirection === "row" ? 1 : undefined} Save
borderColor={hasChanged ? "blue.300" : "transparent"} </Button>
_hover={{ borderColor: "gray.300" }} </HStack>
_focus={{ borderColor: "blue.500", outline: "none", bg: "white" }} )}
/> </Stack>
</Flex> )}
);
})}
{hasChanged && (
<HStack justify="right">
<Button
size="sm"
onMouseDown={() => {
setValues(savedValues);
}}
colorScheme="gray"
>
Reset
</Button>
<Button size="sm" onMouseDown={onSave} colorScheme="blue">
Save
</Button>
</HStack>
)}
</Stack>
</HStack> </HStack>
); );
} }

View File

@@ -1,4 +1,4 @@
import { Box, Grid, GridItem, Heading, type SystemStyleObject } from "@chakra-ui/react"; import { Grid, GridItem, type SystemStyleObject } from "@chakra-ui/react";
import React, { useState } from "react"; import React, { useState } from "react";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import NewScenarioButton from "./NewScenarioButton"; import NewScenarioButton from "./NewScenarioButton";
@@ -8,7 +8,6 @@ import ScenarioEditor from "./ScenarioEditor";
import VariantConfigEditor from "./VariantConfigEditor"; import VariantConfigEditor from "./VariantConfigEditor";
import VariantHeader from "./VariantHeader"; import VariantHeader from "./VariantHeader";
import type { Scenario, PromptVariant } from "./types"; import type { Scenario, PromptVariant } from "./types";
import { cellPadding } from "../constants";
import ScenarioHeader from "~/server/ScenarioHeader"; import ScenarioHeader from "~/server/ScenarioHeader";
const stickyHeaderStyle: SystemStyleObject = { const stickyHeaderStyle: SystemStyleObject = {

View File

@@ -1,19 +0,0 @@
import { Box, Flex } from "@chakra-ui/react";
import Head from "next/head";
export default function AppNav(props: { children: React.ReactNode; title?: string }) {
return (
<Flex minH="100vh">
<Head>
<title>{props.title ? `${props.title} | Prompt Lab` : "Prompt Lab"}</title>
</Head>
{/* Placeholder for now */}
<Box bgColor="gray.100" flexShrink={0} width="200px">
Nav Sidebar
</Box>
<Box flex={1} overflowX="auto" overflowY="auto" h="100vh">
{props.children}
</Box>
</Flex>
);
}

View File

@@ -0,0 +1,67 @@
import { Box, Flex, Stack, Button, Heading, VStack, Icon, HStack } from "@chakra-ui/react";
import Head from "next/head";
import { api } from "~/utils/api";
import { BsPlusSquare } from "react-icons/bs";
import { RiFlaskLine } from "react-icons/ri";
import { useRouter } from "next/router";
import Link from "next/link";
import { useHandledAsyncCallback } from "~/utils/hooks";
const NavButton = ({ label, onClick }) => {
return (
<Button
variant="ghost"
justifyContent="start"
onClick={onClick}
w="full"
leftIcon={<BsPlusSquare />}
>
{label}
</Button>
);
};
export default function AppShell(props: { children: React.ReactNode; title?: string }) {
const experiments = api.experiments.list.useQuery();
const router = useRouter();
const utils = api.useContext();
const createMutation = api.experiments.create.useMutation();
const [createExperiment] = useHandledAsyncCallback(async () => {
const newExperiment = await createMutation.mutateAsync({ label: "New Experiment" });
await utils.experiments.list.invalidate();
await router.push({ pathname: "/experiments/[id]", query: { id: newExperiment.id } });
}, [createMutation, router]);
return (
<Flex minH="100vh">
<Head>
<title>{props.title ? `${props.title} | Prompt Lab` : "Prompt Lab"}</title>
</Head>
<Box bgColor="gray.100" flexShrink={0} width="220px" p={8}>
<VStack align="start" spacing={4}>
<HStack align="center" spacing={2}>
<Icon as={RiFlaskLine} boxSize={6} />
<Heading size="sm" textAlign="center">
Experiments
</Heading>
</HStack>
{experiments?.data?.map((exp) => (
<Box
key={exp.id}
as={Link}
href={{ pathname: "/experiments/[id]", query: { id: exp.id } }}
justifyContent="start"
>
{exp.label}
</Box>
))}
<NavButton label="New Experiment" onClick={createExperiment} />
</VStack>
</Box>
<Box flex={1} overflowX="auto" overflowY="auto" h="100vh">
{props.children}
</Box>
</Flex>
);
}

View File

@@ -1,7 +1,7 @@
import { Box, Center } from "@chakra-ui/react"; import { Box, Center } from "@chakra-ui/react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import OutputsTable from "~/components/OutputsTable"; import OutputsTable from "~/components/OutputsTable";
import AppNav from "~/components/nav/AppNav"; import AppShell from "~/components/nav/AppShell";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
export default function Experiment() { export default function Experiment() {
@@ -14,19 +14,19 @@ export default function Experiment() {
if (!experiment.isLoading && !experiment.data) { if (!experiment.isLoading && !experiment.data) {
return ( return (
<AppNav title="Experiment not found"> <AppShell title="Experiment not found">
<Center h="100vh"> <Center h="100vh">
<div>Experiment not found 😕</div> <div>Experiment not found 😕</div>
</Center> </Center>
</AppNav> </AppShell>
); );
} }
return ( return (
<AppNav title={experiment.data?.label}> <AppShell title={experiment.data?.label}>
<Box minH="100vh" mb={50}> <Box minH="100vh" mb={50}>
<OutputsTable experimentId={router.query.id as string | undefined} /> <OutputsTable experimentId={router.query.id as string | undefined} />
</Box> </Box>
</AppNav> </AppShell>
); );
} }

View File

@@ -1,63 +1,62 @@
import { Text, Box, Button, HStack, Heading, Icon, Input, Stack, Code } from "@chakra-ui/react"; import { Text, Box, Button, HStack, Heading, Icon, Input, Stack, Code } from "@chakra-ui/react";
import { useState } from "react"; import { useState } from "react";
import { BsCheck, BsChevronDown, BsX } from "react-icons/bs"; import { BsCheck, BsChevronDown, BsChevronUp, BsX } from "react-icons/bs";
import { cellPadding } from "~/components/constants"; import { cellPadding } from "~/components/constants";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks"; import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
export default function ScenarioHeader() { export default function ScenarioHeader() {
const experiment = useExperiment(); const experiment = useExperiment();
const vars =
api.templateVars.list.useQuery({ experimentId: experiment.data?.id ?? "" }).data ?? [];
const initialVariables = experiment.data?.TemplateVariable ?? [];
const [variables, setVariables] = useState<string[]>(initialVariables.map((v) => v.label));
const [newVariable, setNewVariable] = useState<string>(""); const [newVariable, setNewVariable] = useState<string>("");
const newVarIsValid = newVariable.length > 0 && !vars.map((v) => v.label).includes(newVariable);
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
const utils = api.useContext(); const utils = api.useContext();
const setVarsMutation = api.experiments.setTemplateVariables.useMutation(); const addVarMutation = api.templateVars.create.useMutation();
const [onSave] = useHandledAsyncCallback(async () => { const [onAddVar] = useHandledAsyncCallback(async () => {
if (!experiment.data?.id) return; if (!experiment.data?.id) return;
setEditing(false); if (!newVarIsValid) return;
await setVarsMutation.mutateAsync({ await addVarMutation.mutateAsync({
id: experiment.data.id, experimentId: experiment.data.id,
labels: variables, label: newVariable,
}); });
await utils.experiments.get.invalidate(); await utils.templateVars.list.invalidate();
}, [setVarsMutation, experiment.data?.id, variables]); setNewVariable("");
}, [addVarMutation, experiment.data?.id, newVarIsValid, newVariable]);
const deleteMutation = api.templateVars.delete.useMutation();
const [onDeleteVar] = useHandledAsyncCallback(async (id: string) => {
await deleteMutation.mutateAsync({ id });
await utils.templateVars.list.invalidate();
}, []);
return ( return (
<Stack flex={1} px={cellPadding.x} py={cellPadding.y}> <Stack flex={1} px={cellPadding.x} py={cellPadding.y}>
<HStack> <HStack>
<Heading size="sm" fontWeight="bold" flex={1}> <Heading size="sm" fontWeight="bold" flex={1}>
Scenario Scenario Vars
</Heading> </Heading>
{editing ? ( {
<HStack>
<Button size="xs" colorScheme="gray" onClick={() => setEditing(false)}>
Cancel
</Button>
<Button size="xs" colorScheme="blue" onClick={onSave}>
Save
</Button>
</HStack>
) : (
<Button <Button
size="xs" size="xs"
variant="outline" variant="outline"
colorScheme="blue" colorScheme="blue"
rightIcon={<Icon as={BsChevronDown} />} rightIcon={<Icon as={editing ? BsCheck : BsChevronDown} />}
onClick={() => setEditing(true)} onClick={() => setEditing((editing) => !editing)}
> >
Configure {editing ? "Finish" : "Configure"}
</Button> </Button>
)} }
</HStack> </HStack>
{editing && ( {editing && (
<Stack spacing={2} pt={2}> <Stack spacing={2} pt={2}>
<Text fontSize="sm"> <Text fontSize="sm">
You can use variables in your prompt text inside <Code>{`{{curly_braces}}`}</Code>. Define scenario variables. Reference them from your prompt variants using{" "}
<Code>{`{{curly_braces}}`}</Code>.
</Text> </Text>
<HStack spacing={0}> <HStack spacing={0}>
<Input <Input
@@ -67,25 +66,28 @@ export default function ScenarioHeader() {
borderRightRadius={0} borderRightRadius={0}
value={newVariable} value={newVariable}
onChange={(e) => setNewVariable(e.target.value)} onChange={(e) => setNewVariable(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
onAddVar();
}
}}
/> />
<Button <Button
size="xs" size="xs"
height="100%" height="100%"
borderLeftRadius={0} borderLeftRadius={0}
isDisabled={newVariable.length === 0 || variables.includes(newVariable)} isDisabled={!newVarIsValid}
onClick={() => { onClick={onAddVar}
setVariables([...variables, newVariable]);
setNewVariable("");
}}
> >
<Icon as={BsCheck} boxSize={8} /> <Icon as={BsCheck} boxSize={8} />
</Button> </Button>
</HStack> </HStack>
<HStack spacing={2} py={4} wrap="wrap"> <HStack spacing={2} py={4} wrap="wrap">
{variables.map((label, i) => ( {vars.map((variable, i) => (
<HStack <HStack
key={label} key={variable.id}
spacing={0} spacing={0}
bgColor="blue.100" bgColor="blue.100"
color="blue.600" color="blue.600"
@@ -94,7 +96,7 @@ export default function ScenarioHeader() {
fontWeight="bold" fontWeight="bold"
> >
<Text fontSize="sm" flex={1}> <Text fontSize="sm" flex={1}>
{label} {variable.label}
</Text> </Text>
<Button <Button
size="xs" size="xs"
@@ -103,11 +105,7 @@ export default function ScenarioHeader() {
p="unset" p="unset"
minW="unset" minW="unset"
px="unset" px="unset"
onClick={() => { onClick={() => onDeleteVar(variable.id)}
const newVariables = [...variables];
newVariables.splice(i, 1);
setVariables(newVariables);
}}
> >
<Icon as={BsX} boxSize={6} color="blue.800" /> <Icon as={BsX} boxSize={6} color="blue.800" />
</Button> </Button>

View File

@@ -3,6 +3,7 @@ import { createTRPCRouter } from "~/server/api/trpc";
import { experimentsRouter } from "./routers/experiments.router"; import { experimentsRouter } from "./routers/experiments.router";
import { scenariosRouter } from "./routers/scenarios.router"; import { scenariosRouter } from "./routers/scenarios.router";
import { modelOutputsRouter } from "./routers/modelOutputs.router"; import { modelOutputsRouter } from "./routers/modelOutputs.router";
import { templateVarsRouter } from "./routers/templateVariables.router";
/** /**
* This is the primary router for your server. * This is the primary router for your server.
@@ -14,6 +15,7 @@ export const appRouter = createTRPCRouter({
experiments: experimentsRouter, experiments: experimentsRouter,
scenarios: scenariosRouter, scenarios: scenariosRouter,
outputs: modelOutputsRouter, outputs: modelOutputsRouter,
templateVars: templateVarsRouter,
}); });
// export type definition of API // export type definition of API

View File

@@ -3,50 +3,64 @@ import { createTRPCRouter, publicProcedure, protectedProcedure } from "~/server/
import { prisma } from "~/server/db"; import { prisma } from "~/server/db";
export const experimentsRouter = createTRPCRouter({ export const experimentsRouter = createTRPCRouter({
list: publicProcedure.query(async () => {
return await prisma.experiment.findMany({
orderBy: {
sortIndex: "asc",
},
});
}),
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => { get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => {
return await prisma.experiment.findFirst({ return await prisma.experiment.findFirst({
where: { where: {
id: input.id, id: input.id,
}, },
include: {
TemplateVariable: {
orderBy: {
createdAt: "asc",
},
select: {
id: true,
label: true,
},
},
},
}); });
}), }),
setTemplateVariables: publicProcedure create: publicProcedure.input(z.object({})).mutation(async () => {
.input( const maxSortIndex =
z.object({ (
id: z.string(), await prisma.experiment.aggregate({
labels: z.array(z.string()), _max: {
}) sortIndex: true,
) },
.mutation(async ({ input }) => { })
const existing = await prisma.templateVariable.findMany({ )._max?.sortIndex ?? 0;
where: { experimentId: input.id },
});
const toDelete = existing.filter((e) => !input.labels.includes(e.label));
const toCreate = new Set( const exp = await prisma.experiment.create({
input.labels.filter((l) => !existing.map((e) => e.label).includes(l)) data: {
).values(); sortIndex: maxSortIndex + 1,
label: `Experiment ${maxSortIndex + 1}`,
},
});
await prisma.$transaction([ await prisma.$transaction([
prisma.templateVariable.deleteMany({ prisma.promptVariant.create({
where: { id: { in: toDelete.map((e) => e.id) } }, data: {
}), experimentId: exp.id,
prisma.templateVariable.createMany({ label: "Prompt Variant 1",
data: [...toCreate].map((l) => ({ label: l, experimentId: input.id })), sortIndex: 0,
}), config: {
]); model: "gpt-3.5-turbo",
return null; messages: [
}), {
role: "system",
content: "count to three in Spanish...",
},
],
},
},
}),
prisma.testScenario.create({
data: {
experimentId: exp.id,
variableValues: {},
},
}),
]);
return exp;
}),
}); });

View File

@@ -0,0 +1,35 @@
import { z } from "zod";
import { createTRPCRouter, publicProcedure, protectedProcedure } from "~/server/api/trpc";
import { prisma } from "~/server/db";
export const templateVarsRouter = createTRPCRouter({
create: publicProcedure
.input(z.object({ experimentId: z.string(), label: z.string() }))
.mutation(async ({ input }) => {
await prisma.templateVariable.create({
data: {
experimentId: input.experimentId,
label: input.label,
},
});
}),
delete: publicProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => {
await prisma.templateVariable.delete({ where: { id: input.id } });
}),
list: publicProcedure.input(z.object({ experimentId: z.string() })).query(async ({ input }) => {
return await prisma.templateVariable.findMany({
where: {
experimentId: input.experimentId,
},
orderBy: {
createdAt: "asc",
},
select: {
id: true,
label: true,
},
});
}),
});

View File

@@ -16,11 +16,17 @@ const theme = extendTheme({
}, },
}, },
Input: { Input: {
sizes: { baseStyle: {
md: { field: {
field: { borderRadius: "sm",
borderRadius: "sm", },
}, },
},
Textarea: {
baseStyle: {
borderRadius: "sm",
field: {
borderRadius: "sm",
}, },
}, },
}, },