Compare commits
1 Commits
org-to-pro
...
function-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
328cd4f5e6 |
@@ -26,11 +26,6 @@ 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"
|
|
||||||
@@ -37,7 +37,6 @@ const config = {
|
|||||||
"warn",
|
"warn",
|
||||||
{ vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_" },
|
{ vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_" },
|
||||||
],
|
],
|
||||||
"react/no-unescaped-entities": "off",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6,10 +6,6 @@ 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
|
||||||
3
app/.gitignore → .gitignore
vendored
3
app/.gitignore → .gitignore
vendored
@@ -40,6 +40,3 @@ yarn-error.log*
|
|||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
# Sentry Auth Token
|
|
||||||
.sentryclirc
|
|
||||||
@@ -12,22 +12,11 @@ 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/openapi">
|
|
||||||
| StaticRoute<"/api/sentry-example-api">
|
|
||||||
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
|
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
|
||||||
| DynamicRoute<"/data/[id]", { "id": string }>
|
|
||||||
| StaticRoute<"/data">
|
|
||||||
| DynamicRoute<"/experiments/[id]", { "id": string }>
|
| DynamicRoute<"/experiments/[id]", { "id": string }>
|
||||||
| StaticRoute<"/experiments">
|
| StaticRoute<"/experiments">
|
||||||
| StaticRoute<"/">
|
| StaticRoute<"/">;
|
||||||
| StaticRoute<"/logged-calls">
|
|
||||||
| StaticRoute<"/project/settings">
|
|
||||||
| StaticRoute<"/sentry-example-page">
|
|
||||||
| StaticRoute<"/world-champs">
|
|
||||||
| StaticRoute<"/world-champs/signup">;
|
|
||||||
|
|
||||||
interface StaticRoute<Pathname> {
|
interface StaticRoute<Pathname> {
|
||||||
pathname: Pathname;
|
pathname: Pathname;
|
||||||
@@ -20,10 +20,6 @@ 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
|
||||||
74
README.md
74
README.md
@@ -1,62 +1,52 @@
|
|||||||
<!-- <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, and can automatically [translate](#-translate-between-model-apis) those prompts between models.
|
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.
|
||||||
|
|
||||||
<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. Feel free to fork them and start experimenting yourself.
|
These are simple experiments users have created that show how OpenPipe works.
|
||||||
|
|
||||||
- [Twitter Sentiment Analysis](https://app.openpipe.ai/experiments/62c20a73-2012-4a64-973c-4b665ad46a57)
|
- [Country Capitals](https://app.openpipe.ai/experiments/11111111-1111-1111-1111-111111111111)
|
||||||
- [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/).
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
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;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
|
||||||
"spaces": 2,
|
|
||||||
"generator-cli": {
|
|
||||||
"version": "6.6.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
Text,sentiment,emotion
|
|
||||||
@dell your customer service is horrible especially agent syedfaisal who has made this experience of purchasing a new computer downright awful and I’ll reconsider ever buying a Dell in the future @DellTech,negative,anger
|
|
||||||
@zacokalo @Dell @DellCares @Dell give the man what he paid for!,neutral,anger
|
|
||||||
"COOKING STREAM DAY!!! Ty to @Alienware for sponsoring this stream! I’ll be making a bunch of Japanese Alien themed foods hehe
|
|
||||||
|
|
||||||
Come check it out! https://t.co/m06tJQ06zk
|
|
||||||
|
|
||||||
#alienwarepartner #intelgaming @Dell @IntelGaming https://t.co/qOdQX2E8VD",positive,joy
|
|
||||||
@emijuju_ @Alienware @Dell @intel Beautiful 😍❤️😻,positive,joy
|
|
||||||
"What's your biggest data management challenge? • Cloud complexity? • Lengthy tech refresh cycles? • Capital budget constraints? Solve your challenges with as-a-Storage. Get simplicity, agility & control with @Dell #APEX. https://t.co/mCblMtH931 https://t.co/eepKNZ4Ai3",neutral,optimism
|
|
||||||
"This week we were at the ""Top Gun"" themed @Dell Product Expo. Eddie Muñoz met Maverick look-alike, California Tom Cruise (Jerome LeBlanc)!
|
|
||||||
|
|
||||||
""I feel the need, the need for speed."" - Maverick
|
|
||||||
#topgun #topgunmaverick #dell #delltechnologies #lockncharge https://t.co/QHYH2EbMjq",positive,joy
|
|
||||||
"Itsss been more than a week...i m following up with dell for troubleshootings...my https://t.co/lWhg2YKhQa suffering so as my hard earned money...hightly disappointed...contd..
|
|
||||||
@DellCares @Dell",negative,sadness
|
|
||||||
"@ashu_k7 @Dell Pathetic!!!!! I Dont mind taking legal action, this is deficency of service for which the customer is nt getting help..",negative,anger
|
|
||||||
@ashu_k7 @Dell Making life unhappy is the new tag line of #Dell,negative,sadness
|
|
||||||
"@Dell If you are buying a Dell, make sure you are making your life hell.
|
|
||||||
Better buy other laptops. If you wanted to opt for Dell better opt for garbage on the streets.",negative,anger
|
|
||||||
"MY DESK'S FINAL FORM? Seriously, I'm finally happy with my monitor setup here... and I'll keep this setup whenever I move... FOREVER. What do you think?
|
|
||||||
https://t.co/WJZ2JXtOnX
|
|
||||||
@Alienware @Dell cheers. https://t.co/6Whhldfpv0",positive,joy
|
|
||||||
"@Dell Dell Alienware computer has had software problems with SupportAssist since purchase. Dell, despite paying for Premium Support, has never fixed issues. Latest solution was to erase everything and reload....SupportAssist still doesn't work.",negative,anger
|
|
||||||
"HUGE congratulations to Startup Battle 3.0 winner ➡️ @Ox_Fulfillment x @cyborgcharu for being featured in @BusinessInsider & @Dell showcasing the journey at Ox! 🚀🚀🚀
|
|
||||||
|
|
||||||
We love to see our portfolio companies continuing to BUILD SOMETHING FROM NOTHING! 🔥 https://t.co/awBkn5ippB",positive,joy
|
|
||||||
@Dell happy Friday!,positive,joy
|
|
||||||
"@intel Core i5 1135G7 - 4732 points
|
|
||||||
@intel Core i5 1235 - 6619 points
|
|
||||||
@Dell Latitude 5420 x 5430.
|
|
||||||
Cinebench R23. Good job Intel!",positive,joy
|
|
||||||
@Dell india we purchased 52 docking station and we have around 100 users using dell laptop as well as dell monitor now they are refusing to replace my faulty product and disconnecting my every call....,negative,anger
|
|
||||||
"It's another year ans another day But cant fill it in yet the child hood dreams.
|
|
||||||
It's my birthdy today. Can anyone of you guys bless me with a simplest gaming oc that can run
|
|
||||||
@DOTA2 ?
|
|
||||||
@Dell @HP @VastGG @Acer @Alienware @Lenovo @toshiba @IBM @Fujitsu_Global @NEC https://t.co/69G8tL9sN8",neutral,joy
|
|
||||||
"@idoccor @Dell That's always the decision—wait, or, look elsewhere. In this case, I think I unfortunately need to wait since there are only two monitors with these specs and I don't like the other one 😂",negative,sadness
|
|
||||||
"@MichaelDell @Dell @DellCares For how long this will continue. It is high time you either fix the problem for good or replace the complete laptop. Spent over 60+ hours with Customer Care teams, which is not helping. Cannot keep going on like this.",negative,anger
|
|
||||||
"@Dell @DellCares but no, not really",neutral,sadness
|
|
||||||
"Business innovation requires insight, agility and efficiency. How do you get there? RP PRO, LLC recommends starting by proactively managing IT infrastructure with #OpenManage Systems from @Dell. https://t.co/fBcK1lfFMu https://t.co/xWHLkkHCjn",neutral,optimism
|
|
||||||
@Dell Yessirrrrr #NationalCoffeeDay,positive,joy
|
|
||||||
"New blog post from @Dell shared on https://t.co/EgfPChB8AT
|
|
||||||
|
|
||||||
Re-routing Our Connected and Autonomous Future https://t.co/AW8EHQrbd6
|
|
||||||
|
|
||||||
#future #futuretech #techinnovation https://t.co/koX8stKPsr",neutral,joy
|
|
||||||
"In a free-market economy, the folks @IronMountain can set prices as they see fit. Their customers are also free to find better prices at competitors like @Dell
|
|
||||||
@H3CGlobal @HPE
|
|
||||||
https://t.co/reZ56DNTBI",neutral,optimism
|
|
||||||
"Delighted to chat with many of our partners here in person at @Intel Innovation! @Dell, @Lenovo, @Supermicro_SMCI, @QuantaQCT #IntelON https://t.co/BxIeGW8deN",positive,joy
|
|
||||||
"A special gracias to our Startup Chica San Antonio 2022 sponsors @eBay, @jcpenney, @Barbie, @HEB, @Dell, @Honda, @SouthsideSATX💜✨ https://t.co/lZ6WWkziHl",positive,joy
|
|
||||||
"When your team decides to start supporting developers, your #ops must change too. More from @cote and @Dell Developer Community Manager @barton808: https://t.co/W6f1oMiTgV",neutral,optimism
|
|
||||||
@EmDStowers @LASERGIANT1 @ohwormongod @Ludovician_Vega @Dell our boy snitchin,neutral,anger
|
|
||||||
A 1st place dmi:Design Value Award goes to @Dell for a packaging modernization initiative that helped them get closer to their corporate Moonshot Sustainability Goal of 100% recycled or renewable packaging by 2030. More at https://t.co/dnhZWWLCQC #designvalue #DVA22,positive,optimism
|
|
||||||
Reducing deployment and maintenance complexity is the goal behind @dell and @WindRiver's new collaboration. https://t.co/2PxQgPuHUU,positive,optimism
|
|
||||||
@jaserhunter @Dell Love the sales pitch lol,positive,joy
|
|
||||||
@Dell india we purchased 52 docking station and we have around 100 users using dell laptop as well as dell monitor now they are refusing to replace my faulty product and disconnecting my every call....,negative,anger
|
|
||||||
@ashu_k7 @Dell One more example.. their technical support is also worse. https://t.co/20atSgI4fg,negative,anger
|
|
||||||
*angry screeches about @Dell proprietary MBR windows 8.1 partitions not being able to save as an img in clonezilla *,negative,anger
|
|
||||||
@socialitebooks @BBYC_Gamers @Dell @Alienware @BestBuyCanada @intelcanada Congratulations!!!,positive,joy
|
|
||||||
"Thank you to the @dell team for coming out to volunteer today! We truly appreciate your hard work and look forward to seeing you again soon!
|
|
||||||
|
|
||||||
If you and your team are interested in helping out at the UMLAUF, visit our website for more information: https://t.co/lVfsZT2ogS https://t.co/eLz0FY0y4M",positive,joy
|
|
||||||
"@TheCaramelGamer @intel @bravadogaming @Intel_Africa @Dell @DellTech @DellTechMEA @Alienware @IntelUK we love to see it.
|
|
||||||
|
|
||||||
Also also actually actually whoever did that artwork? 🔥🔥🔥 am a fan.",positive,joy
|
|
||||||
"LOVING MY DELL 2 IN 1 LAPTOP
|
|
||||||
YAYY 🥳🥳
|
|
||||||
@Dell #DellInspiron #DellLaptop https://t.co/vib96jf3tC",positive,joy
|
|
||||||
@Azure @OracleItalia @AWS_Italy @lenovoitalia @Dell discussing the future of #HPC during the #hpcroundtable22 in Turin today #highperformancecomputing https://t.co/jJ1WqBulPF,neutral,joy
|
|
||||||
Attracting talent @AmericanChamber. @marg_cola @Dell speaks of quality of life connectivity and the Opportunity for development being so crucial. Housing availability is now impacting on decision making for potential candidates. #WhyCork,positive,optimism
|
|
||||||
.@Dell partners with @WindRiver on modular cloud-native telecommunications infrastructure https://t.co/4SWATspwCP @SiliconANGLE @Mike_Wheatley @holgermu @constellationr,neutral,joy
|
|
||||||
@Dell Not buy Dell Inspiron laptop,neutral,sadness
|
|
||||||
"@dell #delltechforum reminding us IDC have predicted that by 2024, 50% of everything we consume in technology will be as a service https://t.co/3UBiZJX0LE",neutral,optimism
|
|
||||||
@RachMurph @HETTShow @Dell Thank you for coming! Great evening,positive,joy
|
|
||||||
Congratulations to Jason M of Moncton NB on winning a @Dell @Alienware m15 R7 15.6″ gaming laptop from @BestBuyCanada and @intelcanada's gaming days #contest on the blog. Visit https://t.co/VryaY5Rvv9 to learn about tech and for chances to win new tech. https://t.co/T6n0dzF6oL,positive,joy
|
|
||||||
@MattVisiwig @Dell Sour taste for sure 😶 But don't let ego distract you from what you really want to buy 😁,neutral,optimism
|
|
||||||
"Massive thank you goes to sponsors @HendersonLoggie @lindsaysnews @Dell @unity, all of our fantastic judges and mentors and the team at @EGX and @ExCeLLondon.
|
|
||||||
|
|
||||||
Big congratulations also to all of our other @AbertayDare teams - an amazing year! #Dare2022 https://t.co/jYe4agO7lW",positive,joy
|
|
||||||
"@timetcetera @rahaug Nah, I just need @Dell to start paying me comissions 😂",neutral,joy
|
|
||||||
"""Whether you’re an engineer, a designer, or work in supply chain management or sales, there are always opportunities to think about sustainability and how you can do things more efficiently."" 👏 — Oliver Campbell, Director of Packaging Engineering, @Dell https://t.co/vUJLTWNFwP https://t.co/GJWAzGfAxJ",positive,optimism
|
|
||||||
"Hi, my name is @listerepvp and I support @Dell, always.",positive,joy
|
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
-- CreateTable
|
|
||||||
CREATE TABLE "WorldChampEntrant" (
|
|
||||||
"id" UUID NOT NULL,
|
|
||||||
"userId" UUID NOT NULL,
|
|
||||||
"approved" BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "WorldChampEntrant_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "WorldChampEntrant_userId_key" ON "WorldChampEntrant"("userId");
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "WorldChampEntrant" ADD CONSTRAINT "WorldChampEntrant_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
-- CreateEnum
|
|
||||||
CREATE TYPE "UserRole" AS ENUM ('ADMIN', 'USER');
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ADD COLUMN "role" "UserRole" NOT NULL DEFAULT 'USER';
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
-- 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;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/*
|
|
||||||
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";
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
-- 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;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Organization" ADD COLUMN "name" TEXT NOT NULL DEFAULT 'Project 1';
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE "LoggedCall" ALTER COLUMN "modelResponseId" DROP NOT NULL;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
-- 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";
|
|
||||||
@@ -1,406 +0,0 @@
|
|||||||
// This is your Prisma schema file,
|
|
||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
||||||
|
|
||||||
generator client {
|
|
||||||
provider = "prisma-client-js"
|
|
||||||
}
|
|
||||||
|
|
||||||
datasource db {
|
|
||||||
provider = "postgresql"
|
|
||||||
url = env("DATABASE_URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
model Experiment {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
label String
|
|
||||||
|
|
||||||
sortIndex Int @default(0)
|
|
||||||
|
|
||||||
projectId String @db.Uuid
|
|
||||||
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
templateVariables TemplateVariable[]
|
|
||||||
promptVariants PromptVariant[]
|
|
||||||
testScenarios TestScenario[]
|
|
||||||
evaluations Evaluation[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model PromptVariant {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
label String
|
|
||||||
promptConstructor String
|
|
||||||
promptConstructorVersion Int
|
|
||||||
model String
|
|
||||||
modelProvider String
|
|
||||||
|
|
||||||
uiId String @default(uuid()) @db.Uuid
|
|
||||||
visible Boolean @default(true)
|
|
||||||
sortIndex Int @default(0)
|
|
||||||
|
|
||||||
experimentId String @db.Uuid
|
|
||||||
experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
scenarioVariantCells ScenarioVariantCell[]
|
|
||||||
|
|
||||||
@@index([uiId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model TestScenario {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
variableValues Json
|
|
||||||
|
|
||||||
uiId String @default(uuid()) @db.Uuid
|
|
||||||
visible Boolean @default(true)
|
|
||||||
sortIndex Int @default(0)
|
|
||||||
|
|
||||||
experimentId String @db.Uuid
|
|
||||||
experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
scenarioVariantCells ScenarioVariantCell[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model TemplateVariable {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
label String
|
|
||||||
|
|
||||||
experimentId String @db.Uuid
|
|
||||||
experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CellRetrievalStatus {
|
|
||||||
PENDING
|
|
||||||
IN_PROGRESS
|
|
||||||
COMPLETE
|
|
||||||
ERROR
|
|
||||||
}
|
|
||||||
|
|
||||||
model ScenarioVariantCell {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
retrievalStatus CellRetrievalStatus @default(COMPLETE)
|
|
||||||
jobQueuedAt DateTime?
|
|
||||||
jobStartedAt DateTime?
|
|
||||||
modelResponses ModelResponse[]
|
|
||||||
errorMessage String? // Contains errors that occurred independently of model responses
|
|
||||||
|
|
||||||
promptVariantId String @db.Uuid
|
|
||||||
promptVariant PromptVariant @relation(fields: [promptVariantId], references: [id], onDelete: Cascade)
|
|
||||||
prompt Json?
|
|
||||||
|
|
||||||
testScenarioId String @db.Uuid
|
|
||||||
testScenario TestScenario @relation(fields: [testScenarioId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
@@unique([promptVariantId, testScenarioId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model ModelResponse {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
inputHash String
|
|
||||||
requestedAt DateTime?
|
|
||||||
receivedAt DateTime?
|
|
||||||
output Json?
|
|
||||||
cost Float?
|
|
||||||
promptTokens Int?
|
|
||||||
completionTokens Int?
|
|
||||||
statusCode Int?
|
|
||||||
errorMessage String?
|
|
||||||
retryTime DateTime?
|
|
||||||
outdated Boolean @default(false)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
scenarioVariantCellId String @db.Uuid
|
|
||||||
scenarioVariantCell ScenarioVariantCell @relation(fields: [scenarioVariantCellId], references: [id], onDelete: Cascade)
|
|
||||||
outputEvaluations OutputEvaluation[]
|
|
||||||
|
|
||||||
@@index([inputHash])
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EvalType {
|
|
||||||
CONTAINS
|
|
||||||
DOES_NOT_CONTAIN
|
|
||||||
GPT4_EVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
model Evaluation {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
label String
|
|
||||||
evalType EvalType
|
|
||||||
value String
|
|
||||||
|
|
||||||
experimentId String @db.Uuid
|
|
||||||
experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
outputEvaluations OutputEvaluation[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model OutputEvaluation {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
// Number between 0 (fail) and 1 (pass)
|
|
||||||
result Float
|
|
||||||
details String?
|
|
||||||
|
|
||||||
modelResponseId String @db.Uuid
|
|
||||||
modelResponse ModelResponse @relation(fields: [modelResponseId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
evaluationId String @db.Uuid
|
|
||||||
evaluation Evaluation @relation(fields: [evaluationId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
@@unique([modelResponseId, evaluationId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model Dataset {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
name String
|
|
||||||
datasetEntries DatasetEntry[]
|
|
||||||
|
|
||||||
projectId String @db.Uuid
|
|
||||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
MEMBER
|
|
||||||
VIEWER
|
|
||||||
}
|
|
||||||
|
|
||||||
model ProjectUser {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
role ProjectUserRole
|
|
||||||
|
|
||||||
projectId String @db.Uuid
|
|
||||||
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
userId String @db.Uuid
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
@@unique([projectId, userId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model WorldChampEntrant {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
userId String @db.Uuid
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
approved Boolean @default(false)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
@@unique([userId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model LoggedCall {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
startTime 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)
|
|
||||||
|
|
||||||
tags LoggedCallTag[]
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
@@index([startTime])
|
|
||||||
}
|
|
||||||
|
|
||||||
model LoggedCallModelResponse {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
|
|
||||||
reqPayload Json
|
|
||||||
|
|
||||||
// The HTTP status returned by the model provider
|
|
||||||
respStatus Int?
|
|
||||||
respPayload Json?
|
|
||||||
|
|
||||||
// Should be null if the request was successful, and some string if the request failed.
|
|
||||||
error String?
|
|
||||||
|
|
||||||
startTime DateTime
|
|
||||||
endTime 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?
|
|
||||||
totalCost 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 {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
userId String @db.Uuid
|
|
||||||
type String
|
|
||||||
provider String
|
|
||||||
providerAccountId String
|
|
||||||
refresh_token String? @db.Text
|
|
||||||
refresh_token_expires_in Int?
|
|
||||||
access_token String? @db.Text
|
|
||||||
expires_at Int?
|
|
||||||
token_type String?
|
|
||||||
scope String?
|
|
||||||
id_token String? @db.Text
|
|
||||||
session_state String?
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@unique([provider, providerAccountId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model Session {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
sessionToken String @unique
|
|
||||||
userId String @db.Uuid
|
|
||||||
expires DateTime
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UserRole {
|
|
||||||
ADMIN
|
|
||||||
USER
|
|
||||||
}
|
|
||||||
|
|
||||||
model User {
|
|
||||||
id String @id @default(uuid()) @db.Uuid
|
|
||||||
name String?
|
|
||||||
email String? @unique
|
|
||||||
emailVerified DateTime?
|
|
||||||
image String?
|
|
||||||
|
|
||||||
role UserRole @default(USER)
|
|
||||||
|
|
||||||
accounts Account[]
|
|
||||||
sessions Session[]
|
|
||||||
projectUsers ProjectUser[]
|
|
||||||
projects Project[]
|
|
||||||
worldChampEntrant WorldChampEntrant?
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
model VerificationToken {
|
|
||||||
identifier String
|
|
||||||
token String @unique
|
|
||||||
expires DateTime
|
|
||||||
|
|
||||||
@@unique([identifier, token])
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
import { prisma } from "~/server/db";
|
|
||||||
import { generateNewCell } from "~/server/utils/generateNewCell";
|
|
||||||
import dedent from "dedent";
|
|
||||||
import { execSync } from "child_process";
|
|
||||||
import fs from "fs";
|
|
||||||
import { promptConstructorVersion } from "~/promptConstructor/version";
|
|
||||||
|
|
||||||
const defaultId = "11111111-1111-1111-1111-111111111112";
|
|
||||||
|
|
||||||
await prisma.project.deleteMany({
|
|
||||||
where: { id: defaultId },
|
|
||||||
});
|
|
||||||
|
|
||||||
// If there's an existing project, just seed into it
|
|
||||||
const project =
|
|
||||||
(await prisma.project.findFirst({})) ??
|
|
||||||
(await prisma.project.create({
|
|
||||||
data: { id: defaultId },
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Clone the repo from git@github.com:microsoft/AGIEval.git into a tmp dir if it doesn't exist
|
|
||||||
const tmpDir = "/tmp/agi-eval";
|
|
||||||
if (!fs.existsSync(tmpDir)) {
|
|
||||||
execSync(`git clone git@github.com:microsoft/AGIEval.git ${tmpDir}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const datasets = [
|
|
||||||
"sat-en",
|
|
||||||
"sat-math",
|
|
||||||
"lsat-rc",
|
|
||||||
"lsat-ar",
|
|
||||||
"aqua-rat",
|
|
||||||
"logiqa-en",
|
|
||||||
"lsat-lr",
|
|
||||||
"math",
|
|
||||||
];
|
|
||||||
|
|
||||||
type Scenario = {
|
|
||||||
passage: string | null;
|
|
||||||
question: string;
|
|
||||||
options: string[] | null;
|
|
||||||
label: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const dataset of datasets) {
|
|
||||||
const experimentName = `AGI-Eval: ${dataset}`;
|
|
||||||
const oldExperiment = await prisma.experiment.findFirst({
|
|
||||||
where: {
|
|
||||||
label: experimentName,
|
|
||||||
projectId: project.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (oldExperiment) {
|
|
||||||
await prisma.experiment.deleteMany({
|
|
||||||
where: { id: oldExperiment.id },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const experiment = await prisma.experiment.create({
|
|
||||||
data: {
|
|
||||||
id: oldExperiment?.id ?? undefined,
|
|
||||||
label: experimentName,
|
|
||||||
projectId: project.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const scenarios: Scenario[] = fs
|
|
||||||
.readFileSync(`${tmpDir}/data/v1/${dataset}.jsonl`, "utf8")
|
|
||||||
.split("\n")
|
|
||||||
.filter((line) => line.length > 0)
|
|
||||||
.map((line) => JSON.parse(line) as Scenario);
|
|
||||||
console.log("scenarios", scenarios.length);
|
|
||||||
|
|
||||||
await prisma.testScenario.createMany({
|
|
||||||
data: scenarios.slice(0, 30).map((scenario, i) => ({
|
|
||||||
experimentId: experiment.id,
|
|
||||||
sortIndex: i,
|
|
||||||
variableValues: {
|
|
||||||
passage: scenario.passage,
|
|
||||||
question: scenario.question,
|
|
||||||
options: scenario.options?.join("\n"),
|
|
||||||
label: scenario.label,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
await prisma.templateVariable.createMany({
|
|
||||||
data: ["passage", "question", "options", "label"].map((label) => ({
|
|
||||||
experimentId: experiment.id,
|
|
||||||
label,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
await prisma.promptVariant.createMany({
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
experimentId: experiment.id,
|
|
||||||
label: "Prompt Variant 1",
|
|
||||||
sortIndex: 0,
|
|
||||||
model: "gpt-3.5-turbo-0613",
|
|
||||||
modelProvider: "openai/ChatCompletion",
|
|
||||||
promptConstructorVersion,
|
|
||||||
promptConstructor: dedent`
|
|
||||||
definePrompt("openai/ChatCompletion", {
|
|
||||||
model: "gpt-3.5-turbo-0613",
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: "user",
|
|
||||||
content: \`Passage: ${"$"}{scenario.passage}\n\nQuestion: ${"$"}{scenario.question}\n\nOptions: ${"$"}{scenario.options}\n\n Respond with just the letter of the best option in the format Answer: (A).\`
|
|
||||||
}
|
|
||||||
],
|
|
||||||
temperature: 0,
|
|
||||||
})`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await prisma.evaluation.createMany({
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
experimentId: experiment.id,
|
|
||||||
label: "Eval",
|
|
||||||
evalType: "CONTAINS",
|
|
||||||
value: "Answer: ({{label}})",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,114 +0,0 @@
|
|||||||
import { prisma } from "~/server/db";
|
|
||||||
import dedent from "dedent";
|
|
||||||
import fs from "fs";
|
|
||||||
import { parse } from "csv-parse/sync";
|
|
||||||
import { promptConstructorVersion } from "~/promptConstructor/version";
|
|
||||||
|
|
||||||
const defaultId = "11111111-1111-1111-1111-111111111112";
|
|
||||||
|
|
||||||
await prisma.project.deleteMany({
|
|
||||||
where: { id: defaultId },
|
|
||||||
});
|
|
||||||
|
|
||||||
// If there's an existing project, just seed into it
|
|
||||||
const project =
|
|
||||||
(await prisma.project.findFirst({})) ??
|
|
||||||
(await prisma.project.create({
|
|
||||||
data: { id: defaultId },
|
|
||||||
}));
|
|
||||||
|
|
||||||
type Scenario = {
|
|
||||||
text: string;
|
|
||||||
sentiment: string;
|
|
||||||
emotion: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const experimentName = `Twitter Sentiment Analysis`;
|
|
||||||
const oldExperiment = await prisma.experiment.findFirst({
|
|
||||||
where: {
|
|
||||||
label: experimentName,
|
|
||||||
projectId: project.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (oldExperiment) {
|
|
||||||
await prisma.experiment.deleteMany({
|
|
||||||
where: { id: oldExperiment.id },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const experiment = await prisma.experiment.create({
|
|
||||||
data: {
|
|
||||||
id: oldExperiment?.id ?? undefined,
|
|
||||||
label: experimentName,
|
|
||||||
projectId: project.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const content = fs.readFileSync("./prisma/datasets/validated_tweets.csv", "utf8");
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const records: any[] = parse(content, { delimiter: ",", from_line: 2 });
|
|
||||||
|
|
||||||
console.log("records", records);
|
|
||||||
|
|
||||||
const scenarios: Scenario[] = records.map((row) => ({
|
|
||||||
text: row[0],
|
|
||||||
sentiment: row[1],
|
|
||||||
emotion: row[2],
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.log("scenarios", scenarios.length);
|
|
||||||
|
|
||||||
await prisma.testScenario.createMany({
|
|
||||||
data: scenarios.slice(0, 30).map((scenario, i) => ({
|
|
||||||
experimentId: experiment.id,
|
|
||||||
sortIndex: i,
|
|
||||||
variableValues: {
|
|
||||||
text: scenario.text,
|
|
||||||
sentiment: scenario.sentiment,
|
|
||||||
emotion: scenario.emotion,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
await prisma.templateVariable.createMany({
|
|
||||||
data: ["text", "sentiment", "emotion"].map((label) => ({
|
|
||||||
experimentId: experiment.id,
|
|
||||||
label,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
await prisma.promptVariant.createMany({
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
experimentId: experiment.id,
|
|
||||||
label: "Prompt Variant 1",
|
|
||||||
sortIndex: 0,
|
|
||||||
model: "gpt-3.5-turbo-0613",
|
|
||||||
modelProvider: "openai/ChatCompletion",
|
|
||||||
promptConstructorVersion,
|
|
||||||
promptConstructor: dedent`
|
|
||||||
definePrompt("openai/ChatCompletion", {
|
|
||||||
model: "gpt-3.5-turbo-0613",
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: "user",
|
|
||||||
content: \`Text: ${"$"}{scenario.text}\n\nRespond with the sentiment (negative|neutral|positive) and emotion (optimism|joy|anger|sadness) of the tweet in this format: "answer: <sentiment>-<emotion>".\`
|
|
||||||
}
|
|
||||||
],
|
|
||||||
temperature: 0,
|
|
||||||
})`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await prisma.evaluation.createMany({
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
experimentId: experiment.id,
|
|
||||||
label: "Eval",
|
|
||||||
evalType: "CONTAINS",
|
|
||||||
value: "answer: {{sentiment}}-{{emotion}}",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB |
@@ -1,33 +0,0 @@
|
|||||||
// 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,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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}>
|
|
||||||
{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,36 +0,0 @@
|
|||||||
import {
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalOverlay,
|
|
||||||
type UseDisclosureReturn,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { type RouterOutputs } from "~/utils/api";
|
|
||||||
import { JSONTree } from "react-json-tree";
|
|
||||||
|
|
||||||
export default function ExpandedModal(props: {
|
|
||||||
cell: NonNullable<RouterOutputs["scenarioVariantCells"]["get"]>;
|
|
||||||
disclosure: UseDisclosureReturn;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Modal isOpen={props.disclosure.isOpen} onClose={props.disclosure.onClose} size="2xl">
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>Prompt</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody>
|
|
||||||
<JSONTree
|
|
||||||
data={props.cell.prompt}
|
|
||||||
invertTheme={true}
|
|
||||||
theme="chalk"
|
|
||||||
shouldExpandNodeInitially={() => true}
|
|
||||||
getItemString={() => ""}
|
|
||||||
hideRoot
|
|
||||||
/>
|
|
||||||
</ModalBody>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { HStack, Icon, IconButton, Spinner, Tooltip, useDisclosure } from "@chakra-ui/react";
|
|
||||||
import { BsArrowClockwise, BsInfoCircle } from "react-icons/bs";
|
|
||||||
import { useExperimentAccess } from "~/utils/hooks";
|
|
||||||
import ExpandedModal from "./PromptModal";
|
|
||||||
import { type RouterOutputs } from "~/utils/api";
|
|
||||||
|
|
||||||
export const CellOptions = ({
|
|
||||||
cell,
|
|
||||||
refetchingOutput,
|
|
||||||
refetchOutput,
|
|
||||||
}: {
|
|
||||||
cell: RouterOutputs["scenarioVariantCells"]["get"];
|
|
||||||
refetchingOutput: boolean;
|
|
||||||
refetchOutput: () => void;
|
|
||||||
}) => {
|
|
||||||
const { canModify } = useExperimentAccess();
|
|
||||||
|
|
||||||
const modalDisclosure = useDisclosure();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HStack justifyContent="flex-end" w="full" spacing={1}>
|
|
||||||
{cell && (
|
|
||||||
<>
|
|
||||||
<Tooltip label="See Prompt">
|
|
||||||
<IconButton
|
|
||||||
aria-label="See Prompt"
|
|
||||||
icon={<Icon as={BsInfoCircle} boxSize={3.5} />}
|
|
||||||
onClick={modalDisclosure.onOpen}
|
|
||||||
size="xs"
|
|
||||||
colorScheme="gray"
|
|
||||||
color="gray.500"
|
|
||||||
variant="ghost"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<ExpandedModal cell={cell} disclosure={modalDisclosure} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{canModify && (
|
|
||||||
<Tooltip label="Refetch output">
|
|
||||||
<IconButton
|
|
||||||
size="xs"
|
|
||||||
color="gray.500"
|
|
||||||
variant="ghost"
|
|
||||||
cursor="pointer"
|
|
||||||
onClick={refetchOutput}
|
|
||||||
aria-label="refetch output"
|
|
||||||
icon={<Icon as={refetchingOutput ? Spinner : BsArrowClockwise} boxSize={4} />}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</HStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
import { isEqual } from "lodash-es";
|
|
||||||
import { useEffect, useState, type DragEvent } from "react";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import { useExperiment, useExperimentAccess, useHandledAsyncCallback } from "~/utils/hooks";
|
|
||||||
import { type Scenario } from "./types";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
HStack,
|
|
||||||
Icon,
|
|
||||||
IconButton,
|
|
||||||
Spinner,
|
|
||||||
Text,
|
|
||||||
Tooltip,
|
|
||||||
VStack,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { BsArrowsAngleExpand, BsX } from "react-icons/bs";
|
|
||||||
import { cellPadding } from "../constants";
|
|
||||||
import { FloatingLabelInput } from "./FloatingLabelInput";
|
|
||||||
import { ScenarioEditorModal } from "./ScenarioEditorModal";
|
|
||||||
|
|
||||||
export default function ScenarioEditor({
|
|
||||||
scenario,
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
scenario: Scenario;
|
|
||||||
hovered: boolean;
|
|
||||||
canHide: boolean;
|
|
||||||
}) {
|
|
||||||
const { canModify } = useExperimentAccess();
|
|
||||||
|
|
||||||
const savedValues = scenario.variableValues as Record<string, string>;
|
|
||||||
const utils = api.useContext();
|
|
||||||
const [isDragTarget, setIsDragTarget] = useState(false);
|
|
||||||
const [variableInputHovered, setVariableInputHovered] = useState(false);
|
|
||||||
|
|
||||||
const [values, setValues] = useState<Record<string, string>>(savedValues);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (savedValues) setValues(savedValues);
|
|
||||||
}, [savedValues]);
|
|
||||||
|
|
||||||
const experiment = useExperiment();
|
|
||||||
const vars = api.templateVars.list.useQuery({ experimentId: experiment.data?.id ?? "" });
|
|
||||||
|
|
||||||
const variableLabels = vars.data?.map((v) => v.label) ?? [];
|
|
||||||
|
|
||||||
const hasChanged = !isEqual(savedValues, values);
|
|
||||||
|
|
||||||
const mutation = api.scenarios.replaceWithValues.useMutation();
|
|
||||||
|
|
||||||
const [onSave] = useHandledAsyncCallback(async () => {
|
|
||||||
await mutation.mutateAsync({
|
|
||||||
id: scenario.id,
|
|
||||||
values,
|
|
||||||
});
|
|
||||||
await utils.scenarios.list.invalidate();
|
|
||||||
}, [mutation, values]);
|
|
||||||
|
|
||||||
const hideMutation = api.scenarios.hide.useMutation();
|
|
||||||
const [onHide, hidingInProgress] = useHandledAsyncCallback(async () => {
|
|
||||||
await hideMutation.mutateAsync({
|
|
||||||
id: scenario.id,
|
|
||||||
});
|
|
||||||
await utils.scenarios.list.invalidate();
|
|
||||||
await utils.promptVariants.stats.invalidate();
|
|
||||||
}, [hideMutation, scenario.id]);
|
|
||||||
|
|
||||||
const reorderMutation = api.scenarios.reorder.useMutation();
|
|
||||||
const [onReorder] = useHandledAsyncCallback(
|
|
||||||
async (e: DragEvent<HTMLDivElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setIsDragTarget(false);
|
|
||||||
const draggedId = e.dataTransfer.getData("text/plain");
|
|
||||||
const droppedId = scenario.id;
|
|
||||||
if (!draggedId || !droppedId || draggedId === droppedId) return;
|
|
||||||
await reorderMutation.mutateAsync({
|
|
||||||
draggedId,
|
|
||||||
droppedId,
|
|
||||||
});
|
|
||||||
await utils.scenarios.list.invalidate();
|
|
||||||
},
|
|
||||||
[reorderMutation, scenario.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [scenarioEditorModalOpen, setScenarioEditorModalOpen] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HStack
|
|
||||||
alignItems="flex-start"
|
|
||||||
px={cellPadding.x}
|
|
||||||
py={cellPadding.y}
|
|
||||||
spacing={0}
|
|
||||||
height="100%"
|
|
||||||
draggable={!variableInputHovered}
|
|
||||||
onDragStart={(e) => {
|
|
||||||
e.dataTransfer.setData("text/plain", scenario.id);
|
|
||||||
e.currentTarget.style.opacity = "0.4";
|
|
||||||
}}
|
|
||||||
onDragEnd={(e) => {
|
|
||||||
e.currentTarget.style.opacity = "1";
|
|
||||||
}}
|
|
||||||
onDragOver={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setIsDragTarget(true);
|
|
||||||
}}
|
|
||||||
onDragLeave={() => {
|
|
||||||
setIsDragTarget(false);
|
|
||||||
}}
|
|
||||||
onDrop={onReorder}
|
|
||||||
backgroundColor={isDragTarget ? "gray.100" : "transparent"}
|
|
||||||
>
|
|
||||||
{variableLabels.length === 0 ? (
|
|
||||||
<Box color="gray.500">
|
|
||||||
{vars.data ? "No scenario variables configured" : "Loading..."}
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<VStack spacing={4} flex={1} py={2}>
|
|
||||||
<HStack justifyContent="space-between" w="100%" align="center" spacing={0}>
|
|
||||||
<Text flex={1}>Scenario</Text>
|
|
||||||
<Tooltip label="Expand" hasArrow>
|
|
||||||
<IconButton
|
|
||||||
aria-label="Expand"
|
|
||||||
icon={<Icon as={BsArrowsAngleExpand} boxSize={3} />}
|
|
||||||
onClick={() => setScenarioEditorModalOpen(true)}
|
|
||||||
size="xs"
|
|
||||||
colorScheme="gray"
|
|
||||||
color="gray.500"
|
|
||||||
variant="ghost"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
{canModify && props.canHide && (
|
|
||||||
<Tooltip label="Delete" hasArrow>
|
|
||||||
<IconButton
|
|
||||||
aria-label="Delete"
|
|
||||||
icon={
|
|
||||||
<Icon
|
|
||||||
as={hidingInProgress ? Spinner : BsX}
|
|
||||||
boxSize={hidingInProgress ? 4 : 6}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
onClick={onHide}
|
|
||||||
size="xs"
|
|
||||||
display="flex"
|
|
||||||
colorScheme="gray"
|
|
||||||
color="gray.500"
|
|
||||||
variant="ghost"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</HStack>
|
|
||||||
{variableLabels.map((key) => {
|
|
||||||
const value = values[key] ?? "";
|
|
||||||
return (
|
|
||||||
<FloatingLabelInput
|
|
||||||
key={key}
|
|
||||||
label={key}
|
|
||||||
isDisabled={!canModify}
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
maxHeight={32}
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => {
|
|
||||||
setValues((prev) => ({ ...prev, [key]: e.target.value }));
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.currentTarget.blur();
|
|
||||||
onSave();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onMouseEnter={() => setVariableInputHovered(true)}
|
|
||||||
onMouseLeave={() => setVariableInputHovered(false)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{hasChanged && (
|
|
||||||
<HStack justify="right">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
onMouseDown={() => {
|
|
||||||
setValues(savedValues);
|
|
||||||
}}
|
|
||||||
colorScheme="gray"
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" onMouseDown={onSave} colorScheme="blue">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
|
||||||
)}
|
|
||||||
</VStack>
|
|
||||||
)}
|
|
||||||
</HStack>
|
|
||||||
{scenarioEditorModalOpen && (
|
|
||||||
<ScenarioEditorModal
|
|
||||||
scenarioId={scenario.id}
|
|
||||||
initialValues={savedValues}
|
|
||||||
onClose={() => setScenarioEditorModalOpen(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
import {
|
|
||||||
Button,
|
|
||||||
HStack,
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalContent,
|
|
||||||
ModalFooter,
|
|
||||||
ModalHeader,
|
|
||||||
ModalOverlay,
|
|
||||||
Spinner,
|
|
||||||
Text,
|
|
||||||
VStack,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { isEqual } from "lodash-es";
|
|
||||||
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import {
|
|
||||||
useScenario,
|
|
||||||
useHandledAsyncCallback,
|
|
||||||
useExperiment,
|
|
||||||
useExperimentAccess,
|
|
||||||
} from "~/utils/hooks";
|
|
||||||
import { FloatingLabelInput } from "./FloatingLabelInput";
|
|
||||||
|
|
||||||
export const ScenarioEditorModal = ({
|
|
||||||
scenarioId,
|
|
||||||
initialValues,
|
|
||||||
onClose,
|
|
||||||
}: {
|
|
||||||
scenarioId: string;
|
|
||||||
initialValues: Record<string, string>;
|
|
||||||
onClose: () => void;
|
|
||||||
}) => {
|
|
||||||
const utils = api.useContext();
|
|
||||||
const experiment = useExperiment();
|
|
||||||
const { canModify } = useExperimentAccess();
|
|
||||||
const scenario = useScenario(scenarioId);
|
|
||||||
|
|
||||||
const savedValues = scenario.data?.variableValues as Record<string, string>;
|
|
||||||
|
|
||||||
const [values, setValues] = useState<Record<string, string>>(initialValues);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (savedValues) setValues(savedValues);
|
|
||||||
}, [savedValues]);
|
|
||||||
|
|
||||||
const hasChanged = !isEqual(savedValues, values);
|
|
||||||
|
|
||||||
const mutation = api.scenarios.replaceWithValues.useMutation();
|
|
||||||
|
|
||||||
const [onSave, saving] = useHandledAsyncCallback(async () => {
|
|
||||||
await mutation.mutateAsync({
|
|
||||||
id: scenarioId,
|
|
||||||
values,
|
|
||||||
});
|
|
||||||
await utils.scenarios.list.invalidate();
|
|
||||||
}, [mutation, values]);
|
|
||||||
|
|
||||||
const vars = api.templateVars.list.useQuery({ experimentId: experiment.data?.id ?? "" });
|
|
||||||
const variableLabels = vars.data?.map((v) => v.label) ?? [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen
|
|
||||||
onClose={onClose}
|
|
||||||
size={{ base: "xl", sm: "2xl", md: "3xl", lg: "5xl", xl: "7xl" }}
|
|
||||||
>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent w={1200}>
|
|
||||||
<ModalHeader />
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody maxW="unset">
|
|
||||||
<VStack spacing={8}>
|
|
||||||
{values &&
|
|
||||||
variableLabels.map((key) => {
|
|
||||||
const value = values[key] ?? "";
|
|
||||||
return (
|
|
||||||
<FloatingLabelInput
|
|
||||||
key={key}
|
|
||||||
label={key}
|
|
||||||
isDisabled={!canModify}
|
|
||||||
_disabled={{ opacity: 1 }}
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => {
|
|
||||||
setValues((prev) => ({ ...prev, [key]: e.target.value }));
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.currentTarget.blur();
|
|
||||||
onSave();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</VStack>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
{canModify && (
|
|
||||||
<HStack>
|
|
||||||
<Button
|
|
||||||
colorScheme="gray"
|
|
||||||
onClick={() => setValues(savedValues)}
|
|
||||||
minW={24}
|
|
||||||
isDisabled={!hasChanged}
|
|
||||||
>
|
|
||||||
<Text>Reset</Text>
|
|
||||||
</Button>
|
|
||||||
<Button colorScheme="blue" onClick={onSave} minW={24} isDisabled={!hasChanged}>
|
|
||||||
{saving ? <Spinner boxSize={4} /> : <Text>Save</Text>}
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
|
||||||
)}
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { useScenarios } from "~/utils/hooks";
|
|
||||||
import Paginator from "../Paginator";
|
|
||||||
|
|
||||||
const ScenarioPaginator = () => {
|
|
||||||
const { data } = useScenarios();
|
|
||||||
|
|
||||||
if (!data) return null;
|
|
||||||
|
|
||||||
const { scenarios, startIndex, lastPage, count } = data;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paginator
|
|
||||||
numItemsLoaded={scenarios.length}
|
|
||||||
startIndex={startIndex}
|
|
||||||
lastPage={lastPage}
|
|
||||||
count={count}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ScenarioPaginator;
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { VStack, HStack, type StackProps, Text, Divider } from "@chakra-ui/react";
|
|
||||||
import Link, { type LinkProps } from "next/link";
|
|
||||||
|
|
||||||
const StatsCard = ({
|
|
||||||
title,
|
|
||||||
href,
|
|
||||||
children,
|
|
||||||
...rest
|
|
||||||
}: { title: string; href: string } & StackProps & LinkProps) => {
|
|
||||||
return (
|
|
||||||
<VStack flex={1} borderWidth={1} padding={4} borderRadius={4} borderColor="gray.300" {...rest}>
|
|
||||||
<HStack w="full" justifyContent="space-between">
|
|
||||||
<Text fontSize="md" fontWeight="bold">
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
<Link href={href}>
|
|
||||||
<Text color="blue">View all</Text>
|
|
||||||
</Link>
|
|
||||||
</HStack>
|
|
||||||
<Divider />
|
|
||||||
{children}
|
|
||||||
</VStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StatsCard;
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
Card,
|
|
||||||
CardHeader,
|
|
||||||
Heading,
|
|
||||||
Table,
|
|
||||||
Tbody,
|
|
||||||
Td,
|
|
||||||
Th,
|
|
||||||
Thead,
|
|
||||||
Tr,
|
|
||||||
Tooltip,
|
|
||||||
Collapse,
|
|
||||||
HStack,
|
|
||||||
VStack,
|
|
||||||
IconButton,
|
|
||||||
useToast,
|
|
||||||
Icon,
|
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
|
||||||
import { ChevronUpIcon, ChevronDownIcon, CopyIcon } from "lucide-react";
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
import { type RouterOutputs, api } from "~/utils/api";
|
|
||||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
|
||||||
import { atelierCaveLight } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
|
||||||
import stringify from "json-stringify-pretty-compact";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
|
||||||
|
|
||||||
type LoggedCall = RouterOutputs["dashboard"]["loggedCalls"][0];
|
|
||||||
|
|
||||||
const FormattedJson = ({ json }: { json: any }) => {
|
|
||||||
const jsonString = stringify(json, { maxLength: 40 });
|
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
const copyToClipboard = async (text: string) => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
toast({
|
|
||||||
title: "Copied to clipboard",
|
|
||||||
status: "success",
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
toast({
|
|
||||||
title: "Failed to copy to clipboard",
|
|
||||||
status: "error",
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box position="relative" fontSize="sm" borderRadius="md" overflow="hidden">
|
|
||||||
<SyntaxHighlighter
|
|
||||||
customStyle={{ overflowX: "unset" }}
|
|
||||||
language="json"
|
|
||||||
style={atelierCaveLight}
|
|
||||||
lineProps={{
|
|
||||||
style: { wordBreak: "break-all", whiteSpace: "pre-wrap" },
|
|
||||||
}}
|
|
||||||
wrapLines
|
|
||||||
>
|
|
||||||
{jsonString}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
<IconButton
|
|
||||||
aria-label="Copy"
|
|
||||||
icon={<CopyIcon />}
|
|
||||||
position="absolute"
|
|
||||||
top={1}
|
|
||||||
right={1}
|
|
||||||
size="xs"
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => void copyToClipboard(jsonString)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function TableRow({
|
|
||||||
loggedCall,
|
|
||||||
isExpanded,
|
|
||||||
onToggle,
|
|
||||||
}: {
|
|
||||||
loggedCall: LoggedCall;
|
|
||||||
isExpanded: boolean;
|
|
||||||
onToggle: () => void;
|
|
||||||
}) {
|
|
||||||
const isError = loggedCall.modelResponse?.respStatus !== 200;
|
|
||||||
const timeAgo = dayjs(loggedCall.startTime).fromNow();
|
|
||||||
const fullTime = dayjs(loggedCall.startTime).toString();
|
|
||||||
|
|
||||||
const model = useMemo(
|
|
||||||
() => loggedCall.tags.find((tag) => tag.name.startsWith("$model"))?.value,
|
|
||||||
[loggedCall.tags],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Tr
|
|
||||||
onClick={onToggle}
|
|
||||||
key={loggedCall.id}
|
|
||||||
_hover={{ bgColor: "gray.100", cursor: "pointer" }}
|
|
||||||
sx={{
|
|
||||||
"> td": { borderBottom: "none" },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Td>
|
|
||||||
<Icon boxSize={6} as={isExpanded ? ChevronUpIcon : ChevronDownIcon} />
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Tooltip label={fullTime} placement="top">
|
|
||||||
<Box whiteSpace="nowrap" minW="120px">
|
|
||||||
{timeAgo}
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
</Td>
|
|
||||||
<Td width="100%">{model}</Td>
|
|
||||||
<Td isNumeric>{((loggedCall.modelResponse?.durationMs ?? 0) / 1000).toFixed(2)}s</Td>
|
|
||||||
<Td isNumeric>{loggedCall.modelResponse?.inputTokens}</Td>
|
|
||||||
<Td isNumeric>{loggedCall.modelResponse?.outputTokens}</Td>
|
|
||||||
<Td sx={{ color: isError ? "red.500" : "green.500", fontWeight: "semibold" }} isNumeric>
|
|
||||||
{loggedCall.modelResponse?.respStatus ?? "No response"}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
<Tr>
|
|
||||||
<Td colSpan={8} p={0}>
|
|
||||||
<Collapse in={isExpanded} unmountOnExit={true}>
|
|
||||||
<VStack p={4} align="stretch">
|
|
||||||
<HStack align="stretch">
|
|
||||||
<VStack flex={1} align="stretch">
|
|
||||||
<Heading size="sm">Input</Heading>
|
|
||||||
<FormattedJson json={loggedCall.modelResponse?.reqPayload} />
|
|
||||||
</VStack>
|
|
||||||
<VStack flex={1} align="stretch">
|
|
||||||
<Heading size="sm">Output</Heading>
|
|
||||||
<FormattedJson json={loggedCall.modelResponse?.respPayload} />
|
|
||||||
</VStack>
|
|
||||||
</HStack>
|
|
||||||
<ButtonGroup alignSelf="flex-end">
|
|
||||||
<Button as={Link} colorScheme="blue" href={{ pathname: "/experiments" }}>
|
|
||||||
Experiments
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</VStack>
|
|
||||||
</Collapse>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LoggedCallTable() {
|
|
||||||
const [expandedRow, setExpandedRow] = useState<string | null>(null);
|
|
||||||
const loggedCalls = api.dashboard.loggedCalls.useQuery({});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card variant="outline" width="100%" overflow="hidden">
|
|
||||||
<CardHeader>
|
|
||||||
<Heading as="h3" size="sm">
|
|
||||||
Logged Calls
|
|
||||||
</Heading>
|
|
||||||
</CardHeader>
|
|
||||||
<Table>
|
|
||||||
<Thead>
|
|
||||||
<Tr>
|
|
||||||
<Th />
|
|
||||||
<Th>Time</Th>
|
|
||||||
<Th>Model</Th>
|
|
||||||
<Th isNumeric>Duration</Th>
|
|
||||||
<Th isNumeric>Input tokens</Th>
|
|
||||||
<Th isNumeric>Output tokens</Th>
|
|
||||||
<Th isNumeric>Status</Th>
|
|
||||||
</Tr>
|
|
||||||
</Thead>
|
|
||||||
<Tbody>
|
|
||||||
{loggedCalls.data?.map((loggedCall) => {
|
|
||||||
return (
|
|
||||||
<TableRow
|
|
||||||
key={loggedCall.id}
|
|
||||||
loggedCall={loggedCall}
|
|
||||||
isExpanded={loggedCall.id === expandedRow}
|
|
||||||
onToggle={() => {
|
|
||||||
if (loggedCall.id === expandedRow) {
|
|
||||||
setExpandedRow(null);
|
|
||||||
} else {
|
|
||||||
setExpandedRow(loggedCall.id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import {
|
|
||||||
HStack,
|
|
||||||
Icon,
|
|
||||||
VStack,
|
|
||||||
Text,
|
|
||||||
Divider,
|
|
||||||
Spinner,
|
|
||||||
AspectRatio,
|
|
||||||
SkeletonText,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { RiDatabase2Line } from "react-icons/ri";
|
|
||||||
import { formatTimePast } from "~/utils/dayjs";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { BsPlusSquare } from "react-icons/bs";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import { useHandledAsyncCallback } from "~/utils/hooks";
|
|
||||||
import { useAppStore } from "~/state/store";
|
|
||||||
|
|
||||||
type DatasetData = {
|
|
||||||
name: string;
|
|
||||||
numEntries: number;
|
|
||||||
id: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DatasetCard = ({ dataset }: { dataset: DatasetData }) => {
|
|
||||||
return (
|
|
||||||
<AspectRatio ratio={1.2} w="full">
|
|
||||||
<VStack
|
|
||||||
as={Link}
|
|
||||||
href={{ pathname: "/data/[id]", query: { id: dataset.id } }}
|
|
||||||
bg="gray.50"
|
|
||||||
_hover={{ bg: "gray.100" }}
|
|
||||||
transition="background 0.2s"
|
|
||||||
cursor="pointer"
|
|
||||||
borderColor="gray.200"
|
|
||||||
borderWidth={1}
|
|
||||||
p={4}
|
|
||||||
justify="space-between"
|
|
||||||
>
|
|
||||||
<HStack w="full" color="gray.700" justify="center">
|
|
||||||
<Icon as={RiDatabase2Line} boxSize={4} />
|
|
||||||
<Text fontWeight="bold">{dataset.name}</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack h="full" spacing={4} flex={1} align="center">
|
|
||||||
<CountLabel label="Rows" count={dataset.numEntries} />
|
|
||||||
</HStack>
|
|
||||||
<HStack w="full" color="gray.500" fontSize="xs" textAlign="center">
|
|
||||||
<Text flex={1}>Created {formatTimePast(dataset.createdAt)}</Text>
|
|
||||||
<Divider h={4} orientation="vertical" />
|
|
||||||
<Text flex={1}>Updated {formatTimePast(dataset.updatedAt)}</Text>
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
|
||||||
</AspectRatio>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CountLabel = ({ label, count }: { label: string; count: number }) => {
|
|
||||||
return (
|
|
||||||
<VStack alignItems="center" flex={1}>
|
|
||||||
<Text color="gray.500" fontWeight="bold">
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="sm" color="gray.500">
|
|
||||||
{count}
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NewDatasetCard = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const selectedProjectId = useAppStore((s) => s.selectedProjectId);
|
|
||||||
const createMutation = api.datasets.create.useMutation();
|
|
||||||
const [createDataset, isLoading] = useHandledAsyncCallback(async () => {
|
|
||||||
const newDataset = await createMutation.mutateAsync({ projectId: selectedProjectId ?? "" });
|
|
||||||
await router.push({ pathname: "/data/[id]", query: { id: newDataset.id } });
|
|
||||||
}, [createMutation, router, selectedProjectId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AspectRatio ratio={1.2} w="full">
|
|
||||||
<VStack
|
|
||||||
align="center"
|
|
||||||
justify="center"
|
|
||||||
_hover={{ cursor: "pointer", bg: "gray.50" }}
|
|
||||||
transition="background 0.2s"
|
|
||||||
cursor="pointer"
|
|
||||||
borderColor="gray.200"
|
|
||||||
borderWidth={1}
|
|
||||||
p={4}
|
|
||||||
onClick={createDataset}
|
|
||||||
>
|
|
||||||
<Icon as={isLoading ? Spinner : BsPlusSquare} boxSize={8} />
|
|
||||||
<Text display={{ base: "none", md: "block" }} ml={2}>
|
|
||||||
New Dataset
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
</AspectRatio>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DatasetCardSkeleton = () => (
|
|
||||||
<AspectRatio ratio={1.2} w="full">
|
|
||||||
<VStack align="center" borderColor="gray.200" borderWidth={1} p={4} bg="gray.50">
|
|
||||||
<SkeletonText noOfLines={1} w="80%" />
|
|
||||||
<SkeletonText noOfLines={2} w="60%" />
|
|
||||||
<SkeletonText noOfLines={1} w="80%" />
|
|
||||||
</VStack>
|
|
||||||
</AspectRatio>
|
|
||||||
);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { useDatasetEntries } from "~/utils/hooks";
|
|
||||||
import Paginator from "../Paginator";
|
|
||||||
|
|
||||||
const DatasetEntriesPaginator = () => {
|
|
||||||
const { data } = useDatasetEntries();
|
|
||||||
|
|
||||||
if (!data) return null;
|
|
||||||
|
|
||||||
const { entries, startIndex, lastPage, count } = data;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paginator
|
|
||||||
numItemsLoaded={entries.length}
|
|
||||||
startIndex={startIndex}
|
|
||||||
lastPage={lastPage}
|
|
||||||
count={count}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DatasetEntriesPaginator;
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { type StackProps, VStack, Table, Th, Tr, Thead, Tbody, Text } from "@chakra-ui/react";
|
|
||||||
import { useDatasetEntries } from "~/utils/hooks";
|
|
||||||
import TableRow from "./TableRow";
|
|
||||||
import DatasetEntriesPaginator from "./DatasetEntriesPaginator";
|
|
||||||
|
|
||||||
const DatasetEntriesTable = (props: StackProps) => {
|
|
||||||
const { data } = useDatasetEntries();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<VStack justifyContent="space-between" {...props}>
|
|
||||||
<Table variant="simple" sx={{ "table-layout": "fixed", width: "full" }}>
|
|
||||||
<Thead>
|
|
||||||
<Tr>
|
|
||||||
<Th>Input</Th>
|
|
||||||
<Th>Output</Th>
|
|
||||||
</Tr>
|
|
||||||
</Thead>
|
|
||||||
<Tbody>{data?.entries.map((entry) => <TableRow key={entry.id} entry={entry} />)}</Tbody>
|
|
||||||
</Table>
|
|
||||||
{(!data || data.entries.length) === 0 ? (
|
|
||||||
<Text alignSelf="flex-start" pl={6} color="gray.500">
|
|
||||||
No entries found
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<DatasetEntriesPaginator />
|
|
||||||
)}
|
|
||||||
</VStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DatasetEntriesTable;
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { Button, HStack, useDisclosure } from "@chakra-ui/react";
|
|
||||||
import { BiImport } from "react-icons/bi";
|
|
||||||
import { BsStars } from "react-icons/bs";
|
|
||||||
|
|
||||||
import { GenerateDataModal } from "./GenerateDataModal";
|
|
||||||
|
|
||||||
export const DatasetHeaderButtons = () => {
|
|
||||||
const generateModalDisclosure = useDisclosure();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HStack>
|
|
||||||
<Button leftIcon={<BiImport />} colorScheme="blue" variant="ghost">
|
|
||||||
Import Data
|
|
||||||
</Button>
|
|
||||||
<Button leftIcon={<BsStars />} colorScheme="blue" onClick={generateModalDisclosure.onOpen}>
|
|
||||||
Generate Data
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
|
||||||
<GenerateDataModal
|
|
||||||
isOpen={generateModalDisclosure.isOpen}
|
|
||||||
onClose={generateModalDisclosure.onClose}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
import {
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalFooter,
|
|
||||||
Text,
|
|
||||||
HStack,
|
|
||||||
VStack,
|
|
||||||
Icon,
|
|
||||||
NumberInput,
|
|
||||||
NumberInputField,
|
|
||||||
NumberInputStepper,
|
|
||||||
NumberIncrementStepper,
|
|
||||||
NumberDecrementStepper,
|
|
||||||
Button,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { BsStars } from "react-icons/bs";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useDataset, useHandledAsyncCallback } from "~/utils/hooks";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import AutoResizeTextArea from "~/components/AutoResizeTextArea";
|
|
||||||
|
|
||||||
export const GenerateDataModal = ({
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
}: {
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}) => {
|
|
||||||
const utils = api.useContext();
|
|
||||||
|
|
||||||
const datasetId = useDataset().data?.id;
|
|
||||||
|
|
||||||
const [numToGenerate, setNumToGenerate] = useState<number>(20);
|
|
||||||
const [inputDescription, setInputDescription] = useState<string>(
|
|
||||||
"Each input should contain an email body. Half of the emails should contain event details, and the other half should not.",
|
|
||||||
);
|
|
||||||
const [outputDescription, setOutputDescription] = useState<string>(
|
|
||||||
`Each output should contain "true" or "false", where "true" indicates that the email contains event details.`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const generateEntriesMutation = api.datasetEntries.autogenerateEntries.useMutation();
|
|
||||||
|
|
||||||
const [generateEntries, generateEntriesInProgress] = useHandledAsyncCallback(async () => {
|
|
||||||
if (!inputDescription || !outputDescription || !numToGenerate || !datasetId) return;
|
|
||||||
await generateEntriesMutation.mutateAsync({
|
|
||||||
datasetId,
|
|
||||||
inputDescription,
|
|
||||||
outputDescription,
|
|
||||||
numToGenerate,
|
|
||||||
});
|
|
||||||
await utils.datasetEntries.list.invalidate();
|
|
||||||
onClose();
|
|
||||||
}, [
|
|
||||||
generateEntriesMutation,
|
|
||||||
onClose,
|
|
||||||
inputDescription,
|
|
||||||
outputDescription,
|
|
||||||
numToGenerate,
|
|
||||||
datasetId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal isOpen={isOpen} onClose={onClose} size={{ base: "xl", sm: "2xl", md: "3xl" }}>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent w={1200}>
|
|
||||||
<ModalHeader>
|
|
||||||
<HStack>
|
|
||||||
<Icon as={BsStars} />
|
|
||||||
<Text>Generate Data</Text>
|
|
||||||
</HStack>
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody maxW="unset">
|
|
||||||
<VStack w="full" spacing={8} padding={8} alignItems="flex-start">
|
|
||||||
<VStack alignItems="flex-start" spacing={2}>
|
|
||||||
<Text fontWeight="bold">Number of Rows:</Text>
|
|
||||||
<NumberInput
|
|
||||||
step={5}
|
|
||||||
defaultValue={15}
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
onChange={(valueString) => setNumToGenerate(parseInt(valueString) || 0)}
|
|
||||||
value={numToGenerate}
|
|
||||||
w="24"
|
|
||||||
>
|
|
||||||
<NumberInputField />
|
|
||||||
<NumberInputStepper>
|
|
||||||
<NumberIncrementStepper />
|
|
||||||
<NumberDecrementStepper />
|
|
||||||
</NumberInputStepper>
|
|
||||||
</NumberInput>
|
|
||||||
</VStack>
|
|
||||||
<VStack alignItems="flex-start" w="full" spacing={2}>
|
|
||||||
<Text fontWeight="bold">Input Description:</Text>
|
|
||||||
<AutoResizeTextArea
|
|
||||||
value={inputDescription}
|
|
||||||
onChange={(e) => setInputDescription(e.target.value)}
|
|
||||||
placeholder="Each input should contain..."
|
|
||||||
/>
|
|
||||||
</VStack>
|
|
||||||
<VStack alignItems="flex-start" w="full" spacing={2}>
|
|
||||||
<Text fontWeight="bold">Output Description (optional):</Text>
|
|
||||||
<AutoResizeTextArea
|
|
||||||
value={outputDescription}
|
|
||||||
onChange={(e) => setOutputDescription(e.target.value)}
|
|
||||||
placeholder="The output should contain..."
|
|
||||||
/>
|
|
||||||
</VStack>
|
|
||||||
</VStack>
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter>
|
|
||||||
<Button
|
|
||||||
colorScheme="blue"
|
|
||||||
isLoading={generateEntriesInProgress}
|
|
||||||
isDisabled={!numToGenerate || !inputDescription || !outputDescription}
|
|
||||||
onClick={generateEntries}
|
|
||||||
>
|
|
||||||
Generate
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { Td, Tr } from "@chakra-ui/react";
|
|
||||||
import { type DatasetEntry } from "@prisma/client";
|
|
||||||
|
|
||||||
const TableRow = ({ entry }: { entry: DatasetEntry }) => {
|
|
||||||
return (
|
|
||||||
<Tr key={entry.id}>
|
|
||||||
<Td>{entry.input}</Td>
|
|
||||||
<Td>{entry.output}</Td>
|
|
||||||
</Tr>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TableRow;
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
import {
|
|
||||||
Heading,
|
|
||||||
VStack,
|
|
||||||
Icon,
|
|
||||||
HStack,
|
|
||||||
Image,
|
|
||||||
Text,
|
|
||||||
Box,
|
|
||||||
Link as ChakraLink,
|
|
||||||
Flex,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import Head from "next/head";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { BsGearFill, BsGithub, BsPersonCircle } from "react-icons/bs";
|
|
||||||
import { IoStatsChartOutline } from "react-icons/io5";
|
|
||||||
import { RiDatabase2Line, RiFlaskLine } from "react-icons/ri";
|
|
||||||
import { signIn, useSession } from "next-auth/react";
|
|
||||||
import UserMenu from "./UserMenu";
|
|
||||||
import { env } from "~/env.mjs";
|
|
||||||
import ProjectMenu from "./ProjectMenu";
|
|
||||||
import NavSidebarOption from "./NavSidebarOption";
|
|
||||||
import IconLink from "./IconLink";
|
|
||||||
|
|
||||||
const Divider = () => <Box h="1px" bgColor="gray.300" w="full" />;
|
|
||||||
|
|
||||||
const NavSidebar = () => {
|
|
||||||
const user = useSession().data;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<VStack
|
|
||||||
align="stretch"
|
|
||||||
bgColor="gray.50"
|
|
||||||
py={2}
|
|
||||||
px={2}
|
|
||||||
pb={0}
|
|
||||||
height="100%"
|
|
||||||
w={{ base: "56px", md: "240px" }}
|
|
||||||
overflow="hidden"
|
|
||||||
borderRightWidth={1}
|
|
||||||
borderColor="gray.300"
|
|
||||||
>
|
|
||||||
<HStack as={Link} href="/" _hover={{ textDecoration: "none" }} spacing={0} px={2} py={2}>
|
|
||||||
<Image src="/logo.svg" alt="" boxSize={6} mr={4} />
|
|
||||||
<Heading size="md" fontFamily="inconsolata, monospace">
|
|
||||||
OpenPipe
|
|
||||||
</Heading>
|
|
||||||
</HStack>
|
|
||||||
<Divider />
|
|
||||||
<VStack align="flex-start" overflowY="auto" overflowX="hidden" flex={1}>
|
|
||||||
{user != null && (
|
|
||||||
<>
|
|
||||||
<ProjectMenu />
|
|
||||||
<Divider />
|
|
||||||
{env.NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS && (
|
|
||||||
<IconLink icon={IoStatsChartOutline} label="Logged Calls" href="/logged-calls" beta />
|
|
||||||
)}
|
|
||||||
<IconLink icon={RiFlaskLine} label="Experiments" href="/experiments" />
|
|
||||||
{env.NEXT_PUBLIC_SHOW_DATA && (
|
|
||||||
<IconLink icon={RiDatabase2Line} label="Data" href="/data" />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{user === null && (
|
|
||||||
<NavSidebarOption>
|
|
||||||
<HStack
|
|
||||||
w="full"
|
|
||||||
p={4}
|
|
||||||
as={ChakraLink}
|
|
||||||
justifyContent="start"
|
|
||||||
onClick={() => {
|
|
||||||
signIn("github").catch(console.error);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon as={BsPersonCircle} boxSize={6} mr={2} />
|
|
||||||
<Text fontWeight="bold" fontSize="sm">
|
|
||||||
Sign In
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</NavSidebarOption>
|
|
||||||
)}
|
|
||||||
</VStack>
|
|
||||||
<VStack w="full" alignItems="flex-start" spacing={0}>
|
|
||||||
<Text
|
|
||||||
pl={2}
|
|
||||||
pb={2}
|
|
||||||
fontSize="xs"
|
|
||||||
fontWeight="bold"
|
|
||||||
color="gray.500"
|
|
||||||
display={{ base: "none", md: "flex" }}
|
|
||||||
>
|
|
||||||
CONFIGURATION
|
|
||||||
</Text>
|
|
||||||
<IconLink icon={BsGearFill} label="Project Settings" href="/project/settings" />
|
|
||||||
</VStack>
|
|
||||||
{user && <UserMenu user={user} borderColor={"gray.200"} />}
|
|
||||||
<Divider />
|
|
||||||
<VStack spacing={0} align="center">
|
|
||||||
<ChakraLink
|
|
||||||
href="https://github.com/openpipe/openpipe"
|
|
||||||
target="_blank"
|
|
||||||
color="gray.500"
|
|
||||||
_hover={{ color: "gray.800" }}
|
|
||||||
p={2}
|
|
||||||
>
|
|
||||||
<Icon as={BsGithub} boxSize={6} />
|
|
||||||
</ChakraLink>
|
|
||||||
</VStack>
|
|
||||||
</VStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function AppShell({
|
|
||||||
children,
|
|
||||||
title,
|
|
||||||
requireAuth,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
title?: string;
|
|
||||||
requireAuth?: boolean;
|
|
||||||
}) {
|
|
||||||
const [vh, setVh] = useState("100vh"); // Default height to prevent flicker on initial render
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const setHeight = () => {
|
|
||||||
const vh = window.innerHeight * 0.01;
|
|
||||||
document.documentElement.style.setProperty("--vh", `${vh}px`);
|
|
||||||
setVh(`calc(var(--vh, 1vh) * 100)`);
|
|
||||||
};
|
|
||||||
setHeight(); // Set the height at the start
|
|
||||||
|
|
||||||
window.addEventListener("resize", setHeight);
|
|
||||||
window.addEventListener("orientationchange", setHeight);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("resize", setHeight);
|
|
||||||
window.removeEventListener("orientationchange", setHeight);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const user = useSession().data;
|
|
||||||
const authLoading = useSession().status === "loading";
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (requireAuth && user === null && !authLoading) {
|
|
||||||
signIn("github").catch(console.error);
|
|
||||||
}
|
|
||||||
}, [requireAuth, user, authLoading]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex h={vh} w="100vw">
|
|
||||||
<Head>
|
|
||||||
<title>{title ? `${title} | OpenPipe` : "OpenPipe"}</title>
|
|
||||||
</Head>
|
|
||||||
<NavSidebar />
|
|
||||||
<Box h="100%" flex={1} overflowY="auto">
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Icon, HStack, Text, type BoxProps } from "@chakra-ui/react";
|
|
||||||
import Link, { type LinkProps } from "next/link";
|
|
||||||
import { type IconType } from "react-icons";
|
|
||||||
import NavSidebarOption from "./NavSidebarOption";
|
|
||||||
|
|
||||||
type IconLinkProps = BoxProps &
|
|
||||||
LinkProps & { label?: string; icon: IconType; href: string; beta?: boolean };
|
|
||||||
|
|
||||||
const IconLink = ({ icon, label, href, color, beta, ...props }: IconLinkProps) => {
|
|
||||||
return (
|
|
||||||
<Link href={href} style={{ width: "100%" }}>
|
|
||||||
<NavSidebarOption activeHrefPattern={href}>
|
|
||||||
<HStack w="full" justifyContent="space-between" p={2} color={color} {...props}>
|
|
||||||
<HStack w="full" justifyContent="start">
|
|
||||||
<Icon as={icon} boxSize={6} mr={2} />
|
|
||||||
<Text fontSize="sm" display={{ base: "none", md: "block" }}>
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
{beta && (
|
|
||||||
<Text fontSize="xs" ml={2} fontWeight="bold" color="orange.400">
|
|
||||||
BETA
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</HStack>
|
|
||||||
</NavSidebarOption>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IconLink;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { Box, type BoxProps } from "@chakra-ui/react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
const NavSidebarOption = ({
|
|
||||||
activeHrefPattern,
|
|
||||||
disableHoverEffect,
|
|
||||||
...props
|
|
||||||
}: { activeHrefPattern?: string; disableHoverEffect?: boolean } & BoxProps) => {
|
|
||||||
const router = useRouter();
|
|
||||||
const isActive = activeHrefPattern && router.pathname.startsWith(activeHrefPattern);
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
w="full"
|
|
||||||
fontWeight={isActive ? "bold" : "500"}
|
|
||||||
bgColor={isActive ? "gray.200" : "transparent"}
|
|
||||||
_hover={disableHoverEffect ? undefined : { bgColor: "gray.200", textDecoration: "none" }}
|
|
||||||
justifyContent="start"
|
|
||||||
cursor="pointer"
|
|
||||||
borderRadius={4}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NavSidebarOption;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Flex, type FlexProps } from "@chakra-ui/react";
|
|
||||||
|
|
||||||
const PageHeaderContainer = (props: FlexProps) => {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
px={8}
|
|
||||||
py={2}
|
|
||||||
minH={16}
|
|
||||||
w="full"
|
|
||||||
direction={{ base: "column", sm: "row" }}
|
|
||||||
alignItems={{ base: "flex-start", sm: "center" }}
|
|
||||||
justifyContent="space-between"
|
|
||||||
fontWeight="500"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PageHeaderContainer;
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { HStack, Flex, Text } from "@chakra-ui/react";
|
|
||||||
import { useSelectedProject } from "~/utils/hooks";
|
|
||||||
|
|
||||||
// Have to export only contents here instead of full BreadcrumbItem because Chakra doesn't
|
|
||||||
// recognize a BreadcrumbItem exported with this component as a valid child of Breadcrumb.
|
|
||||||
export default function ProjectBreadcrumbContents({ projectName = "" }: { projectName?: string }) {
|
|
||||||
const { data: selectedProject } = useSelectedProject();
|
|
||||||
|
|
||||||
projectName = projectName || selectedProject?.name || "";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HStack w="full">
|
|
||||||
<Flex
|
|
||||||
p={1}
|
|
||||||
borderRadius={4}
|
|
||||||
backgroundColor="orange.100"
|
|
||||||
boxSize={6}
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
>
|
|
||||||
<Text>{projectName[0]?.toUpperCase()}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Text display={{ base: "none", md: "block" }} py={1}>
|
|
||||||
{projectName}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
import {
|
|
||||||
HStack,
|
|
||||||
VStack,
|
|
||||||
Text,
|
|
||||||
Popover,
|
|
||||||
PopoverTrigger,
|
|
||||||
PopoverContent,
|
|
||||||
Flex,
|
|
||||||
IconButton,
|
|
||||||
Icon,
|
|
||||||
Divider,
|
|
||||||
Button,
|
|
||||||
useDisclosure,
|
|
||||||
Spinner,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { AiFillCaretDown } from "react-icons/ai";
|
|
||||||
import { BsGear, BsPlus } from "react-icons/bs";
|
|
||||||
import { type Project } from "@prisma/client";
|
|
||||||
|
|
||||||
import { useAppStore } from "~/state/store";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import NavSidebarOption from "./NavSidebarOption";
|
|
||||||
import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
export default function ProjectMenu() {
|
|
||||||
const router = useRouter();
|
|
||||||
const utils = api.useContext();
|
|
||||||
|
|
||||||
const selectedProjectId = useAppStore((s) => s.selectedProjectId);
|
|
||||||
const setselectedProjectId = useAppStore((s) => s.setselectedProjectId);
|
|
||||||
|
|
||||||
const { data: projects } = api.projects.list.useQuery();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
projects &&
|
|
||||||
projects[0] &&
|
|
||||||
(!selectedProjectId || !projects.find((proj) => proj.id === selectedProjectId))
|
|
||||||
) {
|
|
||||||
setselectedProjectId(projects[0].id);
|
|
||||||
}
|
|
||||||
}, [selectedProjectId, setselectedProjectId, projects]);
|
|
||||||
|
|
||||||
const { data: selectedProject } = useSelectedProject();
|
|
||||||
|
|
||||||
const popover = useDisclosure();
|
|
||||||
|
|
||||||
const createMutation = api.projects.create.useMutation();
|
|
||||||
const [createProject, isLoading] = useHandledAsyncCallback(async () => {
|
|
||||||
const newProj = await createMutation.mutateAsync({ name: "New Project" });
|
|
||||||
await utils.projects.list.invalidate();
|
|
||||||
setselectedProjectId(newProj.id);
|
|
||||||
await router.push({ pathname: "/project/settings" });
|
|
||||||
}, [createMutation, router]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<VStack w="full" alignItems="flex-start" spacing={0}>
|
|
||||||
<Text
|
|
||||||
pl={2}
|
|
||||||
pb={2}
|
|
||||||
fontSize="xs"
|
|
||||||
fontWeight="bold"
|
|
||||||
color="gray.500"
|
|
||||||
display={{ base: "none", md: "flex" }}
|
|
||||||
>
|
|
||||||
PROJECT
|
|
||||||
</Text>
|
|
||||||
<NavSidebarOption>
|
|
||||||
<Popover
|
|
||||||
placement="bottom-start"
|
|
||||||
isOpen={popover.isOpen}
|
|
||||||
onClose={popover.onClose}
|
|
||||||
closeOnBlur
|
|
||||||
>
|
|
||||||
<PopoverTrigger>
|
|
||||||
<HStack w="full" onClick={popover.onToggle}>
|
|
||||||
<Flex
|
|
||||||
p={1}
|
|
||||||
borderRadius={4}
|
|
||||||
backgroundColor="orange.100"
|
|
||||||
minW={{ base: 10, md: 8 }}
|
|
||||||
minH={{ base: 10, md: 8 }}
|
|
||||||
m={{ base: 0, md: 1 }}
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
>
|
|
||||||
<Text>{selectedProject?.name[0]?.toUpperCase()}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Text fontSize="sm" display={{ base: "none", md: "block" }} py={1} flex={1}>
|
|
||||||
{selectedProject?.name}
|
|
||||||
</Text>
|
|
||||||
<Icon as={AiFillCaretDown} boxSize={3} size="xs" color="gray.500" mr={2} />
|
|
||||||
</HStack>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent
|
|
||||||
_focusVisible={{ boxShadow: "unset" }}
|
|
||||||
minW={0}
|
|
||||||
borderColor="blue.400"
|
|
||||||
w="full"
|
|
||||||
>
|
|
||||||
<VStack alignItems="flex-start" spacing={2} py={4} px={2}>
|
|
||||||
<Text color="gray.500" fontSize="xs" fontWeight="bold" pb={1}>
|
|
||||||
PROJECTS
|
|
||||||
</Text>
|
|
||||||
<Divider />
|
|
||||||
<VStack spacing={0} w="full">
|
|
||||||
{projects?.map((proj) => (
|
|
||||||
<ProjectOption
|
|
||||||
key={proj.id}
|
|
||||||
proj={proj}
|
|
||||||
isActive={proj.id === selectedProjectId}
|
|
||||||
onClose={popover.onClose}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</VStack>
|
|
||||||
<HStack
|
|
||||||
as={Button}
|
|
||||||
variant="ghost"
|
|
||||||
colorScheme="blue"
|
|
||||||
color="blue.400"
|
|
||||||
pr={8}
|
|
||||||
w="full"
|
|
||||||
onClick={createProject}
|
|
||||||
>
|
|
||||||
<Icon as={isLoading ? Spinner : BsPlus} boxSize={6} />
|
|
||||||
<Text>New project</Text>
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</NavSidebarOption>
|
|
||||||
</VStack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectOption = ({
|
|
||||||
proj,
|
|
||||||
isActive,
|
|
||||||
onClose,
|
|
||||||
}: {
|
|
||||||
proj: Project;
|
|
||||||
isActive: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}) => {
|
|
||||||
const setselectedProjectId = useAppStore((s) => s.setselectedProjectId);
|
|
||||||
const [gearHovered, setGearHovered] = useState(false);
|
|
||||||
return (
|
|
||||||
<HStack
|
|
||||||
as={Link}
|
|
||||||
href="/experiments"
|
|
||||||
onClick={() => {
|
|
||||||
setselectedProjectId(proj.id);
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
w="full"
|
|
||||||
justifyContent="space-between"
|
|
||||||
bgColor={isActive ? "gray.100" : "transparent"}
|
|
||||||
_hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }}
|
|
||||||
p={2}
|
|
||||||
>
|
|
||||||
<Text>{proj.name}</Text>
|
|
||||||
<IconButton
|
|
||||||
as={Link}
|
|
||||||
href="/project/settings"
|
|
||||||
aria-label={`Open ${proj.name} settings`}
|
|
||||||
icon={<Icon as={BsGear} boxSize={5} strokeWidth={0.5} color="gray.500" />}
|
|
||||||
variant="ghost"
|
|
||||||
size="xs"
|
|
||||||
p={0}
|
|
||||||
onMouseEnter={() => setGearHovered(true)}
|
|
||||||
onMouseLeave={() => setGearHovered(false)}
|
|
||||||
_hover={{ bgColor: isActive ? "gray.300" : "gray.100", transitionDelay: 0 }}
|
|
||||||
borderRadius={4}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import {
|
|
||||||
Button,
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogBody,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogOverlay,
|
|
||||||
Input,
|
|
||||||
Text,
|
|
||||||
VStack,
|
|
||||||
Box,
|
|
||||||
Spinner,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useRef, useState } from "react";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
|
|
||||||
|
|
||||||
export const DeleteProjectDialog = ({
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
}: {
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}) => {
|
|
||||||
const selectedProject = useSelectedProject();
|
|
||||||
const deleteMutation = api.projects.delete.useMutation();
|
|
||||||
const utils = api.useContext();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
|
||||||
|
|
||||||
const [onDeleteConfirm, isDeleting] = useHandledAsyncCallback(async () => {
|
|
||||||
if (!selectedProject.data?.id) return;
|
|
||||||
await deleteMutation.mutateAsync({ id: selectedProject.data.id });
|
|
||||||
await utils.projects.list.invalidate();
|
|
||||||
await router.push({ pathname: "/experiments" });
|
|
||||||
onClose();
|
|
||||||
}, [deleteMutation, selectedProject, router]);
|
|
||||||
|
|
||||||
const [nameToDelete, setNameToDelete] = useState("");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
|
|
||||||
<AlertDialogOverlay>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
|
||||||
Delete Project
|
|
||||||
</AlertDialogHeader>
|
|
||||||
|
|
||||||
<AlertDialogBody>
|
|
||||||
<VStack spacing={4} alignItems="flex-start">
|
|
||||||
<Text>
|
|
||||||
If you delete this project all the associated data and experiments will be deleted
|
|
||||||
as well. If you are sure that you want to delete this project, please type the name
|
|
||||||
of the project below.
|
|
||||||
</Text>
|
|
||||||
<Box bgColor="orange.100" w="full" p={2} borderRadius={4}>
|
|
||||||
<Text fontFamily="inconsolata">{selectedProject.data?.name}</Text>
|
|
||||||
</Box>
|
|
||||||
<Input
|
|
||||||
placeholder={selectedProject.data?.name}
|
|
||||||
value={nameToDelete}
|
|
||||||
onChange={(e) => setNameToDelete(e.target.value)}
|
|
||||||
/>
|
|
||||||
</VStack>
|
|
||||||
</AlertDialogBody>
|
|
||||||
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<Button ref={cancelRef} onClick={onClose}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
colorScheme="red"
|
|
||||||
onClick={onDeleteConfirm}
|
|
||||||
ml={3}
|
|
||||||
isDisabled={nameToDelete !== selectedProject.data?.name}
|
|
||||||
w={20}
|
|
||||||
>
|
|
||||||
{isDeleting ? <Spinner /> : "Delete"}
|
|
||||||
</Button>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialogOverlay>
|
|
||||||
</AlertDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"model": {
|
|
||||||
"description": "The model that will complete your prompt.",
|
|
||||||
"x-oaiTypeLabel": "string",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"claude-2",
|
|
||||||
"claude-2.0",
|
|
||||||
"claude-instant-1",
|
|
||||||
"claude-instant-1.1",
|
|
||||||
"claude-instant-1.2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"prompt": {
|
|
||||||
"description": "The prompt that you want Claude to complete.\n\nFor proper response generation you will need to format your prompt as follows:\n\"\\n\\nHuman: all instructions for the assistant\\n\\nAssistant:\". The prompt string should begin with the characters \"Human:\" and end with \"Assistant:\".",
|
|
||||||
"default": "<|endoftext|>",
|
|
||||||
"example": "\\n\\nHuman: What is the correct translation of ${scenario.input}? I would like a long analysis followed by a short answer.\\n\\nAssistant:",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"max_tokens_to_sample": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1,
|
|
||||||
"default": 256,
|
|
||||||
"nullable": true,
|
|
||||||
"description": "The maximum number of tokens to generate before stopping."
|
|
||||||
},
|
|
||||||
"temperature": {
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0,
|
|
||||||
"maximum": 1,
|
|
||||||
"nullable": true,
|
|
||||||
"description": "Amount of randomness injected into the response.\n\nDefaults to 1."
|
|
||||||
},
|
|
||||||
"top_p": {
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0,
|
|
||||||
"maximum": 1,
|
|
||||||
"nullable": true,
|
|
||||||
"description": "Use nucleus sampling.\n\nYou should either alter temperature or top_p, but not both.\n"
|
|
||||||
},
|
|
||||||
"top_k": {
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0,
|
|
||||||
"default": 5,
|
|
||||||
"nullable": true,
|
|
||||||
"description": "Only sample from the top K options for each subsequent token."
|
|
||||||
},
|
|
||||||
"stream": {
|
|
||||||
"description": "Whether to incrementally stream the response using server-sent events.",
|
|
||||||
"type": "boolean",
|
|
||||||
"nullable": true,
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"stop_sequences": {
|
|
||||||
"description": "Sequences that will cause the model to stop generating completion text.\nBy default, our models stop on \"\\n\\nHuman:\".",
|
|
||||||
"default": null,
|
|
||||||
"nullable": true,
|
|
||||||
"type": "array"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["model", "prompt", "max_tokens_to_sample"]
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { type NextApiRequest, type NextApiResponse } from "next";
|
|
||||||
import cors from "nextjs-cors";
|
|
||||||
import { createOpenApiNextHandler } from "trpc-openapi";
|
|
||||||
import { createProcedureCache } from "trpc-openapi/dist/adapters/node-http/procedures";
|
|
||||||
import { appRouter } from "~/server/api/root.router";
|
|
||||||
import { createTRPCContext } from "~/server/api/trpc";
|
|
||||||
|
|
||||||
const openApiHandler = createOpenApiNextHandler({
|
|
||||||
router: appRouter,
|
|
||||||
createContext: createTRPCContext,
|
|
||||||
});
|
|
||||||
|
|
||||||
const cache = createProcedureCache(appRouter);
|
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
// Setup CORS
|
|
||||||
await cors(req, res);
|
|
||||||
|
|
||||||
return openApiHandler(req, res);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default handler;
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import { ImageResponse } from "@vercel/og";
|
|
||||||
import { type NextApiRequest, type NextApiResponse } from "next";
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
runtime: "experimental-edge",
|
|
||||||
};
|
|
||||||
|
|
||||||
const inconsolataRegularFontP = fetch(
|
|
||||||
new URL("../../../../public/fonts/Inconsolata_SemiExpanded-Medium.ttf", import.meta.url),
|
|
||||||
).then((res) => res.arrayBuffer());
|
|
||||||
|
|
||||||
const OgImage = async (req: NextApiRequest, _res: NextApiResponse) => {
|
|
||||||
// @ts-expect-error - nextUrl is not defined on NextApiRequest for some reason
|
|
||||||
const searchParams = req.nextUrl?.searchParams as URLSearchParams;
|
|
||||||
const experimentLabel = searchParams.get("experimentLabel");
|
|
||||||
const variantsCount = searchParams.get("variantsCount");
|
|
||||||
const scenariosCount = searchParams.get("scenariosCount");
|
|
||||||
|
|
||||||
const inconsolataRegularFont = await inconsolataRegularFontP;
|
|
||||||
|
|
||||||
return new ImageResponse(
|
|
||||||
(
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
fontSize: 48,
|
|
||||||
padding: "48px",
|
|
||||||
background: "white",
|
|
||||||
position: "relative",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: 48,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
|
||||||
src="https://app.openpipe.ai/logo.svg"
|
|
||||||
alt="OpenPipe Logo"
|
|
||||||
height={100}
|
|
||||||
width={120}
|
|
||||||
/>
|
|
||||||
<div style={{ marginLeft: 24, fontSize: 64, fontFamily: "Inconsolata" }}>OpenPipe</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ display: "flex", fontSize: 72, marginTop: 108 }}>{experimentLabel}</div>
|
|
||||||
<div style={{ display: "flex", flexDirection: "column", marginTop: 36 }}>
|
|
||||||
<div style={{ display: "flex" }}>
|
|
||||||
<span style={{ width: 320 }}>Variants:</span> {variantsCount}
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", marginTop: 24 }}>
|
|
||||||
<span style={{ width: 320 }}>Scenarios:</span> {scenariosCount}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
{
|
|
||||||
fonts: [
|
|
||||||
{
|
|
||||||
name: "inconsolata",
|
|
||||||
data: inconsolataRegularFont,
|
|
||||||
style: "normal",
|
|
||||||
weight: 400,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OgImage;
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { type NextApiRequest, type NextApiResponse } from "next";
|
|
||||||
import { generateOpenApiDocument } from "trpc-openapi";
|
|
||||||
import { appRouter } from "~/server/api/root.router";
|
|
||||||
|
|
||||||
export const openApiDocument = generateOpenApiDocument(appRouter, {
|
|
||||||
title: "OpenPipe API",
|
|
||||||
description: "The public API for reporting API calls to OpenPipe",
|
|
||||||
version: "0.1.0",
|
|
||||||
baseUrl: "https://app.openpipe.ai/api",
|
|
||||||
});
|
|
||||||
// Respond with our OpenAPI schema
|
|
||||||
const hander = (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
res.status(200).send(openApiDocument);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default hander;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// A faulty API route to test Sentry's error monitoring
|
|
||||||
// @ts-expect-error just a test file, don't care about types
|
|
||||||
export default function handler(_req, res) {
|
|
||||||
throw new Error("Sentry Example API Route Error");
|
|
||||||
res.status(200).json({ name: "John Doe" });
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
Center,
|
|
||||||
Flex,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
VStack,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { RiDatabase2Line } from "react-icons/ri";
|
|
||||||
import AppShell from "~/components/nav/AppShell";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import { useDataset, useHandledAsyncCallback } from "~/utils/hooks";
|
|
||||||
import DatasetEntriesTable from "~/components/datasets/DatasetEntriesTable";
|
|
||||||
import { DatasetHeaderButtons } from "~/components/datasets/DatasetHeaderButtons/DatasetHeaderButtons";
|
|
||||||
import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
|
|
||||||
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
|
||||||
|
|
||||||
export default function Dataset() {
|
|
||||||
const router = useRouter();
|
|
||||||
const utils = api.useContext();
|
|
||||||
|
|
||||||
const dataset = useDataset();
|
|
||||||
const datasetId = router.query.id as string;
|
|
||||||
|
|
||||||
const [name, setName] = useState(dataset.data?.name || "");
|
|
||||||
useEffect(() => {
|
|
||||||
setName(dataset.data?.name || "");
|
|
||||||
}, [dataset.data?.name]);
|
|
||||||
|
|
||||||
const updateMutation = api.datasets.update.useMutation();
|
|
||||||
const [onSaveName] = useHandledAsyncCallback(async () => {
|
|
||||||
if (name && name !== dataset.data?.name && dataset.data?.id) {
|
|
||||||
await updateMutation.mutateAsync({
|
|
||||||
id: dataset.data.id,
|
|
||||||
updates: { name: name },
|
|
||||||
});
|
|
||||||
await Promise.all([utils.datasets.list.invalidate(), utils.datasets.get.invalidate()]);
|
|
||||||
}
|
|
||||||
}, [updateMutation, dataset.data?.id, dataset.data?.name, name]);
|
|
||||||
|
|
||||||
if (!dataset.isLoading && !dataset.data) {
|
|
||||||
return (
|
|
||||||
<AppShell title="Dataset not found">
|
|
||||||
<Center h="100%">
|
|
||||||
<div>Dataset not found 😕</div>
|
|
||||||
</Center>
|
|
||||||
</AppShell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppShell title={dataset.data?.name}>
|
|
||||||
<VStack h="full">
|
|
||||||
<PageHeaderContainer>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<ProjectBreadcrumbContents projectName={dataset.data?.project?.name} />
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<Link href="/data">
|
|
||||||
<Flex alignItems="center" _hover={{ textDecoration: "underline" }}>
|
|
||||||
<Icon as={RiDatabase2Line} boxSize={4} mr={2} /> Datasets
|
|
||||||
</Flex>
|
|
||||||
</Link>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbItem isCurrentPage>
|
|
||||||
<Input
|
|
||||||
size="sm"
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
onBlur={onSaveName}
|
|
||||||
borderWidth={1}
|
|
||||||
borderColor="transparent"
|
|
||||||
fontSize={16}
|
|
||||||
px={0}
|
|
||||||
minW={{ base: 100, lg: 300 }}
|
|
||||||
flex={1}
|
|
||||||
_hover={{ borderColor: "gray.300" }}
|
|
||||||
_focus={{ borderColor: "blue.500", outline: "none" }}
|
|
||||||
/>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</Breadcrumb>
|
|
||||||
<DatasetHeaderButtons />
|
|
||||||
</PageHeaderContainer>
|
|
||||||
<Box w="full" overflowX="auto" flex={1} px={8} pt={8} pb={16}>
|
|
||||||
{datasetId && <DatasetEntriesTable />}
|
|
||||||
</Box>
|
|
||||||
</VStack>
|
|
||||||
</AppShell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { SimpleGrid, Icon, Breadcrumb, BreadcrumbItem, Flex } from "@chakra-ui/react";
|
|
||||||
import AppShell from "~/components/nav/AppShell";
|
|
||||||
import { RiDatabase2Line } from "react-icons/ri";
|
|
||||||
import {
|
|
||||||
DatasetCard,
|
|
||||||
DatasetCardSkeleton,
|
|
||||||
NewDatasetCard,
|
|
||||||
} from "~/components/datasets/DatasetCard";
|
|
||||||
import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
|
|
||||||
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
|
||||||
import { useDatasets } from "~/utils/hooks";
|
|
||||||
|
|
||||||
export default function DatasetsPage() {
|
|
||||||
const datasets = useDatasets();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppShell title="Data" requireAuth>
|
|
||||||
<PageHeaderContainer>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<ProjectBreadcrumbContents />
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbItem minH={8}>
|
|
||||||
<Flex alignItems="center">
|
|
||||||
<Icon as={RiDatabase2Line} boxSize={4} mr={2} /> Datasets
|
|
||||||
</Flex>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</Breadcrumb>
|
|
||||||
</PageHeaderContainer>
|
|
||||||
<SimpleGrid w="full" columns={{ base: 1, md: 2, lg: 3, xl: 4 }} spacing={8} py={4} px={8}>
|
|
||||||
<NewDatasetCard />
|
|
||||||
{datasets.data && !datasets.isLoading ? (
|
|
||||||
datasets?.data?.map((dataset) => (
|
|
||||||
<DatasetCard
|
|
||||||
key={dataset.id}
|
|
||||||
dataset={{ ...dataset, numEntries: dataset._count.datasetEntries }}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<DatasetCardSkeleton />
|
|
||||||
<DatasetCardSkeleton />
|
|
||||||
<DatasetCardSkeleton />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SimpleGrid>
|
|
||||||
</AppShell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
Center,
|
|
||||||
Flex,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
Text,
|
|
||||||
VStack,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { RiFlaskLine } from "react-icons/ri";
|
|
||||||
import OutputsTable from "~/components/OutputsTable";
|
|
||||||
import ExperimentSettingsDrawer from "~/components/ExperimentSettingsDrawer/ExperimentSettingsDrawer";
|
|
||||||
import AppShell from "~/components/nav/AppShell";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
|
|
||||||
import { useAppStore } from "~/state/store";
|
|
||||||
import { useSyncVariantEditor } from "~/state/sync";
|
|
||||||
import { ExperimentHeaderButtons } from "~/components/experiments/ExperimentHeaderButtons/ExperimentHeaderButtons";
|
|
||||||
import Head from "next/head";
|
|
||||||
import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
|
|
||||||
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
|
||||||
|
|
||||||
// TODO: import less to fix deployment with server side props
|
|
||||||
// export const getServerSideProps = async (context: GetServerSidePropsContext<{ id: string }>) => {
|
|
||||||
// const experimentId = context.params?.id as string;
|
|
||||||
|
|
||||||
// const helpers = createServerSideHelpers({
|
|
||||||
// router: appRouter,
|
|
||||||
// ctx: createInnerTRPCContext({ session: null }),
|
|
||||||
// transformer: superjson, // optional - adds superjson serialization
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // prefetch query
|
|
||||||
// await helpers.experiments.stats.prefetch({ id: experimentId });
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// props: {
|
|
||||||
// trpcState: helpers.dehydrate(),
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
export default function Experiment() {
|
|
||||||
const router = useRouter();
|
|
||||||
const utils = api.useContext();
|
|
||||||
useSyncVariantEditor();
|
|
||||||
|
|
||||||
const experiment = useExperiment();
|
|
||||||
const experimentStats = api.experiments.stats.useQuery(
|
|
||||||
{ id: router.query.id as string },
|
|
||||||
{
|
|
||||||
enabled: !!router.query.id,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const stats = experimentStats.data;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
useAppStore.getState().sharedVariantEditor.loadMonaco().catch(console.error);
|
|
||||||
});
|
|
||||||
|
|
||||||
const [label, setLabel] = useState(experiment.data?.label || "");
|
|
||||||
useEffect(() => {
|
|
||||||
setLabel(experiment.data?.label || "");
|
|
||||||
}, [experiment.data?.label]);
|
|
||||||
|
|
||||||
const updateMutation = api.experiments.update.useMutation();
|
|
||||||
const [onSaveLabel] = useHandledAsyncCallback(async () => {
|
|
||||||
if (label && label !== experiment.data?.label && experiment.data?.id) {
|
|
||||||
await updateMutation.mutateAsync({
|
|
||||||
id: experiment.data.id,
|
|
||||||
updates: { label: label },
|
|
||||||
});
|
|
||||||
await Promise.all([utils.experiments.list.invalidate(), utils.experiments.get.invalidate()]);
|
|
||||||
}
|
|
||||||
}, [updateMutation, experiment.data?.id, experiment.data?.label, label]);
|
|
||||||
|
|
||||||
if (!experiment.isLoading && !experiment.data) {
|
|
||||||
return (
|
|
||||||
<AppShell title="Experiment not found">
|
|
||||||
<Center h="100%">
|
|
||||||
<div>Experiment not found 😕</div>
|
|
||||||
</Center>
|
|
||||||
</AppShell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const canModify = experiment.data?.access.canModify ?? false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{stats && (
|
|
||||||
<Head>
|
|
||||||
<meta property="og:title" content={stats.experimentLabel} key="title" />
|
|
||||||
<meta
|
|
||||||
property="og:image"
|
|
||||||
content={`/api/experiments/og-image?experimentLabel=${stats.experimentLabel}&variantsCount=${stats.promptVariantCount}&scenariosCount=${stats.testScenarioCount}`}
|
|
||||||
key="og-image"
|
|
||||||
/>
|
|
||||||
</Head>
|
|
||||||
)}
|
|
||||||
<AppShell title={experiment.data?.label}>
|
|
||||||
<VStack h="full">
|
|
||||||
<PageHeaderContainer>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<ProjectBreadcrumbContents projectName={experiment.data?.project?.name} />
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<Link href="/experiments">
|
|
||||||
<Flex alignItems="center" _hover={{ textDecoration: "underline" }}>
|
|
||||||
<Icon as={RiFlaskLine} boxSize={4} mr={2} /> Experiments
|
|
||||||
</Flex>
|
|
||||||
</Link>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbItem isCurrentPage>
|
|
||||||
{canModify ? (
|
|
||||||
<Input
|
|
||||||
size="sm"
|
|
||||||
value={label}
|
|
||||||
onChange={(e) => setLabel(e.target.value)}
|
|
||||||
onBlur={onSaveLabel}
|
|
||||||
borderWidth={1}
|
|
||||||
borderColor="transparent"
|
|
||||||
fontSize={16}
|
|
||||||
px={0}
|
|
||||||
minW={{ base: 100, lg: 300 }}
|
|
||||||
flex={1}
|
|
||||||
_hover={{ borderColor: "gray.300" }}
|
|
||||||
_focus={{ borderColor: "blue.500", outline: "none" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text fontSize={16} px={0} minW={{ base: 100, lg: 300 }} flex={1}>
|
|
||||||
{experiment.data?.label}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</Breadcrumb>
|
|
||||||
<ExperimentHeaderButtons />
|
|
||||||
</PageHeaderContainer>
|
|
||||||
<ExperimentSettingsDrawer />
|
|
||||||
<Box w="100%" overflowX="auto" flex={1}>
|
|
||||||
<OutputsTable experimentId={router.query.id as string | undefined} />
|
|
||||||
</Box>
|
|
||||||
</VStack>
|
|
||||||
</AppShell>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { SimpleGrid, Icon, Breadcrumb, BreadcrumbItem, Flex } from "@chakra-ui/react";
|
|
||||||
import { RiFlaskLine } from "react-icons/ri";
|
|
||||||
import AppShell from "~/components/nav/AppShell";
|
|
||||||
import {
|
|
||||||
ExperimentCard,
|
|
||||||
ExperimentCardSkeleton,
|
|
||||||
NewExperimentCard,
|
|
||||||
} from "~/components/experiments/ExperimentCard";
|
|
||||||
import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
|
|
||||||
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
|
||||||
import { useExperiments } from "~/utils/hooks";
|
|
||||||
|
|
||||||
export default function ExperimentsPage() {
|
|
||||||
const experiments = useExperiments();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppShell title="Experiments" requireAuth>
|
|
||||||
<PageHeaderContainer>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<ProjectBreadcrumbContents />
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbItem minH={8}>
|
|
||||||
<Flex alignItems="center">
|
|
||||||
<Icon as={RiFlaskLine} boxSize={4} mr={2} /> Experiments
|
|
||||||
</Flex>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</Breadcrumb>
|
|
||||||
</PageHeaderContainer>
|
|
||||||
<SimpleGrid w="full" columns={{ base: 1, md: 2, lg: 3, xl: 4 }} spacing={8} py="4" px={8}>
|
|
||||||
<NewExperimentCard />
|
|
||||||
{experiments.data && !experiments.isLoading ? (
|
|
||||||
experiments?.data?.map((exp) => <ExperimentCard key={exp.id} exp={exp} />)
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ExperimentCardSkeleton />
|
|
||||||
<ExperimentCardSkeleton />
|
|
||||||
<ExperimentCardSkeleton />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SimpleGrid>
|
|
||||||
</AppShell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
import {
|
|
||||||
Heading,
|
|
||||||
Text,
|
|
||||||
Stat,
|
|
||||||
StatLabel,
|
|
||||||
StatNumber,
|
|
||||||
VStack,
|
|
||||||
HStack,
|
|
||||||
Card,
|
|
||||||
CardBody,
|
|
||||||
CardHeader,
|
|
||||||
Icon,
|
|
||||||
Table,
|
|
||||||
Tbody,
|
|
||||||
Tr,
|
|
||||||
Td,
|
|
||||||
Divider,
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import {
|
|
||||||
LineChart,
|
|
||||||
Line,
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
CartesianGrid,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
ResponsiveContainer,
|
|
||||||
} from "recharts";
|
|
||||||
import { Ban, DollarSign, Hash } from "lucide-react";
|
|
||||||
import { useMemo } from "react";
|
|
||||||
|
|
||||||
import AppShell from "~/components/nav/AppShell";
|
|
||||||
import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
|
|
||||||
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
|
||||||
import { useSelectedProject } from "~/utils/hooks";
|
|
||||||
import dayjs from "~/utils/dayjs";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import LoggedCallTable from "~/components/dashboard/LoggedCallTable";
|
|
||||||
|
|
||||||
export default function LoggedCalls() {
|
|
||||||
const { data: selectedProject } = useSelectedProject();
|
|
||||||
|
|
||||||
const stats = api.dashboard.stats.useQuery(
|
|
||||||
{ projectId: selectedProject?.id ?? "" },
|
|
||||||
{ enabled: !!selectedProject },
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = useMemo(() => {
|
|
||||||
return (
|
|
||||||
stats.data?.periods.map(({ period, numQueries, totalCost }) => ({
|
|
||||||
period,
|
|
||||||
Requests: numQueries,
|
|
||||||
"Total Spent (USD)": parseFloat(totalCost.toString()),
|
|
||||||
})) || []
|
|
||||||
);
|
|
||||||
}, [stats.data]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppShell requireAuth>
|
|
||||||
<PageHeaderContainer>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<ProjectBreadcrumbContents />
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbItem isCurrentPage>
|
|
||||||
<Text>Logged Calls</Text>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</Breadcrumb>
|
|
||||||
</PageHeaderContainer>
|
|
||||||
<VStack px={8} pt={4} alignItems="flex-start" spacing={4}>
|
|
||||||
<Text fontSize="2xl" fontWeight="bold">
|
|
||||||
{selectedProject?.name}
|
|
||||||
</Text>
|
|
||||||
<Divider />
|
|
||||||
<VStack margin="auto" spacing={4} align="stretch" w="full">
|
|
||||||
<HStack gap={4} align="start">
|
|
||||||
<Card variant="outline" flex={1}>
|
|
||||||
<CardHeader>
|
|
||||||
<Heading as="h3" size="sm">
|
|
||||||
Usage Statistics
|
|
||||||
</Heading>
|
|
||||||
</CardHeader>
|
|
||||||
<CardBody>
|
|
||||||
<ResponsiveContainer width="100%" height={400}>
|
|
||||||
<LineChart data={data} margin={{ top: 5, right: 20, left: 10, bottom: 5 }}>
|
|
||||||
<XAxis
|
|
||||||
dataKey="period"
|
|
||||||
tickFormatter={(str: string) => dayjs(str).format("MMM D")}
|
|
||||||
/>
|
|
||||||
<YAxis yAxisId="left" dataKey="Requests" orientation="left" stroke="#8884d8" />
|
|
||||||
<YAxis
|
|
||||||
yAxisId="right"
|
|
||||||
dataKey="Total Spent (USD)"
|
|
||||||
orientation="right"
|
|
||||||
unit="$"
|
|
||||||
stroke="#82ca9d"
|
|
||||||
/>
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
<CartesianGrid stroke="#f5f5f5" />
|
|
||||||
<Line
|
|
||||||
dataKey="Requests"
|
|
||||||
stroke="#8884d8"
|
|
||||||
yAxisId="left"
|
|
||||||
dot={false}
|
|
||||||
strokeWidth={2}
|
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
dataKey="Total Spent (USD)"
|
|
||||||
stroke="#82ca9d"
|
|
||||||
yAxisId="right"
|
|
||||||
dot={false}
|
|
||||||
strokeWidth={2}
|
|
||||||
/>
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
<VStack spacing="4" width="300px" align="stretch">
|
|
||||||
<Card variant="outline">
|
|
||||||
<CardBody>
|
|
||||||
<Stat>
|
|
||||||
<HStack>
|
|
||||||
<StatLabel flex={1}>Total Spent</StatLabel>
|
|
||||||
<Icon as={DollarSign} boxSize={4} color="gray.500" />
|
|
||||||
</HStack>
|
|
||||||
<StatNumber>
|
|
||||||
${parseFloat(stats.data?.totals?.totalCost?.toString() ?? "0").toFixed(2)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
<Card variant="outline">
|
|
||||||
<CardBody>
|
|
||||||
<Stat>
|
|
||||||
<HStack>
|
|
||||||
<StatLabel flex={1}>Total Requests</StatLabel>
|
|
||||||
<Icon as={Hash} boxSize={4} color="gray.500" />
|
|
||||||
</HStack>
|
|
||||||
<StatNumber>
|
|
||||||
{stats.data?.totals?.numQueries
|
|
||||||
? parseInt(stats.data?.totals?.numQueries.toString())?.toLocaleString()
|
|
||||||
: undefined}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
<Card variant="outline" overflow="hidden">
|
|
||||||
<Stat>
|
|
||||||
<CardHeader>
|
|
||||||
<HStack>
|
|
||||||
<StatLabel flex={1}>Errors</StatLabel>
|
|
||||||
<Icon as={Ban} boxSize={4} color="gray.500" />
|
|
||||||
</HStack>
|
|
||||||
</CardHeader>
|
|
||||||
<Table variant="simple">
|
|
||||||
<Tbody>
|
|
||||||
{stats.data?.errors?.map((error) => (
|
|
||||||
<Tr key={error.code}>
|
|
||||||
<Td>
|
|
||||||
{error.name} ({error.code})
|
|
||||||
</Td>
|
|
||||||
<Td isNumeric color="red.600">
|
|
||||||
{parseInt(error.count.toString()).toLocaleString()}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
</Stat>
|
|
||||||
</Card>
|
|
||||||
</VStack>
|
|
||||||
</HStack>
|
|
||||||
<LoggedCallTable />
|
|
||||||
</VStack>
|
|
||||||
</VStack>
|
|
||||||
</AppShell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
Text,
|
|
||||||
type TextProps,
|
|
||||||
VStack,
|
|
||||||
HStack,
|
|
||||||
Input,
|
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
Icon,
|
|
||||||
useDisclosure,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { BsTrash } from "react-icons/bs";
|
|
||||||
|
|
||||||
import AppShell from "~/components/nav/AppShell";
|
|
||||||
import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
|
|
||||||
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
|
||||||
import CopiableCode from "~/components/CopiableCode";
|
|
||||||
import { DeleteProjectDialog } from "~/components/projectSettings/DeleteProjectDialog";
|
|
||||||
|
|
||||||
export default function Settings() {
|
|
||||||
const utils = api.useContext();
|
|
||||||
const { data: selectedProject } = useSelectedProject();
|
|
||||||
|
|
||||||
const apiKey =
|
|
||||||
selectedProject?.apiKeys?.length && selectedProject?.apiKeys[0]
|
|
||||||
? selectedProject?.apiKeys[0].apiKey
|
|
||||||
: "";
|
|
||||||
|
|
||||||
const updateMutation = api.projects.update.useMutation();
|
|
||||||
const [onSaveName] = useHandledAsyncCallback(async () => {
|
|
||||||
if (name && name !== selectedProject?.name && selectedProject?.id) {
|
|
||||||
await updateMutation.mutateAsync({
|
|
||||||
id: selectedProject.id,
|
|
||||||
updates: { name },
|
|
||||||
});
|
|
||||||
await Promise.all([utils.projects.get.invalidate({ id: selectedProject.id })]);
|
|
||||||
}
|
|
||||||
}, [updateMutation, selectedProject]);
|
|
||||||
|
|
||||||
const [name, setName] = useState(selectedProject?.name);
|
|
||||||
useEffect(() => {
|
|
||||||
setName(selectedProject?.name);
|
|
||||||
}, [selectedProject?.name]);
|
|
||||||
|
|
||||||
const deleteProjectOpen = useDisclosure();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<AppShell>
|
|
||||||
<PageHeaderContainer>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<ProjectBreadcrumbContents />
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbItem isCurrentPage>
|
|
||||||
<Text>Project Settings</Text>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</Breadcrumb>
|
|
||||||
</PageHeaderContainer>
|
|
||||||
<VStack px={8} pt={4} alignItems="flex-start" spacing={4}>
|
|
||||||
<VStack spacing={0} alignItems="flex-start">
|
|
||||||
<Text fontSize="2xl" fontWeight="bold">
|
|
||||||
Project Settings
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="sm">
|
|
||||||
Configure your project settings. These settings only apply to {selectedProject?.name}.
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
<VStack
|
|
||||||
w="full"
|
|
||||||
alignItems="flex-start"
|
|
||||||
borderWidth={1}
|
|
||||||
borderRadius={4}
|
|
||||||
borderColor="gray.300"
|
|
||||||
p={6}
|
|
||||||
spacing={6}
|
|
||||||
>
|
|
||||||
<VStack alignItems="flex-start" w="full">
|
|
||||||
<Text fontWeight="bold" fontSize="xl">
|
|
||||||
Display Name
|
|
||||||
</Text>
|
|
||||||
<Input
|
|
||||||
w="full"
|
|
||||||
maxW={600}
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
borderColor="gray.300"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
isDisabled={!name || name === selectedProject?.name}
|
|
||||||
colorScheme="orange"
|
|
||||||
borderRadius={4}
|
|
||||||
mt={2}
|
|
||||||
_disabled={{
|
|
||||||
opacity: 0.6,
|
|
||||||
}}
|
|
||||||
onClick={onSaveName}
|
|
||||||
>
|
|
||||||
Rename Project
|
|
||||||
</Button>
|
|
||||||
</VStack>
|
|
||||||
<Divider backgroundColor="gray.300" />
|
|
||||||
<VStack alignItems="flex-start">
|
|
||||||
<Subtitle>Project API Key</Subtitle>
|
|
||||||
<Text fontSize="sm">
|
|
||||||
Use your project API key to authenticate your requests when sending data to
|
|
||||||
OpenPipe. You can set this key in your environment variables, or use it directly in
|
|
||||||
your code.
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
<CopiableCode code={apiKey} />
|
|
||||||
<Divider />
|
|
||||||
{selectedProject?.personalProjectUserId ? (
|
|
||||||
<VStack alignItems="flex-start">
|
|
||||||
<Subtitle>Personal Project</Subtitle>
|
|
||||||
<Text fontSize="sm">
|
|
||||||
This project is {selectedProject?.personalProjectUser?.name}'s personal project.
|
|
||||||
It cannot be deleted.
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
) : (
|
|
||||||
<VStack alignItems="flex-start">
|
|
||||||
<Subtitle color="red.600">Danger Zone</Subtitle>
|
|
||||||
<Text fontSize="sm">
|
|
||||||
Permanently delete your project and all of its data. This action cannot be undone.
|
|
||||||
</Text>
|
|
||||||
<HStack
|
|
||||||
as={Button}
|
|
||||||
isDisabled={selectedProject?.role !== "ADMIN"}
|
|
||||||
colorScheme="red"
|
|
||||||
variant="outline"
|
|
||||||
borderRadius={4}
|
|
||||||
mt={2}
|
|
||||||
onClick={deleteProjectOpen.onOpen}
|
|
||||||
>
|
|
||||||
<Icon as={BsTrash} />
|
|
||||||
<Text>Delete {selectedProject?.name}</Text>
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
|
||||||
)}
|
|
||||||
</VStack>
|
|
||||||
</VStack>
|
|
||||||
</AppShell>
|
|
||||||
<DeleteProjectDialog isOpen={deleteProjectOpen.isOpen} onClose={deleteProjectOpen.onClose} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Subtitle = (props: TextProps) => <Text fontWeight="bold" fontSize="xl" {...props} />;
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import Head from "next/head";
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Head>
|
|
||||||
<title>Sentry Onboarding</title>
|
|
||||||
<meta name="description" content="Test Sentry for your Next.js app!" />
|
|
||||||
</Head>
|
|
||||||
|
|
||||||
<main
|
|
||||||
style={{
|
|
||||||
minHeight: "100vh",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h1 style={{ fontSize: "4rem", margin: "14px 0" }}>
|
|
||||||
<svg
|
|
||||||
style={{
|
|
||||||
height: "1em",
|
|
||||||
}}
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 200 44"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M124.32,28.28,109.56,9.22h-3.68V34.77h3.73V15.19l15.18,19.58h3.26V9.22h-3.73ZM87.15,23.54h13.23V20.22H87.14V12.53h14.93V9.21H83.34V34.77h18.92V31.45H87.14ZM71.59,20.3h0C66.44,19.06,65,18.08,65,15.7c0-2.14,1.89-3.59,4.71-3.59a12.06,12.06,0,0,1,7.07,2.55l2-2.83a14.1,14.1,0,0,0-9-3c-5.06,0-8.59,3-8.59,7.27,0,4.6,3,6.19,8.46,7.52C74.51,24.74,76,25.78,76,28.11s-2,3.77-5.09,3.77a12.34,12.34,0,0,1-8.3-3.26l-2.25,2.69a15.94,15.94,0,0,0,10.42,3.85c5.48,0,9-2.95,9-7.51C79.75,23.79,77.47,21.72,71.59,20.3ZM195.7,9.22l-7.69,12-7.64-12h-4.46L186,24.67V34.78h3.84V24.55L200,9.22Zm-64.63,3.46h8.37v22.1h3.84V12.68h8.37V9.22H131.08ZM169.41,24.8c3.86-1.07,6-3.77,6-7.63,0-4.91-3.59-8-9.38-8H154.67V34.76h3.8V25.58h6.45l6.48,9.2h4.44l-7-9.82Zm-10.95-2.5V12.6h7.17c3.74,0,5.88,1.77,5.88,4.84s-2.29,4.86-5.84,4.86Z M29,2.26a4.67,4.67,0,0,0-8,0L14.42,13.53A32.21,32.21,0,0,1,32.17,40.19H27.55A27.68,27.68,0,0,0,12.09,17.47L6,28a15.92,15.92,0,0,1,9.23,12.17H4.62A.76.76,0,0,1,4,39.06l2.94-5a10.74,10.74,0,0,0-3.36-1.9l-2.91,5a4.54,4.54,0,0,0,1.69,6.24A4.66,4.66,0,0,0,4.62,44H19.15a19.4,19.4,0,0,0-8-17.31l2.31-4A23.87,23.87,0,0,1,23.76,44H36.07a35.88,35.88,0,0,0-16.41-31.8l4.67-8a.77.77,0,0,1,1.05-.27c.53.29,20.29,34.77,20.66,35.17a.76.76,0,0,1-.68,1.13H40.6q.09,1.91,0,3.81h4.78A4.59,4.59,0,0,0,50,39.43a4.49,4.49,0,0,0-.62-2.28Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p>Get started by sending us a sample error:</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
style={{
|
|
||||||
padding: "12px",
|
|
||||||
cursor: "pointer",
|
|
||||||
backgroundColor: "#AD6CAA",
|
|
||||||
borderRadius: "4px",
|
|
||||||
border: "none",
|
|
||||||
color: "white",
|
|
||||||
fontSize: "14px",
|
|
||||||
margin: "18px",
|
|
||||||
}}
|
|
||||||
onClick={async () => {
|
|
||||||
const transaction = Sentry.startTransaction({
|
|
||||||
name: "Example Frontend Transaction",
|
|
||||||
});
|
|
||||||
|
|
||||||
Sentry.configureScope((scope) => {
|
|
||||||
scope.setSpan(transaction);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/sentry-example-api");
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("Sentry Example Frontend Error");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
transaction.finish();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Throw error!
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Next, look for the error on the{" "}
|
|
||||||
<a href="https://openpipe.sentry.io/issues/?project=4505642011394048">Issues Page</a>.
|
|
||||||
</p>
|
|
||||||
<p style={{ marginTop: "24px" }}>
|
|
||||||
For more information, see{" "}
|
|
||||||
<a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/">
|
|
||||||
https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { type GetServerSideProps } from "next";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
|
||||||
export const getServerSideProps: GetServerSideProps = async () => {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/world-champs/signup",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function WorldChamps() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
type BoxProps,
|
|
||||||
Button,
|
|
||||||
DarkMode,
|
|
||||||
GlobalStyle,
|
|
||||||
HStack,
|
|
||||||
Heading,
|
|
||||||
Icon,
|
|
||||||
Link,
|
|
||||||
Table,
|
|
||||||
Tbody,
|
|
||||||
Td,
|
|
||||||
Text,
|
|
||||||
type TextProps,
|
|
||||||
Th,
|
|
||||||
Tr,
|
|
||||||
VStack,
|
|
||||||
useInterval,
|
|
||||||
Image,
|
|
||||||
Flex,
|
|
||||||
Alert,
|
|
||||||
AlertIcon,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { signIn, useSession } from "next-auth/react";
|
|
||||||
import Head from "next/head";
|
|
||||||
import { useCallback, useState } from "react";
|
|
||||||
import { BsGithub } from "react-icons/bs";
|
|
||||||
import UserMenu from "~/components/nav/UserMenu";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import dayjs from "~/utils/dayjs";
|
|
||||||
import { useHandledAsyncCallback } from "~/utils/hooks";
|
|
||||||
import GitHubButton from "react-github-btn";
|
|
||||||
|
|
||||||
const TopNavbar = () => (
|
|
||||||
<HStack px={4} py={2} align="center" justify="center">
|
|
||||||
<HStack
|
|
||||||
as={Link}
|
|
||||||
href="/"
|
|
||||||
_hover={{ textDecoration: "none" }}
|
|
||||||
spacing={0}
|
|
||||||
py={2}
|
|
||||||
pr={16}
|
|
||||||
flex={1}
|
|
||||||
sx={{
|
|
||||||
".widget": {
|
|
||||||
display: "block",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image src="/logo.svg" alt="" boxSize={6} mr={4} />
|
|
||||||
<Heading size="md" fontFamily="inconsolata, monospace">
|
|
||||||
OpenPipe
|
|
||||||
</Heading>
|
|
||||||
</HStack>
|
|
||||||
<Box pt="6px">
|
|
||||||
<GitHubButton
|
|
||||||
href="https://github.com/openpipe/openpipe"
|
|
||||||
data-color-scheme="no-preference: dark; light: dark; dark: dark;"
|
|
||||||
data-size="large"
|
|
||||||
aria-label="Follow @openpipe on GitHub"
|
|
||||||
>
|
|
||||||
Github
|
|
||||||
</GitHubButton>
|
|
||||||
</Box>
|
|
||||||
</HStack>
|
|
||||||
);
|
|
||||||
|
|
||||||
// Shows how long until the competition starts. Refreshes every second
|
|
||||||
function CountdownTimer(props: { date: Date } & TextProps) {
|
|
||||||
const [now, setNow] = useState(dayjs());
|
|
||||||
|
|
||||||
useInterval(() => {
|
|
||||||
setNow(dayjs());
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
const { date, ...rest } = props;
|
|
||||||
|
|
||||||
const kickoff = dayjs(date);
|
|
||||||
const diff = kickoff.diff(now, "second");
|
|
||||||
const days = Math.floor(diff / 86400);
|
|
||||||
const hours = Math.floor((diff % 86400) / 3600);
|
|
||||||
const minutes = Math.floor((diff % 3600) / 60);
|
|
||||||
const seconds = Math.floor(diff % 60);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text {...rest} suppressHydrationWarning>
|
|
||||||
<Text as="span" fontWeight="bold">
|
|
||||||
Kickoff in
|
|
||||||
</Text>{" "}
|
|
||||||
{days}d {hours}h {minutes}m {seconds}s
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ApplicationStatus(props: BoxProps) {
|
|
||||||
const user = useSession().data;
|
|
||||||
const entrant = api.worldChamps.userStatus.useQuery().data;
|
|
||||||
const applyMutation = api.worldChamps.apply.useMutation();
|
|
||||||
|
|
||||||
const utils = api.useContext();
|
|
||||||
|
|
||||||
const [onSignIn] = useHandledAsyncCallback(async () => {
|
|
||||||
await signIn("github");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [onApply] = useHandledAsyncCallback(async () => {
|
|
||||||
await applyMutation.mutateAsync();
|
|
||||||
await utils.worldChamps.userStatus.invalidate();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const Wrapper = useCallback(
|
|
||||||
(wrapperProps: BoxProps) => (
|
|
||||||
<Box {...props} {...wrapperProps} minH="120px" alignItems="center" justifyItems="center" />
|
|
||||||
),
|
|
||||||
[props],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (user === null) {
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<Button onClick={onSignIn} colorScheme="orange" leftIcon={<Icon as={BsGithub} />}>
|
|
||||||
Connect GitHub to apply
|
|
||||||
</Button>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
} else if (user) {
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<Flex flexDirection={{ base: "column", md: "row" }} alignItems="center">
|
|
||||||
<UserMenu
|
|
||||||
user={user}
|
|
||||||
borderRadius={2}
|
|
||||||
borderColor={"gray.700"}
|
|
||||||
borderWidth={1}
|
|
||||||
pr={6}
|
|
||||||
mr={{ base: 0, md: 8 }}
|
|
||||||
mb={{ base: 8, md: 0 }}
|
|
||||||
/>
|
|
||||||
<Box flex={1}>
|
|
||||||
{entrant?.approved ? (
|
|
||||||
<Text fontSize="sm">
|
|
||||||
You're accepted! We'll send you more details before August 14th.
|
|
||||||
</Text>
|
|
||||||
) : entrant ? (
|
|
||||||
<Text fontSize="sm">
|
|
||||||
✅ Application submitted successfully. We'll notify you by email before August 14th.{" "}
|
|
||||||
<Link
|
|
||||||
href="https://github.com/openpipe/openpipe"
|
|
||||||
isExternal
|
|
||||||
textDecor="underline"
|
|
||||||
fontWeight="bold"
|
|
||||||
>
|
|
||||||
Star our Github ⭐
|
|
||||||
</Link>{" "}
|
|
||||||
for updates while you wait!
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<Button onClick={onApply} colorScheme="orange">
|
|
||||||
Apply to compete
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Wrapper />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Signup() {
|
|
||||||
return (
|
|
||||||
<DarkMode>
|
|
||||||
<GlobalStyle />
|
|
||||||
|
|
||||||
<Head>
|
|
||||||
<title>🏆 Prompt Engineering World Championships</title>
|
|
||||||
<meta property="og:title" content="🏆 Prompt Engineering World Championships" key="title" />
|
|
||||||
<meta
|
|
||||||
property="og:description"
|
|
||||||
content="Think you have what it takes to be the best? Compete with the world's top prompt engineers and see where you rank!"
|
|
||||||
key="description"
|
|
||||||
/>
|
|
||||||
</Head>
|
|
||||||
|
|
||||||
<Box color="gray.200" minH="100vh" w="full">
|
|
||||||
<TopNavbar />
|
|
||||||
<VStack mx="auto" py={24} maxW="2xl" px={4} align="center" fontSize="lg">
|
|
||||||
<Heading size="lg" textAlign="center">
|
|
||||||
🏆 Prompt Engineering World Championships
|
|
||||||
</Heading>
|
|
||||||
{/* <CountdownTimer
|
|
||||||
date={new Date("2023-08-14T00:00:00Z")}
|
|
||||||
fontSize="2xl"
|
|
||||||
alignSelf="center"
|
|
||||||
color="gray.500"
|
|
||||||
/> */}
|
|
||||||
<Alert status="warning" mt={4}>
|
|
||||||
<AlertIcon />
|
|
||||||
We've decided to pause the World Championships for the moment because our systems aren't
|
|
||||||
quite ready. You can still sign up if you're interested and we'll notify you once we
|
|
||||||
reschedule!
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<ApplicationStatus py={8} alignSelf="center" />
|
|
||||||
|
|
||||||
<Text fontSize="lg" textAlign="left">
|
|
||||||
Think you have what it takes to be the best? Compete with the world's top prompt
|
|
||||||
engineers and see where you rank!
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Heading size="lg" pt={12} alignSelf="left">
|
|
||||||
Event Details
|
|
||||||
</Heading>
|
|
||||||
<Table variant="simple">
|
|
||||||
<Tbody
|
|
||||||
sx={{
|
|
||||||
th: {
|
|
||||||
base: { px: 0 },
|
|
||||||
md: { px: 6 },
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tr>
|
|
||||||
<Th>Kickoff</Th>
|
|
||||||
<Td>August 14</Td>
|
|
||||||
</Tr>
|
|
||||||
<Tr>
|
|
||||||
<Th>Prize</Th>
|
|
||||||
<Td>$15,000 grand prize + smaller category prizes.</Td>
|
|
||||||
</Tr>
|
|
||||||
<Tr>
|
|
||||||
<Th>Events</Th>
|
|
||||||
<Td>
|
|
||||||
Optimize prompts for multiple tasks selected from academic benchmarks and
|
|
||||||
real-world applications.
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
<Tr>
|
|
||||||
<Th>Models</Th>
|
|
||||||
<Td>Separate "weight classes" for GPT 3.5, Claude Instant, and Llama 2.</Td>
|
|
||||||
</Tr>
|
|
||||||
<Tr>
|
|
||||||
<Th>Qualifications</Th>
|
|
||||||
<Td>Open to entrants with any level of experience.</Td>
|
|
||||||
</Tr>
|
|
||||||
<Tr>
|
|
||||||
<Th>Certificates</Th>
|
|
||||||
<Td>Certificate of mastery for all qualifying participants.</Td>
|
|
||||||
</Tr>
|
|
||||||
<Tr>
|
|
||||||
<Th>Cost</Th>
|
|
||||||
<Td>
|
|
||||||
<strong>Free</strong>. We'll cover your inference budget.
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
<Tr>
|
|
||||||
<Th>Questions?</Th>
|
|
||||||
<Td>
|
|
||||||
<Link href="mailto:world-champs@openpipe.ai" textDecor="underline">
|
|
||||||
Email us
|
|
||||||
</Link>{" "}
|
|
||||||
with any follow-up questions!
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
</DarkMode>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
import "dotenv/config";
|
|
||||||
import * as recast from "recast";
|
|
||||||
import { type ASTNode } from "ast-types";
|
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
import parsePromptConstructor from "./parse";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import { promptConstructorVersion } from "./version";
|
|
||||||
const { builders: b } = recast.types;
|
|
||||||
|
|
||||||
export const migrate1to2 = (fnBody: string): string => {
|
|
||||||
const ast: ASTNode = recast.parse(fnBody);
|
|
||||||
|
|
||||||
recast.visit(ast, {
|
|
||||||
visitAssignmentExpression(path) {
|
|
||||||
const node = path.node;
|
|
||||||
if ("name" in node.left && node.left.name === "prompt") {
|
|
||||||
const functionCall = b.callExpression(b.identifier("definePrompt"), [
|
|
||||||
b.literal("openai/ChatCompletion"),
|
|
||||||
node.right,
|
|
||||||
]);
|
|
||||||
path.replace(functionCall);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return recast.print(ast).code;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const migrate2to3 = (fnBody: string): string => {
|
|
||||||
const ast: ASTNode = recast.parse(fnBody);
|
|
||||||
|
|
||||||
recast.visit(ast, {
|
|
||||||
visitCallExpression(path) {
|
|
||||||
const node = path.node;
|
|
||||||
|
|
||||||
// Check if the function being called is 'definePrompt'
|
|
||||||
if (
|
|
||||||
recast.types.namedTypes.Identifier.check(node.callee) &&
|
|
||||||
node.callee.name === "definePrompt" &&
|
|
||||||
node.arguments.length > 0 &&
|
|
||||||
recast.types.namedTypes.Literal.check(node.arguments[0]) &&
|
|
||||||
node.arguments[0].value === "anthropic"
|
|
||||||
) {
|
|
||||||
node.arguments[0].value = "anthropic/completion";
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return recast.print(ast).code;
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrations: Record<number, (fnBody: string) => string> = {
|
|
||||||
2: migrate1to2,
|
|
||||||
3: migrate2to3,
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyMigrations = (
|
|
||||||
promptConstructor: string,
|
|
||||||
currentVersion: number,
|
|
||||||
targetVersion: number,
|
|
||||||
) => {
|
|
||||||
let migratedFn = promptConstructor;
|
|
||||||
|
|
||||||
for (let v = currentVersion + 1; v <= targetVersion; v++) {
|
|
||||||
const migrationFn = migrations[v];
|
|
||||||
if (migrationFn) {
|
|
||||||
migratedFn = migrationFn(migratedFn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return migratedFn;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function migrateConstructFns(targetVersion: number) {
|
|
||||||
const prompts = await prisma.promptVariant.findMany({
|
|
||||||
where: { promptConstructorVersion: { lt: targetVersion } },
|
|
||||||
});
|
|
||||||
console.log(`Migrating ${prompts.length} prompts to version ${targetVersion}`);
|
|
||||||
await Promise.all(
|
|
||||||
prompts.map(async (variant) => {
|
|
||||||
const currentVersion = variant.promptConstructorVersion;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const migratedFn = applyMigrations(
|
|
||||||
variant.promptConstructor,
|
|
||||||
currentVersion,
|
|
||||||
targetVersion,
|
|
||||||
);
|
|
||||||
|
|
||||||
const parsedFn = await parsePromptConstructor(migratedFn);
|
|
||||||
if ("error" in parsedFn) {
|
|
||||||
throw new Error(parsedFn.error);
|
|
||||||
}
|
|
||||||
await prisma.promptVariant.update({
|
|
||||||
where: {
|
|
||||||
id: variant.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
promptConstructor: migratedFn,
|
|
||||||
promptConstructorVersion: targetVersion,
|
|
||||||
modelProvider: parsedFn.modelProvider,
|
|
||||||
model: parsedFn.model,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error migrating promptConstructor for variant", variant.id, e);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're running this file directly, run the migration to the latest version
|
|
||||||
if (process.argv.at(-1) === fileURLToPath(import.meta.url)) {
|
|
||||||
const latestVersion = Math.max(...Object.keys(migrations).map(Number));
|
|
||||||
if (latestVersion !== promptConstructorVersion) {
|
|
||||||
throw new Error(
|
|
||||||
`The latest migration is ${latestVersion}, but the promptConstructorVersion is ${promptConstructorVersion}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await migrateConstructFns(promptConstructorVersion);
|
|
||||||
console.log("Done");
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const promptConstructorVersion = 3;
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
import { type ChatCompletion } from "openai/resources/chat";
|
|
||||||
import { openai } from "../../utils/openai";
|
|
||||||
import { isAxiosError } from "./utils";
|
|
||||||
import { type APIResponse } from "openai/core";
|
|
||||||
import { sleep } from "~/server/utils/sleep";
|
|
||||||
|
|
||||||
const MAX_AUTO_RETRIES = 50;
|
|
||||||
const MIN_DELAY = 500; // milliseconds
|
|
||||||
const MAX_DELAY = 15000; // milliseconds
|
|
||||||
|
|
||||||
function calculateDelay(numPreviousTries: number): number {
|
|
||||||
const baseDelay = Math.min(MAX_DELAY, MIN_DELAY * Math.pow(2, numPreviousTries));
|
|
||||||
const jitter = Math.random() * baseDelay;
|
|
||||||
return baseDelay + jitter;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCompletionWithBackoff = async (
|
|
||||||
getCompletion: () => Promise<APIResponse<ChatCompletion>>,
|
|
||||||
) => {
|
|
||||||
let completion;
|
|
||||||
let tries = 0;
|
|
||||||
while (tries < MAX_AUTO_RETRIES) {
|
|
||||||
try {
|
|
||||||
completion = await getCompletion();
|
|
||||||
break;
|
|
||||||
} catch (e) {
|
|
||||||
if (isAxiosError(e)) {
|
|
||||||
console.error(e?.response?.data?.error?.message);
|
|
||||||
} else {
|
|
||||||
await sleep(calculateDelay(tries));
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tries++;
|
|
||||||
}
|
|
||||||
return completion;
|
|
||||||
};
|
|
||||||
// TODO: Add seeds to ensure batches don't contain duplicate data
|
|
||||||
const MAX_BATCH_SIZE = 5;
|
|
||||||
|
|
||||||
export const autogenerateDatasetEntries = async (
|
|
||||||
numToGenerate: number,
|
|
||||||
inputDescription: string,
|
|
||||||
outputDescription: string,
|
|
||||||
): Promise<{ input: string; output: string }[]> => {
|
|
||||||
const batchSizes = Array.from({ length: Math.ceil(numToGenerate / MAX_BATCH_SIZE) }, (_, i) =>
|
|
||||||
i === Math.ceil(numToGenerate / MAX_BATCH_SIZE) - 1 && numToGenerate % MAX_BATCH_SIZE
|
|
||||||
? numToGenerate % MAX_BATCH_SIZE
|
|
||||||
: MAX_BATCH_SIZE,
|
|
||||||
);
|
|
||||||
|
|
||||||
const getCompletion = (batchSize: number) =>
|
|
||||||
openai.chat.completions.create({
|
|
||||||
model: "gpt-4",
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: "system",
|
|
||||||
content: `The user needs ${batchSize} rows of data, each with an input and an output.\n---\n The input should follow these requirements: ${inputDescription}\n---\n The output should follow these requirements: ${outputDescription}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
functions: [
|
|
||||||
{
|
|
||||||
name: "add_list_of_data",
|
|
||||||
description: "Add a list of data to the database",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
rows: {
|
|
||||||
type: "array",
|
|
||||||
description: "The rows of data that match the description",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
input: {
|
|
||||||
type: "string",
|
|
||||||
description: "The input for this row",
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
type: "string",
|
|
||||||
description: "The output for this row",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
function_call: { name: "add_list_of_data" },
|
|
||||||
temperature: 0.5,
|
|
||||||
});
|
|
||||||
|
|
||||||
const completionCallbacks = batchSizes.map((batchSize) =>
|
|
||||||
getCompletionWithBackoff(() => getCompletion(batchSize)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const completions = await Promise.all(completionCallbacks);
|
|
||||||
|
|
||||||
const rows = completions.flatMap((completion) => {
|
|
||||||
const parsed = JSON.parse(
|
|
||||||
completion?.choices[0]?.message?.function_call?.arguments ?? "{rows: []}",
|
|
||||||
) as { rows: { input: string; output: string }[] };
|
|
||||||
return parsed.rows;
|
|
||||||
});
|
|
||||||
|
|
||||||
return rows;
|
|
||||||
};
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
type AxiosError = {
|
|
||||||
response?: {
|
|
||||||
data?: {
|
|
||||||
error?: {
|
|
||||||
message?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function isAxiosError(error: unknown): error is AxiosError {
|
|
||||||
if (typeof error === "object" && error !== null) {
|
|
||||||
// Initial check
|
|
||||||
const err = error as AxiosError;
|
|
||||||
return err.response?.data?.error?.message !== undefined; // Check structure
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
import { sql } from "kysely";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
|
||||||
import { kysely, prisma } from "~/server/db";
|
|
||||||
import dayjs from "~/utils/dayjs";
|
|
||||||
|
|
||||||
export const dashboardRouter = createTRPCRouter({
|
|
||||||
stats: publicProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
// TODO: actually take startDate into account
|
|
||||||
startDate: z.string().optional(),
|
|
||||||
projectId: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
// Return the stats group by hour
|
|
||||||
const periods = await kysely
|
|
||||||
.selectFrom("LoggedCall")
|
|
||||||
.leftJoin(
|
|
||||||
"LoggedCallModelResponse",
|
|
||||||
"LoggedCall.id",
|
|
||||||
"LoggedCallModelResponse.originalLoggedCallId",
|
|
||||||
)
|
|
||||||
.where("projectId", "=", input.projectId)
|
|
||||||
.select(({ fn }) => [
|
|
||||||
sql<Date>`date_trunc('day', "LoggedCallModelResponse"."startTime")`.as("period"),
|
|
||||||
sql<number>`count("LoggedCall"."id")::int`.as("numQueries"),
|
|
||||||
fn.sum(fn.coalesce("LoggedCallModelResponse.totalCost", sql<number>`0`)).as("totalCost"),
|
|
||||||
])
|
|
||||||
.groupBy("period")
|
|
||||||
.orderBy("period")
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
let originalDataIndex = periods.length - 1;
|
|
||||||
// *SLAMS DOWN GLASS OF WHISKEY* timezones, amirite?
|
|
||||||
let dayToMatch = dayjs(input.startDate || new Date());
|
|
||||||
// Ensure that the initial date we're matching against is never before the first period
|
|
||||||
if (
|
|
||||||
periods[originalDataIndex] &&
|
|
||||||
dayToMatch.isBefore(periods[originalDataIndex]?.period, "day")
|
|
||||||
) {
|
|
||||||
dayToMatch = dayjs(periods[originalDataIndex]?.period);
|
|
||||||
}
|
|
||||||
const backfilledPeriods: typeof periods = [];
|
|
||||||
|
|
||||||
// Backfill from now to 14 days ago or the date of the first logged call, whichever is earlier
|
|
||||||
while (
|
|
||||||
backfilledPeriods.length < 14 ||
|
|
||||||
(periods[0]?.period && !dayToMatch.isBefore(periods[0]?.period, "day"))
|
|
||||||
) {
|
|
||||||
const nextOriginalPeriod = periods[originalDataIndex];
|
|
||||||
if (nextOriginalPeriod && dayjs(nextOriginalPeriod?.period).isSame(dayToMatch, "day")) {
|
|
||||||
backfilledPeriods.unshift(nextOriginalPeriod);
|
|
||||||
originalDataIndex--;
|
|
||||||
} else {
|
|
||||||
backfilledPeriods.unshift({
|
|
||||||
period: dayjs(dayToMatch).toDate(),
|
|
||||||
numQueries: 0,
|
|
||||||
totalCost: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dayToMatch = dayToMatch.subtract(1, "day");
|
|
||||||
}
|
|
||||||
|
|
||||||
const totals = await kysely
|
|
||||||
.selectFrom("LoggedCall")
|
|
||||||
.leftJoin(
|
|
||||||
"LoggedCallModelResponse",
|
|
||||||
"LoggedCall.id",
|
|
||||||
"LoggedCallModelResponse.originalLoggedCallId",
|
|
||||||
)
|
|
||||||
.where("projectId", "=", input.projectId)
|
|
||||||
.select(({ fn }) => [
|
|
||||||
fn.sum(fn.coalesce("LoggedCallModelResponse.totalCost", sql<number>`0`)).as("totalCost"),
|
|
||||||
fn.count("LoggedCall.id").as("numQueries"),
|
|
||||||
])
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
const errors = await kysely
|
|
||||||
.selectFrom("LoggedCall")
|
|
||||||
.where("projectId", "=", input.projectId)
|
|
||||||
.leftJoin(
|
|
||||||
"LoggedCallModelResponse",
|
|
||||||
"LoggedCall.id",
|
|
||||||
"LoggedCallModelResponse.originalLoggedCallId",
|
|
||||||
)
|
|
||||||
.select(({ fn }) => [fn.count("LoggedCall.id").as("count"), "respStatus as code"])
|
|
||||||
.where("respStatus", ">", 200)
|
|
||||||
.groupBy("code")
|
|
||||||
.orderBy("count", "desc")
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
const namedErrors = errors.map((e) => {
|
|
||||||
if (e.code === 429) {
|
|
||||||
return { ...e, name: "Rate limited" };
|
|
||||||
} else if (e.code === 500) {
|
|
||||||
return { ...e, name: "Internal server error" };
|
|
||||||
} else {
|
|
||||||
return { ...e, name: "Other" };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { periods: backfilledPeriods, totals, errors: namedErrors };
|
|
||||||
}),
|
|
||||||
|
|
||||||
// TODO useInfiniteQuery
|
|
||||||
// https://discord.com/channels/966627436387266600/1122258443886153758/1122258443886153758
|
|
||||||
loggedCalls: publicProcedure.input(z.object({})).query(async ({ input }) => {
|
|
||||||
const loggedCalls = await prisma.loggedCall.findMany({
|
|
||||||
orderBy: { startTime: "desc" },
|
|
||||||
include: { tags: true, modelResponse: true },
|
|
||||||
take: 20,
|
|
||||||
});
|
|
||||||
|
|
||||||
return loggedCalls;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import { requireCanModifyDataset, requireCanViewDataset } from "~/utils/accessControl";
|
|
||||||
import { autogenerateDatasetEntries } from "../autogenerate/autogenerateDatasetEntries";
|
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
|
||||||
|
|
||||||
export const datasetEntries = createTRPCRouter({
|
|
||||||
list: protectedProcedure
|
|
||||||
.input(z.object({ datasetId: z.string(), page: z.number() }))
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
await requireCanViewDataset(input.datasetId, ctx);
|
|
||||||
|
|
||||||
const { datasetId, page } = input;
|
|
||||||
|
|
||||||
const entries = await prisma.datasetEntry.findMany({
|
|
||||||
where: {
|
|
||||||
datasetId,
|
|
||||||
},
|
|
||||||
orderBy: { createdAt: "desc" },
|
|
||||||
skip: (page - 1) * PAGE_SIZE,
|
|
||||||
take: PAGE_SIZE,
|
|
||||||
});
|
|
||||||
|
|
||||||
const count = await prisma.datasetEntry.count({
|
|
||||||
where: {
|
|
||||||
datasetId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
entries,
|
|
||||||
startIndex: (page - 1) * PAGE_SIZE + 1,
|
|
||||||
lastPage: Math.ceil(count / PAGE_SIZE),
|
|
||||||
count,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
createOne: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
datasetId: z.string(),
|
|
||||||
input: z.string(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireCanModifyDataset(input.datasetId, ctx);
|
|
||||||
|
|
||||||
return await prisma.datasetEntry.create({
|
|
||||||
data: {
|
|
||||||
datasetId: input.datasetId,
|
|
||||||
input: input.input,
|
|
||||||
output: input.output,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
autogenerateEntries: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
datasetId: z.string(),
|
|
||||||
numToGenerate: z.number(),
|
|
||||||
inputDescription: z.string(),
|
|
||||||
outputDescription: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireCanModifyDataset(input.datasetId, ctx);
|
|
||||||
|
|
||||||
const dataset = await prisma.dataset.findUnique({
|
|
||||||
where: {
|
|
||||||
id: input.datasetId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!dataset) {
|
|
||||||
throw new Error(`Dataset with id ${input.datasetId} does not exist`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const entries = await autogenerateDatasetEntries(
|
|
||||||
input.numToGenerate,
|
|
||||||
input.inputDescription,
|
|
||||||
input.outputDescription,
|
|
||||||
);
|
|
||||||
|
|
||||||
const createdEntries = await prisma.datasetEntry.createMany({
|
|
||||||
data: entries.map((entry) => ({
|
|
||||||
datasetId: input.datasetId,
|
|
||||||
input: entry.input,
|
|
||||||
output: entry.output,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
return createdEntries;
|
|
||||||
}),
|
|
||||||
|
|
||||||
delete: protectedProcedure
|
|
||||||
.input(z.object({ id: z.string() }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const datasetId = (
|
|
||||||
await prisma.datasetEntry.findUniqueOrThrow({
|
|
||||||
where: { id: input.id },
|
|
||||||
})
|
|
||||||
).datasetId;
|
|
||||||
|
|
||||||
await requireCanModifyDataset(datasetId, ctx);
|
|
||||||
|
|
||||||
return await prisma.datasetEntry.delete({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
id: z.string(),
|
|
||||||
updates: z.object({
|
|
||||||
input: z.string(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const existing = await prisma.datasetEntry.findUnique({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
throw new Error(`dataEntry with id ${input.id} does not exist`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await requireCanModifyDataset(existing.datasetId, ctx);
|
|
||||||
|
|
||||||
return await prisma.datasetEntry.update({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
input: input.updates.input,
|
|
||||||
output: input.updates.output,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "~/server/api/trpc";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import {
|
|
||||||
requireCanModifyDataset,
|
|
||||||
requireCanModifyProject,
|
|
||||||
requireCanViewDataset,
|
|
||||||
requireCanViewProject,
|
|
||||||
} from "~/utils/accessControl";
|
|
||||||
|
|
||||||
export const datasetsRouter = createTRPCRouter({
|
|
||||||
list: protectedProcedure
|
|
||||||
.input(z.object({ projectId: z.string() }))
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
await requireCanViewProject(input.projectId, ctx);
|
|
||||||
|
|
||||||
const datasets = await prisma.dataset.findMany({
|
|
||||||
where: {
|
|
||||||
projectId: input.projectId,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: "desc",
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
_count: {
|
|
||||||
select: { datasetEntries: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return datasets;
|
|
||||||
}),
|
|
||||||
|
|
||||||
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
|
|
||||||
await requireCanViewDataset(input.id, ctx);
|
|
||||||
return await prisma.dataset.findFirstOrThrow({
|
|
||||||
where: { id: input.id },
|
|
||||||
include: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(z.object({ projectId: z.string() }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireCanModifyProject(input.projectId, ctx);
|
|
||||||
|
|
||||||
const numDatasets = await prisma.dataset.count({
|
|
||||||
where: {
|
|
||||||
projectId: input.projectId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await prisma.dataset.create({
|
|
||||||
data: {
|
|
||||||
name: `Dataset ${numDatasets + 1}`,
|
|
||||||
projectId: input.projectId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(z.object({ id: z.string(), updates: z.object({ name: z.string() }) }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireCanModifyDataset(input.id, ctx);
|
|
||||||
return await prisma.dataset.update({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
name: input.updates.name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
delete: protectedProcedure
|
|
||||||
.input(z.object({ id: z.string() }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireCanModifyDataset(input.id, ctx);
|
|
||||||
|
|
||||||
await prisma.dataset.delete({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,421 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "~/server/api/trpc";
|
|
||||||
import { type Prisma } from "@prisma/client";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import dedent from "dedent";
|
|
||||||
import { generateNewCell } from "~/server/utils/generateNewCell";
|
|
||||||
import {
|
|
||||||
canModifyExperiment,
|
|
||||||
requireCanModifyExperiment,
|
|
||||||
requireCanModifyProject,
|
|
||||||
requireCanViewExperiment,
|
|
||||||
requireCanViewProject,
|
|
||||||
} from "~/utils/accessControl";
|
|
||||||
import generateTypes from "~/modelProviders/generateTypes";
|
|
||||||
import { promptConstructorVersion } from "~/promptConstructor/version";
|
|
||||||
|
|
||||||
export const experimentsRouter = createTRPCRouter({
|
|
||||||
stats: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
|
|
||||||
await requireCanViewExperiment(input.id, ctx);
|
|
||||||
|
|
||||||
const [experiment, promptVariantCount, testScenarioCount] = await prisma.$transaction([
|
|
||||||
prisma.experiment.findFirstOrThrow({
|
|
||||||
where: { id: input.id },
|
|
||||||
}),
|
|
||||||
prisma.promptVariant.count({
|
|
||||||
where: {
|
|
||||||
experimentId: input.id,
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.testScenario.count({
|
|
||||||
where: {
|
|
||||||
experimentId: input.id,
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
experimentLabel: experiment.label,
|
|
||||||
promptVariantCount,
|
|
||||||
testScenarioCount,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
list: protectedProcedure
|
|
||||||
.input(z.object({ projectId: z.string() }))
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
await requireCanViewProject(input.projectId, ctx);
|
|
||||||
|
|
||||||
const experiments = await prisma.experiment.findMany({
|
|
||||||
where: {
|
|
||||||
projectId: input.projectId,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
sortIndex: "desc",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: look for cleaner way to do this. Maybe aggregate?
|
|
||||||
const experimentsWithCounts = await Promise.all(
|
|
||||||
experiments.map(async (experiment) => {
|
|
||||||
const visibleTestScenarioCount = await prisma.testScenario.count({
|
|
||||||
where: {
|
|
||||||
experimentId: experiment.id,
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const visiblePromptVariantCount = await prisma.promptVariant.count({
|
|
||||||
where: {
|
|
||||||
experimentId: experiment.id,
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...experiment,
|
|
||||||
testScenarioCount: visibleTestScenarioCount,
|
|
||||||
promptVariantCount: visiblePromptVariantCount,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return experimentsWithCounts;
|
|
||||||
}),
|
|
||||||
|
|
||||||
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
|
|
||||||
await requireCanViewExperiment(input.id, ctx);
|
|
||||||
const experiment = await prisma.experiment.findFirstOrThrow({
|
|
||||||
where: { id: input.id },
|
|
||||||
include: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const canModify = ctx.session?.user.id
|
|
||||||
? await canModifyExperiment(experiment.id, ctx.session?.user.id)
|
|
||||||
: false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...experiment,
|
|
||||||
access: {
|
|
||||||
canView: true,
|
|
||||||
canModify,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
fork: protectedProcedure
|
|
||||||
.input(z.object({ id: z.string(), projectId: z.string() }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireCanViewExperiment(input.id, ctx);
|
|
||||||
await requireCanModifyProject(input.projectId, ctx);
|
|
||||||
|
|
||||||
const [
|
|
||||||
existingExp,
|
|
||||||
existingVariants,
|
|
||||||
existingScenarios,
|
|
||||||
existingCells,
|
|
||||||
evaluations,
|
|
||||||
templateVariables,
|
|
||||||
] = await prisma.$transaction([
|
|
||||||
prisma.experiment.findUniqueOrThrow({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.promptVariant.findMany({
|
|
||||||
where: {
|
|
||||||
experimentId: input.id,
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.testScenario.findMany({
|
|
||||||
where: {
|
|
||||||
experimentId: input.id,
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.scenarioVariantCell.findMany({
|
|
||||||
where: {
|
|
||||||
testScenario: {
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
promptVariant: {
|
|
||||||
experimentId: input.id,
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
modelResponses: {
|
|
||||||
include: {
|
|
||||||
outputEvaluations: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.evaluation.findMany({
|
|
||||||
where: {
|
|
||||||
experimentId: input.id,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.templateVariable.findMany({
|
|
||||||
where: {
|
|
||||||
experimentId: input.id,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const newExperimentId = uuidv4();
|
|
||||||
|
|
||||||
const existingToNewVariantIds = new Map<string, string>();
|
|
||||||
const variantsToCreate: Prisma.PromptVariantCreateManyInput[] = [];
|
|
||||||
for (const variant of existingVariants) {
|
|
||||||
const newVariantId = uuidv4();
|
|
||||||
existingToNewVariantIds.set(variant.id, newVariantId);
|
|
||||||
variantsToCreate.push({
|
|
||||||
...variant,
|
|
||||||
id: newVariantId,
|
|
||||||
experimentId: newExperimentId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingToNewScenarioIds = new Map<string, string>();
|
|
||||||
const scenariosToCreate: Prisma.TestScenarioCreateManyInput[] = [];
|
|
||||||
for (const scenario of existingScenarios) {
|
|
||||||
const newScenarioId = uuidv4();
|
|
||||||
existingToNewScenarioIds.set(scenario.id, newScenarioId);
|
|
||||||
scenariosToCreate.push({
|
|
||||||
...scenario,
|
|
||||||
id: newScenarioId,
|
|
||||||
experimentId: newExperimentId,
|
|
||||||
variableValues: scenario.variableValues as Prisma.InputJsonValue,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingToNewEvaluationIds = new Map<string, string>();
|
|
||||||
const evaluationsToCreate: Prisma.EvaluationCreateManyInput[] = [];
|
|
||||||
for (const evaluation of evaluations) {
|
|
||||||
const newEvaluationId = uuidv4();
|
|
||||||
existingToNewEvaluationIds.set(evaluation.id, newEvaluationId);
|
|
||||||
evaluationsToCreate.push({
|
|
||||||
...evaluation,
|
|
||||||
id: newEvaluationId,
|
|
||||||
experimentId: newExperimentId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const cellsToCreate: Prisma.ScenarioVariantCellCreateManyInput[] = [];
|
|
||||||
const modelResponsesToCreate: Prisma.ModelResponseCreateManyInput[] = [];
|
|
||||||
const outputEvaluationsToCreate: Prisma.OutputEvaluationCreateManyInput[] = [];
|
|
||||||
for (const cell of existingCells) {
|
|
||||||
const newCellId = uuidv4();
|
|
||||||
const { modelResponses, ...cellData } = cell;
|
|
||||||
cellsToCreate.push({
|
|
||||||
...cellData,
|
|
||||||
id: newCellId,
|
|
||||||
promptVariantId: existingToNewVariantIds.get(cell.promptVariantId) ?? "",
|
|
||||||
testScenarioId: existingToNewScenarioIds.get(cell.testScenarioId) ?? "",
|
|
||||||
prompt: (cell.prompt as Prisma.InputJsonValue) ?? undefined,
|
|
||||||
});
|
|
||||||
for (const modelResponse of modelResponses) {
|
|
||||||
const newModelResponseId = uuidv4();
|
|
||||||
const { outputEvaluations, ...modelResponseData } = modelResponse;
|
|
||||||
modelResponsesToCreate.push({
|
|
||||||
...modelResponseData,
|
|
||||||
id: newModelResponseId,
|
|
||||||
scenarioVariantCellId: newCellId,
|
|
||||||
output: (modelResponse.output as Prisma.InputJsonValue) ?? undefined,
|
|
||||||
});
|
|
||||||
for (const evaluation of outputEvaluations) {
|
|
||||||
outputEvaluationsToCreate.push({
|
|
||||||
...evaluation,
|
|
||||||
id: uuidv4(),
|
|
||||||
modelResponseId: newModelResponseId,
|
|
||||||
evaluationId: existingToNewEvaluationIds.get(evaluation.evaluationId) ?? "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateVariablesToCreate: Prisma.TemplateVariableCreateManyInput[] = [];
|
|
||||||
for (const templateVariable of templateVariables) {
|
|
||||||
templateVariablesToCreate.push({
|
|
||||||
...templateVariable,
|
|
||||||
id: uuidv4(),
|
|
||||||
experimentId: newExperimentId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxSortIndex =
|
|
||||||
(
|
|
||||||
await prisma.experiment.aggregate({
|
|
||||||
_max: {
|
|
||||||
sortIndex: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)._max?.sortIndex ?? 0;
|
|
||||||
|
|
||||||
await prisma.$transaction([
|
|
||||||
prisma.experiment.create({
|
|
||||||
data: {
|
|
||||||
id: newExperimentId,
|
|
||||||
sortIndex: maxSortIndex + 1,
|
|
||||||
label: `${existingExp.label} (forked)`,
|
|
||||||
projectId: input.projectId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.promptVariant.createMany({
|
|
||||||
data: variantsToCreate,
|
|
||||||
}),
|
|
||||||
prisma.testScenario.createMany({
|
|
||||||
data: scenariosToCreate,
|
|
||||||
}),
|
|
||||||
prisma.scenarioVariantCell.createMany({
|
|
||||||
data: cellsToCreate,
|
|
||||||
}),
|
|
||||||
prisma.modelResponse.createMany({
|
|
||||||
data: modelResponsesToCreate,
|
|
||||||
}),
|
|
||||||
prisma.evaluation.createMany({
|
|
||||||
data: evaluationsToCreate,
|
|
||||||
}),
|
|
||||||
prisma.outputEvaluation.createMany({
|
|
||||||
data: outputEvaluationsToCreate,
|
|
||||||
}),
|
|
||||||
prisma.templateVariable.createMany({
|
|
||||||
data: templateVariablesToCreate,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return newExperimentId;
|
|
||||||
}),
|
|
||||||
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(z.object({ projectId: z.string() }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireCanModifyProject(input.projectId, ctx);
|
|
||||||
|
|
||||||
const maxSortIndex =
|
|
||||||
(
|
|
||||||
await prisma.experiment.aggregate({
|
|
||||||
_max: {
|
|
||||||
sortIndex: true,
|
|
||||||
},
|
|
||||||
where: { projectId: input.projectId },
|
|
||||||
})
|
|
||||||
)._max?.sortIndex ?? 0;
|
|
||||||
|
|
||||||
const exp = await prisma.experiment.create({
|
|
||||||
data: {
|
|
||||||
sortIndex: maxSortIndex + 1,
|
|
||||||
label: `Experiment ${maxSortIndex + 1}`,
|
|
||||||
projectId: input.projectId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [variant, _, scenario1, scenario2, scenario3] = await prisma.$transaction([
|
|
||||||
prisma.promptVariant.create({
|
|
||||||
data: {
|
|
||||||
experimentId: exp.id,
|
|
||||||
label: "Prompt Variant 1",
|
|
||||||
sortIndex: 0,
|
|
||||||
// The interpolated $ is necessary until dedent incorporates
|
|
||||||
// https://github.com/dmnd/dedent/pull/46
|
|
||||||
promptConstructor: dedent`
|
|
||||||
/**
|
|
||||||
* Use Javascript to define an OpenAI chat completion
|
|
||||||
* (https://platform.openai.com/docs/api-reference/chat/create).
|
|
||||||
*
|
|
||||||
* You have access to the current scenario in the \`scenario\`
|
|
||||||
* variable.
|
|
||||||
*/
|
|
||||||
|
|
||||||
definePrompt("openai/ChatCompletion", {
|
|
||||||
model: "gpt-3.5-turbo-0613",
|
|
||||||
stream: true,
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: "system",
|
|
||||||
content: \`Write 'Start experimenting!' in ${"$"}{scenario.language}\`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});`,
|
|
||||||
model: "gpt-3.5-turbo-0613",
|
|
||||||
modelProvider: "openai/ChatCompletion",
|
|
||||||
promptConstructorVersion,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.templateVariable.create({
|
|
||||||
data: {
|
|
||||||
experimentId: exp.id,
|
|
||||||
label: "language",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.testScenario.create({
|
|
||||||
data: {
|
|
||||||
experimentId: exp.id,
|
|
||||||
variableValues: {
|
|
||||||
language: "English",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.testScenario.create({
|
|
||||||
data: {
|
|
||||||
experimentId: exp.id,
|
|
||||||
variableValues: {
|
|
||||||
language: "Spanish",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.testScenario.create({
|
|
||||||
data: {
|
|
||||||
experimentId: exp.id,
|
|
||||||
variableValues: {
|
|
||||||
language: "German",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await generateNewCell(variant.id, scenario1.id);
|
|
||||||
await generateNewCell(variant.id, scenario2.id);
|
|
||||||
await generateNewCell(variant.id, scenario3.id);
|
|
||||||
|
|
||||||
return exp;
|
|
||||||
}),
|
|
||||||
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(z.object({ id: z.string(), updates: z.object({ label: z.string() }) }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireCanModifyExperiment(input.id, ctx);
|
|
||||||
return await prisma.experiment.update({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
label: input.updates.label,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
delete: protectedProcedure
|
|
||||||
.input(z.object({ id: z.string() }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireCanModifyExperiment(input.id, ctx);
|
|
||||||
|
|
||||||
await prisma.experiment.delete({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Keeping these on `experiment` for now because we might want to limit the
|
|
||||||
// providers based on your account/experiment
|
|
||||||
promptTypes: publicProcedure.query(async () => {
|
|
||||||
return await generateTypes();
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
import { type Prisma } from "@prisma/client";
|
|
||||||
import { type JsonValue } from "type-fest";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
|
|
||||||
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import { hashRequest } from "~/server/utils/hashObject";
|
|
||||||
|
|
||||||
const reqValidator = z.object({
|
|
||||||
model: z.string(),
|
|
||||||
messages: z.array(z.any()),
|
|
||||||
});
|
|
||||||
|
|
||||||
const respValidator = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
model: z.string(),
|
|
||||||
usage: z.object({
|
|
||||||
total_tokens: z.number(),
|
|
||||||
prompt_tokens: z.number(),
|
|
||||||
completion_tokens: z.number(),
|
|
||||||
}),
|
|
||||||
choices: z.array(
|
|
||||||
z.object({
|
|
||||||
finish_reason: z.string(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const externalApiRouter = createTRPCRouter({
|
|
||||||
checkCache: publicProcedure
|
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: "POST",
|
|
||||||
path: "/v1/check-cache",
|
|
||||||
description: "Check if a prompt is cached",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
startTime: z.number().describe("Unix timestamp in milliseconds"),
|
|
||||||
reqPayload: z.unknown().describe("JSON-encoded request payload"),
|
|
||||||
tags: z
|
|
||||||
.record(z.string())
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
'Extra tags to attach to the call for filtering. Eg { "userId": "123", "promptId": "populate-title" }',
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
respPayload: z.unknown().optional().describe("JSON-encoded response payload"),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const apiKey = ctx.apiKey;
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
const key = await prisma.apiKey.findUnique({
|
|
||||||
where: { apiKey },
|
|
||||||
});
|
|
||||||
if (!key) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
const reqPayload = await reqValidator.spa(input.reqPayload);
|
|
||||||
const cacheKey = hashRequest(key.projectId, reqPayload as JsonValue);
|
|
||||||
|
|
||||||
const existingResponse = await prisma.loggedCallModelResponse.findFirst({
|
|
||||||
where: {
|
|
||||||
cacheKey,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
originalLoggedCall: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
startTime: "desc",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!existingResponse) return { respPayload: null };
|
|
||||||
|
|
||||||
await prisma.loggedCall.create({
|
|
||||||
data: {
|
|
||||||
projectId: key.projectId,
|
|
||||||
startTime: new Date(input.startTime),
|
|
||||||
cacheHit: true,
|
|
||||||
modelResponseId: existingResponse.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
respPayload: existingResponse.respPayload,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
report: publicProcedure
|
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: "POST",
|
|
||||||
path: "/v1/report",
|
|
||||||
description: "Report an API call",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
startTime: z.number().describe("Unix timestamp in milliseconds"),
|
|
||||||
endTime: z.number().describe("Unix timestamp in milliseconds"),
|
|
||||||
reqPayload: z.unknown().describe("JSON-encoded request payload"),
|
|
||||||
respPayload: z.unknown().optional().describe("JSON-encoded response payload"),
|
|
||||||
respStatus: z.number().optional().describe("HTTP status code of response"),
|
|
||||||
error: z.string().optional().describe("User-friendly error message"),
|
|
||||||
tags: z
|
|
||||||
.record(z.string())
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
'Extra tags to attach to the call for filtering. Eg { "userId": "123", "promptId": "populate-title" }',
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.output(z.void())
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const apiKey = ctx.apiKey;
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
const key = await prisma.apiKey.findUnique({
|
|
||||||
where: { apiKey },
|
|
||||||
});
|
|
||||||
if (!key) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
const reqPayload = await reqValidator.spa(input.reqPayload);
|
|
||||||
const respPayload = await respValidator.spa(input.respPayload);
|
|
||||||
|
|
||||||
const requestHash = hashRequest(key.projectId, reqPayload as JsonValue);
|
|
||||||
|
|
||||||
const newLoggedCallId = uuidv4();
|
|
||||||
const newModelResponseId = uuidv4();
|
|
||||||
|
|
||||||
const usage = respPayload.success ? respPayload.data.usage : undefined;
|
|
||||||
|
|
||||||
await prisma.$transaction([
|
|
||||||
prisma.loggedCall.create({
|
|
||||||
data: {
|
|
||||||
id: newLoggedCallId,
|
|
||||||
projectId: key.projectId,
|
|
||||||
startTime: new Date(input.startTime),
|
|
||||||
cacheHit: false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.loggedCallModelResponse.create({
|
|
||||||
data: {
|
|
||||||
id: newModelResponseId,
|
|
||||||
originalLoggedCallId: newLoggedCallId,
|
|
||||||
startTime: new Date(input.startTime),
|
|
||||||
endTime: new Date(input.endTime),
|
|
||||||
reqPayload: input.reqPayload as Prisma.InputJsonValue,
|
|
||||||
respPayload: input.respPayload as Prisma.InputJsonValue,
|
|
||||||
respStatus: input.respStatus,
|
|
||||||
error: input.error,
|
|
||||||
durationMs: input.endTime - input.startTime,
|
|
||||||
...(respPayload.success
|
|
||||||
? {
|
|
||||||
cacheKey: requestHash,
|
|
||||||
inputTokens: usage ? usage.prompt_tokens : undefined,
|
|
||||||
outputTokens: usage ? usage.completion_tokens : undefined,
|
|
||||||
}
|
|
||||||
: null),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
// Avoid foreign key constraint error by updating the logged call after the model response is created
|
|
||||||
prisma.loggedCall.update({
|
|
||||||
where: {
|
|
||||||
id: newLoggedCallId,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
modelResponseId: newModelResponseId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (input.tags) {
|
|
||||||
const tagsToCreate = Object.entries(input.tags).map(([name, value]) => ({
|
|
||||||
loggedCallId: newLoggedCallId,
|
|
||||||
// sanitize tags
|
|
||||||
name: name.replaceAll(/[^a-zA-Z0-9_]/g, "_"),
|
|
||||||
value,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (reqPayload.success) {
|
|
||||||
tagsToCreate.push({
|
|
||||||
loggedCallId: newLoggedCallId,
|
|
||||||
name: "$model",
|
|
||||||
value: reqPayload.data.model,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await prisma.loggedCallTag.createMany({
|
|
||||||
data: tagsToCreate,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import { generateApiKey } from "~/server/utils/generateApiKey";
|
|
||||||
import userProject from "~/server/utils/userProject";
|
|
||||||
import {
|
|
||||||
requireCanModifyProject,
|
|
||||||
requireCanViewProject,
|
|
||||||
requireIsProjectAdmin,
|
|
||||||
requireNothing,
|
|
||||||
} from "~/utils/accessControl";
|
|
||||||
|
|
||||||
export const projectsRouter = createTRPCRouter({
|
|
||||||
list: protectedProcedure.query(async ({ ctx }) => {
|
|
||||||
const userId = ctx.session.user.id;
|
|
||||||
requireNothing(ctx);
|
|
||||||
|
|
||||||
if (!userId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const projects = await prisma.project.findMany({
|
|
||||||
where: {
|
|
||||||
projectUsers: {
|
|
||||||
some: { userId: ctx.session.user.id },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: "asc",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!projects.length) {
|
|
||||||
// TODO: We should move this to a separate endpoint that is called on sign up
|
|
||||||
const personalProject = await userProject(userId);
|
|
||||||
projects.push(personalProject);
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects;
|
|
||||||
}),
|
|
||||||
get: protectedProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
|
|
||||||
await requireCanViewProject(input.id, ctx);
|
|
||||||
const [proj, userRole] = await prisma.$transaction([
|
|
||||||
prisma.project.findUnique({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
apiKeys: true,
|
|
||||||
personalProjectUser: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.projectUser.findFirst({
|
|
||||||
where: {
|
|
||||||
userId: ctx.session.user.id,
|
|
||||||
projectId: input.id,
|
|
||||||
role: {
|
|
||||||
in: ["ADMIN", "MEMBER"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!proj) {
|
|
||||||
throw new TRPCError({ code: "NOT_FOUND" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...proj,
|
|
||||||
role: userRole?.role ?? null,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(z.object({ id: z.string(), updates: z.object({ name: z.string() }) }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireCanModifyProject(input.id, ctx);
|
|
||||||
return await prisma.project.update({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
name: input.updates.name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(z.object({ name: z.string() }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
requireNothing(ctx);
|
|
||||||
const newProjectId = uuidv4();
|
|
||||||
const [newProject] = await prisma.$transaction([
|
|
||||||
prisma.project.create({
|
|
||||||
data: {
|
|
||||||
id: newProjectId,
|
|
||||||
name: input.name,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.projectUser.create({
|
|
||||||
data: {
|
|
||||||
userId: ctx.session.user.id,
|
|
||||||
projectId: newProjectId,
|
|
||||||
role: "ADMIN",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.apiKey.create({
|
|
||||||
data: {
|
|
||||||
name: "Default API Key",
|
|
||||||
projectId: newProjectId,
|
|
||||||
apiKey: generateApiKey(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
return newProject;
|
|
||||||
}),
|
|
||||||
delete: protectedProcedure
|
|
||||||
.input(z.object({ id: z.string() }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await requireIsProjectAdmin(input.id, ctx);
|
|
||||||
return await prisma.project.delete({
|
|
||||||
where: {
|
|
||||||
id: input.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "~/server/api/trpc";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import { requireNothing } from "~/utils/accessControl";
|
|
||||||
|
|
||||||
export const worldChampsRouter = createTRPCRouter({
|
|
||||||
userStatus: publicProcedure.query(async ({ ctx }) => {
|
|
||||||
const userId = ctx.session?.user.id;
|
|
||||||
|
|
||||||
if (!userId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await prisma.worldChampEntrant.findUnique({
|
|
||||||
where: { userId },
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
apply: protectedProcedure.mutation(async ({ ctx }) => {
|
|
||||||
const userId = ctx.session.user.id;
|
|
||||||
requireNothing(ctx);
|
|
||||||
|
|
||||||
const existingEntrant = await prisma.worldChampEntrant.findUnique({
|
|
||||||
where: { userId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingEntrant) {
|
|
||||||
return existingEntrant;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await prisma.worldChampEntrant.create({
|
|
||||||
data: {
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import {
|
|
||||||
type Experiment,
|
|
||||||
type PromptVariant,
|
|
||||||
type TestScenario,
|
|
||||||
type TemplateVariable,
|
|
||||||
type ScenarioVariantCell,
|
|
||||||
type ModelResponse,
|
|
||||||
type Evaluation,
|
|
||||||
type OutputEvaluation,
|
|
||||||
type Dataset,
|
|
||||||
type DatasetEntry,
|
|
||||||
type Project,
|
|
||||||
type ProjectUser,
|
|
||||||
type WorldChampEntrant,
|
|
||||||
type LoggedCall,
|
|
||||||
type LoggedCallModelResponse,
|
|
||||||
type LoggedCallTag,
|
|
||||||
type ApiKey,
|
|
||||||
type Account,
|
|
||||||
type Session,
|
|
||||||
type User,
|
|
||||||
type VerificationToken,
|
|
||||||
PrismaClient,
|
|
||||||
} from "@prisma/client";
|
|
||||||
import { Kysely, PostgresDialect } from "kysely";
|
|
||||||
// TODO: Revert to normal import when our tsconfig.json is fixed
|
|
||||||
// import { Pool } from "pg";
|
|
||||||
import PGModule from "pg";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const UntypedPool = PGModule.Pool as any;
|
|
||||||
const Pool = (UntypedPool.default ? UntypedPool.default : UntypedPool) as typeof PGModule.Pool;
|
|
||||||
|
|
||||||
import { env } from "~/env.mjs";
|
|
||||||
|
|
||||||
interface DB {
|
|
||||||
Experiment: Experiment;
|
|
||||||
PromptVariant: PromptVariant;
|
|
||||||
TestScenario: TestScenario;
|
|
||||||
TemplateVariable: TemplateVariable;
|
|
||||||
ScenarioVariantCell: ScenarioVariantCell;
|
|
||||||
ModelResponse: ModelResponse;
|
|
||||||
Evaluation: Evaluation;
|
|
||||||
OutputEvaluation: OutputEvaluation;
|
|
||||||
Dataset: Dataset;
|
|
||||||
DatasetEntry: DatasetEntry;
|
|
||||||
Project: Project;
|
|
||||||
ProjectUser: ProjectUser;
|
|
||||||
WorldChampEntrant: WorldChampEntrant;
|
|
||||||
LoggedCall: LoggedCall;
|
|
||||||
LoggedCallModelResponse: LoggedCallModelResponse;
|
|
||||||
LoggedCallTag: LoggedCallTag;
|
|
||||||
ApiKey: ApiKey;
|
|
||||||
Account: Account;
|
|
||||||
Session: Session;
|
|
||||||
User: User;
|
|
||||||
VerificationToken: VerificationToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
const globalForPrisma = globalThis as unknown as {
|
|
||||||
prisma: PrismaClient | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const prisma =
|
|
||||||
globalForPrisma.prisma ??
|
|
||||||
new PrismaClient({
|
|
||||||
log:
|
|
||||||
env.NODE_ENV === "development" && !env.RESTRICT_PRISMA_LOGS
|
|
||||||
? ["query", "error", "warn"]
|
|
||||||
: ["error"],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const kysely = new Kysely<DB>({
|
|
||||||
dialect: new PostgresDialect({
|
|
||||||
pool: new Pool({
|
|
||||||
connectionString: env.DATABASE_URL,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { type Prisma } from "@prisma/client";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import { generateApiKey } from "~/server/utils/generateApiKey";
|
|
||||||
|
|
||||||
console.log("backfilling api keys");
|
|
||||||
|
|
||||||
const projects = await prisma.project.findMany({
|
|
||||||
include: {
|
|
||||||
apiKeys: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`found ${projects.length} projects`);
|
|
||||||
|
|
||||||
const apiKeysToCreate: Prisma.ApiKeyCreateManyInput[] = [];
|
|
||||||
|
|
||||||
for (const proj of projects) {
|
|
||||||
if (!proj.apiKeys.length) {
|
|
||||||
apiKeysToCreate.push({
|
|
||||||
name: "Default API Key",
|
|
||||||
projectId: proj.id,
|
|
||||||
apiKey: generateApiKey(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`creating ${apiKeysToCreate.length} api keys`);
|
|
||||||
|
|
||||||
await prisma.apiKey.createMany({
|
|
||||||
data: apiKeysToCreate,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("done");
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import "dotenv/config";
|
|
||||||
import { openApiDocument } from "~/pages/api/openapi.json";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import { execSync } from "child_process";
|
|
||||||
|
|
||||||
console.log("Exporting public OpenAPI schema to client-libs/schema.json");
|
|
||||||
|
|
||||||
const scriptPath = import.meta.url.replace("file://", "");
|
|
||||||
const clientLibsPath = path.join(path.dirname(scriptPath), "../../../../client-libs");
|
|
||||||
|
|
||||||
const schemaPath = path.join(clientLibsPath, "schema.json");
|
|
||||||
|
|
||||||
console.log("Exporting schema");
|
|
||||||
fs.writeFileSync(schemaPath, JSON.stringify(openApiDocument, null, 2), "utf-8");
|
|
||||||
|
|
||||||
console.log("Generating Typescript client");
|
|
||||||
|
|
||||||
const tsClientPath = path.join(clientLibsPath, "typescript/codegen");
|
|
||||||
|
|
||||||
fs.rmSync(tsClientPath, { recursive: true, force: true });
|
|
||||||
|
|
||||||
execSync(
|
|
||||||
`pnpm dlx @openapitools/openapi-generator-cli generate -i "${schemaPath}" -g typescript-axios -o "${tsClientPath}"`,
|
|
||||||
{
|
|
||||||
stdio: "inherit",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("Done!");
|
|
||||||
|
|
||||||
process.exit(0);
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
cd "$(dirname "$0")/../../.."
|
|
||||||
|
|
||||||
|
|
||||||
set -o allexport
|
|
||||||
source .env
|
|
||||||
set +o allexport
|
|
||||||
|
|
||||||
echo "Connecting to prod db"
|
|
||||||
DATABASE_URL=$PROD_DATABASE_URL pnpm prisma studio
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import dayjs from "dayjs";
|
|
||||||
import { prisma } from "../db";
|
|
||||||
|
|
||||||
const projectId = "1234";
|
|
||||||
|
|
||||||
// Find all calls in the last 24 hours
|
|
||||||
const responses = await prisma.loggedCall.findMany({
|
|
||||||
where: {
|
|
||||||
projectId: projectId,
|
|
||||||
startTime: {
|
|
||||||
gt: dayjs()
|
|
||||||
.subtract(24 * 3600)
|
|
||||||
.toDate(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
modelResponse: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
startTime: "desc",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find all calls in the last 24 hours with promptId 'hello-world'
|
|
||||||
const helloWorld = await prisma.loggedCall.findMany({
|
|
||||||
where: {
|
|
||||||
projectId: projectId,
|
|
||||||
startTime: {
|
|
||||||
gt: dayjs()
|
|
||||||
.subtract(24 * 3600)
|
|
||||||
.toDate(),
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
some: {
|
|
||||||
name: "promptId",
|
|
||||||
value: "hello-world",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
modelResponse: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
startTime: "desc",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Total spent on OpenAI in the last month
|
|
||||||
const totalSpent = await prisma.loggedCallModelResponse.aggregate({
|
|
||||||
_sum: {
|
|
||||||
totalCost: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
originalLoggedCall: {
|
|
||||||
projectId: projectId,
|
|
||||||
},
|
|
||||||
startTime: {
|
|
||||||
gt: dayjs()
|
|
||||||
.subtract(30 * 24 * 3600)
|
|
||||||
.toDate(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import cryptoRandomString from "crypto-random-string";
|
|
||||||
|
|
||||||
const KEY_LENGTH = 42;
|
|
||||||
|
|
||||||
export const generateApiKey = () => `opc_${cryptoRandomString({ length: KEY_LENGTH })}`;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { env } from "~/env.mjs";
|
|
||||||
|
|
||||||
import { default as OriginalOpenAI } from "openai";
|
|
||||||
// import { OpenAI } from "openpipe";
|
|
||||||
|
|
||||||
const openAIConfig = { apiKey: env.OPENAI_API_KEY ?? "dummy-key" };
|
|
||||||
|
|
||||||
// Set a dummy key so it doesn't fail at build time
|
|
||||||
// export const openai = env.OPENPIPE_API_KEY
|
|
||||||
// ? new OpenAI.OpenAI(openAIConfig)
|
|
||||||
// : new OriginalOpenAI(openAIConfig);
|
|
||||||
|
|
||||||
export const openai = new OriginalOpenAI(openAIConfig);
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { prisma } from "~/server/db";
|
|
||||||
import { generateApiKey } from "./generateApiKey";
|
|
||||||
|
|
||||||
export default async function userProject(userId: string) {
|
|
||||||
return await prisma.project.upsert({
|
|
||||||
where: {
|
|
||||||
personalProjectUserId: userId,
|
|
||||||
},
|
|
||||||
update: {},
|
|
||||||
create: {
|
|
||||||
personalProjectUserId: userId,
|
|
||||||
projectUsers: {
|
|
||||||
create: {
|
|
||||||
userId: userId,
|
|
||||||
role: "ADMIN",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
apiKeys: {
|
|
||||||
create: [
|
|
||||||
{
|
|
||||||
name: "Default API Key",
|
|
||||||
apiKey: generateApiKey(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { extendTheme, defineStyleConfig, ChakraProvider } from "@chakra-ui/react";
|
|
||||||
import "@fontsource/inconsolata";
|
|
||||||
import { modalAnatomy } from "@chakra-ui/anatomy";
|
|
||||||
import { createMultiStyleConfigHelpers } from "@chakra-ui/styled-system";
|
|
||||||
|
|
||||||
const systemFont =
|
|
||||||
'ui-sans-serif, -apple-system, "system-ui", "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"';
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/unbound-method */
|
|
||||||
const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(
|
|
||||||
modalAnatomy.keys,
|
|
||||||
);
|
|
||||||
|
|
||||||
const modalTheme = defineMultiStyleConfig({
|
|
||||||
baseStyle: definePartsStyle({
|
|
||||||
dialog: { borderRadius: "sm" },
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const Divider = defineStyleConfig({
|
|
||||||
baseStyle: {
|
|
||||||
borderColor: "gray.300",
|
|
||||||
backgroundColor: "gray.300",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const theme = extendTheme({
|
|
||||||
styles: {
|
|
||||||
global: (props: { colorMode: "dark" | "light" }) => ({
|
|
||||||
"html, body": {
|
|
||||||
backgroundColor: props.colorMode === "dark" ? "gray.900" : "white",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
fonts: {
|
|
||||||
heading: systemFont,
|
|
||||||
body: systemFont,
|
|
||||||
},
|
|
||||||
|
|
||||||
components: {
|
|
||||||
Button: {
|
|
||||||
baseStyle: {
|
|
||||||
borderRadius: "sm",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Input: {
|
|
||||||
baseStyle: {
|
|
||||||
field: {
|
|
||||||
borderRadius: "sm",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Textarea: {
|
|
||||||
baseStyle: {
|
|
||||||
borderRadius: "sm",
|
|
||||||
field: {
|
|
||||||
borderRadius: "sm",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Modal: modalTheme,
|
|
||||||
Divider,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ChakraThemeProvider = ({ children }: { children: JSX.Element }) => {
|
|
||||||
return <ChakraProvider theme={theme}>{children}</ChakraProvider>;
|
|
||||||
};
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
import { ProjectUserRole } from "@prisma/client";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { type TRPCContext } from "~/server/api/trpc";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
|
|
||||||
const isAdmin = async (userId: string) => {
|
|
||||||
const user = await prisma.user.findFirst({
|
|
||||||
where: { id: userId, role: "ADMIN" },
|
|
||||||
});
|
|
||||||
|
|
||||||
return !!user;
|
|
||||||
};
|
|
||||||
|
|
||||||
// No-op method for protected routes that really should be accessible to anyone.
|
|
||||||
export const requireNothing = (ctx: TRPCContext) => {
|
|
||||||
ctx.markAccessControlRun();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requireIsProjectAdmin = async (projectId: string, ctx: TRPCContext) => {
|
|
||||||
const userId = ctx.session?.user.id;
|
|
||||||
if (!userId) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAdmin = await prisma.projectUser.findFirst({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
projectId,
|
|
||||||
role: "ADMIN",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isAdmin) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.markAccessControlRun();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requireCanViewProject = async (projectId: string, ctx: TRPCContext) => {
|
|
||||||
const userId = ctx.session?.user.id;
|
|
||||||
if (!userId) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const canView = await prisma.projectUser.findFirst({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
projectId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!canView) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.markAccessControlRun();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requireCanModifyProject = async (projectId: string, ctx: TRPCContext) => {
|
|
||||||
const userId = ctx.session?.user.id;
|
|
||||||
if (!userId) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const canModify = await prisma.projectUser.findFirst({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
projectId,
|
|
||||||
role: { in: [ProjectUserRole.ADMIN, ProjectUserRole.MEMBER] },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!canModify) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.markAccessControlRun();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requireCanViewDataset = async (datasetId: string, ctx: TRPCContext) => {
|
|
||||||
const dataset = await prisma.dataset.findFirst({
|
|
||||||
where: {
|
|
||||||
id: datasetId,
|
|
||||||
project: {
|
|
||||||
projectUsers: {
|
|
||||||
some: {
|
|
||||||
role: { in: [ProjectUserRole.ADMIN, ProjectUserRole.MEMBER] },
|
|
||||||
userId: ctx.session?.user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!dataset) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.markAccessControlRun();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requireCanModifyDataset = async (datasetId: string, ctx: TRPCContext) => {
|
|
||||||
// Right now all users who can view a dataset can also modify it
|
|
||||||
await requireCanViewDataset(datasetId, ctx);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requireCanViewExperiment = async (experimentId: string, ctx: TRPCContext) => {
|
|
||||||
await prisma.experiment.findFirst({
|
|
||||||
where: { id: experimentId },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Right now all experiments are publicly viewable, so this is a no-op.
|
|
||||||
ctx.markAccessControlRun();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const canModifyExperiment = async (experimentId: string, userId: string) => {
|
|
||||||
const [adminUser, experiment] = await Promise.all([
|
|
||||||
isAdmin(userId),
|
|
||||||
prisma.experiment.findFirst({
|
|
||||||
where: {
|
|
||||||
id: experimentId,
|
|
||||||
project: {
|
|
||||||
projectUsers: {
|
|
||||||
some: {
|
|
||||||
role: { in: [ProjectUserRole.ADMIN, ProjectUserRole.MEMBER] },
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return adminUser || !!experiment;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requireCanModifyExperiment = async (experimentId: string, ctx: TRPCContext) => {
|
|
||||||
const userId = ctx.session?.user.id;
|
|
||||||
if (!userId) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await canModifyExperiment(experimentId, userId))) {
|
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.markAccessControlRun();
|
|
||||||
};
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { type Session } from "next-auth";
|
|
||||||
import { useSession } from "next-auth/react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
import posthog from "posthog-js";
|
|
||||||
import { env } from "~/env.mjs";
|
|
||||||
|
|
||||||
// Make sure we're in the browser
|
|
||||||
const enableBrowserAnalytics = typeof window !== "undefined";
|
|
||||||
|
|
||||||
if (env.NEXT_PUBLIC_POSTHOG_KEY && enableBrowserAnalytics) {
|
|
||||||
posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, {
|
|
||||||
api_host: `${env.NEXT_PUBLIC_HOST}/ingest`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const identifySession = (session: Session) => {
|
|
||||||
if (!session.user) return;
|
|
||||||
posthog.identify(session.user.id, {
|
|
||||||
name: session.user.name,
|
|
||||||
email: session.user.email,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SessionIdentifier = () => {
|
|
||||||
const session = useSession().data;
|
|
||||||
useEffect(() => {
|
|
||||||
if (session && enableBrowserAnalytics) identifySession(session);
|
|
||||||
}, [session]);
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { type Session } from "next-auth";
|
|
||||||
import { PostHog } from "posthog-node";
|
|
||||||
import { env } from "~/env.mjs";
|
|
||||||
|
|
||||||
export const posthogServerClient = env.NEXT_PUBLIC_POSTHOG_KEY
|
|
||||||
? new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
|
|
||||||
host: "https://app.posthog.com",
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
|
|
||||||
export const capturePath = (session: Session, path: string) => {
|
|
||||||
if (!session.user || !posthogServerClient) return;
|
|
||||||
posthogServerClient?.capture({ distinctId: session.user.id, event: path });
|
|
||||||
};
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
{
|
|
||||||
"openapi": "3.0.3",
|
|
||||||
"info": {
|
|
||||||
"title": "OpenPipe API",
|
|
||||||
"description": "The public API for reporting API calls to OpenPipe",
|
|
||||||
"version": "0.1.0"
|
|
||||||
},
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"url": "https://app.openpipe.ai/api"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"/v1/check-cache": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "externalApi-checkCache",
|
|
||||||
"description": "Check if a prompt is cached",
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"startTime": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Unix timestamp in milliseconds"
|
|
||||||
},
|
|
||||||
"reqPayload": {
|
|
||||||
"description": "JSON-encoded request payload"
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": "Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" }"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"startTime"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"respPayload": {
|
|
||||||
"description": "JSON-encoded response payload"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"default": {
|
|
||||||
"$ref": "#/components/responses/error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/report": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "externalApi-report",
|
|
||||||
"description": "Report an API call",
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"startTime": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Unix timestamp in milliseconds"
|
|
||||||
},
|
|
||||||
"endTime": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Unix timestamp in milliseconds"
|
|
||||||
},
|
|
||||||
"reqPayload": {
|
|
||||||
"description": "JSON-encoded request payload"
|
|
||||||
},
|
|
||||||
"respPayload": {
|
|
||||||
"description": "JSON-encoded response payload"
|
|
||||||
},
|
|
||||||
"respStatus": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "HTTP status code of response"
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "User-friendly error message"
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": "Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" }"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"startTime",
|
|
||||||
"endTime"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"default": {
|
|
||||||
"$ref": "#/components/responses/error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"securitySchemes": {
|
|
||||||
"Authorization": {
|
|
||||||
"type": "http",
|
|
||||||
"scheme": "bearer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"error": {
|
|
||||||
"description": "Error response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"code": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"issues": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"message"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"message",
|
|
||||||
"code"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
client-libs/typescript/.gitignore
vendored
2
client-libs/typescript/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
4
client-libs/typescript/codegen/.gitignore
vendored
4
client-libs/typescript/codegen/.gitignore
vendored
@@ -1,4 +0,0 @@
|
|||||||
wwwroot/*.js
|
|
||||||
node_modules
|
|
||||||
typings
|
|
||||||
dist
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# OpenAPI Generator Ignore
|
|
||||||
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
|
||||||
|
|
||||||
# Use this file to prevent files from being overwritten by the generator.
|
|
||||||
# The patterns follow closely to .gitignore or .dockerignore.
|
|
||||||
|
|
||||||
# As an example, the C# client generator defines ApiClient.cs.
|
|
||||||
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
|
||||||
#ApiClient.cs
|
|
||||||
|
|
||||||
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
|
||||||
#foo/*/qux
|
|
||||||
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
|
||||||
|
|
||||||
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
|
||||||
#foo/**/qux
|
|
||||||
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
|
||||||
|
|
||||||
# You can also negate patterns with an exclamation (!).
|
|
||||||
# For example, you can ignore all files in a docs folder with the file extension .md:
|
|
||||||
#docs/*.md
|
|
||||||
# Then explicitly reverse the ignore rule for a single file:
|
|
||||||
#!docs/README.md
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
.gitignore
|
|
||||||
.npmignore
|
|
||||||
.openapi-generator-ignore
|
|
||||||
api.ts
|
|
||||||
base.ts
|
|
||||||
common.ts
|
|
||||||
configuration.ts
|
|
||||||
git_push.sh
|
|
||||||
index.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
6.6.0
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* OpenPipe API
|
|
||||||
* The public API for reporting API calls to OpenPipe
|
|
||||||
*
|
|
||||||
* The version of the OpenAPI document: 0.1.0
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
||||||
* https://openapi-generator.tech
|
|
||||||
* Do not edit the class manually.
|
|
||||||
*/
|
|
||||||
var __extends = (this && this.__extends) || (function () {
|
|
||||||
var extendStatics = function (d, b) {
|
|
||||||
extendStatics = Object.setPrototypeOf ||
|
|
||||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
||||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
||||||
return extendStatics(d, b);
|
|
||||||
};
|
|
||||||
return function (d, b) {
|
|
||||||
if (typeof b !== "function" && b !== null)
|
|
||||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
||||||
extendStatics(d, b);
|
|
||||||
function __() { this.constructor = d; }
|
|
||||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
var __assign = (this && this.__assign) || function () {
|
|
||||||
__assign = Object.assign || function(t) {
|
|
||||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
||||||
s = arguments[i];
|
|
||||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
||||||
t[p] = s[p];
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
};
|
|
||||||
return __assign.apply(this, arguments);
|
|
||||||
};
|
|
||||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
||||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
||||||
return new (P || (P = Promise))(function (resolve, reject) {
|
|
||||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
||||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
||||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
||||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
||||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
||||||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
||||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
||||||
function step(op) {
|
|
||||||
if (f) throw new TypeError("Generator is already executing.");
|
|
||||||
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
||||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
||||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
||||||
switch (op[0]) {
|
|
||||||
case 0: case 1: t = op; break;
|
|
||||||
case 4: _.label++; return { value: op[1], done: false };
|
|
||||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
||||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
||||||
default:
|
|
||||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
||||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
||||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
||||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
||||||
if (t[2]) _.ops.pop();
|
|
||||||
_.trys.pop(); continue;
|
|
||||||
}
|
|
||||||
op = body.call(thisArg, _);
|
|
||||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
||||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.DefaultApi = exports.DefaultApiFactory = exports.DefaultApiFp = exports.DefaultApiAxiosParamCreator = void 0;
|
|
||||||
var axios_1 = require("axios");
|
|
||||||
// Some imports not used depending on template conditions
|
|
||||||
// @ts-ignore
|
|
||||||
var common_1 = require("./common");
|
|
||||||
// @ts-ignore
|
|
||||||
var base_1 = require("./base");
|
|
||||||
/**
|
|
||||||
* DefaultApi - axios parameter creator
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
var DefaultApiAxiosParamCreator = function (configuration) {
|
|
||||||
var _this = this;
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Check if a prompt is cached
|
|
||||||
* @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
externalApiCheckCache: function (externalApiCheckCacheRequest, options) {
|
|
||||||
if (options === void 0) { options = {}; }
|
|
||||||
return __awaiter(_this, void 0, void 0, function () {
|
|
||||||
var localVarPath, localVarUrlObj, baseOptions, localVarRequestOptions, localVarHeaderParameter, localVarQueryParameter, headersFromBaseOptions;
|
|
||||||
return __generator(this, function (_a) {
|
|
||||||
// verify required parameter 'externalApiCheckCacheRequest' is not null or undefined
|
|
||||||
(0, common_1.assertParamExists)('externalApiCheckCache', 'externalApiCheckCacheRequest', externalApiCheckCacheRequest);
|
|
||||||
localVarPath = "/v1/check-cache";
|
|
||||||
localVarUrlObj = new URL(localVarPath, common_1.DUMMY_BASE_URL);
|
|
||||||
if (configuration) {
|
|
||||||
baseOptions = configuration.baseOptions;
|
|
||||||
}
|
|
||||||
localVarRequestOptions = __assign(__assign({ method: 'POST' }, baseOptions), options);
|
|
||||||
localVarHeaderParameter = {};
|
|
||||||
localVarQueryParameter = {};
|
|
||||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
|
||||||
(0, common_1.setSearchParams)(localVarUrlObj, localVarQueryParameter);
|
|
||||||
headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
|
||||||
localVarRequestOptions.headers = __assign(__assign(__assign({}, localVarHeaderParameter), headersFromBaseOptions), options.headers);
|
|
||||||
localVarRequestOptions.data = (0, common_1.serializeDataIfNeeded)(externalApiCheckCacheRequest, localVarRequestOptions, configuration);
|
|
||||||
return [2 /*return*/, {
|
|
||||||
url: (0, common_1.toPathString)(localVarUrlObj),
|
|
||||||
options: localVarRequestOptions,
|
|
||||||
}];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Report an API call
|
|
||||||
* @param {ExternalApiReportRequest} externalApiReportRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
externalApiReport: function (externalApiReportRequest, options) {
|
|
||||||
if (options === void 0) { options = {}; }
|
|
||||||
return __awaiter(_this, void 0, void 0, function () {
|
|
||||||
var localVarPath, localVarUrlObj, baseOptions, localVarRequestOptions, localVarHeaderParameter, localVarQueryParameter, headersFromBaseOptions;
|
|
||||||
return __generator(this, function (_a) {
|
|
||||||
// verify required parameter 'externalApiReportRequest' is not null or undefined
|
|
||||||
(0, common_1.assertParamExists)('externalApiReport', 'externalApiReportRequest', externalApiReportRequest);
|
|
||||||
localVarPath = "/v1/report";
|
|
||||||
localVarUrlObj = new URL(localVarPath, common_1.DUMMY_BASE_URL);
|
|
||||||
if (configuration) {
|
|
||||||
baseOptions = configuration.baseOptions;
|
|
||||||
}
|
|
||||||
localVarRequestOptions = __assign(__assign({ method: 'POST' }, baseOptions), options);
|
|
||||||
localVarHeaderParameter = {};
|
|
||||||
localVarQueryParameter = {};
|
|
||||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
|
||||||
(0, common_1.setSearchParams)(localVarUrlObj, localVarQueryParameter);
|
|
||||||
headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
|
||||||
localVarRequestOptions.headers = __assign(__assign(__assign({}, localVarHeaderParameter), headersFromBaseOptions), options.headers);
|
|
||||||
localVarRequestOptions.data = (0, common_1.serializeDataIfNeeded)(externalApiReportRequest, localVarRequestOptions, configuration);
|
|
||||||
return [2 /*return*/, {
|
|
||||||
url: (0, common_1.toPathString)(localVarUrlObj),
|
|
||||||
options: localVarRequestOptions,
|
|
||||||
}];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
exports.DefaultApiAxiosParamCreator = DefaultApiAxiosParamCreator;
|
|
||||||
/**
|
|
||||||
* DefaultApi - functional programming interface
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
var DefaultApiFp = function (configuration) {
|
|
||||||
var localVarAxiosParamCreator = (0, exports.DefaultApiAxiosParamCreator)(configuration);
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Check if a prompt is cached
|
|
||||||
* @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
externalApiCheckCache: function (externalApiCheckCacheRequest, options) {
|
|
||||||
return __awaiter(this, void 0, void 0, function () {
|
|
||||||
var localVarAxiosArgs;
|
|
||||||
return __generator(this, function (_a) {
|
|
||||||
switch (_a.label) {
|
|
||||||
case 0: return [4 /*yield*/, localVarAxiosParamCreator.externalApiCheckCache(externalApiCheckCacheRequest, options)];
|
|
||||||
case 1:
|
|
||||||
localVarAxiosArgs = _a.sent();
|
|
||||||
return [2 /*return*/, (0, common_1.createRequestFunction)(localVarAxiosArgs, axios_1.default, base_1.BASE_PATH, configuration)];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Report an API call
|
|
||||||
* @param {ExternalApiReportRequest} externalApiReportRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
externalApiReport: function (externalApiReportRequest, options) {
|
|
||||||
return __awaiter(this, void 0, void 0, function () {
|
|
||||||
var localVarAxiosArgs;
|
|
||||||
return __generator(this, function (_a) {
|
|
||||||
switch (_a.label) {
|
|
||||||
case 0: return [4 /*yield*/, localVarAxiosParamCreator.externalApiReport(externalApiReportRequest, options)];
|
|
||||||
case 1:
|
|
||||||
localVarAxiosArgs = _a.sent();
|
|
||||||
return [2 /*return*/, (0, common_1.createRequestFunction)(localVarAxiosArgs, axios_1.default, base_1.BASE_PATH, configuration)];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
exports.DefaultApiFp = DefaultApiFp;
|
|
||||||
/**
|
|
||||||
* DefaultApi - factory interface
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
var DefaultApiFactory = function (configuration, basePath, axios) {
|
|
||||||
var localVarFp = (0, exports.DefaultApiFp)(configuration);
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Check if a prompt is cached
|
|
||||||
* @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
externalApiCheckCache: function (externalApiCheckCacheRequest, options) {
|
|
||||||
return localVarFp.externalApiCheckCache(externalApiCheckCacheRequest, options).then(function (request) { return request(axios, basePath); });
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Report an API call
|
|
||||||
* @param {ExternalApiReportRequest} externalApiReportRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
externalApiReport: function (externalApiReportRequest, options) {
|
|
||||||
return localVarFp.externalApiReport(externalApiReportRequest, options).then(function (request) { return request(axios, basePath); });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
exports.DefaultApiFactory = DefaultApiFactory;
|
|
||||||
/**
|
|
||||||
* DefaultApi - object-oriented interface
|
|
||||||
* @export
|
|
||||||
* @class DefaultApi
|
|
||||||
* @extends {BaseAPI}
|
|
||||||
*/
|
|
||||||
var DefaultApi = /** @class */ (function (_super) {
|
|
||||||
__extends(DefaultApi, _super);
|
|
||||||
function DefaultApi() {
|
|
||||||
return _super !== null && _super.apply(this, arguments) || this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Check if a prompt is cached
|
|
||||||
* @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
* @memberof DefaultApi
|
|
||||||
*/
|
|
||||||
DefaultApi.prototype.externalApiCheckCache = function (externalApiCheckCacheRequest, options) {
|
|
||||||
var _this = this;
|
|
||||||
return (0, exports.DefaultApiFp)(this.configuration).externalApiCheckCache(externalApiCheckCacheRequest, options).then(function (request) { return request(_this.axios, _this.basePath); });
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Report an API call
|
|
||||||
* @param {ExternalApiReportRequest} externalApiReportRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
* @memberof DefaultApi
|
|
||||||
*/
|
|
||||||
DefaultApi.prototype.externalApiReport = function (externalApiReportRequest, options) {
|
|
||||||
var _this = this;
|
|
||||||
return (0, exports.DefaultApiFp)(this.configuration).externalApiReport(externalApiReportRequest, options).then(function (request) { return request(_this.axios, _this.basePath); });
|
|
||||||
};
|
|
||||||
return DefaultApi;
|
|
||||||
}(base_1.BaseAPI));
|
|
||||||
exports.DefaultApi = DefaultApi;
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* OpenPipe API
|
|
||||||
* The public API for reporting API calls to OpenPipe
|
|
||||||
*
|
|
||||||
* The version of the OpenAPI document: 0.1.0
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
||||||
* https://openapi-generator.tech
|
|
||||||
* Do not edit the class manually.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import type { Configuration } from './configuration';
|
|
||||||
import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
||||||
import globalAxios from 'axios';
|
|
||||||
// Some imports not used depending on template conditions
|
|
||||||
// @ts-ignore
|
|
||||||
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';
|
|
||||||
import type { RequestArgs } from './base';
|
|
||||||
// @ts-ignore
|
|
||||||
import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface ExternalApiCheckCache200Response
|
|
||||||
*/
|
|
||||||
export interface ExternalApiCheckCache200Response {
|
|
||||||
/**
|
|
||||||
* JSON-encoded response payload
|
|
||||||
* @type {any}
|
|
||||||
* @memberof ExternalApiCheckCache200Response
|
|
||||||
*/
|
|
||||||
'respPayload'?: any;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface ExternalApiCheckCacheDefaultResponse
|
|
||||||
*/
|
|
||||||
export interface ExternalApiCheckCacheDefaultResponse {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof ExternalApiCheckCacheDefaultResponse
|
|
||||||
*/
|
|
||||||
'message': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof ExternalApiCheckCacheDefaultResponse
|
|
||||||
*/
|
|
||||||
'code': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<ExternalApiCheckCacheDefaultResponseIssuesInner>}
|
|
||||||
* @memberof ExternalApiCheckCacheDefaultResponse
|
|
||||||
*/
|
|
||||||
'issues'?: Array<ExternalApiCheckCacheDefaultResponseIssuesInner>;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface ExternalApiCheckCacheDefaultResponseIssuesInner
|
|
||||||
*/
|
|
||||||
export interface ExternalApiCheckCacheDefaultResponseIssuesInner {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof ExternalApiCheckCacheDefaultResponseIssuesInner
|
|
||||||
*/
|
|
||||||
'message': string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface ExternalApiCheckCacheRequest
|
|
||||||
*/
|
|
||||||
export interface ExternalApiCheckCacheRequest {
|
|
||||||
/**
|
|
||||||
* Unix timestamp in milliseconds
|
|
||||||
* @type {number}
|
|
||||||
* @memberof ExternalApiCheckCacheRequest
|
|
||||||
*/
|
|
||||||
'startTime': number;
|
|
||||||
/**
|
|
||||||
* JSON-encoded request payload
|
|
||||||
* @type {any}
|
|
||||||
* @memberof ExternalApiCheckCacheRequest
|
|
||||||
*/
|
|
||||||
'reqPayload'?: any;
|
|
||||||
/**
|
|
||||||
* Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" }
|
|
||||||
* @type {{ [key: string]: string; }}
|
|
||||||
* @memberof ExternalApiCheckCacheRequest
|
|
||||||
*/
|
|
||||||
'tags'?: { [key: string]: string; };
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface ExternalApiReportRequest
|
|
||||||
*/
|
|
||||||
export interface ExternalApiReportRequest {
|
|
||||||
/**
|
|
||||||
* Unix timestamp in milliseconds
|
|
||||||
* @type {number}
|
|
||||||
* @memberof ExternalApiReportRequest
|
|
||||||
*/
|
|
||||||
'startTime': number;
|
|
||||||
/**
|
|
||||||
* Unix timestamp in milliseconds
|
|
||||||
* @type {number}
|
|
||||||
* @memberof ExternalApiReportRequest
|
|
||||||
*/
|
|
||||||
'endTime': number;
|
|
||||||
/**
|
|
||||||
* JSON-encoded request payload
|
|
||||||
* @type {any}
|
|
||||||
* @memberof ExternalApiReportRequest
|
|
||||||
*/
|
|
||||||
'reqPayload'?: any;
|
|
||||||
/**
|
|
||||||
* JSON-encoded response payload
|
|
||||||
* @type {any}
|
|
||||||
* @memberof ExternalApiReportRequest
|
|
||||||
*/
|
|
||||||
'respPayload'?: any;
|
|
||||||
/**
|
|
||||||
* HTTP status code of response
|
|
||||||
* @type {number}
|
|
||||||
* @memberof ExternalApiReportRequest
|
|
||||||
*/
|
|
||||||
'respStatus'?: number;
|
|
||||||
/**
|
|
||||||
* User-friendly error message
|
|
||||||
* @type {string}
|
|
||||||
* @memberof ExternalApiReportRequest
|
|
||||||
*/
|
|
||||||
'error'?: string;
|
|
||||||
/**
|
|
||||||
* Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" }
|
|
||||||
* @type {{ [key: string]: string; }}
|
|
||||||
* @memberof ExternalApiReportRequest
|
|
||||||
*/
|
|
||||||
'tags'?: { [key: string]: string; };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DefaultApi - axios parameter creator
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Check if a prompt is cached
|
|
||||||
* @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
externalApiCheckCache: async (externalApiCheckCacheRequest: ExternalApiCheckCacheRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
|
||||||
// verify required parameter 'externalApiCheckCacheRequest' is not null or undefined
|
|
||||||
assertParamExists('externalApiCheckCache', 'externalApiCheckCacheRequest', externalApiCheckCacheRequest)
|
|
||||||
const localVarPath = `/v1/check-cache`;
|
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
|
||||||
let baseOptions;
|
|
||||||
if (configuration) {
|
|
||||||
baseOptions = configuration.baseOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
|
||||||
const localVarHeaderParameter = {} as any;
|
|
||||||
const localVarQueryParameter = {} as any;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
|
||||||
localVarRequestOptions.data = serializeDataIfNeeded(externalApiCheckCacheRequest, localVarRequestOptions, configuration)
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: toPathString(localVarUrlObj),
|
|
||||||
options: localVarRequestOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Report an API call
|
|
||||||
* @param {ExternalApiReportRequest} externalApiReportRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
externalApiReport: async (externalApiReportRequest: ExternalApiReportRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
|
||||||
// verify required parameter 'externalApiReportRequest' is not null or undefined
|
|
||||||
assertParamExists('externalApiReport', 'externalApiReportRequest', externalApiReportRequest)
|
|
||||||
const localVarPath = `/v1/report`;
|
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
|
||||||
let baseOptions;
|
|
||||||
if (configuration) {
|
|
||||||
baseOptions = configuration.baseOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
|
||||||
const localVarHeaderParameter = {} as any;
|
|
||||||
const localVarQueryParameter = {} as any;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
|
||||||
localVarRequestOptions.data = serializeDataIfNeeded(externalApiReportRequest, localVarRequestOptions, configuration)
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: toPathString(localVarUrlObj),
|
|
||||||
options: localVarRequestOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DefaultApi - functional programming interface
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
export const DefaultApiFp = function(configuration?: Configuration) {
|
|
||||||
const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration)
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Check if a prompt is cached
|
|
||||||
* @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
async externalApiCheckCache(externalApiCheckCacheRequest: ExternalApiCheckCacheRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ExternalApiCheckCache200Response>> {
|
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.externalApiCheckCache(externalApiCheckCacheRequest, options);
|
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Report an API call
|
|
||||||
* @param {ExternalApiReportRequest} externalApiReportRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
async externalApiReport(externalApiReportRequest: ExternalApiReportRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
|
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.externalApiReport(externalApiReportRequest, options);
|
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DefaultApi - factory interface
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
|
||||||
const localVarFp = DefaultApiFp(configuration)
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Check if a prompt is cached
|
|
||||||
* @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
externalApiCheckCache(externalApiCheckCacheRequest: ExternalApiCheckCacheRequest, options?: any): AxiosPromise<ExternalApiCheckCache200Response> {
|
|
||||||
return localVarFp.externalApiCheckCache(externalApiCheckCacheRequest, options).then((request) => request(axios, basePath));
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Report an API call
|
|
||||||
* @param {ExternalApiReportRequest} externalApiReportRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
externalApiReport(externalApiReportRequest: ExternalApiReportRequest, options?: any): AxiosPromise<any> {
|
|
||||||
return localVarFp.externalApiReport(externalApiReportRequest, options).then((request) => request(axios, basePath));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DefaultApi - object-oriented interface
|
|
||||||
* @export
|
|
||||||
* @class DefaultApi
|
|
||||||
* @extends {BaseAPI}
|
|
||||||
*/
|
|
||||||
export class DefaultApi extends BaseAPI {
|
|
||||||
/**
|
|
||||||
* Check if a prompt is cached
|
|
||||||
* @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
* @memberof DefaultApi
|
|
||||||
*/
|
|
||||||
public externalApiCheckCache(externalApiCheckCacheRequest: ExternalApiCheckCacheRequest, options?: AxiosRequestConfig) {
|
|
||||||
return DefaultApiFp(this.configuration).externalApiCheckCache(externalApiCheckCacheRequest, options).then((request) => request(this.axios, this.basePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report an API call
|
|
||||||
* @param {ExternalApiReportRequest} externalApiReportRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
* @memberof DefaultApi
|
|
||||||
*/
|
|
||||||
public externalApiReport(externalApiReportRequest: ExternalApiReportRequest, options?: AxiosRequestConfig) {
|
|
||||||
return DefaultApiFp(this.configuration).externalApiReport(externalApiReportRequest, options).then((request) => request(this.axios, this.basePath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user