lots of changes on making new experiments work
This commit is contained in:
176
@types/nextjs-routes.d.ts
vendored
Normal file
176
@types/nextjs-routes.d.ts
vendored
Normal 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>;
|
||||
}
|
||||
@@ -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
|
||||
* for Docker builds.
|
||||
@@ -20,4 +22,4 @@ const config = {
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
export default nextRoutes()(config);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"next": "^13.4.2",
|
||||
"next-auth": "^4.22.1",
|
||||
"nextjs-routes": "^2.0.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.10.1",
|
||||
|
||||
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@@ -68,6 +68,9 @@ dependencies:
|
||||
next-auth:
|
||||
specifier: ^4.22.1
|
||||
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:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0
|
||||
@@ -2256,6 +2259,14 @@ packages:
|
||||
color-convert: 2.0.1
|
||||
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:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
dev: true
|
||||
@@ -2364,6 +2375,11 @@ packages:
|
||||
engines: {node: '>=0.6'}
|
||||
dev: true
|
||||
|
||||
/binary-extensions@2.2.0:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/bplist-parser@0.2.0:
|
||||
resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==}
|
||||
engines: {node: '>= 5.10.0'}
|
||||
@@ -2383,7 +2399,6 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
fill-range: 7.0.1
|
||||
dev: true
|
||||
|
||||
/buffer-from@0.1.2:
|
||||
resolution: {integrity: sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==}
|
||||
@@ -2438,6 +2453,21 @@ packages:
|
||||
supports-color: 7.2.0
|
||||
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:
|
||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||
dev: false
|
||||
@@ -3117,7 +3147,6 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
dev: true
|
||||
|
||||
/find-root@1.1.0:
|
||||
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
|
||||
@@ -3246,7 +3275,6 @@ packages:
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
dev: true
|
||||
|
||||
/glob-parent@6.0.2:
|
||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||
@@ -3466,6 +3494,13 @@ packages:
|
||||
has-bigints: 1.0.2
|
||||
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:
|
||||
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -3506,14 +3541,12 @@ packages:
|
||||
/is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
dev: true
|
||||
|
||||
/is-inside-container@1.0.0:
|
||||
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
|
||||
@@ -3538,7 +3571,6 @@ packages:
|
||||
/is-number@7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: true
|
||||
|
||||
/is-path-inside@3.0.3:
|
||||
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
|
||||
@@ -3867,6 +3899,21 @@ packages:
|
||||
- babel-plugin-macros
|
||||
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:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4083,7 +4130,6 @@ packages:
|
||||
/picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
|
||||
/postcss@8.4.14:
|
||||
resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==}
|
||||
@@ -4305,6 +4351,13 @@ packages:
|
||||
util-deprecate: 1.0.2
|
||||
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:
|
||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
||||
|
||||
@@ -4636,7 +4689,6 @@ packages:
|
||||
engines: {node: '>=8.0'}
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
dev: true
|
||||
|
||||
/toggle-selection@1.0.6:
|
||||
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
|
||||
|
||||
@@ -15,6 +15,8 @@ model Experiment {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
label String
|
||||
|
||||
sortIndex Int @default(0)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
TemplateVariable TemplateVariable[]
|
||||
@@ -33,7 +35,7 @@ model PromptVariant {
|
||||
sortIndex Int @default(0)
|
||||
|
||||
experimentId String @db.Uuid
|
||||
experiment Experiment @relation(fields: [experimentId], references: [id])
|
||||
experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -52,7 +54,7 @@ model TestScenario {
|
||||
sortIndex Int @default(0)
|
||||
|
||||
experimentId String @db.Uuid
|
||||
experiment Experiment @relation(fields: [experimentId], references: [id])
|
||||
experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -65,7 +67,7 @@ model TemplateVariable {
|
||||
label String
|
||||
|
||||
experimentId String @db.Uuid
|
||||
experiment Experiment @relation(fields: [experimentId], references: [id])
|
||||
experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -78,10 +80,10 @@ model ModelOutput {
|
||||
output Json
|
||||
|
||||
promptVariantId String @db.Uuid
|
||||
promptVariant PromptVariant @relation(fields: [promptVariantId], references: [id])
|
||||
promptVariant PromptVariant @relation(fields: [promptVariantId], references: [id], onDelete: Cascade)
|
||||
|
||||
testScenarioId String @db.Uuid
|
||||
testScenario TestScenario @relation(fields: [testScenarioId], references: [id])
|
||||
testScenario TestScenario @relation(fields: [testScenarioId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { prisma } from "~/server/db";
|
||||
|
||||
const experimentId = "11111111-1111-1111-1111-111111111111";
|
||||
const experiment = await prisma.experiment.upsert({
|
||||
|
||||
// Delete the existing experiment
|
||||
await prisma.experiment.delete({
|
||||
where: {
|
||||
id: experimentId,
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
});
|
||||
|
||||
const experiment = await prisma.experiment.create({
|
||||
data: {
|
||||
id: experimentId,
|
||||
label: "Quick Start",
|
||||
label: "Country Capitals Example",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -34,7 +38,7 @@ const resp = await prisma.promptVariant.createMany({
|
||||
sortIndex: 0,
|
||||
config: {
|
||||
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,
|
||||
},
|
||||
},
|
||||
@@ -44,7 +48,12 @@ const resp = await prisma.promptVariant.createMany({
|
||||
sortIndex: 1,
|
||||
config: {
|
||||
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,
|
||||
},
|
||||
},
|
||||
@@ -61,7 +70,7 @@ await prisma.templateVariable.createMany({
|
||||
data: [
|
||||
{
|
||||
experimentId,
|
||||
label: "state",
|
||||
label: "country",
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -78,21 +87,21 @@ await prisma.testScenario.createMany({
|
||||
experimentId,
|
||||
sortIndex: 0,
|
||||
variableValues: {
|
||||
state: "Washington",
|
||||
country: "USA",
|
||||
},
|
||||
},
|
||||
{
|
||||
experimentId,
|
||||
sortIndex: 1,
|
||||
variableValues: {
|
||||
state: "California",
|
||||
country: "Spain",
|
||||
},
|
||||
},
|
||||
{
|
||||
experimentId,
|
||||
sortIndex: 2,
|
||||
variableValues: {
|
||||
state: "Utah",
|
||||
country: "Chile",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -2,7 +2,6 @@ import { api } from "~/utils/api";
|
||||
import { PromptVariant, Scenario } from "./types";
|
||||
import { Center, Spinner, Text } from "@chakra-ui/react";
|
||||
import { useExperiment } from "~/utils/hooks";
|
||||
import { JSONSerializable } from "~/server/types";
|
||||
import { cellPadding } from "../constants";
|
||||
|
||||
const CellShell = ({ children }: { children: React.ReactNode }) => (
|
||||
@@ -19,12 +18,11 @@ export default function OutputCell({
|
||||
variant: PromptVariant;
|
||||
}) {
|
||||
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 templateHasVariables =
|
||||
experimentVariables.length === 0 ||
|
||||
experimentVariables.some((v) => scenarioVariables[v] !== undefined);
|
||||
vars?.length === 0 || vars?.some((v) => scenarioVariables[v.label] !== undefined);
|
||||
|
||||
let disabledReason: string | null = null;
|
||||
|
||||
@@ -41,6 +39,8 @@ export default function OutputCell({
|
||||
{ enabled: disabledReason === null }
|
||||
);
|
||||
|
||||
if (!vars) return;
|
||||
|
||||
if (disabledReason)
|
||||
return (
|
||||
<CellShell>
|
||||
|
||||
@@ -25,8 +25,9 @@ export default function ScenarioEditor({
|
||||
const [values, setValues] = useState<Record<string, string>>(savedValues);
|
||||
|
||||
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);
|
||||
|
||||
@@ -113,6 +114,9 @@ export default function ScenarioEditor({
|
||||
_hover={{ color: "gray.800", cursor: "pointer" }}
|
||||
/>
|
||||
</Stack>
|
||||
{variableLabels.length === 0 ? (
|
||||
<Box color="gray.500">No scenario variables configured</Box>
|
||||
) : (
|
||||
<Stack>
|
||||
{variableLabels.map((key) => {
|
||||
const value = values[key] ?? "";
|
||||
@@ -131,10 +135,17 @@ export default function ScenarioEditor({
|
||||
px={2}
|
||||
py={1}
|
||||
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}
|
||||
@@ -165,6 +176,7 @@ export default function ScenarioEditor({
|
||||
</HStack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 { api } from "~/utils/api";
|
||||
import NewScenarioButton from "./NewScenarioButton";
|
||||
@@ -8,7 +8,6 @@ import ScenarioEditor from "./ScenarioEditor";
|
||||
import VariantConfigEditor from "./VariantConfigEditor";
|
||||
import VariantHeader from "./VariantHeader";
|
||||
import type { Scenario, PromptVariant } from "./types";
|
||||
import { cellPadding } from "../constants";
|
||||
import ScenarioHeader from "~/server/ScenarioHeader";
|
||||
|
||||
const stickyHeaderStyle: SystemStyleObject = {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
67
src/components/nav/AppShell.tsx
Normal file
67
src/components/nav/AppShell.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Box, Center } from "@chakra-ui/react";
|
||||
import { useRouter } from "next/router";
|
||||
import OutputsTable from "~/components/OutputsTable";
|
||||
import AppNav from "~/components/nav/AppNav";
|
||||
import AppShell from "~/components/nav/AppShell";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
export default function Experiment() {
|
||||
@@ -14,19 +14,19 @@ export default function Experiment() {
|
||||
|
||||
if (!experiment.isLoading && !experiment.data) {
|
||||
return (
|
||||
<AppNav title="Experiment not found">
|
||||
<AppShell title="Experiment not found">
|
||||
<Center h="100vh">
|
||||
<div>Experiment not found 😕</div>
|
||||
</Center>
|
||||
</AppNav>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AppNav title={experiment.data?.label}>
|
||||
<AppShell title={experiment.data?.label}>
|
||||
<Box minH="100vh" mb={50}>
|
||||
<OutputsTable experimentId={router.query.id as string | undefined} />
|
||||
</Box>
|
||||
</AppNav>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,63 +1,62 @@
|
||||
import { Text, Box, Button, HStack, Heading, Icon, Input, Stack, Code } from "@chakra-ui/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 { api } from "~/utils/api";
|
||||
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
|
||||
|
||||
export default function ScenarioHeader() {
|
||||
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 newVarIsValid = newVariable.length > 0 && !vars.map((v) => v.label).includes(newVariable);
|
||||
|
||||
const [editing, setEditing] = useState(false);
|
||||
|
||||
const utils = api.useContext();
|
||||
const setVarsMutation = api.experiments.setTemplateVariables.useMutation();
|
||||
const [onSave] = useHandledAsyncCallback(async () => {
|
||||
const addVarMutation = api.templateVars.create.useMutation();
|
||||
const [onAddVar] = useHandledAsyncCallback(async () => {
|
||||
if (!experiment.data?.id) return;
|
||||
setEditing(false);
|
||||
await setVarsMutation.mutateAsync({
|
||||
id: experiment.data.id,
|
||||
labels: variables,
|
||||
if (!newVarIsValid) return;
|
||||
await addVarMutation.mutateAsync({
|
||||
experimentId: experiment.data.id,
|
||||
label: newVariable,
|
||||
});
|
||||
await utils.experiments.get.invalidate();
|
||||
}, [setVarsMutation, experiment.data?.id, variables]);
|
||||
await utils.templateVars.list.invalidate();
|
||||
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 (
|
||||
<Stack flex={1} px={cellPadding.x} py={cellPadding.y}>
|
||||
<HStack>
|
||||
<Heading size="sm" fontWeight="bold" flex={1}>
|
||||
Scenario
|
||||
Scenario Vars
|
||||
</Heading>
|
||||
{editing ? (
|
||||
<HStack>
|
||||
<Button size="xs" colorScheme="gray" onClick={() => setEditing(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="xs" colorScheme="blue" onClick={onSave}>
|
||||
Save
|
||||
</Button>
|
||||
</HStack>
|
||||
) : (
|
||||
{
|
||||
<Button
|
||||
size="xs"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
rightIcon={<Icon as={BsChevronDown} />}
|
||||
onClick={() => setEditing(true)}
|
||||
rightIcon={<Icon as={editing ? BsCheck : BsChevronDown} />}
|
||||
onClick={() => setEditing((editing) => !editing)}
|
||||
>
|
||||
Configure
|
||||
{editing ? "Finish" : "Configure"}
|
||||
</Button>
|
||||
)}
|
||||
}
|
||||
</HStack>
|
||||
{editing && (
|
||||
<Stack spacing={2} pt={2}>
|
||||
<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>
|
||||
<HStack spacing={0}>
|
||||
<Input
|
||||
@@ -67,25 +66,28 @@ export default function ScenarioHeader() {
|
||||
borderRightRadius={0}
|
||||
value={newVariable}
|
||||
onChange={(e) => setNewVariable(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
onAddVar();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
height="100%"
|
||||
borderLeftRadius={0}
|
||||
isDisabled={newVariable.length === 0 || variables.includes(newVariable)}
|
||||
onClick={() => {
|
||||
setVariables([...variables, newVariable]);
|
||||
setNewVariable("");
|
||||
}}
|
||||
isDisabled={!newVarIsValid}
|
||||
onClick={onAddVar}
|
||||
>
|
||||
<Icon as={BsCheck} boxSize={8} />
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
<HStack spacing={2} py={4} wrap="wrap">
|
||||
{variables.map((label, i) => (
|
||||
{vars.map((variable, i) => (
|
||||
<HStack
|
||||
key={label}
|
||||
key={variable.id}
|
||||
spacing={0}
|
||||
bgColor="blue.100"
|
||||
color="blue.600"
|
||||
@@ -94,7 +96,7 @@ export default function ScenarioHeader() {
|
||||
fontWeight="bold"
|
||||
>
|
||||
<Text fontSize="sm" flex={1}>
|
||||
{label}
|
||||
{variable.label}
|
||||
</Text>
|
||||
<Button
|
||||
size="xs"
|
||||
@@ -103,11 +105,7 @@ export default function ScenarioHeader() {
|
||||
p="unset"
|
||||
minW="unset"
|
||||
px="unset"
|
||||
onClick={() => {
|
||||
const newVariables = [...variables];
|
||||
newVariables.splice(i, 1);
|
||||
setVariables(newVariables);
|
||||
}}
|
||||
onClick={() => onDeleteVar(variable.id)}
|
||||
>
|
||||
<Icon as={BsX} boxSize={6} color="blue.800" />
|
||||
</Button>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createTRPCRouter } from "~/server/api/trpc";
|
||||
import { experimentsRouter } from "./routers/experiments.router";
|
||||
import { scenariosRouter } from "./routers/scenarios.router";
|
||||
import { modelOutputsRouter } from "./routers/modelOutputs.router";
|
||||
import { templateVarsRouter } from "./routers/templateVariables.router";
|
||||
|
||||
/**
|
||||
* This is the primary router for your server.
|
||||
@@ -14,6 +15,7 @@ export const appRouter = createTRPCRouter({
|
||||
experiments: experimentsRouter,
|
||||
scenarios: scenariosRouter,
|
||||
outputs: modelOutputsRouter,
|
||||
templateVars: templateVarsRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
|
||||
@@ -3,50 +3,64 @@ import { createTRPCRouter, publicProcedure, protectedProcedure } from "~/server/
|
||||
import { prisma } from "~/server/db";
|
||||
|
||||
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 }) => {
|
||||
return await prisma.experiment.findFirst({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
include: {
|
||||
TemplateVariable: {
|
||||
orderBy: {
|
||||
createdAt: "asc",
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
label: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
setTemplateVariables: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
labels: z.array(z.string()),
|
||||
create: publicProcedure.input(z.object({})).mutation(async () => {
|
||||
const maxSortIndex =
|
||||
(
|
||||
await prisma.experiment.aggregate({
|
||||
_max: {
|
||||
sortIndex: true,
|
||||
},
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const existing = await prisma.templateVariable.findMany({
|
||||
where: { experimentId: input.id },
|
||||
});
|
||||
const toDelete = existing.filter((e) => !input.labels.includes(e.label));
|
||||
)._max?.sortIndex ?? 0;
|
||||
|
||||
const toCreate = new Set(
|
||||
input.labels.filter((l) => !existing.map((e) => e.label).includes(l))
|
||||
).values();
|
||||
const exp = await prisma.experiment.create({
|
||||
data: {
|
||||
sortIndex: maxSortIndex + 1,
|
||||
label: `Experiment ${maxSortIndex + 1}`,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.$transaction([
|
||||
prisma.templateVariable.deleteMany({
|
||||
where: { id: { in: toDelete.map((e) => e.id) } },
|
||||
prisma.promptVariant.create({
|
||||
data: {
|
||||
experimentId: exp.id,
|
||||
label: "Prompt Variant 1",
|
||||
sortIndex: 0,
|
||||
config: {
|
||||
model: "gpt-3.5-turbo",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "count to three in Spanish...",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.templateVariable.createMany({
|
||||
data: [...toCreate].map((l) => ({ label: l, experimentId: input.id })),
|
||||
prisma.testScenario.create({
|
||||
data: {
|
||||
experimentId: exp.id,
|
||||
variableValues: {},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return null;
|
||||
|
||||
return exp;
|
||||
}),
|
||||
});
|
||||
|
||||
35
src/server/api/routers/templateVariables.router.ts
Normal file
35
src/server/api/routers/templateVariables.router.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -16,13 +16,19 @@ const theme = extendTheme({
|
||||
},
|
||||
},
|
||||
Input: {
|
||||
sizes: {
|
||||
md: {
|
||||
baseStyle: {
|
||||
field: {
|
||||
borderRadius: "sm",
|
||||
},
|
||||
},
|
||||
},
|
||||
Textarea: {
|
||||
baseStyle: {
|
||||
borderRadius: "sm",
|
||||
field: {
|
||||
borderRadius: "sm",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user