Compare commits
142 Commits
debug-prom
...
styling
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e56c93c3f | ||
|
|
e423ad656a | ||
|
|
7d0d94de3a | ||
|
|
344b257db4 | ||
|
|
28b43b6e6d | ||
|
|
8d373ec9b5 | ||
|
|
537525667d | ||
|
|
519367c553 | ||
|
|
1a338ec863 | ||
|
|
01d0b8f778 | ||
|
|
d99836ec30 | ||
|
|
33751c12d2 | ||
|
|
89815e1f7f | ||
|
|
5fa5109f34 | ||
|
|
b06ab2cbf9 | ||
|
|
35fb554038 | ||
|
|
f238177277 | ||
|
|
723c0f7505 | ||
|
|
ce6936f753 | ||
|
|
2a80cbf74a | ||
|
|
098805ef25 | ||
|
|
ed90bc5a99 | ||
|
|
de9be8c7ce | ||
|
|
3e02bcf9b8 | ||
|
|
cef2ee31fb | ||
|
|
d7cff0f52e | ||
|
|
228c547839 | ||
|
|
e1fcc8fb38 | ||
|
|
8ed47eb4dd | ||
|
|
3a908d51aa | ||
|
|
d9db6d80ea | ||
|
|
8d1ee62ff1 | ||
|
|
f270579283 | ||
|
|
81fbaeae44 | ||
|
|
5277afa199 | ||
|
|
76c34d64e6 | ||
|
|
454ac9a0d3 | ||
|
|
5ed7adadf9 | ||
|
|
b8e0f392ab | ||
|
|
b2af83341d | ||
|
|
e6d229d5f9 | ||
|
|
1a6ae3aef7 | ||
|
|
9051d80775 | ||
|
|
6c060c6ea0 | ||
|
|
f70e73e338 | ||
|
|
16aa6672fc | ||
|
|
ac99c8e0f7 | ||
|
|
df121db78c | ||
|
|
816b41adad | ||
|
|
f09dfe18be | ||
|
|
868d7084f0 | ||
|
|
f6f04e537e | ||
|
|
4feb3e5829 | ||
|
|
c62ced867a | ||
|
|
7bb414026e | ||
|
|
1b2b6c1456 | ||
|
|
760bfbbe32 | ||
|
|
3424aa36ba | ||
|
|
ded86cba08 | ||
|
|
65a0f9065f | ||
|
|
2861c64428 | ||
|
|
ca33bb0b08 | ||
|
|
72e680e77c | ||
|
|
5dd7e67396 | ||
|
|
fd286f6874 | ||
|
|
7a4aa5f0aa | ||
|
|
cb791e3c73 | ||
|
|
a2c322ff43 | ||
|
|
a2ace63f25 | ||
|
|
41d06596cb | ||
|
|
49c68fdbf2 | ||
|
|
6188f55569 | ||
|
|
ea91d692d3 | ||
|
|
ae7acbfdd4 | ||
|
|
b9396e63cc | ||
|
|
753a48f6e9 | ||
|
|
bd7c8b43b0 | ||
|
|
a1249f17c9 | ||
|
|
6f8db40f74 | ||
|
|
8c5345a291 | ||
|
|
f47010a6e7 | ||
|
|
6d32f1c06e | ||
|
|
8fed9730da | ||
|
|
0f9a83cf45 | ||
|
|
9f17d98736 | ||
|
|
74029e5478 | ||
|
|
d220cd30e8 | ||
|
|
c0f10cd522 | ||
|
|
dc497dbd99 | ||
|
|
f8f855adf4 | ||
|
|
8f49bace53 | ||
|
|
c9f59bfb79 | ||
|
|
57166e96b4 | ||
|
|
1a838824ae | ||
|
|
6b304f8456 | ||
|
|
a53d70d8b2 | ||
|
|
109a9ddb1e | ||
|
|
7f8b574c9f | ||
|
|
9e859c199e | ||
|
|
deabbb094b | ||
|
|
7637b94ea7 | ||
|
|
721f1726eb | ||
|
|
cfeb4dfa92 | ||
|
|
21ef67ed4c | ||
|
|
7707d451e0 | ||
|
|
d82782adb4 | ||
|
|
e10589abff | ||
|
|
01dcbfc896 | ||
|
|
50e0b34d30 | ||
|
|
44bb9fc58d | ||
|
|
c0d3784f0c | ||
|
|
e522026b71 | ||
|
|
46b13d85b7 | ||
|
|
c12aa82a3e | ||
|
|
b98bce8944 | ||
|
|
f045c80dfd | ||
|
|
3b460dff2a | ||
|
|
5fa5732804 | ||
|
|
28e6e2b9df | ||
|
|
54d1df4442 | ||
|
|
f69c2b5f23 | ||
|
|
51f0666f6a | ||
|
|
b67d974f4c | ||
|
|
33fb2db981 | ||
|
|
e391379c3e | ||
|
|
8d1609dd52 | ||
|
|
f3380f302d | ||
|
|
3dba9c7ee1 | ||
|
|
e0e4f7a9d6 | ||
|
|
48293dc579 | ||
|
|
38ac6243a0 | ||
|
|
bd2f58e2a5 | ||
|
|
808e47c6b9 | ||
|
|
5945f0ed6b | ||
|
|
6bc7d76d15 | ||
|
|
e9ed173e34 | ||
|
|
75d58d7021 | ||
|
|
896c8c5c57 | ||
|
|
ec5547d0b0 | ||
|
|
77e4e3b8c3 | ||
|
|
a1b03ddad1 | ||
|
|
72c70e2a55 |
@@ -6,6 +6,10 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: app
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-checks:
|
run-checks:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
43
.gitignore
vendored
@@ -1,42 +1,5 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# database
|
|
||||||
/prisma/db.sqlite
|
|
||||||
/prisma/db.sqlite-journal
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
next-env.d.ts
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
|
|
||||||
.env
|
.env
|
||||||
.env*.local
|
.venv/
|
||||||
|
*.pyc
|
||||||
# vercel
|
node_modules/
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript
|
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|||||||
83
README.md
@@ -1,52 +1,62 @@
|
|||||||
<img src="https://github.com/openpipe/openpipe/assets/41524992/ca59596e-eb80-40f9-921f-6d67f6e6d8fa" width="72px" />
|
<!-- <img src="https://github.com/openpipe/openpipe/assets/41524992/ca59596e-eb80-40f9-921f-6d67f6e6d8fa" width="72px" /> -->
|
||||||
|
|
||||||
# OpenPipe
|
# OpenPipe
|
||||||
|
|
||||||
OpenPipe is a flexible playground for comparing and optimizing LLM prompts. It lets you quickly generate, test and compare candidate prompts with realistic sample data.
|
OpenPipe is a flexible playground for comparing and optimizing LLM prompts. It lets you quickly generate, test and compare candidate prompts, and can automatically [translate](#-translate-between-model-apis) those prompts between models.
|
||||||
|
|
||||||
|
<img src="https://github.com/openpipe/openpipe/assets/41524992/219a844e-3f4e-4f6b-8066-41348b42977b" alt="demo">
|
||||||
|
|
||||||
|
You can use our hosted version of OpenPipe at https://openpipe.ai. You can also clone this repository and [run it locally](#running-locally).
|
||||||
|
|
||||||
## Sample Experiments
|
## Sample Experiments
|
||||||
|
|
||||||
These are simple experiments users have created that show how OpenPipe works.
|
These are simple experiments users have created that show how OpenPipe works. Feel free to fork them and start experimenting yourself.
|
||||||
|
|
||||||
- [Country Capitals](https://app.openpipe.ai/experiments/11111111-1111-1111-1111-111111111111)
|
- [Twitter Sentiment Analysis](https://app.openpipe.ai/experiments/62c20a73-2012-4a64-973c-4b665ad46a57)
|
||||||
- [Reddit User Needs](https://app.openpipe.ai/experiments/22222222-2222-2222-2222-222222222222)
|
- [Reddit User Needs](https://app.openpipe.ai/experiments/22222222-2222-2222-2222-222222222222)
|
||||||
- [OpenAI Function Calls](https://app.openpipe.ai/experiments/2ebbdcb3-ed51-456e-87dc-91f72eaf3e2b)
|
- [OpenAI Function Calls](https://app.openpipe.ai/experiments/2ebbdcb3-ed51-456e-87dc-91f72eaf3e2b)
|
||||||
- [Activity Classification](https://app.openpipe.ai/experiments/3950940f-ab6b-4b74-841d-7e9dbc4e4ff8)
|
- [Activity Classification](https://app.openpipe.ai/experiments/3950940f-ab6b-4b74-841d-7e9dbc4e4ff8)
|
||||||
|
|
||||||
<img src="https://github.com/openpipe/openpipe/assets/176426/fc7624c6-5b65-4d4d-82b7-4a816f3e5678" alt="demo" height="400px">
|
|
||||||
|
|
||||||
You can use our hosted version of OpenPipe at [https://openpipe.ai]. You can also clone this repository and [run it locally](#running-locally).
|
|
||||||
|
|
||||||
## High-Level Features
|
|
||||||
|
|
||||||
**Configure Multiple Prompts**
|
|
||||||
Set up multiple prompt configurations and compare their output side-by-side. Each configuration can be configured independently.
|
|
||||||
|
|
||||||
**Visualize Responses**
|
|
||||||
Inspect prompt completions side-by-side.
|
|
||||||
|
|
||||||
**Test Many Inputs**
|
|
||||||
OpenPipe lets you _template_ a prompt. Use the templating feature to run the prompts you're testing against many potential inputs for broader coverage of your problem space than you'd get with manual testing.
|
|
||||||
|
|
||||||
**🪄 Auto-generate Test Scenarios**
|
|
||||||
OpenPipe includes a tool to generate new test scenarios based on your existing prompts and scenarios. Just click "Autogenerate Scenario" to try it out!
|
|
||||||
|
|
||||||
**Prompt Validation and Typeahead**
|
|
||||||
We use OpenAI's OpenAPI spec to automatically provide typeahead and validate prompts.
|
|
||||||
|
|
||||||
<img alt="typeahead" src="https://github.com/openpipe/openpipe/assets/176426/acc638f8-d851-4742-8d01-fe6f98890840" height="300px">
|
|
||||||
|
|
||||||
**Function Call Support**
|
|
||||||
Natively supports [OpenAI function calls](https://openai.com/blog/function-calling-and-other-api-updates) on supported models.
|
|
||||||
|
|
||||||
<img height="300px" alt="function calls" src="https://github.com/openpipe/openpipe/assets/176426/48ad13fe-af2f-4294-bf32-62015597fd9b">
|
|
||||||
|
|
||||||
## Supported Models
|
## Supported Models
|
||||||
|
|
||||||
- All models available through the OpenAI [chat completion API](https://platform.openai.com/docs/guides/gpt/chat-completions-api)
|
- All models available through the OpenAI [chat completion API](https://platform.openai.com/docs/guides/gpt/chat-completions-api)
|
||||||
- Llama2 [7b chat](https://replicate.com/a16z-infra/llama7b-v2-chat), [13b chat](https://replicate.com/a16z-infra/llama13b-v2-chat), [70b chat](https://replicate.com/replicate/llama70b-v2-chat).
|
- Llama2 [7b chat](https://replicate.com/a16z-infra/llama7b-v2-chat), [13b chat](https://replicate.com/a16z-infra/llama13b-v2-chat), [70b chat](https://replicate.com/replicate/llama70b-v2-chat).
|
||||||
- Anthropic's [Claude 1 Instant](https://www.anthropic.com/index/introducing-claude) and [Claude 2](https://www.anthropic.com/index/claude-2)
|
- Anthropic's [Claude 1 Instant](https://www.anthropic.com/index/introducing-claude) and [Claude 2](https://www.anthropic.com/index/claude-2)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### 🔍 Visualize Responses
|
||||||
|
|
||||||
|
Inspect prompt completions side-by-side.
|
||||||
|
|
||||||
|
### 🧪 Bulk-Test
|
||||||
|
|
||||||
|
OpenPipe lets you _template_ a prompt. Use the templating feature to run the prompts you're testing against many potential inputs for broad coverage of your problem space.
|
||||||
|
|
||||||
|
### 📟 Translate between Model APIs
|
||||||
|
|
||||||
|
Write your prompt in one format and automatically convert it to work with any other model.
|
||||||
|
|
||||||
|
<img width="480" alt="Screenshot 2023-08-01 at 11 55 38 PM" src="https://github.com/OpenPipe/OpenPipe/assets/41524992/1e19ccf2-96b6-4e93-a3a5-1449710d1b5b" alt="translate between models">
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
### 🛠️ Refine Your Prompts Automatically
|
||||||
|
|
||||||
|
Use a growing database of best-practice refinements to improve your prompts automatically.
|
||||||
|
|
||||||
|
<img width="480" alt="Screenshot 2023-08-01 at 11 55 38 PM" src="https://github.com/OpenPipe/OpenPipe/assets/41524992/87a27fe7-daef-445c-a5e2-1c82b23f9f99" alt="add function call">
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
### 🪄 Auto-generate Test Scenarios
|
||||||
|
|
||||||
|
OpenPipe includes a tool to generate new test scenarios based on your existing prompts and scenarios. Just click "Autogenerate Scenario" to try it out!
|
||||||
|
|
||||||
|
<img width="600" src="https://github.com/openpipe/openpipe/assets/41524992/219a844e-3f4e-4f6b-8066-41348b42977b" alt="auto-generate">
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
## Running Locally
|
## Running Locally
|
||||||
|
|
||||||
1. Install [Postgresql](https://www.postgresql.org/download/).
|
1. Install [Postgresql](https://www.postgresql.org/download/).
|
||||||
@@ -55,7 +65,14 @@ Natively supports [OpenAI function calls](https://openai.com/blog/function-calli
|
|||||||
4. Clone this repository: `git clone https://github.com/openpipe/openpipe`
|
4. Clone this repository: `git clone https://github.com/openpipe/openpipe`
|
||||||
5. Install the dependencies: `cd openpipe && pnpm install`
|
5. Install the dependencies: `cd openpipe && pnpm install`
|
||||||
6. Create a `.env` file (`cp .env.example .env`) and enter your `OPENAI_API_KEY`.
|
6. Create a `.env` file (`cp .env.example .env`) and enter your `OPENAI_API_KEY`.
|
||||||
7. Update `DATABASE_URL` if necessary to point to your Postgres instance and run `pnpm prisma db push` to create the database.
|
7. Update `DATABASE_URL` if necessary to point to your Postgres instance and run `pnpm prisma migrate dev` to create the database.
|
||||||
8. Create a [GitHub OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) and update the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` values. (Note: a PR to make auth optional when running locally would be a great contribution!)
|
8. Create a [GitHub OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) and update the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` values. (Note: a PR to make auth optional when running locally would be a great contribution!)
|
||||||
9. Start the app: `pnpm dev`.
|
9. Start the app: `pnpm dev`.
|
||||||
10. Navigate to [http://localhost:3000](http://localhost:3000)
|
10. Navigate to [http://localhost:3000](http://localhost:3000)
|
||||||
|
|
||||||
|
## Testing Locally
|
||||||
|
|
||||||
|
1. Copy your `.env` file to `.env.test`.
|
||||||
|
2. Update the `DATABASE_URL` to have a different database name than your development one
|
||||||
|
3. Run `DATABASE_URL=[your new datatase url] pnpm prisma migrate dev --skip-seed --skip-generate`
|
||||||
|
4. Run `pnpm test`
|
||||||
@@ -26,6 +26,11 @@ NEXT_PUBLIC_SOCKET_URL="http://localhost:3318"
|
|||||||
NEXTAUTH_SECRET="your_secret"
|
NEXTAUTH_SECRET="your_secret"
|
||||||
NEXTAUTH_URL="http://localhost:3000"
|
NEXTAUTH_URL="http://localhost:3000"
|
||||||
|
|
||||||
|
NEXT_PUBLIC_HOST="http://localhost:3000"
|
||||||
|
|
||||||
# Next Auth Github Provider
|
# Next Auth Github Provider
|
||||||
GITHUB_CLIENT_ID="your_client_id"
|
GITHUB_CLIENT_ID="your_client_id"
|
||||||
GITHUB_CLIENT_SECRET="your_secret"
|
GITHUB_CLIENT_SECRET="your_secret"
|
||||||
|
|
||||||
|
OPENPIPE_BASE_URL="http://localhost:3000/api"
|
||||||
|
OPENPIPE_API_KEY="your_key"
|
||||||
@@ -6,7 +6,7 @@ const config = {
|
|||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
extends: ["plugin:@typescript-eslint/recommended-requiring-type-checking"],
|
extends: ["plugin:@typescript-eslint/recommended-requiring-type-checking"],
|
||||||
files: ["*.ts", "*.tsx"],
|
files: ["*.mts", "*.ts", "*.tsx"],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: path.join(__dirname, "tsconfig.json"),
|
project: path.join(__dirname, "tsconfig.json"),
|
||||||
},
|
},
|
||||||
49
app/.gitignore
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# database
|
||||||
|
/prisma/db.sqlite
|
||||||
|
/prisma/db.sqlite-journal
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
|
||||||
|
.env
|
||||||
|
.env*.local
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Sentry Auth Token
|
||||||
|
.sentryclirc
|
||||||
|
|
||||||
|
# custom openai intialization
|
||||||
|
src/server/utils/openaiCustomConfig.json
|
||||||
@@ -12,12 +12,21 @@ declare module "nextjs-routes" {
|
|||||||
|
|
||||||
export type Route =
|
export type Route =
|
||||||
| StaticRoute<"/account/signin">
|
| StaticRoute<"/account/signin">
|
||||||
|
| DynamicRoute<"/api/[...trpc]", { "trpc": string[] }>
|
||||||
| DynamicRoute<"/api/auth/[...nextauth]", { "nextauth": string[] }>
|
| DynamicRoute<"/api/auth/[...nextauth]", { "nextauth": string[] }>
|
||||||
| StaticRoute<"/api/experiments/og-image">
|
| StaticRoute<"/api/experiments/og-image">
|
||||||
|
| StaticRoute<"/api/openapi">
|
||||||
|
| StaticRoute<"/api/sentry-example-api">
|
||||||
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
|
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
|
||||||
|
| StaticRoute<"/dashboard">
|
||||||
|
| DynamicRoute<"/data/[id]", { "id": string }>
|
||||||
|
| StaticRoute<"/data">
|
||||||
| DynamicRoute<"/experiments/[id]", { "id": string }>
|
| DynamicRoute<"/experiments/[id]", { "id": string }>
|
||||||
| StaticRoute<"/experiments">
|
| StaticRoute<"/experiments">
|
||||||
| StaticRoute<"/">
|
| StaticRoute<"/">
|
||||||
|
| StaticRoute<"/project/settings">
|
||||||
|
| StaticRoute<"/request-logs">
|
||||||
|
| StaticRoute<"/sentry-example-page">
|
||||||
| StaticRoute<"/world-champs">
|
| StaticRoute<"/world-champs">
|
||||||
| StaticRoute<"/world-champs/signup">;
|
| StaticRoute<"/world-champs/signup">;
|
||||||
|
|
||||||
@@ -20,6 +20,10 @@ FROM base as builder
|
|||||||
# Include all NEXT_PUBLIC_* env vars here
|
# Include all NEXT_PUBLIC_* env vars here
|
||||||
ARG NEXT_PUBLIC_POSTHOG_KEY
|
ARG NEXT_PUBLIC_POSTHOG_KEY
|
||||||
ARG NEXT_PUBLIC_SOCKET_URL
|
ARG NEXT_PUBLIC_SOCKET_URL
|
||||||
|
ARG NEXT_PUBLIC_HOST
|
||||||
|
ARG NEXT_PUBLIC_SENTRY_DSN
|
||||||
|
ARG SENTRY_AUTH_TOKEN
|
||||||
|
ARG NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
63
app/next.config.mjs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import nextRoutes from "nextjs-routes/config";
|
||||||
|
import { withSentryConfig } from "@sentry/nextjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
|
||||||
|
* for Docker builds.
|
||||||
|
*/
|
||||||
|
const { env } = await import("./src/env.mjs");
|
||||||
|
|
||||||
|
/** @type {import("next").NextConfig} */
|
||||||
|
let config = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config
|
||||||
|
* out.
|
||||||
|
*
|
||||||
|
* @see https://github.com/vercel/next.js/issues/41980
|
||||||
|
*/
|
||||||
|
i18n: {
|
||||||
|
locales: ["en"],
|
||||||
|
defaultLocale: "en",
|
||||||
|
},
|
||||||
|
|
||||||
|
rewrites: async () => [
|
||||||
|
{
|
||||||
|
source: "/ingest/:path*",
|
||||||
|
destination: "https://app.posthog.com/:path*",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
webpack: (config) => {
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.txt$/,
|
||||||
|
use: "raw-loader",
|
||||||
|
});
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
|
||||||
|
transpilePackages: ["openpipe"],
|
||||||
|
};
|
||||||
|
|
||||||
|
config = nextRoutes()(config);
|
||||||
|
|
||||||
|
if (env.NEXT_PUBLIC_SENTRY_DSN && env.SENTRY_AUTH_TOKEN) {
|
||||||
|
// @ts-expect-error - `withSentryConfig` is not typed correctly
|
||||||
|
config = withSentryConfig(
|
||||||
|
config,
|
||||||
|
{
|
||||||
|
authToken: env.SENTRY_AUTH_TOKEN,
|
||||||
|
silent: true,
|
||||||
|
org: "openpipe",
|
||||||
|
project: "openpipe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
widenClientFileUpload: true,
|
||||||
|
tunnelRoute: "/monitoring",
|
||||||
|
disableLogger: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config;
|
||||||
7
app/openapitools.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
||||||
|
"spaces": 2,
|
||||||
|
"generator-cli": {
|
||||||
|
"version": "6.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openpipe",
|
"name": "openpipe-app",
|
||||||
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@@ -16,14 +17,14 @@
|
|||||||
"postinstall": "prisma generate",
|
"postinstall": "prisma generate",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"codegen": "tsx src/codegen/export-openai-types.ts",
|
"codegen:clients": "tsx src/server/scripts/client-codegen.ts",
|
||||||
"seed": "tsx prisma/seed.ts",
|
"seed": "tsx prisma/seed.ts",
|
||||||
"check": "concurrently 'pnpm lint' 'pnpm tsc' 'pnpm prettier . --check'"
|
"check": "concurrently 'pnpm lint' 'pnpm tsc' 'pnpm prettier . --check'",
|
||||||
|
"test": "pnpm vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.5.8",
|
"@anthropic-ai/sdk": "^0.5.8",
|
||||||
"@apidevtools/json-schema-ref-parser": "^10.1.0",
|
"@apidevtools/json-schema-ref-parser": "^10.1.0",
|
||||||
"@babel/preset-typescript": "^7.22.5",
|
|
||||||
"@babel/standalone": "^7.22.9",
|
"@babel/standalone": "^7.22.9",
|
||||||
"@chakra-ui/anatomy": "^2.2.0",
|
"@chakra-ui/anatomy": "^2.2.0",
|
||||||
"@chakra-ui/next-js": "^2.1.4",
|
"@chakra-ui/next-js": "^2.1.4",
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
"@monaco-editor/loader": "^1.3.3",
|
"@monaco-editor/loader": "^1.3.3",
|
||||||
"@next-auth/prisma-adapter": "^1.0.5",
|
"@next-auth/prisma-adapter": "^1.0.5",
|
||||||
"@prisma/client": "^4.14.0",
|
"@prisma/client": "^4.14.0",
|
||||||
|
"@sentry/nextjs": "^7.61.0",
|
||||||
"@t3-oss/env-nextjs": "^0.3.1",
|
"@t3-oss/env-nextjs": "^0.3.1",
|
||||||
"@tabler/icons-react": "^2.22.0",
|
"@tabler/icons-react": "^2.22.0",
|
||||||
"@tanstack/react-query": "^4.29.7",
|
"@tanstack/react-query": "^4.29.7",
|
||||||
@@ -48,6 +50,7 @@
|
|||||||
"chroma-js": "^2.4.2",
|
"chroma-js": "^2.4.2",
|
||||||
"concurrently": "^8.2.0",
|
"concurrently": "^8.2.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"crypto-random-string": "^5.0.0",
|
||||||
"dayjs": "^1.11.8",
|
"dayjs": "^1.11.8",
|
||||||
"dedent": "^1.0.1",
|
"dedent": "^1.0.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
@@ -60,36 +63,45 @@
|
|||||||
"json-schema-to-typescript": "^13.0.2",
|
"json-schema-to-typescript": "^13.0.2",
|
||||||
"json-stringify-pretty-compact": "^4.0.0",
|
"json-stringify-pretty-compact": "^4.0.0",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
|
"kysely": "^0.26.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"lucide-react": "^0.265.0",
|
||||||
"next": "^13.4.2",
|
"next": "^13.4.2",
|
||||||
"next-auth": "^4.22.1",
|
"next-auth": "^4.22.1",
|
||||||
"next-query-params": "^4.2.3",
|
"next-query-params": "^4.2.3",
|
||||||
|
"nextjs-cors": "^2.1.2",
|
||||||
"nextjs-routes": "^2.0.1",
|
"nextjs-routes": "^2.0.1",
|
||||||
"openai": "4.0.0-beta.7",
|
"openai": "4.0.0-beta.7",
|
||||||
|
"pg": "^8.11.2",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"posthog-js": "^1.68.4",
|
"posthog-js": "^1.75.3",
|
||||||
|
"posthog-node": "^3.1.1",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"prismjs": "^1.29.0",
|
"prismjs": "^1.29.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-diff-viewer": "^3.1.1",
|
"react-diff-viewer": "^3.1.1",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-github-btn": "^1.4.0",
|
||||||
"react-icons": "^4.10.1",
|
"react-icons": "^4.10.1",
|
||||||
"react-json-tree": "^0.18.0",
|
"react-json-tree": "^0.18.0",
|
||||||
"react-select": "^5.7.4",
|
"react-select": "^5.7.4",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"react-textarea-autosize": "^8.5.0",
|
"react-textarea-autosize": "^8.5.0",
|
||||||
"recast": "^0.23.3",
|
"recast": "^0.23.3",
|
||||||
|
"recharts": "^2.7.2",
|
||||||
"replicate": "^0.12.3",
|
"replicate": "^0.12.3",
|
||||||
"socket.io": "^4.7.1",
|
"socket.io": "^4.7.1",
|
||||||
"socket.io-client": "^4.7.1",
|
"socket.io-client": "^4.7.1",
|
||||||
"superjson": "1.12.2",
|
"superjson": "1.12.2",
|
||||||
|
"trpc-openapi": "^1.2.0",
|
||||||
"tsx": "^3.12.7",
|
"tsx": "^3.12.7",
|
||||||
"type-fest": "^4.0.0",
|
"type-fest": "^4.0.0",
|
||||||
"use-query-params": "^2.2.1",
|
"use-query-params": "^2.2.1",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vite-tsconfig-paths": "^4.2.0",
|
"vite-tsconfig-paths": "^4.2.0",
|
||||||
"zod": "^3.21.4",
|
"zod": "^3.21.4",
|
||||||
"zustand": "^4.3.9"
|
"zustand": "^4.3.9",
|
||||||
|
"openpipe": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@openapi-contrib/openapi-schema-to-json-schema": "^4.0.5",
|
"@openapi-contrib/openapi-schema-to-json-schema": "^4.0.5",
|
||||||
@@ -102,6 +114,7 @@
|
|||||||
"@types/json-schema": "^7.0.12",
|
"@types/json-schema": "^7.0.12",
|
||||||
"@types/lodash-es": "^4.17.8",
|
"@types/lodash-es": "^4.17.8",
|
||||||
"@types/node": "^18.16.0",
|
"@types/node": "^18.16.0",
|
||||||
|
"@types/pg": "^8.10.2",
|
||||||
"@types/pluralize": "^0.0.30",
|
"@types/pluralize": "^0.0.30",
|
||||||
"@types/prismjs": "^1.26.0",
|
"@types/prismjs": "^1.26.0",
|
||||||
"@types/react": "^18.2.6",
|
"@types/react": "^18.2.6",
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "UserRole" AS ENUM ('ADMIN', 'USER');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "role" "UserRole" NOT NULL DEFAULT 'USER';
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Dataset" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"organizationId" UUID NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Dataset_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DatasetEntry" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"input" TEXT NOT NULL,
|
||||||
|
"output" TEXT,
|
||||||
|
"datasetId" UUID NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "DatasetEntry_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Dataset" ADD CONSTRAINT "Dataset_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DatasetEntry" ADD CONSTRAINT "DatasetEntry_datasetId_fkey" FOREIGN KEY ("datasetId") REFERENCES "Dataset"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `constructFn` on the `PromptVariant` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `constructFnVersion` on the `PromptVariant` table. All the data in the column will be lost.
|
||||||
|
- Added the required column `promptConstructor` to the `PromptVariant` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `promptConstructorVersion` to the `PromptVariant` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
|
||||||
|
ALTER TABLE "PromptVariant" RENAME COLUMN "constructFn" TO "promptConstructor";
|
||||||
|
ALTER TABLE "PromptVariant" RENAME COLUMN "constructFnVersion" TO "promptConstructorVersion";
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "LoggedCall" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"startTime" TIMESTAMP(3) NOT NULL,
|
||||||
|
"cacheHit" BOOLEAN NOT NULL,
|
||||||
|
"modelResponseId" UUID NOT NULL,
|
||||||
|
"organizationId" UUID NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "LoggedCall_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "LoggedCallModelResponse" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"reqPayload" JSONB NOT NULL,
|
||||||
|
"respStatus" INTEGER,
|
||||||
|
"respPayload" JSONB,
|
||||||
|
"error" TEXT,
|
||||||
|
"startTime" TIMESTAMP(3) NOT NULL,
|
||||||
|
"endTime" TIMESTAMP(3) NOT NULL,
|
||||||
|
"cacheKey" TEXT,
|
||||||
|
"durationMs" INTEGER,
|
||||||
|
"inputTokens" INTEGER,
|
||||||
|
"outputTokens" INTEGER,
|
||||||
|
"finishReason" TEXT,
|
||||||
|
"completionId" TEXT,
|
||||||
|
"totalCost" DECIMAL(18,12),
|
||||||
|
"originalLoggedCallId" UUID NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "LoggedCallModelResponse_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "LoggedCallTag" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"value" TEXT,
|
||||||
|
"loggedCallId" UUID NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "LoggedCallTag_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ApiKey" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"apiKey" TEXT NOT NULL,
|
||||||
|
"organizationId" UUID NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "LoggedCall_startTime_idx" ON "LoggedCall"("startTime");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "LoggedCallModelResponse_originalLoggedCallId_key" ON "LoggedCallModelResponse"("originalLoggedCallId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "LoggedCallModelResponse_cacheKey_idx" ON "LoggedCallModelResponse"("cacheKey");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "LoggedCallTag_name_idx" ON "LoggedCallTag"("name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "LoggedCallTag_name_value_idx" ON "LoggedCallTag"("name", "value");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "ApiKey_apiKey_key" ON "ApiKey"("apiKey");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "LoggedCall" ADD CONSTRAINT "LoggedCall_modelResponseId_fkey" FOREIGN KEY ("modelResponseId") REFERENCES "LoggedCallModelResponse"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "LoggedCall" ADD CONSTRAINT "LoggedCall_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "LoggedCallModelResponse" ADD CONSTRAINT "LoggedCallModelResponse_originalLoggedCallId_fkey" FOREIGN KEY ("originalLoggedCallId") REFERENCES "LoggedCall"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "LoggedCallTag" ADD CONSTRAINT "LoggedCallTag_loggedCallId_fkey" FOREIGN KEY ("loggedCallId") REFERENCES "LoggedCall"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Organization" ADD COLUMN "name" TEXT NOT NULL DEFAULT 'Project 1';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LoggedCall" ALTER COLUMN "modelResponseId" DROP NOT NULL;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
-- Rename Enum
|
||||||
|
ALTER TYPE "OrganizationUserRole" RENAME TO "ProjectUserRole";
|
||||||
|
|
||||||
|
-- Drop and recreate foreign keys
|
||||||
|
ALTER TABLE "ApiKey" DROP CONSTRAINT "ApiKey_organizationId_fkey";
|
||||||
|
ALTER TABLE "Dataset" DROP CONSTRAINT "Dataset_organizationId_fkey";
|
||||||
|
ALTER TABLE "Experiment" DROP CONSTRAINT "Experiment_organizationId_fkey";
|
||||||
|
ALTER TABLE "LoggedCall" DROP CONSTRAINT "LoggedCall_organizationId_fkey";
|
||||||
|
ALTER TABLE "OrganizationUser" DROP CONSTRAINT "OrganizationUser_organizationId_fkey";
|
||||||
|
ALTER TABLE "OrganizationUser" DROP CONSTRAINT "OrganizationUser_userId_fkey";
|
||||||
|
|
||||||
|
-- Rename columns
|
||||||
|
ALTER TABLE "ApiKey" RENAME COLUMN "organizationId" TO "projectId";
|
||||||
|
ALTER TABLE "Dataset" RENAME COLUMN "organizationId" TO "projectId";
|
||||||
|
ALTER TABLE "Experiment" RENAME COLUMN "organizationId" TO "projectId";
|
||||||
|
ALTER TABLE "LoggedCall" RENAME COLUMN "organizationId" TO "projectId";
|
||||||
|
ALTER TABLE "OrganizationUser" RENAME COLUMN "organizationId" TO "projectId";
|
||||||
|
ALTER TABLE "Organization" RENAME COLUMN "personalOrgUserId" TO "personalProjectUserId";
|
||||||
|
|
||||||
|
-- Rename table
|
||||||
|
ALTER TABLE "Organization" RENAME TO "Project";
|
||||||
|
ALTER TABLE "OrganizationUser" RENAME TO "ProjectUser";
|
||||||
|
|
||||||
|
-- Recreate foreign keys
|
||||||
|
ALTER TABLE "Experiment" ADD CONSTRAINT "Experiment_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
ALTER TABLE "Dataset" ADD CONSTRAINT "Dataset_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
ALTER TABLE "ProjectUser" ADD CONSTRAINT "ProjectUser_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
ALTER TABLE "ProjectUser" ADD CONSTRAINT "ProjectUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
ALTER TABLE "LoggedCall" ADD CONSTRAINT "LoggedCall_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- Rename indexes
|
||||||
|
ALTER TABLE "Project" RENAME CONSTRAINT "Organization_pkey" TO "Project_pkey";
|
||||||
|
ALTER TABLE "ProjectUser" RENAME CONSTRAINT "OrganizationUser_pkey" TO "ProjectUser_pkey";
|
||||||
|
ALTER TABLE "Project" RENAME CONSTRAINT "Organization_personalOrgUserId_fkey" TO "Project_personalProjectUserId_fkey";
|
||||||
|
ALTER INDEX "Organization_personalOrgUserId_key" RENAME TO "Project_personalProjectUserId_key";
|
||||||
|
ALTER INDEX "OrganizationUser_organizationId_userId_key" RENAME TO "ProjectUser_projectId_userId_key";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to rename the column `completionTokens` to `outputTokens` on the `ModelResponse` table.
|
||||||
|
- You are about to rename the column `promptTokens` to `inputTokens` on the `ModelResponse` table.
|
||||||
|
- You are about to rename the column `startTime` on the `LoggedCall` table to `requestedAt`. Ensure compatibility with application logic.
|
||||||
|
- You are about to rename the column `startTime` on the `LoggedCallModelResponse` table to `requestedAt`. Ensure compatibility with application logic.
|
||||||
|
- You are about to rename the column `endTime` on the `LoggedCallModelResponse` table to `receivedAt`. Ensure compatibility with application logic.
|
||||||
|
- You are about to rename the column `error` on the `LoggedCallModelResponse` table to `errorMessage`. Ensure compatibility with application logic.
|
||||||
|
- You are about to rename the column `respStatus` on the `LoggedCallModelResponse` table to `statusCode`. Ensure compatibility with application logic.
|
||||||
|
- You are about to rename the column `totalCost` on the `LoggedCallModelResponse` table to `cost`. Ensure compatibility with application logic.
|
||||||
|
- You are about to rename the column `inputHash` on the `ModelResponse` table to `cacheKey`. Ensure compatibility with application logic.
|
||||||
|
- You are about to rename the column `output` on the `ModelResponse` table to `respPayload`. Ensure compatibility with application logic.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "LoggedCall_startTime_idx";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "ModelResponse_inputHash_idx";
|
||||||
|
|
||||||
|
-- Rename completionTokens to outputTokens
|
||||||
|
ALTER TABLE "ModelResponse"
|
||||||
|
RENAME COLUMN "completionTokens" TO "outputTokens";
|
||||||
|
|
||||||
|
-- Rename promptTokens to inputTokens
|
||||||
|
ALTER TABLE "ModelResponse"
|
||||||
|
RENAME COLUMN "promptTokens" TO "inputTokens";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LoggedCall"
|
||||||
|
RENAME COLUMN "startTime" TO "requestedAt";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LoggedCallModelResponse"
|
||||||
|
RENAME COLUMN "startTime" TO "requestedAt";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LoggedCallModelResponse"
|
||||||
|
RENAME COLUMN "endTime" TO "receivedAt";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LoggedCallModelResponse"
|
||||||
|
RENAME COLUMN "error" TO "errorMessage";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LoggedCallModelResponse"
|
||||||
|
RENAME COLUMN "respStatus" TO "statusCode";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LoggedCallModelResponse"
|
||||||
|
RENAME COLUMN "totalCost" TO "cost";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ModelResponse"
|
||||||
|
RENAME COLUMN "inputHash" TO "cacheKey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ModelResponse"
|
||||||
|
RENAME COLUMN "output" TO "respPayload";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "LoggedCall_requestedAt_idx" ON "LoggedCall"("requestedAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "ModelResponse_cacheKey_idx" ON "ModelResponse"("cacheKey");
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LoggedCall" ADD COLUMN "model" TEXT;
|
||||||
@@ -16,8 +16,8 @@ model Experiment {
|
|||||||
|
|
||||||
sortIndex Int @default(0)
|
sortIndex Int @default(0)
|
||||||
|
|
||||||
organizationId String @db.Uuid
|
projectId String @db.Uuid
|
||||||
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@ -31,11 +31,11 @@ model Experiment {
|
|||||||
model PromptVariant {
|
model PromptVariant {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
|
||||||
label String
|
label String
|
||||||
constructFn String
|
promptConstructor String
|
||||||
constructFnVersion Int
|
promptConstructorVersion Int
|
||||||
model String
|
model String
|
||||||
modelProvider String
|
modelProvider String
|
||||||
|
|
||||||
uiId String @default(uuid()) @db.Uuid
|
uiId String @default(uuid()) @db.Uuid
|
||||||
visible Boolean @default(true)
|
visible Boolean @default(true)
|
||||||
@@ -112,13 +112,13 @@ model ScenarioVariantCell {
|
|||||||
model ModelResponse {
|
model ModelResponse {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
|
||||||
inputHash String
|
cacheKey String
|
||||||
requestedAt DateTime?
|
requestedAt DateTime?
|
||||||
receivedAt DateTime?
|
receivedAt DateTime?
|
||||||
output Json?
|
respPayload Json?
|
||||||
cost Float?
|
cost Float?
|
||||||
promptTokens Int?
|
inputTokens Int?
|
||||||
completionTokens Int?
|
outputTokens Int?
|
||||||
statusCode Int?
|
statusCode Int?
|
||||||
errorMessage String?
|
errorMessage String?
|
||||||
retryTime DateTime?
|
retryTime DateTime?
|
||||||
@@ -131,7 +131,7 @@ model ModelResponse {
|
|||||||
scenarioVariantCell ScenarioVariantCell @relation(fields: [scenarioVariantCellId], references: [id], onDelete: Cascade)
|
scenarioVariantCell ScenarioVariantCell @relation(fields: [scenarioVariantCellId], references: [id], onDelete: Cascade)
|
||||||
outputEvaluations OutputEvaluation[]
|
outputEvaluations OutputEvaluation[]
|
||||||
|
|
||||||
@@index([inputHash])
|
@@index([cacheKey])
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EvalType {
|
enum EvalType {
|
||||||
@@ -174,30 +174,61 @@ model OutputEvaluation {
|
|||||||
@@unique([modelResponseId, evaluationId])
|
@@unique([modelResponseId, evaluationId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Organization {
|
model Dataset {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
personalOrgUserId String? @unique @db.Uuid
|
|
||||||
PersonalOrgUser User? @relation(fields: [personalOrgUserId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
name String
|
||||||
updatedAt DateTime @updatedAt
|
datasetEntries DatasetEntry[]
|
||||||
organizationUsers OrganizationUser[]
|
|
||||||
experiments Experiment[]
|
projectId String @db.Uuid
|
||||||
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
enum OrganizationUserRole {
|
model DatasetEntry {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
|
||||||
|
input String
|
||||||
|
output String?
|
||||||
|
|
||||||
|
datasetId String @db.Uuid
|
||||||
|
dataset Dataset? @relation(fields: [datasetId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model Project {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
name String @default("Project 1")
|
||||||
|
|
||||||
|
personalProjectUserId String? @unique @db.Uuid
|
||||||
|
personalProjectUser User? @relation(fields: [personalProjectUserId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
projectUsers ProjectUser[]
|
||||||
|
experiments Experiment[]
|
||||||
|
datasets Dataset[]
|
||||||
|
loggedCalls LoggedCall[]
|
||||||
|
apiKeys ApiKey[]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ProjectUserRole {
|
||||||
ADMIN
|
ADMIN
|
||||||
MEMBER
|
MEMBER
|
||||||
VIEWER
|
VIEWER
|
||||||
}
|
}
|
||||||
|
|
||||||
model OrganizationUser {
|
model ProjectUser {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
|
||||||
role OrganizationUserRole
|
role ProjectUserRole
|
||||||
|
|
||||||
organizationId String @db.Uuid
|
projectId String @db.Uuid
|
||||||
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
userId String @db.Uuid
|
userId String @db.Uuid
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
@@ -205,7 +236,7 @@ model OrganizationUser {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@unique([organizationId, userId])
|
@@unique([projectId, userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model WorldChampEntrant {
|
model WorldChampEntrant {
|
||||||
@@ -222,6 +253,100 @@ model WorldChampEntrant {
|
|||||||
@@unique([userId])
|
@@unique([userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model LoggedCall {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
|
||||||
|
requestedAt DateTime
|
||||||
|
|
||||||
|
// True if this call was served from the cache, false otherwise
|
||||||
|
cacheHit Boolean
|
||||||
|
|
||||||
|
// A LoggedCall is always associated with a LoggedCallModelResponse. If this
|
||||||
|
// is a cache miss, we create a new LoggedCallModelResponse.
|
||||||
|
// If it's a cache hit, it's a pre-existing LoggedCallModelResponse.
|
||||||
|
modelResponseId String? @db.Uuid
|
||||||
|
modelResponse LoggedCallModelResponse? @relation(fields: [modelResponseId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
// The responses created by this LoggedCall. Will be empty if this LoggedCall was a cache hit.
|
||||||
|
createdResponses LoggedCallModelResponse[] @relation(name: "ModelResponseOriginalCall")
|
||||||
|
|
||||||
|
projectId String @db.Uuid
|
||||||
|
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
model String?
|
||||||
|
tags LoggedCallTag[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@index([requestedAt])
|
||||||
|
}
|
||||||
|
|
||||||
|
model LoggedCallModelResponse {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
|
||||||
|
reqPayload Json
|
||||||
|
|
||||||
|
// The HTTP status returned by the model provider
|
||||||
|
statusCode Int?
|
||||||
|
respPayload Json?
|
||||||
|
|
||||||
|
// Should be null if the request was successful, and some string if the request failed.
|
||||||
|
errorMessage String?
|
||||||
|
|
||||||
|
requestedAt DateTime
|
||||||
|
receivedAt DateTime
|
||||||
|
|
||||||
|
// Note: the function to calculate the cacheKey should include the project
|
||||||
|
// ID so we don't share cached responses between projects, which could be an
|
||||||
|
// attack vector. Also, we should only set the cacheKey on the model if the
|
||||||
|
// request was successful.
|
||||||
|
cacheKey String?
|
||||||
|
|
||||||
|
// Derived fields
|
||||||
|
durationMs Int?
|
||||||
|
inputTokens Int?
|
||||||
|
outputTokens Int?
|
||||||
|
finishReason String?
|
||||||
|
completionId String?
|
||||||
|
cost Decimal? @db.Decimal(18, 12)
|
||||||
|
|
||||||
|
// The LoggedCall that created this LoggedCallModelResponse
|
||||||
|
originalLoggedCallId String @unique @db.Uuid
|
||||||
|
originalLoggedCall LoggedCall @relation(name: "ModelResponseOriginalCall", fields: [originalLoggedCallId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
loggedCalls LoggedCall[]
|
||||||
|
|
||||||
|
@@index([cacheKey])
|
||||||
|
}
|
||||||
|
|
||||||
|
model LoggedCallTag {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
name String
|
||||||
|
value String?
|
||||||
|
|
||||||
|
loggedCallId String @db.Uuid
|
||||||
|
loggedCall LoggedCall @relation(fields: [loggedCallId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@index([name])
|
||||||
|
@@index([name, value])
|
||||||
|
}
|
||||||
|
|
||||||
|
model ApiKey {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
|
||||||
|
name String
|
||||||
|
apiKey String @unique
|
||||||
|
|
||||||
|
projectId String @db.Uuid
|
||||||
|
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
model Account {
|
model Account {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
userId String @db.Uuid
|
userId String @db.Uuid
|
||||||
@@ -249,16 +374,24 @@ model Session {
|
|||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum UserRole {
|
||||||
|
ADMIN
|
||||||
|
USER
|
||||||
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
name String?
|
name String?
|
||||||
email String? @unique
|
email String? @unique
|
||||||
emailVerified DateTime?
|
emailVerified DateTime?
|
||||||
image String?
|
image String?
|
||||||
|
|
||||||
|
role UserRole @default(USER)
|
||||||
|
|
||||||
accounts Account[]
|
accounts Account[]
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
organizationUsers OrganizationUser[]
|
projectUsers ProjectUser[]
|
||||||
organizations Organization[]
|
projects Project[]
|
||||||
worldChampEntrant WorldChampEntrant?
|
worldChampEntrant WorldChampEntrant?
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
import { prisma } from "~/server/db";
|
import { prisma } from "~/server/db";
|
||||||
import dedent from "dedent";
|
import dedent from "dedent";
|
||||||
import { generateNewCell } from "~/server/utils/generateNewCell";
|
import { generateNewCell } from "~/server/utils/generateNewCell";
|
||||||
|
import { promptConstructorVersion } from "~/promptConstructor/version";
|
||||||
|
|
||||||
const defaultId = "11111111-1111-1111-1111-111111111111";
|
const defaultId = "11111111-1111-1111-1111-111111111111";
|
||||||
|
|
||||||
await prisma.organization.deleteMany({
|
await prisma.project.deleteMany({
|
||||||
where: { id: defaultId },
|
where: { id: defaultId },
|
||||||
});
|
});
|
||||||
|
|
||||||
// If there's an existing org, just seed into it
|
// If there's an existing project, just seed into it
|
||||||
const org =
|
const project =
|
||||||
(await prisma.organization.findFirst({})) ??
|
(await prisma.project.findFirst({})) ??
|
||||||
(await prisma.organization.create({
|
(await prisma.project.create({
|
||||||
data: { id: defaultId },
|
data: { id: defaultId },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ await prisma.experiment.create({
|
|||||||
data: {
|
data: {
|
||||||
id: defaultId,
|
id: defaultId,
|
||||||
label: "Country Capitals Example",
|
label: "Country Capitals Example",
|
||||||
organizationId: org.id,
|
projectId: project.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -51,8 +52,8 @@ await prisma.promptVariant.createMany({
|
|||||||
sortIndex: 0,
|
sortIndex: 0,
|
||||||
model: "gpt-3.5-turbo-0613",
|
model: "gpt-3.5-turbo-0613",
|
||||||
modelProvider: "openai/ChatCompletion",
|
modelProvider: "openai/ChatCompletion",
|
||||||
constructFnVersion: 1,
|
promptConstructorVersion,
|
||||||
constructFn: dedent`
|
promptConstructor: dedent`
|
||||||
definePrompt("openai/ChatCompletion", {
|
definePrompt("openai/ChatCompletion", {
|
||||||
model: "gpt-3.5-turbo-0613",
|
model: "gpt-3.5-turbo-0613",
|
||||||
messages: [
|
messages: [
|
||||||
@@ -70,8 +71,8 @@ await prisma.promptVariant.createMany({
|
|||||||
sortIndex: 1,
|
sortIndex: 1,
|
||||||
model: "gpt-3.5-turbo-0613",
|
model: "gpt-3.5-turbo-0613",
|
||||||
modelProvider: "openai/ChatCompletion",
|
modelProvider: "openai/ChatCompletion",
|
||||||
constructFnVersion: 1,
|
promptConstructorVersion,
|
||||||
constructFn: dedent`
|
promptConstructor: dedent`
|
||||||
definePrompt("openai/ChatCompletion", {
|
definePrompt("openai/ChatCompletion", {
|
||||||
model: "gpt-3.5-turbo-0613",
|
model: "gpt-3.5-turbo-0613",
|
||||||
messages: [
|
messages: [
|
||||||
@@ -3,17 +3,18 @@ import { generateNewCell } from "~/server/utils/generateNewCell";
|
|||||||
import dedent from "dedent";
|
import dedent from "dedent";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import { promptConstructorVersion } from "~/promptConstructor/version";
|
||||||
|
|
||||||
const defaultId = "11111111-1111-1111-1111-111111111112";
|
const defaultId = "11111111-1111-1111-1111-111111111112";
|
||||||
|
|
||||||
await prisma.organization.deleteMany({
|
await prisma.project.deleteMany({
|
||||||
where: { id: defaultId },
|
where: { id: defaultId },
|
||||||
});
|
});
|
||||||
|
|
||||||
// If there's an existing org, just seed into it
|
// If there's an existing project, just seed into it
|
||||||
const org =
|
const project =
|
||||||
(await prisma.organization.findFirst({})) ??
|
(await prisma.project.findFirst({})) ??
|
||||||
(await prisma.organization.create({
|
(await prisma.project.create({
|
||||||
data: { id: defaultId },
|
data: { id: defaultId },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ for (const dataset of datasets) {
|
|||||||
const oldExperiment = await prisma.experiment.findFirst({
|
const oldExperiment = await prisma.experiment.findFirst({
|
||||||
where: {
|
where: {
|
||||||
label: experimentName,
|
label: experimentName,
|
||||||
organizationId: org.id,
|
projectId: project.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (oldExperiment) {
|
if (oldExperiment) {
|
||||||
@@ -59,7 +60,7 @@ for (const dataset of datasets) {
|
|||||||
data: {
|
data: {
|
||||||
id: oldExperiment?.id ?? undefined,
|
id: oldExperiment?.id ?? undefined,
|
||||||
label: experimentName,
|
label: experimentName,
|
||||||
organizationId: org.id,
|
projectId: project.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -98,8 +99,8 @@ for (const dataset of datasets) {
|
|||||||
sortIndex: 0,
|
sortIndex: 0,
|
||||||
model: "gpt-3.5-turbo-0613",
|
model: "gpt-3.5-turbo-0613",
|
||||||
modelProvider: "openai/ChatCompletion",
|
modelProvider: "openai/ChatCompletion",
|
||||||
constructFnVersion: 1,
|
promptConstructorVersion,
|
||||||
constructFn: dedent`
|
promptConstructor: dedent`
|
||||||
definePrompt("openai/ChatCompletion", {
|
definePrompt("openai/ChatCompletion", {
|
||||||
model: "gpt-3.5-turbo-0613",
|
model: "gpt-3.5-turbo-0613",
|
||||||
messages: [
|
messages: [
|
||||||
409
app/prisma/seedDashboard.ts
Normal file
@@ -2,17 +2,18 @@ import { prisma } from "~/server/db";
|
|||||||
import dedent from "dedent";
|
import dedent from "dedent";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { parse } from "csv-parse/sync";
|
import { parse } from "csv-parse/sync";
|
||||||
|
import { promptConstructorVersion } from "~/promptConstructor/version";
|
||||||
|
|
||||||
const defaultId = "11111111-1111-1111-1111-111111111112";
|
const defaultId = "11111111-1111-1111-1111-111111111112";
|
||||||
|
|
||||||
await prisma.organization.deleteMany({
|
await prisma.project.deleteMany({
|
||||||
where: { id: defaultId },
|
where: { id: defaultId },
|
||||||
});
|
});
|
||||||
|
|
||||||
// If there's an existing org, just seed into it
|
// If there's an existing project, just seed into it
|
||||||
const org =
|
const project =
|
||||||
(await prisma.organization.findFirst({})) ??
|
(await prisma.project.findFirst({})) ??
|
||||||
(await prisma.organization.create({
|
(await prisma.project.create({
|
||||||
data: { id: defaultId },
|
data: { id: defaultId },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ const experimentName = `Twitter Sentiment Analysis`;
|
|||||||
const oldExperiment = await prisma.experiment.findFirst({
|
const oldExperiment = await prisma.experiment.findFirst({
|
||||||
where: {
|
where: {
|
||||||
label: experimentName,
|
label: experimentName,
|
||||||
organizationId: org.id,
|
projectId: project.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (oldExperiment) {
|
if (oldExperiment) {
|
||||||
@@ -39,7 +40,7 @@ const experiment = await prisma.experiment.create({
|
|||||||
data: {
|
data: {
|
||||||
id: oldExperiment?.id ?? undefined,
|
id: oldExperiment?.id ?? undefined,
|
||||||
label: experimentName,
|
label: experimentName,
|
||||||
organizationId: org.id,
|
projectId: project.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,8 +86,8 @@ await prisma.promptVariant.createMany({
|
|||||||
sortIndex: 0,
|
sortIndex: 0,
|
||||||
model: "gpt-3.5-turbo-0613",
|
model: "gpt-3.5-turbo-0613",
|
||||||
modelProvider: "openai/ChatCompletion",
|
modelProvider: "openai/ChatCompletion",
|
||||||
constructFnVersion: 1,
|
promptConstructorVersion,
|
||||||
constructFn: dedent`
|
promptConstructor: dedent`
|
||||||
definePrompt("openai/ChatCompletion", {
|
definePrompt("openai/ChatCompletion", {
|
||||||
model: "gpt-3.5-turbo-0613",
|
model: "gpt-3.5-turbo-0613",
|
||||||
messages: [
|
messages: [
|
||||||
BIN
app/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/public/favicons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
app/public/favicons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
app/public/favicons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
app/public/favicons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 800 B |
BIN
app/public/favicons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/public/favicons/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/public/favicons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
@@ -9,10 +9,9 @@ Created by potrace 1.14, written by Peter Selinger 2001-2017
|
|||||||
</metadata>
|
</metadata>
|
||||||
<g transform="translate(0.000000,550.000000) scale(0.100000,-0.100000)"
|
<g transform="translate(0.000000,550.000000) scale(0.100000,-0.100000)"
|
||||||
fill="#000000" stroke="none">
|
fill="#000000" stroke="none">
|
||||||
<path d="M813 5478 c-18 -13 -37 -36 -43 -52 -6 -19 -10 -236 -10 -603 0 -638
|
<path d="M785 5474 l-25 -27 0 -622 0 -622 25 -27 24 -26 171 0 170 0 0 -2050
|
||||||
-1 -626 65 -657 25 -12 67 -16 179 -16 l146 0 0 -2032 0 -2032 23 -33 c12 -18
|
0 -2051 25 -25 24 -24 1557 2 1556 3 19 24 c19 23 19 70 19 2072 l0 2049 169
|
||||||
35 -37 51 -43 19 -7 539 -10 1528 -10 1663 0 1549 -5 1582 65 14 30 16 235 16
|
0 c165 0 169 1 195 25 l26 24 0 626 0 626 -26 24 -27 25 -1939 0 -1939 0 -24
|
||||||
2059 l0 2026 156 0 156 0 39 39 39 39 0 587 c0 651 1 638 -65 669 -30 14 -223
|
-26z"/>
|
||||||
16 -1932 16 l-1898 0 -32 -22z"/>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 858 B After Width: | Height: | Size: 755 B |
28
app/public/logo.svg
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<svg width="398" height="550" viewBox="0 0 398 550" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M39 125H359V542C359 546.418 355.418 550 351 550H47C42.5817 550 39 546.418 39 542V125Z" fill="black"/>
|
||||||
|
<path d="M0 8C0 3.58172 3.58172 0 8 0H390C394.418 0 398 3.58172 398 8V127C398 131.418 394.418 135 390 135H7.99999C3.58171 135 0 131.418 0 127V8Z" fill="black"/>
|
||||||
|
<path d="M50 135H348V535C348 537.209 346.209 539 344 539H54C51.7909 539 50 537.209 50 535V135Z" fill="#FF5733"/>
|
||||||
|
<path d="M11 14.0001C11 11.791 12.7909 10.0001 15 10.0001H384C386.209 10.0001 388 11.791 388 14.0001V120C388 122.209 386.209 124 384 124H15C12.7909 124 11 122.209 11 120V14.0001Z" fill="#FF5733"/>
|
||||||
|
<path d="M11 14.0001C11 11.791 12.7909 10.0001 15 10.0001H384C386.209 10.0001 388 11.791 388 14.0001V120C388 122.209 386.209 124 384 124H15C12.7909 124 11 122.209 11 120V14.0001Z" fill="url(#paint0_linear_102_49)"/>
|
||||||
|
<path d="M50 134H348V535C348 537.209 346.209 539 344 539H54C51.7909 539 50 537.209 50 535V134Z" fill="url(#paint1_linear_102_49)"/>
|
||||||
|
<path d="M108 142H156V535H108V142Z" fill="white"/>
|
||||||
|
<path d="M300 135H348V535C348 537.209 346.209 539 344 539H300V135Z" fill="white" fill-opacity="0.25"/>
|
||||||
|
<path d="M96 142H108V535H96V142Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M84 10.0001H133V120H84V10.0001Z" fill="white"/>
|
||||||
|
<path d="M339 10.0001H384C386.209 10.0001 388 11.791 388 14.0001V120C388 122.209 386.209 124 384 124H339V10.0001Z" fill="white" fill-opacity="0.25"/>
|
||||||
|
<path d="M71.9995 10.0001H83.9995V120H71.9995V10.0001Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path d="M108 534.529H156V539.019H108V534.529Z" fill="#AAAAAA"/>
|
||||||
|
<path opacity="0.5" d="M95.9927 534.529H107.982V539.019H95.9927V534.529Z" fill="#AAAAAA"/>
|
||||||
|
<path d="M84.0029 119.887H133.007V124.027H84.0029V119.887Z" fill="#AAAAAA"/>
|
||||||
|
<path opacity="0.5" d="M71.9883 119.887H83.978V124.027H71.9883V119.887Z" fill="#AAAAAA"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_102_49" x1="335" y1="67.0001" x2="137" y2="67.0001" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#D62600"/>
|
||||||
|
<stop offset="1" stop-color="#FF5733" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear_102_49" x1="306.106" y1="336.5" x2="149.597" y2="336.5" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#D62600"/>
|
||||||
|
<stop offset="1" stop-color="#FF5733" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
BIN
app/public/og.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
@@ -5,6 +5,9 @@ set -e
|
|||||||
echo "Migrating the database"
|
echo "Migrating the database"
|
||||||
pnpm prisma migrate deploy
|
pnpm prisma migrate deploy
|
||||||
|
|
||||||
|
echo "Migrating promptConstructors"
|
||||||
|
pnpm tsx src/promptConstructor/migrate.ts
|
||||||
|
|
||||||
echo "Starting the server"
|
echo "Starting the server"
|
||||||
|
|
||||||
pnpm concurrently --kill-others \
|
pnpm concurrently --kill-others \
|
||||||
33
app/sentry.client.config.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// This file configures the initialization of Sentry on the client.
|
||||||
|
// The config you add here will be used whenever a users loads a page in their browser.
|
||||||
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||||
|
|
||||||
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
import { env } from "~/env.mjs";
|
||||||
|
|
||||||
|
if (env.NEXT_PUBLIC_SENTRY_DSN) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
|
|
||||||
|
// Adjust this value in production, or use tracesSampler for greater control
|
||||||
|
tracesSampleRate: 1,
|
||||||
|
|
||||||
|
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||||
|
debug: false,
|
||||||
|
|
||||||
|
replaysOnErrorSampleRate: 1.0,
|
||||||
|
|
||||||
|
// This sets the sample rate to be 10%. You may want this to be 100% while
|
||||||
|
// in development and sample at a lower rate in production
|
||||||
|
replaysSessionSampleRate: 0.1,
|
||||||
|
|
||||||
|
// You can remove this option if you're not planning to use the Sentry Session Replay feature:
|
||||||
|
integrations: [
|
||||||
|
new Sentry.Replay({
|
||||||
|
// Additional Replay configuration goes in here, for example:
|
||||||
|
maskAllText: true,
|
||||||
|
blockAllMedia: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
19
app/sentry.edge.config.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
|
||||||
|
// The config you add here will be used whenever one of the edge features is loaded.
|
||||||
|
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
|
||||||
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||||
|
|
||||||
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
import { env } from "~/env.mjs";
|
||||||
|
|
||||||
|
if (env.NEXT_PUBLIC_SENTRY_DSN) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
|
|
||||||
|
// Adjust this value in production, or use tracesSampler for greater control
|
||||||
|
tracesSampleRate: 1,
|
||||||
|
|
||||||
|
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||||
|
debug: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
18
app/sentry.server.config.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// This file configures the initialization of Sentry on the server.
|
||||||
|
// The config you add here will be used whenever the server handles a request.
|
||||||
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||||
|
|
||||||
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
import { env } from "~/env.mjs";
|
||||||
|
|
||||||
|
if (env.NEXT_PUBLIC_SENTRY_DSN) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
|
|
||||||
|
// Adjust this value in production, or use tracesSampler for greater control
|
||||||
|
tracesSampleRate: 1,
|
||||||
|
|
||||||
|
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||||
|
debug: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -68,7 +68,7 @@ export const ChangeModelModal = ({
|
|||||||
return;
|
return;
|
||||||
await replaceVariantMutation.mutateAsync({
|
await replaceVariantMutation.mutateAsync({
|
||||||
id: variant.id,
|
id: variant.id,
|
||||||
constructFn: modifiedPromptFn,
|
promptConstructor: modifiedPromptFn,
|
||||||
streamScenarios: visibleScenarios,
|
streamScenarios: visibleScenarios,
|
||||||
});
|
});
|
||||||
await utils.promptVariants.list.invalidate();
|
await utils.promptVariants.list.invalidate();
|
||||||
@@ -107,7 +107,7 @@ export const ChangeModelModal = ({
|
|||||||
<ModelSearch selectedModel={selectedModel} setSelectedModel={setSelectedModel} />
|
<ModelSearch selectedModel={selectedModel} setSelectedModel={setSelectedModel} />
|
||||||
{isString(modifiedPromptFn) && (
|
{isString(modifiedPromptFn) && (
|
||||||
<CompareFunctions
|
<CompareFunctions
|
||||||
originalFunction={variant.constructFn}
|
originalFunction={variant.promptConstructor}
|
||||||
newFunction={modifiedPromptFn}
|
newFunction={modifiedPromptFn}
|
||||||
leftTitle={originalLabel}
|
leftTitle={originalLabel}
|
||||||
rightTitle={convertedLabel}
|
rightTitle={convertedLabel}
|
||||||
40
app/src/components/CopiableCode.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { HStack, Icon, IconButton, Tooltip, Text } from "@chakra-ui/react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { MdContentCopy } from "react-icons/md";
|
||||||
|
import { useHandledAsyncCallback } from "~/utils/hooks";
|
||||||
|
|
||||||
|
const CopiableCode = ({ code }: { code: string }) => {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const [copyToClipboard] = useHandledAsyncCallback(async () => {
|
||||||
|
await navigator.clipboard.writeText(code);
|
||||||
|
setCopied(true);
|
||||||
|
}, [code]);
|
||||||
|
return (
|
||||||
|
<HStack
|
||||||
|
backgroundColor="blackAlpha.800"
|
||||||
|
color="white"
|
||||||
|
borderRadius={4}
|
||||||
|
padding={3}
|
||||||
|
w="full"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<Text fontFamily="inconsolata" fontWeight="bold" letterSpacing={0.5} overflowX="auto">
|
||||||
|
{code}
|
||||||
|
</Text>
|
||||||
|
<Tooltip closeOnClick={false} label={copied ? "Copied!" : "Copy to clipboard"}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Copy"
|
||||||
|
icon={<Icon as={MdContentCopy} boxSize={5} />}
|
||||||
|
size="xs"
|
||||||
|
colorScheme="white"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
onMouseLeave={() => setCopied(false)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CopiableCode;
|
||||||
@@ -1,18 +1,29 @@
|
|||||||
import { Button, Spinner, InputGroup, InputRightElement, Icon, HStack } from "@chakra-ui/react";
|
import {
|
||||||
|
Button,
|
||||||
|
Spinner,
|
||||||
|
InputGroup,
|
||||||
|
InputRightElement,
|
||||||
|
Icon,
|
||||||
|
HStack,
|
||||||
|
type InputGroupProps,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
import { IoMdSend } from "react-icons/io";
|
import { IoMdSend } from "react-icons/io";
|
||||||
import AutoResizeTextArea from "../AutoResizeTextArea";
|
import AutoResizeTextArea from "./AutoResizeTextArea";
|
||||||
|
|
||||||
export const CustomInstructionsInput = ({
|
export const CustomInstructionsInput = ({
|
||||||
instructions,
|
instructions,
|
||||||
setInstructions,
|
setInstructions,
|
||||||
loading,
|
loading,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
placeholder = "Send custom instructions",
|
||||||
|
...props
|
||||||
}: {
|
}: {
|
||||||
instructions: string;
|
instructions: string;
|
||||||
setInstructions: (instructions: string) => void;
|
setInstructions: (instructions: string) => void;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
}) => {
|
placeholder?: string;
|
||||||
|
} & InputGroupProps) => {
|
||||||
return (
|
return (
|
||||||
<InputGroup
|
<InputGroup
|
||||||
size="md"
|
size="md"
|
||||||
@@ -22,6 +33,7 @@ export const CustomInstructionsInput = ({
|
|||||||
borderRadius={8}
|
borderRadius={8}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
colorScheme="orange"
|
colorScheme="orange"
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<AutoResizeTextArea
|
<AutoResizeTextArea
|
||||||
value={instructions}
|
value={instructions}
|
||||||
@@ -33,7 +45,7 @@ export const CustomInstructionsInput = ({
|
|||||||
onSubmit();
|
onSubmit();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder="Send custom instructions"
|
placeholder={placeholder}
|
||||||
py={4}
|
py={4}
|
||||||
pl={4}
|
pl={4}
|
||||||
pr={12}
|
pr={12}
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { BsTrash } from "react-icons/bs";
|
import { BsTrash } from "react-icons/bs";
|
||||||
|
import { useAppStore } from "~/state/store";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
|
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
|
||||||
|
|
||||||
@@ -23,6 +24,8 @@ export const DeleteButton = () => {
|
|||||||
const utils = api.useContext();
|
const utils = api.useContext();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const closeDrawer = useAppStore((s) => s.closeDrawer);
|
||||||
|
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
@@ -31,6 +34,8 @@ export const DeleteButton = () => {
|
|||||||
await mutation.mutateAsync({ id: experiment.data.id });
|
await mutation.mutateAsync({ id: experiment.data.id });
|
||||||
await utils.experiments.list.invalidate();
|
await utils.experiments.list.invalidate();
|
||||||
await router.push({ pathname: "/experiments" });
|
await router.push({ pathname: "/experiments" });
|
||||||
|
closeDrawer();
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
}, [mutation, experiment.data?.id, router]);
|
}, [mutation, experiment.data?.id, router]);
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@ export default function Favicon() {
|
|||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png" />
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png" />
|
||||||
<link rel="manifest" href="/favicons/site.webmanifest" />
|
<link rel="manifest" href="/favicons/site.webmanifest" />
|
||||||
<link rel="shortcut icon" href="/favicons/favicon.ico" />
|
<link rel="shortcut icon" href="/favicons/favicon.ico" />
|
||||||
|
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#5bbad5" />
|
||||||
<meta name="msapplication-TileColor" content="#da532c" />
|
<meta name="msapplication-TileColor" content="#da532c" />
|
||||||
<meta name="msapplication-config" content="/favicons/browserconfig.xml" />
|
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
</Head>
|
</Head>
|
||||||
);
|
);
|
||||||
@@ -33,25 +33,11 @@ export default function AddVariantButton() {
|
|||||||
<Flex w="100%" justifyContent="flex-end">
|
<Flex w="100%" justifyContent="flex-end">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
py={5}
|
py={7}
|
||||||
leftIcon={<Icon as={loading ? Spinner : BsPlus} boxSize={6} mr={loading ? 1 : 0} />}
|
leftIcon={<Icon as={loading ? Spinner : BsPlus} boxSize={6} mr={loading ? 1 : 0} />}
|
||||||
>
|
>
|
||||||
<Text display={{ base: "none", md: "flex" }}>Add Variant</Text>
|
<Text display={{ base: "none", md: "flex" }}>Add Variant</Text>
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
{/* <Button
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
fontWeight="normal"
|
|
||||||
bgColor="transparent"
|
|
||||||
_hover={{ bgColor: "gray.100" }}
|
|
||||||
px={cellPadding.x}
|
|
||||||
onClick={onClick}
|
|
||||||
height="unset"
|
|
||||||
minH={headerMinHeight}
|
|
||||||
>
|
|
||||||
<Icon as={loading ? Spinner : BsPlus} boxSize={6} mr={loading ? 1 : 0} />
|
|
||||||
<Text display={{ base: "none", md: "flex" }}>Add Variant</Text>
|
|
||||||
</Button> */}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
Code,
|
Code,
|
||||||
|
IconButton,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { type Evaluation, EvalType } from "@prisma/client";
|
import { type Evaluation, EvalType } from "@prisma/client";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
@@ -183,46 +184,37 @@ export default function EditEvaluations() {
|
|||||||
<Text flex={1}>
|
<Text flex={1}>
|
||||||
{evaluation.evalType}: "{evaluation.value}"
|
{evaluation.evalType}: "{evaluation.value}"
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
|
||||||
|
<IconButton
|
||||||
|
aria-label="Edit"
|
||||||
variant="unstyled"
|
variant="unstyled"
|
||||||
color="gray.400"
|
|
||||||
height="unset"
|
|
||||||
width="unset"
|
|
||||||
minW="unset"
|
minW="unset"
|
||||||
|
color="gray.400"
|
||||||
onClick={() => setEditingId(evaluation.id)}
|
onClick={() => setEditingId(evaluation.id)}
|
||||||
_hover={{
|
_hover={{ color: "gray.800", cursor: "pointer" }}
|
||||||
color: "gray.800",
|
icon={<Icon as={BsPencil} />}
|
||||||
cursor: "pointer",
|
/>
|
||||||
}}
|
<IconButton
|
||||||
>
|
aria-label="Delete"
|
||||||
<Icon as={BsPencil} boxSize={4} />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="unstyled"
|
variant="unstyled"
|
||||||
color="gray.400"
|
|
||||||
height="unset"
|
|
||||||
width="unset"
|
|
||||||
minW="unset"
|
minW="unset"
|
||||||
|
color="gray.400"
|
||||||
onClick={() => onDelete(evaluation.id)}
|
onClick={() => onDelete(evaluation.id)}
|
||||||
_hover={{
|
_hover={{ color: "gray.800", cursor: "pointer" }}
|
||||||
color: "gray.800",
|
icon={<Icon as={BsX} boxSize={6} />}
|
||||||
cursor: "pointer",
|
/>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon as={BsX} boxSize={6} />
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
{editingId == null && (
|
{editingId == null && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setEditingId("new")}
|
onClick={() => setEditingId("new")}
|
||||||
alignSelf="flex-start"
|
alignSelf="end"
|
||||||
size="sm"
|
size="sm"
|
||||||
mt={4}
|
mt={4}
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
>
|
>
|
||||||
Add Evaluation
|
New Evaluation
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{editingId == "new" && (
|
{editingId == "new" && (
|
||||||
185
app/src/components/OutputsTable/EditScenarioVars.tsx
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import { Text, Button, HStack, Heading, Icon, IconButton, Stack, VStack } from "@chakra-ui/react";
|
||||||
|
import { type TemplateVariable } from "@prisma/client";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { BsPencil, BsX } from "react-icons/bs";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
import { useExperiment, useHandledAsyncCallback, useScenarioVars } from "~/utils/hooks";
|
||||||
|
import { maybeReportError } from "~/utils/errorHandling/maybeReportError";
|
||||||
|
import { FloatingLabelInput } from "./FloatingLabelInput";
|
||||||
|
|
||||||
|
export const ScenarioVar = ({
|
||||||
|
variable,
|
||||||
|
isEditing,
|
||||||
|
setIsEditing,
|
||||||
|
}: {
|
||||||
|
variable: Pick<TemplateVariable, "id" | "label">;
|
||||||
|
isEditing: boolean;
|
||||||
|
setIsEditing: (isEditing: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const utils = api.useContext();
|
||||||
|
|
||||||
|
const [label, setLabel] = useState(variable.label);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLabel(variable.label);
|
||||||
|
}, [variable.label]);
|
||||||
|
|
||||||
|
const renameVarMutation = api.scenarioVars.rename.useMutation();
|
||||||
|
const [onRename] = useHandledAsyncCallback(async () => {
|
||||||
|
const resp = await renameVarMutation.mutateAsync({ id: variable.id, label });
|
||||||
|
if (maybeReportError(resp)) return;
|
||||||
|
|
||||||
|
setIsEditing(false);
|
||||||
|
await utils.scenarioVars.list.invalidate();
|
||||||
|
await utils.scenarios.list.invalidate();
|
||||||
|
}, [label, variable.id]);
|
||||||
|
|
||||||
|
const deleteMutation = api.scenarioVars.delete.useMutation();
|
||||||
|
const [onDeleteVar] = useHandledAsyncCallback(async () => {
|
||||||
|
await deleteMutation.mutateAsync({ id: variable.id });
|
||||||
|
await utils.scenarioVars.list.invalidate();
|
||||||
|
}, [variable.id]);
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
return (
|
||||||
|
<HStack w="full">
|
||||||
|
<FloatingLabelInput
|
||||||
|
flex={1}
|
||||||
|
label="Renamed Variable"
|
||||||
|
value={label}
|
||||||
|
onChange={(e) => setLabel(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
onRename();
|
||||||
|
}
|
||||||
|
// If the user types a space, replace it with an underscore
|
||||||
|
if (e.key === " ") {
|
||||||
|
e.preventDefault();
|
||||||
|
setLabel((label) => label && `${label}_`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button size="sm" onClick={() => setIsEditing(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" colorScheme="blue" onClick={onRename}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<HStack w="full" borderTopWidth={1} borderColor="gray.200">
|
||||||
|
<Text flex={1}>{variable.label}</Text>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Edit"
|
||||||
|
variant="unstyled"
|
||||||
|
minW="unset"
|
||||||
|
color="gray.400"
|
||||||
|
onClick={() => setIsEditing(true)}
|
||||||
|
_hover={{ color: "gray.800", cursor: "pointer" }}
|
||||||
|
icon={<Icon as={BsPencil} />}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Delete"
|
||||||
|
variant="unstyled"
|
||||||
|
minW="unset"
|
||||||
|
color="gray.400"
|
||||||
|
onClick={onDeleteVar}
|
||||||
|
_hover={{ color: "gray.800", cursor: "pointer" }}
|
||||||
|
icon={<Icon as={BsX} boxSize={6} />}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function EditScenarioVars() {
|
||||||
|
const experiment = useExperiment();
|
||||||
|
const vars = useScenarioVars();
|
||||||
|
|
||||||
|
const [currentlyEditingId, setCurrentlyEditingId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [newVariable, setNewVariable] = useState<string>("");
|
||||||
|
const newVarIsValid = newVariable?.length ?? 0 > 0;
|
||||||
|
|
||||||
|
const utils = api.useContext();
|
||||||
|
const addVarMutation = api.scenarioVars.create.useMutation();
|
||||||
|
const [onAddVar] = useHandledAsyncCallback(async () => {
|
||||||
|
if (!experiment.data?.id) return;
|
||||||
|
if (!newVariable) return;
|
||||||
|
const resp = await addVarMutation.mutateAsync({
|
||||||
|
experimentId: experiment.data.id,
|
||||||
|
label: newVariable,
|
||||||
|
});
|
||||||
|
if (maybeReportError(resp)) return;
|
||||||
|
|
||||||
|
await utils.scenarioVars.list.invalidate();
|
||||||
|
setNewVariable("");
|
||||||
|
}, [addVarMutation, experiment.data?.id, newVarIsValid, newVariable]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Heading size="sm">Scenario Variables</Heading>
|
||||||
|
<VStack spacing={4}>
|
||||||
|
<Text fontSize="sm">
|
||||||
|
Scenario variables can be used in your prompt variants as well as evaluations.
|
||||||
|
</Text>
|
||||||
|
<VStack spacing={0} w="full">
|
||||||
|
{vars.data?.map((variable) => (
|
||||||
|
<ScenarioVar
|
||||||
|
variable={variable}
|
||||||
|
key={variable.id}
|
||||||
|
isEditing={currentlyEditingId === variable.id}
|
||||||
|
setIsEditing={(isEditing) => {
|
||||||
|
if (isEditing) {
|
||||||
|
setCurrentlyEditingId(variable.id);
|
||||||
|
} else {
|
||||||
|
setCurrentlyEditingId(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
{currentlyEditingId !== "new" && (
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setCurrentlyEditingId("new")}
|
||||||
|
alignSelf="end"
|
||||||
|
>
|
||||||
|
New Variable
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{currentlyEditingId === "new" && (
|
||||||
|
<HStack w="full">
|
||||||
|
<FloatingLabelInput
|
||||||
|
flex={1}
|
||||||
|
label="New Variable"
|
||||||
|
value={newVariable}
|
||||||
|
onChange={(e) => setNewVariable(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
onAddVar();
|
||||||
|
}
|
||||||
|
// If the user types a space, replace it with an underscore
|
||||||
|
if (e.key === " ") {
|
||||||
|
e.preventDefault();
|
||||||
|
setNewVariable((v) => v && `${v}_`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button size="sm" onClick={() => setCurrentlyEditingId(null)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" colorScheme="blue" onClick={onAddVar}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { type PromptVariant, type Scenario } from "../types";
|
import { type PromptVariant, type Scenario } from "../types";
|
||||||
import { type StackProps, Text, VStack } from "@chakra-ui/react";
|
import { type StackProps, Text, VStack } from "@chakra-ui/react";
|
||||||
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
|
import { useScenarioVars, useHandledAsyncCallback } from "~/utils/hooks";
|
||||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||||
import { docco } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
import { docco } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
||||||
import stringify from "json-stringify-pretty-compact";
|
import stringify from "json-stringify-pretty-compact";
|
||||||
@@ -23,10 +23,7 @@ export default function OutputCell({
|
|||||||
variant: PromptVariant;
|
variant: PromptVariant;
|
||||||
}): ReactElement | null {
|
}): ReactElement | null {
|
||||||
const utils = api.useContext();
|
const utils = api.useContext();
|
||||||
const experiment = useExperiment();
|
const vars = useScenarioVars().data;
|
||||||
const vars = api.templateVars.list.useQuery({
|
|
||||||
experimentId: experiment.data?.id ?? "",
|
|
||||||
}).data;
|
|
||||||
|
|
||||||
const scenarioVariables = scenario.variableValues as Record<string, string>;
|
const scenarioVariables = scenario.variableValues as Record<string, string>;
|
||||||
const templateHasVariables =
|
const templateHasVariables =
|
||||||
@@ -88,7 +85,7 @@ export default function OutputCell({
|
|||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
),
|
),
|
||||||
[hardRefetching, hardRefetch, mostRecentResponse, scenario],
|
[hardRefetching, hardRefetch, mostRecentResponse, scenario, cell],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!vars) return null;
|
if (!vars) return null;
|
||||||
@@ -110,7 +107,7 @@ export default function OutputCell({
|
|||||||
|
|
||||||
if (disabledReason) return <Text color="gray.500">{disabledReason}</Text>;
|
if (disabledReason) return <Text color="gray.500">{disabledReason}</Text>;
|
||||||
|
|
||||||
const showLogs = !streamedMessage && !mostRecentResponse?.output;
|
const showLogs = !streamedMessage && !mostRecentResponse?.respPayload;
|
||||||
|
|
||||||
if (showLogs)
|
if (showLogs)
|
||||||
return (
|
return (
|
||||||
@@ -163,13 +160,13 @@ export default function OutputCell({
|
|||||||
</CellWrapper>
|
</CellWrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
const normalizedOutput = mostRecentResponse?.output
|
const normalizedOutput = mostRecentResponse?.respPayload
|
||||||
? provider.normalizeOutput(mostRecentResponse?.output)
|
? provider.normalizeOutput(mostRecentResponse?.respPayload)
|
||||||
: streamedMessage
|
: streamedMessage
|
||||||
? provider.normalizeOutput(streamedMessage)
|
? provider.normalizeOutput(streamedMessage)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (mostRecentResponse?.output && normalizedOutput?.type === "json") {
|
if (mostRecentResponse?.respPayload && normalizedOutput?.type === "json") {
|
||||||
return (
|
return (
|
||||||
<CellWrapper>
|
<CellWrapper>
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
@@ -191,7 +188,7 @@ export default function OutputCell({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CellWrapper>
|
<CellWrapper>
|
||||||
<Text>{contentToDisplay}</Text>
|
<Text whiteSpace="pre-wrap">{contentToDisplay}</Text>
|
||||||
</CellWrapper>
|
</CellWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -19,12 +19,19 @@ export const OutputStats = ({
|
|||||||
? modelResponse.receivedAt.getTime() - modelResponse.requestedAt.getTime()
|
? modelResponse.receivedAt.getTime() - modelResponse.requestedAt.getTime()
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const promptTokens = modelResponse.promptTokens;
|
const inputTokens = modelResponse.inputTokens;
|
||||||
const completionTokens = modelResponse.completionTokens;
|
const outputTokens = modelResponse.outputTokens;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack w="full" align="center" color="gray.500" fontSize="2xs" mt={{ base: 0, md: 1 }}>
|
<HStack
|
||||||
<HStack flex={1}>
|
w="full"
|
||||||
|
align="center"
|
||||||
|
color="gray.500"
|
||||||
|
fontSize="2xs"
|
||||||
|
mt={{ base: 0, md: 1 }}
|
||||||
|
alignItems="flex-end"
|
||||||
|
>
|
||||||
|
<HStack flex={1} flexWrap="wrap">
|
||||||
{modelResponse.outputEvaluations.map((evaluation) => {
|
{modelResponse.outputEvaluations.map((evaluation) => {
|
||||||
const passed = evaluation.result > 0.5;
|
const passed = evaluation.result > 0.5;
|
||||||
return (
|
return (
|
||||||
@@ -48,8 +55,8 @@ export const OutputStats = ({
|
|||||||
</HStack>
|
</HStack>
|
||||||
{modelResponse.cost && (
|
{modelResponse.cost && (
|
||||||
<CostTooltip
|
<CostTooltip
|
||||||
promptTokens={promptTokens}
|
inputTokens={inputTokens}
|
||||||
completionTokens={completionTokens}
|
outputTokens={outputTokens}
|
||||||
cost={modelResponse.cost}
|
cost={modelResponse.cost}
|
||||||
>
|
>
|
||||||
<HStack spacing={0}>
|
<HStack spacing={0}>
|
||||||
@@ -18,13 +18,13 @@ export const CellOptions = ({
|
|||||||
const modalDisclosure = useDisclosure();
|
const modalDisclosure = useDisclosure();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack justifyContent="flex-end" w="full">
|
<HStack justifyContent="flex-end" w="full" spacing={1}>
|
||||||
{cell && (
|
{cell && (
|
||||||
<>
|
<>
|
||||||
<Tooltip label="See Prompt">
|
<Tooltip label="See Prompt">
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="See Prompt"
|
aria-label="See Prompt"
|
||||||
icon={<Icon as={BsInfoCircle} boxSize={4} />}
|
icon={<Icon as={BsInfoCircle} boxSize={3.5} />}
|
||||||
onClick={modalDisclosure.onOpen}
|
onClick={modalDisclosure.onOpen}
|
||||||
size="xs"
|
size="xs"
|
||||||
colorScheme="gray"
|
colorScheme="gray"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
import { useEffect, useState, type DragEvent } from "react";
|
import { useEffect, useState, type DragEvent } from "react";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { useExperiment, useExperimentAccess, useHandledAsyncCallback } from "~/utils/hooks";
|
import { useExperimentAccess, useHandledAsyncCallback, useScenarioVars } from "~/utils/hooks";
|
||||||
import { type Scenario } from "./types";
|
import { type Scenario } from "./types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -41,8 +41,7 @@ export default function ScenarioEditor({
|
|||||||
if (savedValues) setValues(savedValues);
|
if (savedValues) setValues(savedValues);
|
||||||
}, [savedValues]);
|
}, [savedValues]);
|
||||||
|
|
||||||
const experiment = useExperiment();
|
const vars = useScenarioVars();
|
||||||
const vars = api.templateVars.list.useQuery({ experimentId: experiment.data?.id ?? "" });
|
|
||||||
|
|
||||||
const variableLabels = vars.data?.map((v) => v.label) ?? [];
|
const variableLabels = vars.data?.map((v) => v.label) ?? [];
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ export const ScenarioEditorModal = ({
|
|||||||
await utils.scenarios.list.invalidate();
|
await utils.scenarios.list.invalidate();
|
||||||
}, [mutation, values]);
|
}, [mutation, values]);
|
||||||
|
|
||||||
const vars = api.templateVars.list.useQuery({ experimentId: experiment.data?.id ?? "" });
|
const vars = api.scenarioVars.list.useQuery({ experimentId: experiment.data?.id ?? "" });
|
||||||
const variableLabels = vars.data?.map((v) => v.label) ?? [];
|
const variableLabels = vars.data?.map((v) => v.label) ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
16
app/src/components/OutputsTable/ScenarioPaginator.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { type StackProps } from "@chakra-ui/react";
|
||||||
|
|
||||||
|
import { useScenarios } from "~/utils/hooks";
|
||||||
|
import Paginator from "../Paginator";
|
||||||
|
|
||||||
|
const ScenarioPaginator = (props: StackProps) => {
|
||||||
|
const { data } = useScenarios();
|
||||||
|
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
const { count } = data;
|
||||||
|
|
||||||
|
return <Paginator count={count} condense {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScenarioPaginator;
|
||||||
@@ -10,6 +10,8 @@ const ScenarioRow = (props: {
|
|||||||
variants: PromptVariant[];
|
variants: PromptVariant[];
|
||||||
canHide: boolean;
|
canHide: boolean;
|
||||||
rowStart: number;
|
rowStart: number;
|
||||||
|
isFirst: boolean;
|
||||||
|
isLast: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
@@ -21,10 +23,14 @@ const ScenarioRow = (props: {
|
|||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
sx={isHovered ? highlightStyle : undefined}
|
sx={isHovered ? highlightStyle : undefined}
|
||||||
borderLeftWidth={1}
|
bgColor="white"
|
||||||
{...borders}
|
|
||||||
rowStart={props.rowStart}
|
rowStart={props.rowStart}
|
||||||
colStart={1}
|
colStart={1}
|
||||||
|
borderLeftWidth={1}
|
||||||
|
borderTopWidth={props.isFirst ? 1 : 0}
|
||||||
|
borderTopLeftRadius={props.isFirst ? 8 : 0}
|
||||||
|
borderBottomLeftRadius={props.isLast ? 8 : 0}
|
||||||
|
{...borders}
|
||||||
>
|
>
|
||||||
<ScenarioEditor scenario={props.scenario} hovered={isHovered} canHide={props.canHide} />
|
<ScenarioEditor scenario={props.scenario} hovered={isHovered} canHide={props.canHide} />
|
||||||
</GridItem>
|
</GridItem>
|
||||||
@@ -34,8 +40,12 @@ const ScenarioRow = (props: {
|
|||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
sx={isHovered ? highlightStyle : undefined}
|
sx={isHovered ? highlightStyle : undefined}
|
||||||
|
bgColor="white"
|
||||||
rowStart={props.rowStart}
|
rowStart={props.rowStart}
|
||||||
colStart={i + 2}
|
colStart={i + 2}
|
||||||
|
borderTopWidth={props.isFirst ? 1 : 0}
|
||||||
|
borderTopRightRadius={props.isFirst && i === props.variants.length - 1 ? 8 : 0}
|
||||||
|
borderBottomRightRadius={props.isLast && i === props.variants.length - 1 ? 8 : 0}
|
||||||
{...borders}
|
{...borders}
|
||||||
>
|
>
|
||||||
<OutputCell key={variant.id} scenario={props.scenario} variant={variant} />
|
<OutputCell key={variant.id} scenario={props.scenario} variant={variant} />
|
||||||
@@ -48,7 +48,7 @@ export const ScenariosHeader = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack w="100%" pb={cellPadding.y} pt={0} align="center" spacing={0}>
|
<HStack w="100%" py={cellPadding.y} px={cellPadding.x} align="center" spacing={0}>
|
||||||
<Text fontSize={16} fontWeight="bold">
|
<Text fontSize={16} fontWeight="bold">
|
||||||
Scenarios ({scenarios.data?.count})
|
Scenarios ({scenarios.data?.count})
|
||||||
</Text>
|
</Text>
|
||||||
@@ -57,11 +57,16 @@ export const ScenariosHeader = () => {
|
|||||||
<MenuButton
|
<MenuButton
|
||||||
as={IconButton}
|
as={IconButton}
|
||||||
mt={1}
|
mt={1}
|
||||||
|
ml={2}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
aria-label="Edit Scenarios"
|
aria-label="Edit Scenarios"
|
||||||
icon={<Icon as={loading ? Spinner : BsGear} />}
|
icon={<Icon as={loading ? Spinner : BsGear} />}
|
||||||
|
maxW={8}
|
||||||
|
minW={8}
|
||||||
|
minH={8}
|
||||||
|
maxH={8}
|
||||||
/>
|
/>
|
||||||
<MenuList fontSize="md" zIndex="dropdown" mt={-3}>
|
<MenuList fontSize="md" zIndex="dropdown" mt={-1}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon as={BsPlus} boxSize={6} mx="-5px" />}
|
icon={<Icon as={BsPlus} boxSize={6} mx="-5px" />}
|
||||||
onClick={() => onAddScenario(false)}
|
onClick={() => onAddScenario(false)}
|
||||||
@@ -72,7 +77,7 @@ export const ScenariosHeader = () => {
|
|||||||
Autogenerate Scenario
|
Autogenerate Scenario
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem icon={<BsPencil />} onClick={openDrawer}>
|
<MenuItem icon={<BsPencil />} onClick={openDrawer}>
|
||||||
Edit Vars
|
Add or Remove Variables
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
@@ -47,7 +47,7 @@ export default function VariantEditor(props: { variant: PromptVariant }) {
|
|||||||
return () => window.removeEventListener("keydown", handleEsc);
|
return () => window.removeEventListener("keydown", handleEsc);
|
||||||
}, [isFullscreen, toggleFullscreen]);
|
}, [isFullscreen, toggleFullscreen]);
|
||||||
|
|
||||||
const lastSavedFn = props.variant.constructFn;
|
const lastSavedFn = props.variant.promptConstructor;
|
||||||
|
|
||||||
const modifierKey = useModifierKeyLabel();
|
const modifierKey = useModifierKeyLabel();
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ export default function VariantEditor(props: { variant: PromptVariant }) {
|
|||||||
|
|
||||||
const resp = await replaceVariant.mutateAsync({
|
const resp = await replaceVariant.mutateAsync({
|
||||||
id: props.variant.id,
|
id: props.variant.id,
|
||||||
constructFn: currentFn,
|
promptConstructor: currentFn,
|
||||||
streamScenarios: visibleScenarios,
|
streamScenarios: visibleScenarios,
|
||||||
});
|
});
|
||||||
if (resp.status === "error") {
|
if (resp.status === "error") {
|
||||||
@@ -17,8 +17,8 @@ export default function VariantStats(props: { variant: PromptVariant }) {
|
|||||||
initialData: {
|
initialData: {
|
||||||
evalResults: [],
|
evalResults: [],
|
||||||
overallCost: 0,
|
overallCost: 0,
|
||||||
promptTokens: 0,
|
inputTokens: 0,
|
||||||
completionTokens: 0,
|
outputTokens: 0,
|
||||||
scenarioCount: 0,
|
scenarioCount: 0,
|
||||||
outputCount: 0,
|
outputCount: 0,
|
||||||
awaitingEvals: false,
|
awaitingEvals: false,
|
||||||
@@ -43,12 +43,12 @@ export default function VariantStats(props: { variant: PromptVariant }) {
|
|||||||
return (
|
return (
|
||||||
<HStack
|
<HStack
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
alignItems="center"
|
alignItems="flex-end"
|
||||||
mx="2"
|
mx="2"
|
||||||
fontSize="xs"
|
fontSize="xs"
|
||||||
py={cellPadding.y}
|
py={cellPadding.y}
|
||||||
>
|
>
|
||||||
<HStack px={cellPadding.x}>
|
<HStack px={cellPadding.x} flexWrap="wrap">
|
||||||
{showNumFinished && (
|
{showNumFinished && (
|
||||||
<Text>
|
<Text>
|
||||||
{data.outputCount} / {data.scenarioCount}
|
{data.outputCount} / {data.scenarioCount}
|
||||||
@@ -68,8 +68,8 @@ export default function VariantStats(props: { variant: PromptVariant }) {
|
|||||||
</HStack>
|
</HStack>
|
||||||
{data.overallCost && (
|
{data.overallCost && (
|
||||||
<CostTooltip
|
<CostTooltip
|
||||||
promptTokens={data.promptTokens}
|
inputTokens={data.inputTokens}
|
||||||
completionTokens={data.completionTokens}
|
outputTokens={data.outputTokens}
|
||||||
cost={data.overallCost}
|
cost={data.overallCost}
|
||||||
>
|
>
|
||||||
<HStack spacing={0} align="center" color="gray.500">
|
<HStack spacing={0} align="center" color="gray.500">
|
||||||
@@ -35,7 +35,7 @@ export default function OutputsTable({ experimentId }: { experimentId: string |
|
|||||||
pb={24}
|
pb={24}
|
||||||
pl={8}
|
pl={8}
|
||||||
display="grid"
|
display="grid"
|
||||||
gridTemplateColumns={`250px repeat(${variants.data.length}, minmax(300px, 1fr)) auto`}
|
gridTemplateColumns={`250px repeat(${variants.data.length}, minmax(320px, 1fr)) auto`}
|
||||||
sx={{
|
sx={{
|
||||||
"> *": {
|
"> *": {
|
||||||
borderColor: "gray.300",
|
borderColor: "gray.300",
|
||||||
@@ -53,20 +53,29 @@ export default function OutputsTable({ experimentId }: { experimentId: string |
|
|||||||
colStart: i + 2,
|
colStart: i + 2,
|
||||||
borderLeftWidth: i === 0 ? 1 : 0,
|
borderLeftWidth: i === 0 ? 1 : 0,
|
||||||
marginLeft: i === 0 ? "-1px" : 0,
|
marginLeft: i === 0 ? "-1px" : 0,
|
||||||
backgroundColor: "gray.100",
|
backgroundColor: "white",
|
||||||
};
|
};
|
||||||
|
const isFirst = i === 0;
|
||||||
|
const isLast = i === variants.data.length - 1;
|
||||||
return (
|
return (
|
||||||
<Fragment key={variant.uiId}>
|
<Fragment key={variant.uiId}>
|
||||||
<VariantHeader
|
<VariantHeader
|
||||||
variant={variant}
|
variant={variant}
|
||||||
canHide={variants.data.length > 1}
|
canHide={variants.data.length > 1}
|
||||||
rowStart={1}
|
rowStart={1}
|
||||||
|
borderTopLeftRadius={isFirst ? 8 : 0}
|
||||||
|
borderTopRightRadius={isLast ? 8 : 0}
|
||||||
{...sharedProps}
|
{...sharedProps}
|
||||||
/>
|
/>
|
||||||
<GridItem rowStart={2} {...sharedProps}>
|
<GridItem rowStart={2} {...sharedProps}>
|
||||||
<VariantEditor variant={variant} />
|
<VariantEditor variant={variant} />
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem rowStart={3} {...sharedProps}>
|
<GridItem
|
||||||
|
rowStart={3}
|
||||||
|
{...sharedProps}
|
||||||
|
borderBottomLeftRadius={isFirst ? 8 : 0}
|
||||||
|
borderBottomRightRadius={isLast ? 8 : 0}
|
||||||
|
>
|
||||||
<VariantStats variant={variant} />
|
<VariantStats variant={variant} />
|
||||||
</GridItem>
|
</GridItem>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@@ -77,7 +86,6 @@ export default function OutputsTable({ experimentId }: { experimentId: string |
|
|||||||
colSpan={allCols - 1}
|
colSpan={allCols - 1}
|
||||||
rowStart={variantHeaderRows + 1}
|
rowStart={variantHeaderRows + 1}
|
||||||
colStart={1}
|
colStart={1}
|
||||||
{...borders}
|
|
||||||
borderRightWidth={0}
|
borderRightWidth={0}
|
||||||
>
|
>
|
||||||
<ScenariosHeader />
|
<ScenariosHeader />
|
||||||
@@ -90,6 +98,8 @@ export default function OutputsTable({ experimentId }: { experimentId: string |
|
|||||||
scenario={scenario}
|
scenario={scenario}
|
||||||
variants={variants.data}
|
variants={variants.data}
|
||||||
canHide={visibleScenariosCount > 1}
|
canHide={visibleScenariosCount > 1}
|
||||||
|
isFirst={i === 0}
|
||||||
|
isLast={i === visibleScenariosCount - 1}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<GridItem
|
<GridItem
|
||||||
119
app/src/components/Paginator.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { HStack, IconButton, Text, Select, type StackProps, Icon } from "@chakra-ui/react";
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { FiChevronsLeft, FiChevronsRight, FiChevronLeft, FiChevronRight } from "react-icons/fi";
|
||||||
|
import { usePageParams } from "~/utils/hooks";
|
||||||
|
|
||||||
|
const pageSizeOptions = [10, 25, 50, 100];
|
||||||
|
|
||||||
|
const Paginator = ({
|
||||||
|
count,
|
||||||
|
condense,
|
||||||
|
...props
|
||||||
|
}: { count: number; condense?: boolean } & StackProps) => {
|
||||||
|
const { page, pageSize, setPageParams } = usePageParams();
|
||||||
|
|
||||||
|
const lastPage = Math.ceil(count / pageSize);
|
||||||
|
|
||||||
|
const updatePageSize = useCallback(
|
||||||
|
(newPageSize: number) => {
|
||||||
|
const newPage = Math.floor(((page - 1) * pageSize) / newPageSize) + 1;
|
||||||
|
setPageParams({ page: newPage, pageSize: newPageSize }, "replace");
|
||||||
|
},
|
||||||
|
[page, pageSize, setPageParams],
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextPage = () => {
|
||||||
|
if (page < lastPage) {
|
||||||
|
setPageParams({ page: page + 1 }, "replace");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevPage = () => {
|
||||||
|
if (page > 1) {
|
||||||
|
setPageParams({ page: page - 1 }, "replace");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToLastPage = () => setPageParams({ page: lastPage }, "replace");
|
||||||
|
const goToFirstPage = () => setPageParams({ page: 1 }, "replace");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack
|
||||||
|
pt={4}
|
||||||
|
spacing={8}
|
||||||
|
justifyContent={condense ? "flex-start" : "space-between"}
|
||||||
|
alignItems="center"
|
||||||
|
w="full"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{!condense && (
|
||||||
|
<>
|
||||||
|
<HStack>
|
||||||
|
<Text>Rows</Text>
|
||||||
|
<Select
|
||||||
|
value={pageSize}
|
||||||
|
onChange={(e) => updatePageSize(parseInt(e.target.value))}
|
||||||
|
w={20}
|
||||||
|
backgroundColor="white"
|
||||||
|
>
|
||||||
|
{pageSizeOptions.map((option) => (
|
||||||
|
<option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</HStack>
|
||||||
|
<Text>
|
||||||
|
Page {page} of {lastPage}
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<HStack>
|
||||||
|
<IconButton
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={goToFirstPage}
|
||||||
|
isDisabled={page === 1}
|
||||||
|
aria-label="Go to first page"
|
||||||
|
icon={<Icon as={FiChevronsLeft} boxSize={5} strokeWidth={1.5} />}
|
||||||
|
bgColor="white"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={prevPage}
|
||||||
|
isDisabled={page === 1}
|
||||||
|
aria-label="Previous page"
|
||||||
|
icon={<Icon as={FiChevronLeft} boxSize={5} strokeWidth={1.5} />}
|
||||||
|
bgColor="white"
|
||||||
|
/>
|
||||||
|
{condense && (
|
||||||
|
<Text>
|
||||||
|
Page {page} of {lastPage}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={nextPage}
|
||||||
|
isDisabled={page === lastPage}
|
||||||
|
aria-label="Next page"
|
||||||
|
icon={<Icon as={FiChevronRight} boxSize={5} strokeWidth={1.5} />}
|
||||||
|
bgColor="white"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={goToLastPage}
|
||||||
|
isDisabled={page === lastPage}
|
||||||
|
aria-label="Go to last page"
|
||||||
|
icon={<Icon as={FiChevronsRight} boxSize={5} strokeWidth={1.5} />}
|
||||||
|
bgColor="white"
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Paginator;
|
||||||