Compare commits

...

323 Commits

Author SHA1 Message Date
David Corbitt
03a8d094fc Add header to scenario modal 2023-08-18 00:07:48 -07:00
David Corbitt
2b990622f5 Hide expand button for empty scenario editor 2023-08-18 00:06:05 -07:00
David Corbitt
d079eba557 Allow user to delete scenario without variables 2023-08-18 00:02:26 -07:00
Kyle Corbitt
d0102e3202 Merge pull request #171 from OpenPipe/experiment-slug
Use shorter experiment IDs
2023-08-17 23:33:30 -07:00
Kyle Corbitt
bd571c4c4e Merge pull request #170 from OpenPipe/jobs-log
Enqueue tasks more efficiently
2023-08-17 23:33:20 -07:00
Kyle Corbitt
296eb23d97 Use shorter experiment IDs
Because https://app.openpipe.ai/experiments/B1EtN6oHeXMele2 is a cooler URL than https://app.openpipe.ai/experiments/3692942c-6f1b-4bef-83b1-c11f00a3fbdd
2023-08-17 23:28:56 -07:00
Kyle Corbitt
4e2ae7a441 Enqueue tasks more efficiently
Previously we were opening a new database connection for each task we added. Not a problem at small scale but kinda overwhelming for Postgres now that we have more usage.
2023-08-17 22:42:46 -07:00
Kyle Corbitt
072dcee376 Merge pull request #168 from OpenPipe/jobs-log
Admin dashboard for jobs
2023-08-17 22:26:10 -07:00
Kyle Corbitt
94464c0617 Admin dashboard for jobs
Extremely simple jobs dashboard to sanity-check what we've got going on in the job queue.
2023-08-17 22:20:39 -07:00
arcticfly
980644f13c Support vicuna system message (#167)
* Support vicuna system message

* Change tags to USER and ASSISTANT
2023-08-17 21:02:27 -07:00
arcticfly
6a56250001 Add platypus 13b, vicuna 13b, and nous hermes 7b (#166)
* Add platypus

* Add vicuna 13b and nous hermes 7b
2023-08-17 20:01:10 -07:00
Kyle Corbitt
b1c7bbbd4a Merge pull request #165 from OpenPipe/better-output
Don't define CellWrapper inline
2023-08-17 19:07:32 -07:00
Kyle Corbitt
3e20fa31ca Don't define CellWrapper inline
This way we don't re-render the entire cell every time a variable changes. Better performance and handles modals correctly.

OutputCell is still a pretty messy component, which we'll have to address at some point, but the complexity is still manageable for now.
2023-08-17 17:52:45 -07:00
Kyle Corbitt
48a8e64be1 Merge pull request #164 from OpenPipe/more-models
Add Nous-Hermes and Airoboros models
2023-08-17 17:51:28 -07:00
David Corbitt
f3a5f11195 Temporarilyt remove platypus and stableBeluga models 2023-08-17 16:58:52 -07:00
David Corbitt
da5cbaf4dc Remove console.log 2023-08-17 16:16:22 -07:00
David Corbitt
acf74909c9 Ensure ending newline is displayed 2023-08-17 03:37:32 -07:00
David Corbitt
edac8da4a8 Convert system to user prompt for airoboros 2023-08-17 03:10:55 -07:00
David Corbitt
687f3dd85f Rename prompt modal 2023-08-17 02:34:26 -07:00
David Corbitt
0cef3ab5bd Only enable getTemplatedPromptMessage when modal open 2023-08-17 02:32:02 -07:00
David Corbitt
756b3185de Rename CellOptions 2023-08-17 01:44:18 -07:00
David Corbitt
3776ffc4c3 Change ScenarioRow background color 2023-08-17 01:44:06 -07:00
David Corbitt
82549122e1 Add 4 more models 2023-08-17 01:40:05 -07:00
David Corbitt
56a96a7db6 Use different color for row highlight style 2023-08-16 22:46:22 -07:00
David Corbitt
1596b15727 Fix warning from useLayoutEffect 2023-08-16 22:44:18 -07:00
David Corbitt
70d4a5bd9a Fix project settings padding on desktop 2023-08-16 22:40:27 -07:00
arcticfly
c6ec901374 Ad openpipe/Chat provider with Open-Orca/OpenOrcaxOpenChat-Preview2-13B model (#163)
* Display 4 decimal points in ModelStatsCard

* Add openpipe-chat provider
2023-08-16 22:37:37 -07:00
David Corbitt
ad7665664a Update 7b-chat version 2023-08-16 19:23:01 -07:00
David Corbitt
108e3d1e85 Revert email to table-cell display on md screens 2023-08-16 18:49:14 -07:00
David Corbitt
76f600722a Sort project members by role 2023-08-16 18:30:27 -07:00
David Corbitt
d9a0e4581f Add bgColor behind selected project in menu 2023-08-16 18:16:44 -07:00
arcticfly
b9251ad93c Fix members table mobile styles (#162) 2023-08-16 17:52:25 -07:00
arcticfly
809ef04dc1 Invite members (#161)
* Allow user invitations

* Restyle inviting members

* Remove annoying comment

* Add page for accepting an invitation

* Send invitation email with Brevo

* Prevent admins from removing personal project users

* Mark access ceontrol for cancelProjectInvitation

* Make RadioGroup controlled

* Shorten form helper text

* Use nodemailer to send emails

* Update .env.example
2023-08-16 17:25:31 -07:00
arcticfly
0fba2c9ee7 Add NOT_CONTAINS, fix bugs (#160)
* Fix null case for tag comparisons

* Change debounce time to 500ms

* Add NOT_CONTAINS

* Avoid sql injection

* Store filters by id

* Fix chained NOT_CONTAINS
2023-08-15 16:43:59 -07:00
Kyle Corbitt
ac2ca0f617 Merge pull request #158 from OpenPipe/log-filters
Filter logged calls
2023-08-15 10:16:59 -07:00
David Corbitt
73b9e40ced Give LoggedCallsTable scrollbar 2023-08-15 03:12:59 -07:00
David Corbitt
3447e863cc Prevent model name from wrapping 2023-08-15 02:53:24 -07:00
David Corbitt
897e77b054 Prevent logged calls table flashes 2023-08-15 02:49:46 -07:00
David Corbitt
b22a4cd93b Combine migrations 2023-08-15 02:34:27 -07:00
David Corbitt
3547c85c86 Display tag values 2023-08-15 02:32:05 -07:00
David Corbitt
9636fa033e Add second tag to seed 2023-08-15 02:31:24 -07:00
David Corbitt
890a738568 Filter by tags 2023-08-15 01:50:48 -07:00
David Corbitt
7003595e76 Install lodash-es in client-libs for omit function 2023-08-15 00:59:23 -07:00
David Corbitt
00df4453d3 Remove old prettier files 2023-08-15 00:55:05 -07:00
David Corbitt
4c325fc1cc Move prettier files to top directory 2023-08-15 00:54:52 -07:00
David Corbitt
dfee8a0ed7 Merge branch 'main' into log-filters 2023-08-15 00:41:28 -07:00
David Corbitt
0b4e116783 Undo changes in client-libs 2023-08-15 00:30:35 -07:00
David Corbitt
2bcb1d16a3 Autoresize InputDropdown 2023-08-15 00:27:12 -07:00
David Corbitt
6e7efee21e Seed with tags 2023-08-15 00:26:11 -07:00
David Corbitt
bb9c3a9e61 Condense table 2023-08-15 00:26:05 -07:00
David Corbitt
11bfb5d5e4 Start server with timezone 2023-08-14 23:37:23 -07:00
Kyle Corbitt
b00ab933b3 Merge pull request #157 from OpenPipe/more-js-api
TypeScript SDK mostly working
2023-08-14 23:25:33 -07:00
Kyle Corbitt
8f4e7f7e2e TypeScript SDK mostly working
Ok so this is still pretty rough, and notably there's no reporting for streaming. But for non-streaming requests I've verified that this does in fact report requests locally.
2023-08-14 23:22:27 -07:00
David Corbitt
634739c045 Add InputDropdown 2023-08-14 23:02:08 -07:00
David Corbitt
9a9cbe8fd4 Hide paginators for empty lists 2023-08-14 21:17:03 -07:00
David Corbitt
649dc3376b Debounce filter value updates 2023-08-14 21:00:42 -07:00
David Corbitt
05e774d021 Style filters title 2023-08-14 20:47:18 -07:00
David Corbitt
0e328b13dc Style add filter button 2023-08-14 20:42:51 -07:00
David Corbitt
0a18ca9cd6 Allow filtering by response, model, and status code 2023-08-14 20:16:44 -07:00
David Corbitt
a5fe35912e Allow filter by request contains 2023-08-14 20:01:17 -07:00
David Corbitt
3d3ddbe7a9 Show number of rows in table header 2023-08-14 19:56:15 -07:00
David Corbitt
d8a5617dee Increase button radius 2023-08-14 19:51:06 -07:00
Kyle Corbitt
5da62fdc29 Merge pull request #156 from OpenPipe/move-api
Python package improvements
2023-08-14 19:45:14 -07:00
Kyle Corbitt
754e273049 Python package improvements
Added an endpoint for getting the actual stored responses, and used it to test and improve the python package.
2023-08-14 19:07:03 -07:00
Kyle Corbitt
2863dc2f89 Merge pull request #155 from OpenPipe/move-api
Move the external API into its own router
2023-08-14 17:02:34 -07:00
Kyle Corbitt
c4cef35717 Move the external API into its own router
Auth logic isn't shared between the clients anyway, so co-locating them is confusing since you can't use the same clients to call both. This also makes the codegen clients less verbose.
2023-08-14 16:56:50 -07:00
Kyle Corbitt
8552baf632 Merge pull request #154 from OpenPipe/broken-page
Cap the number of waiting messages we try to render
2023-08-14 15:47:48 -07:00
Kyle Corbitt
f41e2229ca Cap the number of waiting messages we try to render
If an cell was attempted several hours ago and never resolved, it crashes the UI because we try to render thousands of log messages once a second (eg. https://app.openpipe.ai/experiments/372d0827-186e-4a7d-a8a6-1bf7050eb5fd) We should probably have a different UI for cells that have hung for a long time to let you know you should just retry, but this quick fix should work for now.
2023-08-14 15:44:03 -07:00
arcticfly
e649f42c9c Await completions (#153)
* Continue polling stats while waiting for completions to finish

* Clarify convert to function call instructions
2023-08-14 13:03:48 -07:00
Kyle Corbitt
99f305483b Merge pull request #150 from OpenPipe/fix-build
(Probably) fixes the build
2023-08-14 07:59:20 -07:00
arcticfly
b28f4cad57 Remove scenarios header from output table card (#151) 2023-08-13 03:26:58 -07:00
Kyle Corbitt
df4a3a0950 (Probably) fixes the build
This probably fixes the build that I broke in https://github.com/OpenPipe/OpenPipe/pull/149. However, there's a small chance that it fixes it enough to deploy, but not enough to actually work. That would be bad, so not merging until I have time to monitor the deploy.
2023-08-12 23:50:31 -07:00
David Corbitt
e423ad656a Fix ExperimentCard aspect ratio 2023-08-12 23:31:25 -07:00
Kyle Corbitt
7d0d94de3a Merge pull request #149 from OpenPipe/js-client
Load the JS client using pnpm workspaces
2023-08-12 22:56:17 -07:00
Kyle Corbitt
344b257db4 Load the JS client using pnpm workspaces
This makes it so we're using our own openpipe client for all OpenAI calls from the OpenPipe app.

The client doesn't do anything at the moment beyond proxying to the OpenAI lib. But this infra work should make it easier to quickly iterate on the client and test the changes in our own app.
2023-08-12 15:24:48 -07:00
Kyle Corbitt
28b43b6e6d Merge pull request #148 from OpenPipe/js-client
Fix client bugs
2023-08-12 10:38:49 -07:00
Kyle Corbitt
8d373ec9b5 remove unused imports 2023-08-12 10:02:23 -07:00
Kyle Corbitt
537525667d don't reload monaco every render cycle
oops
2023-08-12 09:59:07 -07:00
Kyle Corbitt
519367c553 Fix client bugs
1. PostHog can only be used client-side
2. Can't nest <a> tags in the ProjectMenu
2023-08-12 09:35:52 -07:00
Kyle Corbitt
1a338ec863 Merge pull request #147 from OpenPipe/logs-ui
Style overhaul, make logged calls selectable
2023-08-12 08:48:49 -07:00
David Corbitt
01d0b8f778 Resurrect UserMenu 2023-08-12 04:28:41 -07:00
David Corbitt
d99836ec30 Add experiment button 2023-08-12 04:18:39 -07:00
David Corbitt
33751c12d2 Allow user to select logs 2023-08-12 04:07:58 -07:00
David Corbitt
89815e1f7f Add selectedLogs, rename setSelectedProjectId 2023-08-12 03:35:54 -07:00
David Corbitt
5fa5109f34 Make cache text gray 2023-08-12 03:06:19 -07:00
David Corbitt
b06ab2cbf9 Properly show model 2023-08-12 02:58:28 -07:00
David Corbitt
35fb554038 Center Add Variant button 2023-08-12 02:48:22 -07:00
David Corbitt
f238177277 Fix variant header top right border radius 2023-08-12 02:46:09 -07:00
David Corbitt
723c0f7505 Update colors throughout app 2023-08-12 02:32:09 -07:00
David Corbitt
ce6936f753 Change overall background color and menu 2023-08-12 02:31:52 -07:00
David Corbitt
2a80cbf74a Add relative time back in 2023-08-12 02:06:56 -07:00
David Corbitt
098805ef25 Create loggedCalls.router 2023-08-12 00:03:06 -07:00
David Corbitt
ed90bc5a99 Add dashboard page 2023-08-11 23:34:53 -07:00
arcticfly
de9be8c7ce Allow custom config file (#143)
* Allow custom config file

* Temporarily remove dependency on local openpipe
2023-08-11 23:07:04 -07:00
arcticfly
3e02bcf9b8 Update paginator styles (#142)
* Change paginator icons

* Remove horizontal spacing
2023-08-11 21:11:25 -07:00
Kyle Corbitt
cef2ee31fb Merge pull request #141 from OpenPipe/python-sdk
Add caching in Python
2023-08-11 19:04:18 -07:00
Kyle Corbitt
d7cff0f52e Add caching in Python
Still need it in JS
2023-08-11 19:02:35 -07:00
arcticfly
228c547839 Add logged calls pagination (#140)
* Store model on LoggedCall

* Allow mulitple page sizes

* Add logged calls pagination
2023-08-11 19:00:09 -07:00
Kyle Corbitt
e1fcc8fb38 Merge pull request #139 from OpenPipe/python-sdk
Add a python client library
2023-08-11 17:48:07 -07:00
Kyle Corbitt
8ed47eb4dd Add a python client library
We still don't have any documentation and things are in flux, but you can report your OpenAI API calls to OpenPipe now.
2023-08-11 16:54:50 -07:00
arcticfly
3a908d51aa Store model on LoggedCall (#138) 2023-08-11 16:39:04 -07:00
arcticfly
d9db6d80ea Update external types (#137)
* Separate server and frontend error logic

* Update types in external api
2023-08-11 15:02:14 -07:00
arcticfly
8d1ee62ff1 Record model and cost when reporting logs (#136)
* Rename prompt and completion tokens to input and output tokens

* Add getUsage function

* Record model and cost when reporting log

* Remove unused imports

* Move UsageGraph to its own component

* Standardize model response fields

* Fix types
2023-08-11 13:56:47 -07:00
arcticfly
f270579283 Auto-resize project menu width (#135) 2023-08-10 22:50:39 -07:00
arcticfly
81fbaeae44 Style project settings on mobile (#134)
* Style project settings on mobile

* Use auto-resize text area for display name

* Remove unused import
2023-08-10 22:15:45 -07:00
arcticfly
5277afa199 Change logo (#133)
* Change logo

* Add more vertical padding on desktop

* Fix prettier
2023-08-10 21:44:33 -07:00
arcticfly
76c34d64e6 Change menu styles (#132)
* Change ProjectMenu placement

* Reduce UserMenu width
2023-08-10 18:48:23 -07:00
Kyle Corbitt
454ac9a0d3 Merge pull request #131 from OpenPipe/better-template-vars
Better scenario variable editing
2023-08-10 12:25:54 -07:00
Kyle Corbitt
5ed7adadf9 Better scenario variable editing
Some users have gotten confused by the scenario variable editing interface. This change makes the interface easier to understand.
2023-08-10 12:08:17 -07:00
Kyle Corbitt
b8e0f392ab Merge pull request #130 from OpenPipe/output-wrapping
Preserve linebreaks in model output
2023-08-10 07:26:55 -07:00
Kyle Corbitt
b2af83341d Preserve linebreaks in model output 2023-08-09 21:58:41 -07:00
Kyle Corbitt
e6d229d5f9 Merge pull request #129 from OpenPipe/persist-proj
persist the currently-selected project
2023-08-09 17:05:17 -07:00
Kyle Corbitt
1a6ae3aef7 Merge pull request #128 from OpenPipe/proj-styling
Sidebar styling
2023-08-09 17:05:02 -07:00
Kyle Corbitt
9051d80775 Sidebar styling
Unify the menu styles between the UserMenu and ProjectMenu
2023-08-09 16:47:09 -07:00
Kyle Corbitt
6c060c6ea0 persist the currently-selected project 2023-08-09 16:45:54 -07:00
Kyle Corbitt
f70e73e338 Merge pull request #126 from OpenPipe/org-to-proj
Rename Organization to Project
2023-08-09 16:04:36 -07:00
Kyle Corbitt
16aa6672fc Rename Organization to Project
We'll probably need a concept of organizations at some point in the future, but in practice the way we're using these in the codebase right now is as a project, so this renames it to that to avoid confusion.
2023-08-09 16:01:13 -07:00
Kyle Corbitt
ac99c8e0f7 Merge pull request #127 from OpenPipe/pause-champs
Pause world championships
2023-08-09 15:59:15 -07:00
Kyle Corbitt
df121db78c Merge pull request #125 from OpenPipe/claude-1.1
Support Claude Instant 1.2
2023-08-09 15:58:36 -07:00
Kyle Corbitt
816b41adad Pause world championships
We aren't actually quite ready to run this yet, so announcing a pause it for now.
2023-08-09 15:55:40 -07:00
Kyle Corbitt
f09dfe18be Support Claude Instant 1.2 2023-08-09 14:43:54 -07:00
Kyle Corbitt
868d7084f0 Merge pull request #123 from OpenPipe/add-openapi
Add Logged Calls and projects
2023-08-09 14:37:17 -07:00
Kyle Corbitt
f6f04e537e project menu updates 2023-08-09 14:26:15 -07:00
David Corbitt
4feb3e5829 Avoid adding extra day to usage statistics 2023-08-09 01:22:04 -07:00
David Corbitt
c62ced867a Increase dashboard seeding number 2023-08-09 01:21:42 -07:00
David Corbitt
7bb414026e Fix typescript error 2023-08-08 18:30:02 -07:00
David Corbitt
1b2b6c1456 Send organizationId in fork mutation 2023-08-08 18:19:59 -07:00
David Corbitt
760bfbbe32 Fix issue with timezones 2023-08-08 18:15:42 -07:00
David Corbitt
3424aa36ba Automatically push personalOrg into list 2023-08-08 17:20:20 -07:00
David Corbitt
ded86cba08 Add dashboard seed command 2023-08-08 16:39:42 -07:00
David Corbitt
65a0f9065f Backfill usage statistics 2023-08-08 15:26:14 -07:00
David Corbitt
2861c64428 Fix prettier 2023-08-08 14:31:08 -07:00
David Corbitt
ca33bb0b08 Add beta to logged calls 2023-08-08 14:27:14 -07:00
David Corbitt
72e680e77c Replace USE_OPENPIPE with OPENPIPE_API_KEY 2023-08-08 13:57:10 -07:00
David Corbitt
5dd7e67396 Make project options full width 2023-08-08 13:50:57 -07:00
David Corbitt
fd286f6874 Fix prettier 2023-08-08 13:47:54 -07:00
David Corbitt
7a4aa5f0aa Use openpipe optionally in app 2023-08-08 13:45:46 -07:00
David Corbitt
cb791e3c73 Replace home page with logged calls page 2023-08-08 13:37:55 -07:00
David Corbitt
a2c322ff43 Add requireAuth to AppShell 2023-08-08 13:20:02 -07:00
David Corbitt
a2ace63f25 Hackily fix seeding 2023-08-08 12:24:14 -07:00
David Corbitt
41d06596cb Fix prettier 2023-08-08 12:09:09 -07:00
David Corbitt
49c68fdbf2 Upsert personalOrg when listing orgs 2023-08-08 12:07:18 -07:00
David Corbitt
6188f55569 Fix dashboard stats 2023-08-08 11:49:08 -07:00
David Corbitt
ea91d692d3 Use crypto-random-string 2023-08-08 11:45:03 -07:00
David Corbitt
ae7acbfdd4 Require user to be able to view organization to get it 2023-08-08 11:40:58 -07:00
David Corbitt
b9396e63cc Change /settings to /project/settings 2023-08-08 11:34:15 -07:00
David Corbitt
753a48f6e9 Use boxSize on ProjectBreadcrumbContents 2023-08-08 11:32:13 -07:00
David Corbitt
bd7c8b43b0 utlilize useHandledAsyncCallback in CopiableCode 2023-08-08 11:27:59 -07:00
David Corbitt
a1249f17c9 Add basic dashboard to homepage 2023-08-08 11:18:35 -07:00
David Corbitt
6f8db40f74 Fix logging 2023-08-08 11:12:04 -07:00
David Corbitt
8c5345a291 Allow user to open project menu on mobile 2023-08-08 10:20:10 -07:00
David Corbitt
f47010a6e7 Fix prettier 2023-08-07 21:45:36 -07:00
David Corbitt
6d32f1c06e Allow admins to delete projects 2023-08-07 21:45:21 -07:00
David Corbitt
8fed9730da Send api token properly 2023-08-07 21:04:38 -07:00
David Corbitt
0f9a83cf45 Assign experiments and datasets to correct org 2023-08-07 19:10:27 -07:00
David Corbitt
9f17d98736 Attempt to log (without api key) 2023-08-07 17:12:09 -07:00
David Corbitt
74029e5478 Close project menu after navigating 2023-08-07 15:21:03 -07:00
David Corbitt
d220cd30e8 Allow user to change projects 2023-08-07 15:18:23 -07:00
David Corbitt
c0f10cd522 Remove comment 2023-08-07 14:12:22 -07:00
David Corbitt
dc497dbd99 Query experiments and datasets by org 2023-08-07 14:10:32 -07:00
David Corbitt
f8f855adf4 Theme default divider 2023-08-07 14:10:01 -07:00
David Corbitt
8f49bace53 Backfill api keys 2023-08-07 13:08:33 -07:00
David Corbitt
c9f59bfb79 Add project to breadcrumb 2023-08-07 11:50:59 -07:00
David Corbitt
57166e96b4 Fix project settings IconLink 2023-08-07 11:21:59 -07:00
David Corbitt
1a838824ae Use PageHeaderContainer for all breadcrumbs 2023-08-07 11:16:54 -07:00
David Corbitt
6b304f8456 Show selected org 2023-08-06 23:23:20 -07:00
David Corbitt
a53d70d8b2 Add basic typescript lib 2023-08-06 17:29:06 -07:00
David Corbitt
109a9ddb1e Add js client lib 2023-08-06 13:50:43 -07:00
David Corbitt
7f8b574c9f Add checkCache and report routes 2023-08-05 20:37:16 -07:00
Kyle Corbitt
9e859c199e two LoggedCall tables 2023-08-05 17:56:51 -07:00
David Corbitt
deabbb094b condense CellOptions 2023-08-05 16:01:09 -07:00
Kyle Corbitt
7637b94ea7 schema changes 2023-08-05 13:49:03 -07:00
Kyle Corbitt
721f1726eb Merge pull request #122 from OpenPipe/app-dir
Move app to app/ subdir
2023-08-05 10:08:31 -07:00
Kyle Corbitt
cfeb4dfa92 run render and CI in app subdir 2023-08-05 10:06:06 -07:00
Kyle Corbitt
21ef67ed4c move app to app/ subdir 2023-08-05 10:00:10 -07:00
Kyle Corbitt
7707d451e0 Merge pull request #121 from OpenPipe/prompt-constructor
Rename constructFn to promptConstructor
2023-08-05 09:32:35 -07:00
Kyle Corbitt
d82782adb4 Number experiments based only on current org
Previously we were naming each new experiment based on the highest existing sort index globally, which doesn't make sense. Better to just use the local one.
2023-08-05 09:26:55 -07:00
Kyle Corbitt
e10589abff Rename constructFn to promptConstructor
It's a clearer name. Also reorganize the filesystem so all the promptConstructor related files are colocated.
2023-08-04 23:09:39 -07:00
Kyle Corbitt
01dcbfc896 Rename 'anthropic' to 'anthropic/completion' (#120)
More consistency in the way we name our model providers.
2023-08-04 22:07:23 -07:00
Kyle Corbitt
50e0b34d30 newer replicate models 2023-08-04 21:18:52 -07:00
arcticfly
44bb9fc58d Add outputs to entry generation (#119) 2023-08-04 16:14:49 -07:00
David Corbitt
c0d3784f0c Merge branch 'main' of github.com:corbt/prompt-lab 2023-08-04 16:06:45 -07:00
David Corbitt
e522026b71 Embold star 2023-08-04 16:06:34 -07:00
arcticfly
46b13d85b7 Update README.md 2023-08-04 12:00:38 -07:00
arcticfly
c12aa82a3e Update README.md 2023-08-04 11:58:47 -07:00
arcticfly
b98bce8944 Add Datasets (#118)
* Add dataset (without entries)

* Fix dataset hook

* Add dataset rows

* Add buttons to import/generate data

* Add GenerateDataModal

* Autogenerate and save data

* Fix prettier

* Fix types

* Add dataset pagination

* Fix prettier

* Use useDisclosure

* Allow generate data modal fadeaway

* hide/show data in env var

* Fix prettier
2023-08-04 11:52:03 -07:00
arcticfly
f045c80dfd Update README.md 2023-08-03 18:31:24 -07:00
arcticfly
3b460dff2a Update README.md 2023-08-03 18:16:54 -07:00
David Corbitt
5fa5732804 Move demo up 2023-08-03 12:02:10 -07:00
arcticfly
28e6e2b9df Wrap evals (#117)
* Wrap eval outputs

* Fix prettier

* Decrease variant minWidth
2023-08-03 11:58:39 -07:00
Kyle Corbitt
54d1df4442 upload sourcemaps 2023-08-03 11:53:13 -07:00
David Corbitt
f69c2b5f23 Fix prettier 2023-08-03 11:48:05 -07:00
David Corbitt
51f0666f6a Add table of contents to README 2023-08-03 11:40:29 -07:00
Kyle Corbitt
b67d974f4c Merge pull request #116 from OpenPipe/sentry
Add Sentry
2023-08-03 10:23:22 -07:00
Kyle Corbitt
33fb2db981 Add Sentry
Visibility into errors in prod
2023-08-03 10:18:17 -07:00
Kyle Corbitt
e391379c3e Merge pull request #115 from OpenPipe/admin
Add admin role
2023-08-03 09:39:00 -07:00
Kyle Corbitt
8d1609dd52 Add admin role
Allow privileged users to administer the system.
2023-08-03 09:35:13 -07:00
David Corbitt
f3380f302d Simplify world champs screen 2023-08-02 23:57:44 -07:00
David Corbitt
3dba9c7ee1 Update posthog version 2023-08-02 23:30:15 -07:00
David Corbitt
e0e4f7a9d6 Fix mobile table padding 2023-08-02 23:08:49 -07:00
arcticfly
48293dc579 Add link to demo experiment (#114) 2023-08-02 22:50:09 -07:00
arcticfly
38ac6243a0 Add server posthog events (#113) 2023-08-02 14:21:07 -07:00
arcticfly
bd2f58e2a5 Improve posthog (#112)
* Add SessionIdentifier

* Identify by id

* Rewrite posthog events

* Add NEXT_PUBLIC_HOST to dockerfile

* Fix default url

* Move SessionIdentifier into analytics file
2023-08-02 13:30:25 -07:00
Kyle Corbitt
808e47c6b9 Merge pull request #111 from OpenPipe/gh-btn
Update TopNavbar component to include a GitHub button
2023-08-02 10:15:26 -07:00
Kyle Corbitt
5945f0ed6b Update TopNavbar component to include a GitHub button 2023-08-02 10:11:41 -07:00
arcticfly
6bc7d76d15 Update README.md 2023-08-02 00:59:05 -07:00
arcticfly
e9ed173e34 Update README.md 2023-08-02 00:57:24 -07:00
arcticfly
75d58d7021 Update README.md 2023-08-02 00:56:19 -07:00
arcticfly
896c8c5c57 Update README.md 2023-08-02 00:51:57 -07:00
arcticfly
ec5547d0b0 Update README.md with new features and gifs (#110) 2023-08-02 00:46:48 -07:00
Kyle Corbitt
77e4e3b8c3 mobile styles 2023-08-01 23:08:35 -07:00
Kyle Corbitt
a1b03ddad1 Merge pull request #109 from OpenPipe/debug-prompts
Add debug modal for output cells
2023-08-01 22:51:39 -07:00
Kyle Corbitt
6be32bea4c Add debug modal for output cells
See the actual input that a model got for a specific cell. The formatting isn't great right now; should probably iterate on that.
2023-08-01 22:49:38 -07:00
arcticfly
72c70e2a55 Improve conversion to/from Claude (#108)
* Increase min width of prompt variant

* Increase width of custom instructions input

* Start recording API docs

* Provide better instructions for converting to/from Claude

* Fix prettier
2023-08-01 21:03:23 -07:00
arcticfly
026532f2c2 Model selection styling changes (#107)
* Model selection styling changes

* Fix prettier
2023-08-01 18:45:15 -07:00
Kyle Corbitt
f88538336f fix types 2023-08-01 18:31:34 -07:00
Kyle Corbitt
3c7178115e Merge pull request #105 from OpenPipe/bump-models
Bump Replicate models
2023-08-01 18:26:16 -07:00
Kyle Corbitt
292aaf090a Merge pull request #106 from OpenPipe/dark-mode
Update global background color in ChakraThemeProvider
2023-08-01 18:25:57 -07:00
Kyle Corbitt
d9915dc41b Update global background color in ChakraThemeProvider 2023-08-01 18:25:29 -07:00
David Corbitt
3560bcff14 Correct time stamps on waiting message 2023-08-01 18:09:23 -07:00
Kyle Corbitt
6982339a1a Bump Replicate models 2023-08-01 18:08:02 -07:00
arcticfly
d348b130d5 Add navbar to world-champs (#104)
* Add navbar to world-champs

* Move TopNavbar to signup.tsx
2023-08-01 16:59:46 -07:00
Kyle Corbitt
bf67580991 Merge pull request #103 from OpenPipe/world-champs
add created_at and updated_at to users
2023-08-01 16:49:11 -07:00
Kyle Corbitt
156f248c3a add created_at and updated_at to users 2023-08-01 16:43:35 -07:00
Kyle Corbitt
6184498810 Merge pull request #102 from OpenPipe/world-champs
world champs signup
2023-08-01 13:09:06 -07:00
Kyle Corbitt
65a76cddc5 world champs signup
Basic landing page to sign up for the "world champs"
2023-08-01 13:07:14 -07:00
David Corbitt
c88266bcd4 Add twitter card 2023-08-01 12:47:05 -07:00
David Corbitt
1bf9554eca Comment out getServerSideProps in [id].tsx 2023-08-01 01:29:28 -07:00
arcticfly
1fb428ef4a Add scenario editing modal, twitter sentiment seeding (#101)
* testing agi-eval benchmark

* Add scenario modal editor

* Add initial values to ScenarioEditorModal

* Add seedTwitterSentiment.ts

---------

Co-authored-by: Kyle Corbitt <kyle@corbt.com>
2023-08-01 01:26:43 -07:00
Kyle Corbitt
6316eaae6d dummy key at build time 2023-07-31 18:03:51 -07:00
Kyle Corbitt
8513924ea5 give the openai lib a dummy default value to try to fix the build 2023-07-31 17:39:45 -07:00
arcticfly
51d64baae9 Add og image for experiments (#100)
* Add og image for experiments

* Update meta tags

* Fix prettier

* Add key to meta tags

* Remove ngrok from og:image
2023-07-31 16:50:15 -07:00
arcticfly
26b6fa4f0c Requeue rate-limited query model tasks (#99)
* Continue polling stats until all evals complete

* Return evaluation changes early, before it has run

* Add task for running new eval

* requeue rate-limited tasks

* Fix prettier
2023-07-26 16:30:50 -07:00
Kyle Corbitt
807665fdc1 Merge pull request #98 from OpenPipe/anthropic
Add Anthropic provider
2023-07-26 15:44:39 -07:00
Kyle Corbitt
d6597d2c8a Add Anthropic provider
Adds support for Claude 1 and 2 in experiments! 🎉
2023-07-26 15:37:27 -07:00
arcticfly
566d67bf48 Change prompt variant section background color (#97)
* Change backgroundColor of variant editor section

* Move refresh control out of scrollable area
2023-07-26 10:51:23 -07:00
arcticfly
d4fb8b689a Ensure evals run properly (#96)
* Run evals against llama output

* Continue polling in OutputCell until evals complete

* Remove unnecessary check
2023-07-25 20:01:58 -07:00
arcticfly
98b231c8bd Store multiple ModelResponses (#95)
* Store multiple ModelResponses

* Fix prettier

* Add CellContent container
2023-07-25 18:54:38 -07:00
Kyle Corbitt
45afb1f1f4 Merge pull request #94 from OpenPipe/scenario-shape
updated replicate llama2 models
2023-07-25 17:40:05 -07:00
Kyle Corbitt
2bffb03766 updated replicate llama2 models 2023-07-25 16:40:12 -07:00
arcticfly
223b990005 Change experiment button styling (#93)
* Change button styling

* Fix prettier
2023-07-25 11:41:02 -07:00
Kyle Corbitt
fa61c9c472 prettier 2023-07-25 09:24:28 -07:00
Kyle Corbitt
1309a6ec5d Merge pull request #91 from OpenPipe/better-streaming
Better streaming
2023-07-25 06:46:01 -07:00
arcticfly
17a6fd31a5 Update HeaderButtons.tsx marginTop (#92) 2023-07-24 21:09:08 -07:00
Kyle Corbitt
e1cbeccb90 Better streaming
- Always stream the visible scenarios, if the modelProvider supports it
 - Never stream the invisible scenarios

Also actually runs our query tasks in a background worker, which we weren't quite doing before.
2023-07-24 18:34:30 -07:00
arcticfly
d6b97b29f7 Allow experiment forking (#89)
* Move DeleteButton into a separate file

* Rename plural relations

* Add ability to fork

* Fork automatically after auth upon return

* Add experiment card skeleton

* Create HeaderButtons component

* return no header buttons while experiment loading

* Fix prettier

* Remove unused variable

* Remove newline

* Default json values to undefined

* Change header styles

* Fix prettier

* Give AddScenario icon less width

* Move useEffect

* Skip invalidating experiments list after forking

* Require user to be able to view experiment to fork it

* Move experiment creation into same transaction

* Only return the forked experiment id

* Put delete button in experiment settings drawer

* Move useEffect hook
2023-07-24 18:10:59 -07:00
Kyle Corbitt
09140f8b5f Merge pull request #90 from OpenPipe/simplify-model
Only pass in model and provider
2023-07-24 17:43:24 -07:00
Kyle Corbitt
9952dd93d8 Only pass in model and provider
I got somewhat confused by the extra fields, sorry.

Also makes some frontend changes to track that state more directly although in retrospect not sure the frontend changes make things any better.
2023-07-24 17:21:25 -07:00
Kyle Corbitt
e0b457c6c5 Merge pull request #88 from OpenPipe/load-delete
Bugfix: show prompts for view-only experiments
2023-07-24 14:58:58 -07:00
Kyle Corbitt
0c37506975 Bugfix: show prompts for view-only experiments
Accidentally put the Monaco loading code in the wrong place! This fixes.
2023-07-24 14:57:33 -07:00
arcticfly
2b2e0ab8ee Define refinement actions in the model providers (#87)
* Add descriptions of fields in llama 2 input schema

* Let GPT-4 know when the provider stays the same

* Allow refetching in the event of any errors

* Define refinement actions in model providers

* Fix prettier
2023-07-23 17:37:08 -07:00
arcticfly
3dbb06ec00 Give negative margin to account for border (#86) 2023-07-23 16:49:12 -07:00
David Corbitt
85d42a014b Fix padding on AddVariant button 2023-07-22 16:27:59 -07:00
arcticfly
7d1ded3b18 Improve menu styling (#85) 2023-07-22 16:22:00 -07:00
Kyle Corbitt
b00f6dd04b Merge pull request #84 from OpenPipe/paginated-scenarios
Paginate scenarios
2023-07-22 16:12:02 -07:00
Kyle Corbitt
2e395e4d39 Paginate scenarios
Show 10 scenarios at a time and let the user paginate through them to keep the interface responsive with potentially 1000s of scenarios.
2023-07-22 16:10:16 -07:00
Kyle Corbitt
4b06d05908 Merge pull request #82 from OpenPipe/space-out-scenarios
Separate scenarios from prompts in outputs table
2023-07-22 14:44:51 -07:00
Kyle Corbitt
aabf355b81 Merge pull request #81 from OpenPipe/fullscreen-editor
Fullscreen editor
2023-07-22 14:44:42 -07:00
Kyle Corbitt
61e5f0775d separate scenarios from prompts in outputs table 2023-07-22 07:38:19 -07:00
Kyle Corbitt
cc1d1178da Fullscreen editor 2023-07-21 22:19:38 -07:00
David Corbitt
7466db63df Make REPLICATE_API_TOKEN optional 2023-07-21 20:23:38 -07:00
David Corbitt
79a0b03bf8 Add another function call example 2023-07-21 20:16:36 -07:00
arcticfly
6fb7a82d72 Add support for switching to Llama models (#80)
* Add support for switching to Llama models

* Fix prettier
2023-07-21 20:10:59 -07:00
Kyle Corbitt
4ea30a3ba3 Merge pull request #79 from OpenPipe/copy-evals
Copy over evals when new cell created
2023-07-21 18:43:44 -07:00
Kyle Corbitt
52d1d5c7ee Copy over evals when new cell created
Fixes a bug where new cells generated as clones of existing cells didn't get the eval results cloned as well.
2023-07-21 18:40:40 -07:00
Kyle Corbitt
46036a44d2 small README update 2023-07-21 14:32:07 -07:00
Kyle Corbitt
3753fe5c16 Merge pull request #78 from OpenPipe/bugfix-max-tokens
Fix typescript hints for max_tokens
2023-07-21 12:10:00 -07:00
Kyle Corbitt
213a00a8e6 Fix typescript hints for max_tokens 2023-07-21 12:04:58 -07:00
Kyle Corbitt
af9943eefc Merge pull request #77 from OpenPipe/provider-types
Slightly better typings for ModelProviders
2023-07-21 11:51:25 -07:00
Kyle Corbitt
741128e0f4 Better division of labor between frontend and backend model providers
A bit better thinking on which types go where.
2023-07-21 11:49:35 -07:00
David Corbitt
aff14539d8 Add comment to .env.example 2023-07-21 11:29:21 -07:00
David Corbitt
1af81a50a9 Add REPLICATE_API_TOKEN to .env.example 2023-07-21 11:28:14 -07:00
Kyle Corbitt
7e1fbb3767 Slightly better typings for ModelProviders
Still not great because the `any`s loosen some call sites up more than I'd like, but better than the broken types before.
2023-07-21 06:50:05 -07:00
David Corbitt
a5d972005e Add user's current prompt to prompt derivation 2023-07-21 00:43:39 -07:00
arcticfly
a180b5bef2 Show prompt diff when changing models (#76)
* Make CompareFunctions more configurable

* Change RefinePromptModal styles

* Accept newModel in getModifiedPromptFn

* Show prompt comparison in SelectModelModal

* Pass variant to SelectModelModal

* Update instructions

* Properly use isDisabled
2023-07-20 23:26:49 -07:00
Kyle Corbitt
55c697223e Merge pull request #74 from OpenPipe/model-providers
replicate/llama2 provider
2023-07-20 23:21:42 -07:00
arcticfly
9978075867 Fix auth flicker (#75)
* Remove experiments flicker for unauthenticated users

* Decrease size of NewScenarioButton spinner
2023-07-20 20:46:31 -07:00
Kyle Corbitt
847753c32b replicate/llama2 provider
Still need to fix the types but it runs
2023-07-20 19:55:03 -07:00
Kyle Corbitt
372c2512c9 Merge pull request #73 from OpenPipe/model-providers
More work on modelProviders
2023-07-20 18:56:14 -07:00
Kyle Corbitt
332a2101c0 More work on modelProviders
I think everything that's OpenAI-specific is inside modelProviders at this point, so we can get started adding more providers.
2023-07-20 18:54:26 -07:00
arcticfly
1822fe198e Initially render AutoResizeTextArea without overflow (#72)
* Rerender resized text area with scroll

* Remove default hidden overflow
2023-07-20 15:00:09 -07:00
Kyle Corbitt
f06e1db3db Merge pull request #71 from OpenPipe/model-providers
Prep for more model providers
2023-07-20 14:55:31 -07:00
Kyle Corbitt
ded6678e97 Prep for more model providers
Adds a `modelProvider` field to `promptVariants`, currently just set to "openai/ChatCompletion" for all variants for now.

Adds a `modelProviders/` directory where we can define and store pluggable model providers. Currently just OpenAI. Not everything is pluggable yet -- notably the code to actually generate completions hasn't been migrated to this setup yet.

Does a lot of work to get the types working. Prompts are now defined with a function `definePrompt(modelProvider, config)` instead of `prompt = config`. Added a script to migrate old prompt definitions.

This is still partial work, but the diff is large enough that I want to get it in. I don't think anything is broken but I haven't tested thoroughly.
2023-07-20 14:49:22 -07:00
arcticfly
9314a86857 Use translation in initial scenarios (#70) 2023-07-20 14:28:48 -07:00
David Corbitt
54dcb4a567 Prevent text input labels from overlaying scenarios header 2023-07-20 14:28:36 -07:00
David Corbitt
2c8c8d07cf Merge branch 'main' of github.com:corbt/prompt-lab 2023-07-20 13:38:58 -07:00
David Corbitt
e885bdd365 Fix ScenarioEditor padding 2023-07-20 13:38:46 -07:00
arcticfly
86dc36a656 Improve refinement (#69)
* Format construction function on return

* Add more refinement examples

* Treat 503 like 429

* Define prompt as object

* Fix prettier
2023-07-20 13:05:27 -07:00
arcticfly
55c077d604 Create FloatingLabelInput for scenario variables (#68)
* Create FloatingLabelInput

* Fix prettier

* Simplify changes
2023-07-20 12:20:12 -07:00
arcticfly
e598e454d0 Add new predefined refinement options (#67)
* Add new predefined refinement options

* Fix prettier

* Add icon to SelectModelModal title
2023-07-19 20:10:08 -07:00
David Corbitt
6e3f90cd2f Add more info to refinement 2023-07-19 18:10:23 -07:00
David Corbitt
eec894e101 Allow multiline instructions 2023-07-19 18:10:04 -07:00
David Corbitt
f797fc3fa4 Eliminate spinner flicker in OutputCell 2023-07-19 18:09:47 -07:00
David Corbitt
335dc0357f Fix CompareFunctions for mobile 2023-07-19 17:24:19 -07:00
arcticfly
e6e2c706c2 Change up refinement UI (#66)
* Remove unused ScenarioVariantCell fields

* Refine deriveNewConstructFn

* Fix prettier

* Remove migration script

* Add refine modal

* Fix prettier

* Fix diff checker overflow

* Decrease diff height

* Add more context to prompt refining

* Auto-expand prompt when refining
2023-07-19 17:19:45 -07:00
Kyle Corbitt
7d2166b305 Merge pull request #65 from OpenPipe/no-model
Cache cost on ModelOutput
2023-07-19 16:22:35 -07:00
Kyle Corbitt
60765e51ac Remove model from promptVariant and add cost
Storing the model on promptVariant is problematic because it isn't always in sync with the actual prompt definition. I'm removing it for now to see if we can get away with that -- might have to add it back in later if this causes trouble.

Added `cost` to modelOutput as well so we can cache that, which is important given that the cost calculations won't be the same between different API providers.
2023-07-19 16:20:53 -07:00
arcticfly
2c4ba6eb9b Update README.md (#64) 2023-07-19 15:39:21 -07:00
arcticfly
4c97b9f147 Refine prompt (#63)
* Remove unused ScenarioVariantCell fields

* Refine deriveNewConstructFn

* Fix prettier

* Remove migration script

* Add refine modal

* Fix prettier

* Fix diff checker overflow

* Decrease diff height
2023-07-19 15:31:40 -07:00
arcticfly
58892d8b63 Remove unused fields, refine model translation (#62)
* Remove unused ScenarioVariantCell fields

* Refine deriveNewConstructFn

* Fix prettier
2023-07-19 13:59:11 -07:00
Kyle Corbitt
4fa2dffbcb styling tweaks for SelectModelModal 2023-07-19 07:17:56 -07:00
Kyle Corbitt
654f8c7cf2 Merge pull request #61 from OpenPipe/experiment-page
More visual tweaks
2023-07-19 06:56:58 -07:00
Kyle Corbitt
d02482468d more visual tweaks 2023-07-19 06:54:07 -07:00
Kyle Corbitt
5c6ed22f1d Merge pull request #60 from OpenPipe/experiment-page
experiment page visual tweaks
2023-07-18 22:26:05 -07:00
Kyle Corbitt
2cb623f332 experiment page visual tweaks 2023-07-18 22:22:58 -07:00
Kyle Corbitt
1c1cefe286 Merge pull request #59 from OpenPipe/auth
User accounts
2023-07-18 21:21:46 -07:00
Kyle Corbitt
b4aa95edca sidebar mobile styles 2023-07-18 21:19:06 -07:00
Kyle Corbitt
1dcdba04a6 User accounts
Allows for the creation of user accounts. A few notes on the specifics:

 - Experiments are the main access control objects. If you can view an experiment, you can view all its prompts/scenarios/evals. If you can edit it, you can edit or delete all of those as well.
 - Experiments are owned by Organizations in the database. Organizations can have multiple members and members can have roles of ADMIN, MEMBER or VIEWER.
 - Organizations can either be "personal" or general. Each user has a "personal" organization created as soon as they try to create an experiment. There's currently no UI support for creating general orgs or adding users to them; they're just in the database to future-proof all the ACL logic.
 - You can require that a user is signed-in to see a route using the `protectedProcedure` helper. When you use `protectedProcedure`, you also have to call `ctx.markAccessControlRun()` (or delegate to a function that does it for you; see accessControl.ts). This is to remind us to actually check for access control when we define a new endpoint.
2023-07-18 21:19:03 -07:00
arcticfly
e0e64c4207 Allow user to create a version of their current prompt with a new model (#58)
* Add dropdown header for model switching

* Allow variant duplication

* Fix prettier

* Use env variable to restrict prisma logs

* Fix env.mjs

* Remove unnecessary scroll bar from function call output

* Properly record when 404 error occurs in queryLLM task

* Add SelectedModelInfo in SelectModelModal

* Add react-select

* Calculate new prompt after switching model

* Send newly selected model with creation request

* Get new prompt construction function back from GPT-4

* Fix prettier

* Fix prettier
2023-07-18 18:24:04 -07:00
arcticfly
fa5b1ab1c5 Allow user to duplicate prompt (#57)
* Add dropdown header for model switching

* Allow variant duplication

* Fix prettier
2023-07-18 13:49:33 -07:00
David Corbitt
999a4c08fa Fix lint and prettier 2023-07-18 11:11:20 -07:00
arcticfly
374d0237ee Escape characters in Regex evaluations, minor UI fixes (#56)
* Fix ScenariosHeader stickiness

* Move meta tag from _app.tsx to _document.tsx

* Show spinner when saving variant

* Escape quotes and regex in evaluations
2023-07-18 11:07:04 -07:00
David Corbitt
b1f873623d Invalidate prompt stats after cell refetch 2023-07-18 09:45:11 -07:00
arcticfly
4131aa67d0 Continue polling VariantStats while LLM retrieval in progress, minor UI fixes (#54)
* Prevent zoom in on iOS

* Expand function return code background to fill cell

* Keep OutputStats on far right of cells

* Continue polling prompt stats while cells are retrieving from LLM

* Add comment to _document.tsx

* Fix prettier
2023-07-17 18:04:38 -07:00
Kyle Corbitt
8e7a6d3ae2 Merge pull request #55 from OpenPipe/more-eval
Add GPT4 Evals
2023-07-17 18:01:47 -07:00
Kyle Corbitt
7d41e94ca2 cache eval outputs and add gpt4 eval 2023-07-17 17:55:36 -07:00
Kyle Corbitt
011b12abb9 cache output evals 2023-07-17 17:52:30 -07:00
Kyle Corbitt
1ba18015bc Merge pull request #53 from OpenPipe/more-eval
Fix seeds and update eval field names
2023-07-17 14:26:29 -07:00
Kyle Corbitt
54369dba54 Fix seeds and update eval field names 2023-07-17 14:14:20 -07:00
arcticfly
6b84a59372 Properly catch completion errors (#51) 2023-07-17 10:50:25 -07:00
Kyle Corbitt
8db8aeacd3 Replace function chrome with comment
Use a block comment to explain the expected prompt formatting instead of function chrome. The advantage here is that once a user builds a mental model of how OpenPipe works they can just delete the comment, instead of the function chrome sitting around and taking up space in the UI forever.
2023-07-17 10:30:22 -07:00
Kyle Corbitt
64bd71e370 Merge pull request #50 from OpenPipe/remove-default
remove the default value for PromptVariant.model
2023-07-14 17:55:38 -07:00
Kyle Corbitt
3b99b7bd2b remove the default value for PromptVariant.model
We should be explicit about setting the appropriate model so it always matches the constructFn.
2023-07-14 17:43:52 -07:00
398 changed files with 24163 additions and 8885 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
**/node_modules/
.git
**/.venv/
**/.env*
**/.next/

View File

@@ -6,6 +6,10 @@ on:
push:
branches: [main]
defaults:
run:
working-directory: app
jobs:
run-checks:
runs-on: ubuntu-latest

43
.gitignore vendored
View File

@@ -1,42 +1,5 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# database
/prisma/db.sqlite
/prisma/db.sqlite-journal
# next.js
/.next/
/out/
next-env.d.ts
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
.env
.env*.local
# vercel
.vercel
# typescript
.venv/
*.pyc
node_modules/
*.tsbuildinfo

View File

@@ -1,2 +1,2 @@
src/codegen/openai.schema.json
pnpm-lock.yaml
*.schema.json
app/pnpm-lock.yaml

View File

@@ -1,6 +0,0 @@
{
"eslint.format.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}

View File

@@ -1,43 +0,0 @@
# Adapted from https://create.t3.gg/en/deployment/docker#3-create-dockerfile
FROM node:20.1.0-bullseye as base
RUN yarn global add pnpm
# DEPS
FROM base as deps
WORKDIR /app
COPY prisma ./
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# BUILDER
FROM base as builder
# Include all NEXT_PUBLIC_* env vars here
ARG NEXT_PUBLIC_POSTHOG_KEY
ARG NEXT_PUBLIC_IS_PUBLIC_PLAYGROUND
ARG NEXT_PUBLIC_SOCKET_URL
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN SKIP_ENV_VALIDATION=1 pnpm build
# RUNNER
FROM base as runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
COPY --from=builder /app/ ./
EXPOSE 3000
ENV PORT 3000
# Run the "run-prod.sh" script
CMD /app/run-prod.sh

View File

@@ -1,42 +1,61 @@
<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 is a flexible playground for comparing and optimizing LLM prompts. It lets you quickly generate, test and compare candidate prompts with realistic sample data.
OpenPipe is a flexible playground for comparing and optimizing LLM prompts. It lets you quickly generate, test and compare candidate prompts, and can automatically [translate](#-translate-between-model-apis) those prompts between models.
**Live Demo:** https://openpipe.ai
<img src="https://github.com/openpipe/openpipe/assets/41524992/219a844e-3f4e-4f6b-8066-41348b42977b" alt="demo">
<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).
Currently there's a public playground available at [https://openpipe.ai/](https://openpipe.ai/), but the recommended approach is to [run locally](#running-locally).
## Sample Experiments
## High-Level Features
These are simple experiments users have created that show how OpenPipe works. Feel free to fork them and start experimenting yourself.
**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">
- [Twitter Sentiment Analysis](https://app.openpipe.ai/experiments/62c20a73-2012-4a64-973c-4b665ad46a57)
- [Reddit User Needs](https://app.openpipe.ai/experiments/22222222-2222-2222-2222-222222222222)
- [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)
## Supported Models
OpenPipe currently supports GPT-3.5 and GPT-4. Wider model support is planned.
- 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).
- 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
@@ -46,6 +65,14 @@ OpenPipe currently supports GPT-3.5 and GPT-4. Wider model support is planned.
4. Clone this repository: `git clone https://github.com/openpipe/openpipe`
5. Install the dependencies: `cd openpipe && pnpm install`
6. Create a `.env` file (`cp .env.example .env`) and enter your `OPENAI_API_KEY`.
7. Update `DATABASE_URL` if necessary to point to your Postgres instance and run `pnpm prisma db push` to create the database.
8. Start the app: `pnpm dev`.
9. Navigate to [http://localhost:3000](http://localhost:3000)
7. Update `DATABASE_URL` if necessary to point to your Postgres instance and run `pnpm prisma migrate dev` to create the database.
8. Create a [GitHub OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) and update the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` values. (Note: a PR to make auth optional when running locally would be a great contribution!)
9. Start the app: `pnpm dev`.
10. Navigate to [http://localhost:3000](http://localhost:3000)
## Testing Locally
1. Copy your `.env` file to `.env.test`.
2. Update the `DATABASE_URL` to have a different database name than your development one
3. Run `DATABASE_URL=[your new datatase url] pnpm prisma migrate dev --skip-seed --skip-generate`
4. Run `pnpm test`

View File

@@ -17,4 +17,26 @@ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/openpipe?schema=publ
# https://help.openai.com/en/articles/4936850-where-do-i-find-my-secret-api-key
OPENAI_API_KEY=""
# Replicate API token. Create a token here: https://replicate.com/account/api-tokens
REPLICATE_API_TOKEN=""
NEXT_PUBLIC_SOCKET_URL="http://localhost:3318"
# Next Auth
NEXTAUTH_SECRET="your_secret"
NEXTAUTH_URL="http://localhost:3000"
NEXT_PUBLIC_HOST="http://localhost:3000"
# Next Auth Github Provider
GITHUB_CLIENT_ID="your_client_id"
GITHUB_CLIENT_SECRET="your_secret"
OPENPIPE_BASE_URL="http://localhost:3000/api/v1"
OPENPIPE_API_KEY="your_key"
SENDER_EMAIL="placeholder"
SMTP_HOST="placeholder"
SMTP_PORT="placeholder"
SMTP_LOGIN="placeholder"
SMTP_PASSWORD="placeholder"

View File

@@ -6,7 +6,7 @@ const config = {
overrides: [
{
extends: ["plugin:@typescript-eslint/recommended-requiring-type-checking"],
files: ["*.ts", "*.tsx"],
files: ["*.mts", "*.ts", "*.tsx"],
parserOptions: {
project: path.join(__dirname, "tsconfig.json"),
},
@@ -37,6 +37,7 @@ const config = {
"warn",
{ vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_" },
],
"react/no-unescaped-entities": "off",
},
};

49
app/.gitignore vendored Normal file
View File

@@ -0,0 +1,49 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# database
/prisma/db.sqlite
/prisma/db.sqlite-journal
# next.js
/.next/
/out/
next-env.d.ts
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
.env
.env*.local
.env.test
# vercel
.vercel
# typescript
*.tsbuildinfo
# Sentry Auth Token
.sentryclirc
# custom openai intialization
src/server/utils/openaiCustomConfig.json

3
app/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"eslint.format.enable": true
}

View File

@@ -11,11 +11,25 @@ declare module "nextjs-routes" {
} from "next";
export type Route =
| StaticRoute<"/account/signin">
| StaticRoute<"/admin/jobs">
| DynamicRoute<"/api/auth/[...nextauth]", { "nextauth": string[] }>
| StaticRoute<"/api/experiments/og-image">
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
| DynamicRoute<"/experiments/[id]", { "id": string }>
| DynamicRoute<"/api/v1/[...trpc]", { "trpc": string[] }>
| StaticRoute<"/api/v1/openapi">
| StaticRoute<"/dashboard">
| DynamicRoute<"/data/[id]", { "id": string }>
| StaticRoute<"/data">
| DynamicRoute<"/experiments/[experimentSlug]", { "experimentSlug": string }>
| StaticRoute<"/experiments">
| StaticRoute<"/">;
| StaticRoute<"/">
| DynamicRoute<"/invitations/[invitationToken]", { "invitationToken": string }>
| StaticRoute<"/project/settings">
| StaticRoute<"/request-logs">
| StaticRoute<"/sentry-example-page">
| StaticRoute<"/world-champs">
| StaticRoute<"/world-champs/signup">;
interface StaticRoute<Pathname> {
pathname: Pathname;

48
app/Dockerfile Normal file
View File

@@ -0,0 +1,48 @@
# Adapted from https://create.t3.gg/en/deployment/docker#3-create-dockerfile
FROM node:20.1.0-bullseye as base
RUN yarn global add pnpm
# DEPS
FROM base as deps
WORKDIR /code
COPY app/prisma app/package.json ./app/
COPY client-libs/typescript/package.json ./client-libs/typescript/
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
RUN cd app && pnpm install --frozen-lockfile
# BUILDER
FROM base as builder
# Include all NEXT_PUBLIC_* env vars here
ARG NEXT_PUBLIC_POSTHOG_KEY
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 /code
COPY --from=deps /code/node_modules ./node_modules
COPY --from=deps /code/app/node_modules ./app/node_modules
COPY --from=deps /code/client-libs/typescript/node_modules ./client-libs/typescript/node_modules
COPY . .
RUN cd app && SKIP_ENV_VALIDATION=1 pnpm build
# RUNNER
FROM base as runner
WORKDIR /code/app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
COPY --from=builder /code/ /code/
EXPOSE 3000
ENV PORT 3000
# Run the "run-prod.sh" script
CMD /code/app/run-prod.sh

63
app/next.config.mjs Normal file
View File

@@ -0,0 +1,63 @@
import nextRoutes from "nextjs-routes/config";
import { withSentryConfig } from "@sentry/nextjs";
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds.
*/
const { env } = await import("./src/env.mjs");
/** @type {import("next").NextConfig} */
let config = {
reactStrictMode: true,
/**
* If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config
* out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ["en"],
defaultLocale: "en",
},
rewrites: async () => [
{
source: "/ingest/:path*",
destination: "https://app.posthog.com/:path*",
},
],
webpack: (config) => {
config.module.rules.push({
test: /\.txt$/,
use: "raw-loader",
});
return config;
},
transpilePackages: ["openpipe"],
};
config = nextRoutes()(config);
if (env.NEXT_PUBLIC_SENTRY_DSN && env.SENTRY_AUTH_TOKEN) {
// @ts-expect-error - `withSentryConfig` is not typed correctly
config = withSentryConfig(
config,
{
authToken: env.SENTRY_AUTH_TOKEN,
silent: true,
org: "openpipe",
project: "openpipe",
},
{
widenClientFileUpload: true,
tunnelRoute: "/monitoring",
disableLogger: true,
},
);
}
export default config;

7
app/openapitools.json Normal file
View File

@@ -0,0 +1,7 @@
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "6.6.0"
}
}

View File

@@ -1,5 +1,6 @@
{
"name": "openpipe",
"name": "openpipe-app",
"private": true,
"type": "module",
"version": "0.1.0",
"license": "Apache-2.0",
@@ -9,26 +10,36 @@
},
"scripts": {
"build": "next build",
"dev:next": "next dev",
"dev:next": "TZ=UTC next dev",
"dev:wss": "pnpm tsx --watch src/wss-server.ts",
"dev:worker": "NODE_ENV='development' pnpm tsx --watch src/server/tasks/worker.ts",
"dev": "concurrently --kill-others 'pnpm dev:next' 'pnpm dev:wss'",
"dev": "concurrently --kill-others 'pnpm dev:next' 'pnpm dev:wss' 'pnpm dev:worker'",
"postinstall": "prisma generate",
"lint": "next lint",
"start": "next start",
"codegen": "tsx src/codegen/export-openai-types.ts"
"start": "TZ=UTC next start",
"codegen:clients": "tsx src/server/scripts/client-codegen.ts",
"codegen:db": "prisma generate && kysely-codegen --dialect postgres --out-file src/server/db.types.ts",
"seed": "tsx prisma/seed.ts",
"check": "concurrently 'pnpm lint' 'pnpm tsc' 'pnpm prettier . --check'",
"test": "pnpm vitest"
},
"dependencies": {
"@babel/preset-typescript": "^7.22.5",
"@anthropic-ai/sdk": "^0.5.8",
"@apidevtools/json-schema-ref-parser": "^10.1.0",
"@babel/standalone": "^7.22.9",
"@chakra-ui/anatomy": "^2.2.0",
"@chakra-ui/next-js": "^2.1.4",
"@chakra-ui/react": "^2.7.1",
"@chakra-ui/styled-system": "^2.9.1",
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@fontsource/inconsolata": "^5.0.5",
"@monaco-editor/loader": "^1.3.3",
"@next-auth/prisma-adapter": "^1.0.5",
"@prisma/client": "^4.14.0",
"@sendinblue/client": "^3.3.1",
"@sentry/nextjs": "^7.61.0",
"@t3-oss/env-nextjs": "^0.3.1",
"@tabler/icons-react": "^2.22.0",
"@tanstack/react-query": "^4.29.7",
@@ -36,9 +47,12 @@
"@trpc/next": "^10.26.0",
"@trpc/react-query": "^10.26.0",
"@trpc/server": "^10.26.0",
"@vercel/og": "^0.5.9",
"ast-types": "^0.14.2",
"chroma-js": "^2.4.2",
"concurrently": "^8.2.0",
"cors": "^2.8.5",
"crypto-random-string": "^5.0.0",
"dayjs": "^1.11.8",
"dedent": "^1.0.1",
"dotenv": "^16.3.1",
@@ -48,24 +62,49 @@
"graphile-worker": "^0.13.0",
"immer": "^10.0.2",
"isolated-vm": "^4.5.0",
"json-schema-to-typescript": "^13.0.2",
"json-stringify-pretty-compact": "^4.0.0",
"lodash": "^4.17.21",
"jsonschema": "^1.4.1",
"kysely": "^0.26.1",
"kysely-codegen": "^0.10.1",
"lodash-es": "^4.17.21",
"lucide-react": "^0.265.0",
"marked": "^7.0.3",
"next": "^13.4.2",
"next-auth": "^4.22.1",
"next-query-params": "^4.2.3",
"nextjs-cors": "^2.1.2",
"nextjs-routes": "^2.0.1",
"openai": "4.0.0-beta.2",
"nodemailer": "^6.9.4",
"openai": "4.0.0-beta.7",
"openpipe": "workspace:*",
"pg": "^8.11.2",
"pluralize": "^8.0.0",
"posthog-js": "^1.68.4",
"posthog-js": "^1.75.3",
"posthog-node": "^3.1.1",
"prettier": "^3.0.0",
"prismjs": "^1.29.0",
"react": "18.2.0",
"react-diff-viewer": "^3.1.1",
"react-dom": "18.2.0",
"react-github-btn": "^1.4.0",
"react-icons": "^4.10.1",
"react-json-tree": "^0.18.0",
"react-select": "^5.7.4",
"react-syntax-highlighter": "^15.5.0",
"react-textarea-autosize": "^8.5.0",
"recast": "^0.23.3",
"recharts": "^2.7.2",
"replicate": "^0.12.3",
"socket.io": "^4.7.1",
"socket.io-client": "^4.7.1",
"superjson": "1.12.2",
"trpc-openapi": "^1.2.0",
"tsx": "^3.12.7",
"type-fest": "^4.0.0",
"use-query-params": "^2.2.1",
"uuid": "^9.0.0",
"vite-tsconfig-paths": "^4.2.0",
"zod": "^3.21.4",
"zustand": "^4.3.9"
},
@@ -77,19 +116,26 @@
"@types/cors": "^2.8.13",
"@types/eslint": "^8.37.0",
"@types/express": "^4.17.17",
"@types/lodash": "^4.14.195",
"@types/json-schema": "^7.0.12",
"@types/lodash-es": "^4.17.8",
"@types/node": "^18.16.0",
"@types/nodemailer": "^6.4.9",
"@types/pg": "^8.10.2",
"@types/pluralize": "^0.0.30",
"@types/prismjs": "^1.26.0",
"@types/react": "^18.2.6",
"@types/react-dom": "^18.2.4",
"@types/react-syntax-highlighter": "^15.5.7",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^5.59.6",
"@typescript-eslint/parser": "^5.59.6",
"csv-parse": "^5.4.0",
"eslint": "^8.40.0",
"eslint-config-next": "^13.4.2",
"eslint-plugin-unused-imports": "^2.0.0",
"monaco-editor": "^0.40.0",
"openapi-typescript": "^6.3.4",
"openapi-typescript-codegen": "^0.25.0",
"prisma": "^4.14.0",
"raw-loader": "^4.0.2",
"typescript": "^5.0.4",
@@ -100,6 +146,6 @@
"initVersion": "7.14.0"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
"seed": "pnpm seed"
}
}

View File

@@ -0,0 +1,84 @@
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 Ill 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! Ill 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 &amp; 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 &amp; @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 youre 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 Text sentiment emotion
2 @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
3 @zacokalo @Dell @DellCares @Dell give the man what he paid for! neutral anger
4 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
5 @emijuju_ @Alienware @Dell @intel Beautiful 😍❤️😻 positive joy
6 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 &amp; control with @Dell #APEX. https://t.co/mCblMtH931 https://t.co/eepKNZ4Ai3 neutral optimism
7 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
8 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
9 @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
10 @ashu_k7 @Dell Making life unhappy is the new tag line of #Dell negative sadness
11 @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
12 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
13 @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
14 HUGE congratulations to Startup Battle 3.0 winner ➡️ @Ox_Fulfillment x @cyborgcharu for being featured in @BusinessInsider &amp; @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
15 @Dell happy Friday! positive joy
16 @intel Core i5 1135G7 - 4732 points @intel Core i5 1235 - 6619 points @Dell Latitude 5420 x 5430. Cinebench R23. Good job Intel! positive joy
17 @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
18 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
19 @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
20 @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
21 @Dell @DellCares but no, not really neutral sadness
22 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
23 @Dell Yessirrrrr #NationalCoffeeDay positive joy
24 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
25 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
26 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
27 A special gracias to our Startup Chica San Antonio 2022 sponsors @eBay, @jcpenney, @Barbie, @HEB, @Dell, @Honda, @SouthsideSATX💜✨ https://t.co/lZ6WWkziHl positive joy
28 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
29 @EmDStowers @LASERGIANT1 @ohwormongod @Ludovician_Vega @Dell our boy snitchin neutral anger
30 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
31 Reducing deployment and maintenance complexity is the goal behind @dell and @WindRiver's new collaboration. https://t.co/2PxQgPuHUU positive optimism
32 @jaserhunter @Dell Love the sales pitch lol positive joy
33 @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
34 @ashu_k7 @Dell One more example.. their technical support is also worse. https://t.co/20atSgI4fg negative anger
35 *angry screeches about @Dell proprietary MBR windows 8.1 partitions not being able to save as an img in clonezilla * negative anger
36 @socialitebooks @BBYC_Gamers @Dell @Alienware @BestBuyCanada @intelcanada Congratulations!!! positive joy
37 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
38 @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
39 LOVING MY DELL 2 IN 1 LAPTOP YAYY 🥳🥳 @Dell #DellInspiron #DellLaptop https://t.co/vib96jf3tC positive joy
40 @Azure @OracleItalia @AWS_Italy @lenovoitalia @Dell discussing the future of #HPC during the #hpcroundtable22 in Turin today #highperformancecomputing https://t.co/jJ1WqBulPF neutral joy
41 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
42 .@Dell partners with @WindRiver on modular cloud-native telecommunications infrastructure https://t.co/4SWATspwCP @SiliconANGLE @Mike_Wheatley @holgermu @constellationr neutral joy
43 @Dell Not buy Dell Inspiron laptop neutral sadness
44 @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
45 @RachMurph @HETTShow @Dell Thank you for coming! Great evening positive joy
46 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
47 @MattVisiwig @Dell Sour taste for sure 😶 But don't let ego distract you from what you really want to buy 😁 neutral optimism
48 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
49 @timetcetera @rahaug Nah, I just need @Dell to start paying me comissions 😂 neutral joy
50 "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
51 Hi, my name is @listerepvp and I support @Dell, always. positive joy

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "PromptVariant" ALTER COLUMN "model" DROP DEFAULT;

View File

@@ -0,0 +1,24 @@
/*
Warnings:
- You are about to rename the column `matchString` on the `Evaluation` table. If there is any code or views referring to the old name, they will break.
- You are about to rename the column `matchType` on the `Evaluation` table. If there is any code or views referring to the old name, they will break.
- You are about to rename the column `name` on the `Evaluation` table. If there is any code or views referring to the old name, they will break.
- You are about to rename the enum `EvaluationMatchType` to `EvalType`. If there is any code or views referring to the old name, they will break.
*/
-- RenameEnum
ALTER TYPE "EvaluationMatchType" RENAME TO "EvalType";
-- AlterTable
ALTER TABLE "Evaluation" RENAME COLUMN "matchString" TO "value";
ALTER TABLE "Evaluation" RENAME COLUMN "matchType" TO "evalType";
ALTER TABLE "Evaluation" RENAME COLUMN "name" TO "label";
-- AlterColumnType
ALTER TABLE "Evaluation" ALTER COLUMN "evalType" TYPE "EvalType" USING "evalType"::text::"EvalType";
-- SetNotNullConstraint
ALTER TABLE "Evaluation" ALTER COLUMN "evalType" SET NOT NULL;
ALTER TABLE "Evaluation" ALTER COLUMN "label" SET NOT NULL;
ALTER TABLE "Evaluation" ALTER COLUMN "value" SET NOT NULL;

View File

@@ -0,0 +1,39 @@
/*
Warnings:
- You are about to drop the `EvaluationResult` table. If the table is not empty, all the data it contains will be lost.
*/
-- AlterEnum
ALTER TYPE "EvalType" ADD VALUE 'GPT4_EVAL';
-- DropForeignKey
ALTER TABLE "EvaluationResult" DROP CONSTRAINT "EvaluationResult_evaluationId_fkey";
-- DropForeignKey
ALTER TABLE "EvaluationResult" DROP CONSTRAINT "EvaluationResult_promptVariantId_fkey";
-- DropTable
DROP TABLE "EvaluationResult";
-- CreateTable
CREATE TABLE "OutputEvaluation" (
"id" UUID NOT NULL,
"result" DOUBLE PRECISION NOT NULL,
"details" TEXT,
"modelOutputId" UUID NOT NULL,
"evaluationId" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "OutputEvaluation_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "OutputEvaluation_modelOutputId_evaluationId_key" ON "OutputEvaluation"("modelOutputId", "evaluationId");
-- AddForeignKey
ALTER TABLE "OutputEvaluation" ADD CONSTRAINT "OutputEvaluation_modelOutputId_fkey" FOREIGN KEY ("modelOutputId") REFERENCES "ModelOutput"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OutputEvaluation" ADD CONSTRAINT "OutputEvaluation_evaluationId_fkey" FOREIGN KEY ("evaluationId") REFERENCES "Evaluation"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,124 @@
DROP TABLE "Account";
DROP TABLE "Session";
DROP TABLE "User";
DROP TABLE "VerificationToken";
CREATE TYPE "OrganizationUserRole" AS ENUM ('ADMIN', 'MEMBER', 'VIEWER');
-- CreateTable
CREATE TABLE "Organization" (
"id" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"personalOrgUserId" UUID,
CONSTRAINT "Organization_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OrganizationUser" (
"id" UUID NOT NULL,
"role" "OrganizationUserRole" NOT NULL,
"organizationId" UUID NOT NULL,
"userId" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "OrganizationUser_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Account" (
"id" UUID NOT NULL,
"userId" UUID NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"refresh_token_expires_in" INTEGER,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" UUID NOT NULL,
"sessionToken" TEXT NOT NULL,
"userId" UUID NOT NULL,
"expires" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" UUID NOT NULL,
"name" TEXT,
"email" TEXT,
"emailVerified" TIMESTAMP(3),
"image" TEXT,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL
);
INSERT INTO "Organization" ("id", "updatedAt") VALUES ('11111111-1111-1111-1111-111111111111', CURRENT_TIMESTAMP);
-- AlterTable add organizationId as NULLABLE
ALTER TABLE "Experiment" ADD COLUMN "organizationId" UUID;
-- Set default organization for existing experiments
UPDATE "Experiment" SET "organizationId" = '11111111-1111-1111-1111-111111111111';
-- AlterTable set organizationId as NOT NULL
ALTER TABLE "Experiment" ALTER COLUMN "organizationId" SET NOT NULL;
-- CreateIndex
CREATE UNIQUE INDEX "OrganizationUser_organizationId_userId_key" ON "OrganizationUser"("organizationId", "userId");
-- CreateIndex
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
-- AddForeignKey
ALTER TABLE "Experiment" ADD CONSTRAINT "Experiment_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrganizationUser" ADD CONSTRAINT "OrganizationUser_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrganizationUser" ADD CONSTRAINT "OrganizationUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
CREATE UNIQUE INDEX "Organization_personalOrgUserId_key" ON "Organization"("personalOrgUserId");
ALTER TABLE "Organization" ADD CONSTRAINT "Organization_personalOrgUserId_fkey" FOREIGN KEY ("personalOrgUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,16 @@
/*
Warnings:
- You are about to drop the column `completionTokens` on the `ScenarioVariantCell` table. All the data in the column will be lost.
- You are about to drop the column `inputHash` on the `ScenarioVariantCell` table. All the data in the column will be lost.
- You are about to drop the column `output` on the `ScenarioVariantCell` table. All the data in the column will be lost.
- You are about to drop the column `promptTokens` on the `ScenarioVariantCell` table. All the data in the column will be lost.
- You are about to drop the column `timeToComplete` on the `ScenarioVariantCell` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "ScenarioVariantCell" DROP COLUMN "completionTokens",
DROP COLUMN "inputHash",
DROP COLUMN "output",
DROP COLUMN "promptTokens",
DROP COLUMN "timeToComplete";

View File

@@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `model` on the `PromptVariant` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "ModelOutput" ADD COLUMN "cost" DOUBLE PRECISION;

View File

@@ -0,0 +1,17 @@
-- Add new columns allowing NULL values
ALTER TABLE "PromptVariant"
ADD COLUMN "constructFnVersion" INTEGER,
ADD COLUMN "modelProvider" TEXT;
-- Update existing records to have the default values
UPDATE "PromptVariant"
SET "constructFnVersion" = 1,
"modelProvider" = 'openai/ChatCompletion'
WHERE "constructFnVersion" IS NULL OR "modelProvider" IS NULL;
-- Alter table to set NOT NULL constraint
ALTER TABLE "PromptVariant"
ALTER COLUMN "constructFnVersion" SET NOT NULL,
ALTER COLUMN "modelProvider" SET NOT NULL;
ALTER TABLE "ScenarioVariantCell" ADD COLUMN "prompt" JSONB;

View File

@@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `streamingChannel` on the `ScenarioVariantCell` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "ScenarioVariantCell" DROP COLUMN "streamingChannel";

View File

@@ -0,0 +1,52 @@
-- DropForeignKey
ALTER TABLE "ModelOutput" DROP CONSTRAINT "ModelOutput_scenarioVariantCellId_fkey";
-- DropForeignKey
ALTER TABLE "OutputEvaluation" DROP CONSTRAINT "OutputEvaluation_modelOutputId_fkey";
-- DropIndex
DROP INDEX "OutputEvaluation_modelOutputId_evaluationId_key";
-- AlterTable
ALTER TABLE "OutputEvaluation" RENAME COLUMN "modelOutputId" TO "modelResponseId";
-- AlterTable
ALTER TABLE "ScenarioVariantCell" DROP COLUMN "retryTime",
DROP COLUMN "statusCode",
ADD COLUMN "jobQueuedAt" TIMESTAMP(3),
ADD COLUMN "jobStartedAt" TIMESTAMP(3);
ALTER TABLE "ModelOutput" RENAME TO "ModelResponse";
ALTER TABLE "ModelResponse"
ADD COLUMN "requestedAt" TIMESTAMP(3),
ADD COLUMN "receivedAt" TIMESTAMP(3),
ADD COLUMN "statusCode" INTEGER,
ADD COLUMN "errorMessage" TEXT,
ADD COLUMN "retryTime" TIMESTAMP(3),
ADD COLUMN "outdated" BOOLEAN NOT NULL DEFAULT false;
-- 3. Remove the unnecessary column
ALTER TABLE "ModelResponse"
DROP COLUMN "timeToComplete";
-- AlterTable
ALTER TABLE "ModelResponse" RENAME CONSTRAINT "ModelOutput_pkey" TO "ModelResponse_pkey";
ALTER TABLE "ModelResponse" ALTER COLUMN "output" DROP NOT NULL;
-- DropIndex
DROP INDEX "ModelOutput_scenarioVariantCellId_key";
-- AddForeignKey
ALTER TABLE "ModelResponse" ADD CONSTRAINT "ModelResponse_scenarioVariantCellId_fkey" FOREIGN KEY ("scenarioVariantCellId") REFERENCES "ScenarioVariantCell"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- RenameIndex
ALTER INDEX "ModelOutput_inputHash_idx" RENAME TO "ModelResponse_inputHash_idx";
-- CreateIndex
CREATE UNIQUE INDEX "OutputEvaluation_modelResponseId_evaluationId_key" ON "OutputEvaluation"("modelResponseId", "evaluationId");
-- AddForeignKey
ALTER TABLE "OutputEvaluation" ADD CONSTRAINT "OutputEvaluation_modelResponseId_fkey" FOREIGN KEY ("modelResponseId") REFERENCES "ModelResponse"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,16 @@
-- 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;

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;

View File

@@ -0,0 +1,5 @@
-- CreateEnum
CREATE TYPE "UserRole" AS ENUM ('ADMIN', 'USER');
-- AlterTable
ALTER TABLE "User" ADD COLUMN "role" "UserRole" NOT NULL DEFAULT 'USER';

View File

@@ -0,0 +1,28 @@
-- CreateTable
CREATE TABLE "Dataset" (
"id" UUID NOT NULL,
"name" TEXT NOT NULL,
"organizationId" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Dataset_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DatasetEntry" (
"id" UUID NOT NULL,
"input" TEXT NOT NULL,
"output" TEXT,
"datasetId" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DatasetEntry_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Dataset" ADD CONSTRAINT "Dataset_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DatasetEntry" ADD CONSTRAINT "DatasetEntry_datasetId_fkey" FOREIGN KEY ("datasetId") REFERENCES "Dataset"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,13 @@
/*
Warnings:
- You are about to drop the column `constructFn` on the `PromptVariant` table. All the data in the column will be lost.
- You are about to drop the column `constructFnVersion` on the `PromptVariant` table. All the data in the column will be lost.
- Added the required column `promptConstructor` to the `PromptVariant` table without a default value. This is not possible if the table is not empty.
- Added the required column `promptConstructorVersion` to the `PromptVariant` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "PromptVariant" RENAME COLUMN "constructFn" TO "promptConstructor";
ALTER TABLE "PromptVariant" RENAME COLUMN "constructFnVersion" TO "promptConstructorVersion";

View File

@@ -0,0 +1,90 @@
-- CreateTable
CREATE TABLE "LoggedCall" (
"id" UUID NOT NULL,
"startTime" TIMESTAMP(3) NOT NULL,
"cacheHit" BOOLEAN NOT NULL,
"modelResponseId" UUID NOT NULL,
"organizationId" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "LoggedCall_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LoggedCallModelResponse" (
"id" UUID NOT NULL,
"reqPayload" JSONB NOT NULL,
"respStatus" INTEGER,
"respPayload" JSONB,
"error" TEXT,
"startTime" TIMESTAMP(3) NOT NULL,
"endTime" TIMESTAMP(3) NOT NULL,
"cacheKey" TEXT,
"durationMs" INTEGER,
"inputTokens" INTEGER,
"outputTokens" INTEGER,
"finishReason" TEXT,
"completionId" TEXT,
"totalCost" DECIMAL(18,12),
"originalLoggedCallId" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "LoggedCallModelResponse_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LoggedCallTag" (
"id" UUID NOT NULL,
"name" TEXT NOT NULL,
"value" TEXT,
"loggedCallId" UUID NOT NULL,
CONSTRAINT "LoggedCallTag_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ApiKey" (
"id" UUID NOT NULL,
"name" TEXT NOT NULL,
"apiKey" TEXT NOT NULL,
"organizationId" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "LoggedCall_startTime_idx" ON "LoggedCall"("startTime");
-- CreateIndex
CREATE UNIQUE INDEX "LoggedCallModelResponse_originalLoggedCallId_key" ON "LoggedCallModelResponse"("originalLoggedCallId");
-- CreateIndex
CREATE INDEX "LoggedCallModelResponse_cacheKey_idx" ON "LoggedCallModelResponse"("cacheKey");
-- CreateIndex
CREATE INDEX "LoggedCallTag_name_idx" ON "LoggedCallTag"("name");
-- CreateIndex
CREATE INDEX "LoggedCallTag_name_value_idx" ON "LoggedCallTag"("name", "value");
-- CreateIndex
CREATE UNIQUE INDEX "ApiKey_apiKey_key" ON "ApiKey"("apiKey");
-- AddForeignKey
ALTER TABLE "LoggedCall" ADD CONSTRAINT "LoggedCall_modelResponseId_fkey" FOREIGN KEY ("modelResponseId") REFERENCES "LoggedCallModelResponse"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LoggedCall" ADD CONSTRAINT "LoggedCall_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LoggedCallModelResponse" ADD CONSTRAINT "LoggedCallModelResponse_originalLoggedCallId_fkey" FOREIGN KEY ("originalLoggedCallId") REFERENCES "LoggedCall"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LoggedCallTag" ADD CONSTRAINT "LoggedCallTag_loggedCallId_fkey" FOREIGN KEY ("loggedCallId") REFERENCES "LoggedCall"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Organization" ADD COLUMN "name" TEXT NOT NULL DEFAULT 'Project 1';

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "LoggedCall" ALTER COLUMN "modelResponseId" DROP NOT NULL;

View File

@@ -0,0 +1,37 @@
-- Rename Enum
ALTER TYPE "OrganizationUserRole" RENAME TO "ProjectUserRole";
-- Drop and recreate foreign keys
ALTER TABLE "ApiKey" DROP CONSTRAINT "ApiKey_organizationId_fkey";
ALTER TABLE "Dataset" DROP CONSTRAINT "Dataset_organizationId_fkey";
ALTER TABLE "Experiment" DROP CONSTRAINT "Experiment_organizationId_fkey";
ALTER TABLE "LoggedCall" DROP CONSTRAINT "LoggedCall_organizationId_fkey";
ALTER TABLE "OrganizationUser" DROP CONSTRAINT "OrganizationUser_organizationId_fkey";
ALTER TABLE "OrganizationUser" DROP CONSTRAINT "OrganizationUser_userId_fkey";
-- Rename columns
ALTER TABLE "ApiKey" RENAME COLUMN "organizationId" TO "projectId";
ALTER TABLE "Dataset" RENAME COLUMN "organizationId" TO "projectId";
ALTER TABLE "Experiment" RENAME COLUMN "organizationId" TO "projectId";
ALTER TABLE "LoggedCall" RENAME COLUMN "organizationId" TO "projectId";
ALTER TABLE "OrganizationUser" RENAME COLUMN "organizationId" TO "projectId";
ALTER TABLE "Organization" RENAME COLUMN "personalOrgUserId" TO "personalProjectUserId";
-- Rename table
ALTER TABLE "Organization" RENAME TO "Project";
ALTER TABLE "OrganizationUser" RENAME TO "ProjectUser";
-- Recreate foreign keys
ALTER TABLE "Experiment" ADD CONSTRAINT "Experiment_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Dataset" ADD CONSTRAINT "Dataset_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ProjectUser" ADD CONSTRAINT "ProjectUser_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ProjectUser" ADD CONSTRAINT "ProjectUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "LoggedCall" ADD CONSTRAINT "LoggedCall_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Rename indexes
ALTER TABLE "Project" RENAME CONSTRAINT "Organization_pkey" TO "Project_pkey";
ALTER TABLE "ProjectUser" RENAME CONSTRAINT "OrganizationUser_pkey" TO "ProjectUser_pkey";
ALTER TABLE "Project" RENAME CONSTRAINT "Organization_personalOrgUserId_fkey" TO "Project_personalProjectUserId_fkey";
ALTER INDEX "Organization_personalOrgUserId_key" RENAME TO "Project_personalProjectUserId_key";
ALTER INDEX "OrganizationUser_organizationId_userId_key" RENAME TO "ProjectUser_projectId_userId_key";

View File

@@ -0,0 +1 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

View File

@@ -0,0 +1,66 @@
/*
Warnings:
- You are about to rename the column `completionTokens` to `outputTokens` on the `ModelResponse` table.
- You are about to rename the column `promptTokens` to `inputTokens` on the `ModelResponse` table.
- You are about to rename the column `startTime` on the `LoggedCall` table to `requestedAt`. Ensure compatibility with application logic.
- You are about to rename the column `startTime` on the `LoggedCallModelResponse` table to `requestedAt`. Ensure compatibility with application logic.
- You are about to rename the column `endTime` on the `LoggedCallModelResponse` table to `receivedAt`. Ensure compatibility with application logic.
- You are about to rename the column `error` on the `LoggedCallModelResponse` table to `errorMessage`. Ensure compatibility with application logic.
- You are about to rename the column `respStatus` on the `LoggedCallModelResponse` table to `statusCode`. Ensure compatibility with application logic.
- You are about to rename the column `totalCost` on the `LoggedCallModelResponse` table to `cost`. Ensure compatibility with application logic.
- You are about to rename the column `inputHash` on the `ModelResponse` table to `cacheKey`. Ensure compatibility with application logic.
- You are about to rename the column `output` on the `ModelResponse` table to `respPayload`. Ensure compatibility with application logic.
*/
-- DropIndex
DROP INDEX "LoggedCall_startTime_idx";
-- DropIndex
DROP INDEX "ModelResponse_inputHash_idx";
-- Rename completionTokens to outputTokens
ALTER TABLE "ModelResponse"
RENAME COLUMN "completionTokens" TO "outputTokens";
-- Rename promptTokens to inputTokens
ALTER TABLE "ModelResponse"
RENAME COLUMN "promptTokens" TO "inputTokens";
-- AlterTable
ALTER TABLE "LoggedCall"
RENAME COLUMN "startTime" TO "requestedAt";
-- AlterTable
ALTER TABLE "LoggedCallModelResponse"
RENAME COLUMN "startTime" TO "requestedAt";
-- AlterTable
ALTER TABLE "LoggedCallModelResponse"
RENAME COLUMN "endTime" TO "receivedAt";
-- AlterTable
ALTER TABLE "LoggedCallModelResponse"
RENAME COLUMN "error" TO "errorMessage";
-- AlterTable
ALTER TABLE "LoggedCallModelResponse"
RENAME COLUMN "respStatus" TO "statusCode";
-- AlterTable
ALTER TABLE "LoggedCallModelResponse"
RENAME COLUMN "totalCost" TO "cost";
-- AlterTable
ALTER TABLE "ModelResponse"
RENAME COLUMN "inputHash" TO "cacheKey";
-- AlterTable
ALTER TABLE "ModelResponse"
RENAME COLUMN "output" TO "respPayload";
-- CreateIndex
CREATE INDEX "LoggedCall_requestedAt_idx" ON "LoggedCall"("requestedAt");
-- CreateIndex
CREATE INDEX "ModelResponse_cacheKey_idx" ON "ModelResponse"("cacheKey");

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "LoggedCall" ADD COLUMN "model" TEXT;

View File

@@ -0,0 +1,22 @@
-- DropIndex
DROP INDEX "LoggedCallTag_name_idx";
DROP INDEX "LoggedCallTag_name_value_idx";
-- AlterTable: Add projectId column without NOT NULL constraint for now
ALTER TABLE "LoggedCallTag" ADD COLUMN "projectId" UUID;
-- Set the default value
UPDATE "LoggedCallTag" lct
SET "projectId" = lc."projectId"
FROM "LoggedCall" lc
WHERE lct."loggedCallId" = lc.id;
-- Now set the NOT NULL constraint
ALTER TABLE "LoggedCallTag" ALTER COLUMN "projectId" SET NOT NULL;
-- CreateIndex
CREATE INDEX "LoggedCallTag_projectId_name_idx" ON "LoggedCallTag"("projectId", "name");
CREATE INDEX "LoggedCallTag_projectId_name_value_idx" ON "LoggedCallTag"("projectId", "name", "value");
-- CreateIndex
CREATE UNIQUE INDEX "LoggedCallTag_loggedCallId_name_key" ON "LoggedCallTag"("loggedCallId", "name");

View File

@@ -0,0 +1,25 @@
-- CreateTable
CREATE TABLE "UserInvitation" (
"id" UUID NOT NULL,
"projectId" UUID NOT NULL,
"email" TEXT NOT NULL,
"role" "ProjectUserRole" NOT NULL,
"invitationToken" TEXT NOT NULL,
"senderId" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "UserInvitation_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "UserInvitation_invitationToken_key" ON "UserInvitation"("invitationToken");
-- CreateIndex
CREATE UNIQUE INDEX "UserInvitation_projectId_email_key" ON "UserInvitation"("projectId", "email");
-- AddForeignKey
ALTER TABLE "UserInvitation" ADD CONSTRAINT "UserInvitation_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UserInvitation" ADD CONSTRAINT "UserInvitation_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2023 Viascom Ltd liab. Co
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE OR REPLACE FUNCTION nanoid(
size int DEFAULT 21,
alphabet text DEFAULT '_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
)
RETURNS text
LANGUAGE plpgsql
volatile
AS
$$
DECLARE
idBuilder text := '';
counter int := 0;
bytes bytea;
alphabetIndex int;
alphabetArray text[];
alphabetLength int;
mask int;
step int;
BEGIN
alphabetArray := regexp_split_to_array(alphabet, '');
alphabetLength := array_length(alphabetArray, 1);
mask := (2 << cast(floor(log(alphabetLength - 1) / log(2)) as int)) - 1;
step := cast(ceil(1.6 * mask * size / alphabetLength) AS int);
while true
loop
bytes := gen_random_bytes(step);
while counter < step
loop
alphabetIndex := (get_byte(bytes, counter) & mask) + 1;
if alphabetIndex <= alphabetLength then
idBuilder := idBuilder || alphabetArray[alphabetIndex];
if length(idBuilder) = size then
return idBuilder;
end if;
end if;
counter := counter + 1;
end loop;
counter := 0;
end loop;
END
$$;
-- Make a short_nanoid function that uses the default alphabet and length of 15
CREATE OR REPLACE FUNCTION short_nanoid()
RETURNS text
LANGUAGE plpgsql
volatile
AS
$$
BEGIN
RETURN nanoid(15, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
END
$$;
-- AlterTable
ALTER TABLE "Experiment" ADD COLUMN "slug" TEXT NOT NULL DEFAULT short_nanoid();
-- For existing experiments, keep the existing id as the slug for backwards compatibility
UPDATE "Experiment" SET "slug" = "id";
-- CreateIndex
CREATE UNIQUE INDEX "Experiment_slug_key" ON "Experiment"("slug");

429
app/prisma/schema.prisma Normal file
View File

@@ -0,0 +1,429 @@
// 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
slug String @unique @default(dbgenerated("short_nanoid()"))
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
cacheKey String
requestedAt DateTime?
receivedAt DateTime?
respPayload Json?
cost Float?
inputTokens Int?
outputTokens 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([cacheKey])
}
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[]
projectUserInvitations UserInvitation[]
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
requestedAt DateTime
// True if this call was served from the cache, false otherwise
cacheHit Boolean
// A LoggedCall is always associated with a LoggedCallModelResponse. If this
// is a cache miss, we create a new LoggedCallModelResponse.
// If it's a cache hit, it's a pre-existing LoggedCallModelResponse.
modelResponseId String? @db.Uuid
modelResponse LoggedCallModelResponse? @relation(fields: [modelResponseId], references: [id], onDelete: Cascade)
// The responses created by this LoggedCall. Will be empty if this LoggedCall was a cache hit.
createdResponses LoggedCallModelResponse[] @relation(name: "ModelResponseOriginalCall")
projectId String @db.Uuid
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
model String?
tags LoggedCallTag[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([requestedAt])
}
model LoggedCallModelResponse {
id String @id @default(uuid()) @db.Uuid
reqPayload Json
// The HTTP status returned by the model provider
statusCode Int?
respPayload Json?
// Should be null if the request was successful, and some string if the request failed.
errorMessage String?
requestedAt DateTime
receivedAt DateTime
// Note: the function to calculate the cacheKey should include the project
// ID so we don't share cached responses between projects, which could be an
// attack vector. Also, we should only set the cacheKey on the model if the
// request was successful.
cacheKey String?
// Derived fields
durationMs Int?
inputTokens Int?
outputTokens Int?
finishReason String?
completionId String?
cost Decimal? @db.Decimal(18, 12)
// The LoggedCall that created this LoggedCallModelResponse
originalLoggedCallId String @unique @db.Uuid
originalLoggedCall LoggedCall @relation(name: "ModelResponseOriginalCall", fields: [originalLoggedCallId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
loggedCalls LoggedCall[]
@@index([cacheKey])
}
model LoggedCallTag {
id String @id @default(uuid()) @db.Uuid
name String
value String?
projectId String @db.Uuid
loggedCallId String @db.Uuid
loggedCall LoggedCall @relation(fields: [loggedCallId], references: [id], onDelete: Cascade)
@@unique([loggedCallId, name])
@@index([projectId, name])
@@index([projectId, 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?
sentUserInvitations UserInvitation[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
}
model UserInvitation {
id String @id @default(uuid()) @db.Uuid
projectId String @db.Uuid
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
email String
role ProjectUserRole
invitationToken String @unique
senderId String @db.Uuid
sender User @relation(fields: [senderId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([projectId, email])
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}

192
app/prisma/seed.ts Normal file
View File

@@ -0,0 +1,192 @@
import { prisma } from "~/server/db";
import dedent from "dedent";
import { generateNewCell } from "~/server/utils/generateNewCell";
import { promptConstructorVersion } from "~/promptConstructor/version";
import { env } from "~/env.mjs";
const defaultId = "11111111-1111-1111-1111-111111111111";
await prisma.project.deleteMany({
where: { id: defaultId },
});
// Mark all users as admins
await prisma.user.updateMany({
where: {},
data: {
role: "ADMIN",
},
});
// If there's an existing project, just seed into it
const project =
(await prisma.project.findFirst({})) ??
(await prisma.project.create({
data: { id: defaultId },
}));
if (env.OPENPIPE_API_KEY) {
await prisma.apiKey.upsert({
where: {
apiKey: env.OPENPIPE_API_KEY,
},
create: {
projectId: project.id,
name: "Default API Key",
apiKey: env.OPENPIPE_API_KEY,
},
update: {},
});
}
await prisma.experiment.deleteMany({
where: {
id: defaultId,
},
});
await prisma.experiment.create({
data: {
id: defaultId,
label: "Country Capitals Example",
projectId: project.id,
},
});
await prisma.scenarioVariantCell.deleteMany({
where: {
promptVariant: {
experimentId: defaultId,
},
},
});
await prisma.promptVariant.deleteMany({
where: {
experimentId: defaultId,
},
});
await prisma.promptVariant.createMany({
data: [
{
experimentId: defaultId,
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: \`What is the capital of ${"$"}{scenario.country}?\`
}
],
temperature: 0,
})`,
},
{
experimentId: defaultId,
label: "Prompt Variant 2",
sortIndex: 1,
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: \`What is the capital of ${"$"}{scenario.country}? Return just the city name and nothing else.\`
}
],
temperature: 0,
})`,
},
],
});
await prisma.templateVariable.deleteMany({
where: {
experimentId: defaultId,
},
});
await prisma.templateVariable.createMany({
data: [
{
experimentId: defaultId,
label: "country",
},
],
});
await prisma.testScenario.deleteMany({
where: {
experimentId: defaultId,
},
});
const countries = [
"Afghanistan",
"Albania",
"Algeria",
"Andorra",
"Angola",
"Antigua and Barbuda",
"Argentina",
"Armenia",
"Australia",
"Austria",
"Austrian Empire",
"Azerbaijan",
"Baden",
"Bahamas, The",
"Bahrain",
"Bangladesh",
"Barbados",
"Bavaria",
"Belarus",
"Belgium",
"Belize",
"Benin (Dahomey)",
"Bolivia",
"Bosnia and Herzegovina",
"Botswana",
];
await prisma.testScenario.createMany({
data: countries.map((country, i) => ({
experimentId: defaultId,
sortIndex: i,
variableValues: {
country: country,
},
})),
});
const variants = await prisma.promptVariant.findMany({
where: {
experimentId: defaultId,
},
});
const scenarios = await prisma.testScenario.findMany({
where: {
experimentId: defaultId,
},
});
await Promise.all(
variants
.flatMap((variant) =>
scenarios.map((scenario) => ({
promptVariantId: variant.id,
testScenarioId: scenario.id,
})),
)
.map((cell) => generateNewCell(cell.promptVariantId, cell.testScenarioId, { stream: false })),
);

128
app/prisma/seedAgiEval.ts Normal file
View File

@@ -0,0 +1,128 @@
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}})",
},
],
});
}

421
app/prisma/seedDashboard.ts Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,114 @@
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}}",
},
],
});

BIN
app/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -9,10 +9,9 @@ Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,550.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M813 5478 c-18 -13 -37 -36 -43 -52 -6 -19 -10 -236 -10 -603 0 -638
-1 -626 65 -657 25 -12 67 -16 179 -16 l146 0 0 -2032 0 -2032 23 -33 c12 -18
35 -37 51 -43 19 -7 539 -10 1528 -10 1663 0 1549 -5 1582 65 14 30 16 235 16
2059 l0 2026 156 0 156 0 39 39 39 39 0 587 c0 651 1 638 -65 669 -30 14 -223
16 -1932 16 l-1898 0 -32 -22z"/>
<path d="M785 5474 l-25 -27 0 -622 0 -622 25 -27 24 -26 171 0 170 0 0 -2050
0 -2051 25 -25 24 -24 1557 2 1556 3 19 24 c19 23 19 70 19 2072 l0 2049 169
0 c165 0 169 1 195 25 l26 24 0 626 0 626 -26 24 -27 25 -1939 0 -1939 0 -24
-26z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 858 B

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

28
app/public/logo.svg Normal file
View File

@@ -0,0 +1,28 @@
<svg width="398" height="550" viewBox="0 0 398 550" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M39 125H359V542C359 546.418 355.418 550 351 550H47C42.5817 550 39 546.418 39 542V125Z" fill="black"/>
<path d="M0 8C0 3.58172 3.58172 0 8 0H390C394.418 0 398 3.58172 398 8V127C398 131.418 394.418 135 390 135H7.99999C3.58171 135 0 131.418 0 127V8Z" fill="black"/>
<path d="M50 135H348V535C348 537.209 346.209 539 344 539H54C51.7909 539 50 537.209 50 535V135Z" fill="#FF5733"/>
<path d="M11 14.0001C11 11.791 12.7909 10.0001 15 10.0001H384C386.209 10.0001 388 11.791 388 14.0001V120C388 122.209 386.209 124 384 124H15C12.7909 124 11 122.209 11 120V14.0001Z" fill="#FF5733"/>
<path d="M11 14.0001C11 11.791 12.7909 10.0001 15 10.0001H384C386.209 10.0001 388 11.791 388 14.0001V120C388 122.209 386.209 124 384 124H15C12.7909 124 11 122.209 11 120V14.0001Z" fill="url(#paint0_linear_102_49)"/>
<path d="M50 134H348V535C348 537.209 346.209 539 344 539H54C51.7909 539 50 537.209 50 535V134Z" fill="url(#paint1_linear_102_49)"/>
<path d="M108 142H156V535H108V142Z" fill="white"/>
<path d="M300 135H348V535C348 537.209 346.209 539 344 539H300V135Z" fill="white" fill-opacity="0.25"/>
<path d="M96 142H108V535H96V142Z" fill="white" fill-opacity="0.5"/>
<path d="M84 10.0001H133V120H84V10.0001Z" fill="white"/>
<path d="M339 10.0001H384C386.209 10.0001 388 11.791 388 14.0001V120C388 122.209 386.209 124 384 124H339V10.0001Z" fill="white" fill-opacity="0.25"/>
<path d="M71.9995 10.0001H83.9995V120H71.9995V10.0001Z" fill="white" fill-opacity="0.5"/>
<path d="M108 534.529H156V539.019H108V534.529Z" fill="#AAAAAA"/>
<path opacity="0.5" d="M95.9927 534.529H107.982V539.019H95.9927V534.529Z" fill="#AAAAAA"/>
<path d="M84.0029 119.887H133.007V124.027H84.0029V119.887Z" fill="#AAAAAA"/>
<path opacity="0.5" d="M71.9883 119.887H83.978V124.027H71.9883V119.887Z" fill="#AAAAAA"/>
<defs>
<linearGradient id="paint0_linear_102_49" x1="335" y1="67.0001" x2="137" y2="67.0001" gradientUnits="userSpaceOnUse">
<stop stop-color="#D62600"/>
<stop offset="1" stop-color="#FF5733" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear_102_49" x1="306.106" y1="336.5" x2="149.597" y2="336.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#D62600"/>
<stop offset="1" stop-color="#FF5733" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
app/public/og.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

15
app/run-prod.sh Executable file
View File

@@ -0,0 +1,15 @@
#! /bin/bash
set -e
echo "Migrating the database"
pnpm prisma migrate deploy
echo "Migrating promptConstructors"
pnpm tsx src/promptConstructor/migrate.ts
echo "Starting the server"
pnpm concurrently --kill-others \
"pnpm start" \
"pnpm tsx src/server/tasks/worker.ts"

View File

@@ -0,0 +1,33 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
import { env } from "~/env.mjs";
if (env.NEXT_PUBLIC_SENTRY_DSN) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
replaysOnErrorSampleRate: 1.0,
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: 0.1,
// You can remove this option if you're not planning to use the Sentry Session Replay feature:
integrations: [
new Sentry.Replay({
// Additional Replay configuration goes in here, for example:
maskAllText: true,
blockAllMedia: true,
}),
],
});
}

19
app/sentry.edge.config.ts Normal file
View File

@@ -0,0 +1,19 @@
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
// The config you add here will be used whenever one of the edge features is loaded.
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
import { env } from "~/env.mjs";
if (env.NEXT_PUBLIC_SENTRY_DSN) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
}

View File

@@ -0,0 +1,18 @@
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
import { env } from "~/env.mjs";
if (env.NEXT_PUBLIC_SENTRY_DSN) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
}

View File

@@ -1,19 +1,22 @@
import { Textarea, type TextareaProps } from "@chakra-ui/react";
import ResizeTextarea from "react-textarea-autosize";
import React from "react";
import React, { useEffect, useState } from "react";
export const AutoResizeTextarea: React.ForwardRefRenderFunction<
HTMLTextAreaElement,
TextareaProps
> = (props, ref) => {
TextareaProps & { minRows?: number }
> = ({ minRows = 1, overflowY = "hidden", ...props }, ref) => {
const [isRerendered, setIsRerendered] = useState(false);
useEffect(() => setIsRerendered(true), []);
return (
<Textarea
minH="unset"
overflow="hidden"
minRows={minRows}
overflowY={isRerendered ? overflowY : "hidden"}
w="100%"
resize="none"
ref={ref}
minRows={1}
transition="height none"
as={ResizeTextarea}
{...props}

View File

@@ -0,0 +1,142 @@
import {
Button,
HStack,
Icon,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Spinner,
Text,
VStack,
} from "@chakra-ui/react";
import { type PromptVariant } from "@prisma/client";
import { isObject, isString } from "lodash-es";
import { useState } from "react";
import { RiExchangeFundsFill } from "react-icons/ri";
import { type ProviderModel } from "~/modelProviders/types";
import { api } from "~/utils/api";
import { useExperiment, useHandledAsyncCallback, useVisibleScenarioIds } from "~/utils/hooks";
import { lookupModel, modelLabel } from "~/utils/utils";
import CompareFunctions from "../RefinePromptModal/CompareFunctions";
import { ModelSearch } from "./ModelSearch";
import { ModelStatsCard } from "./ModelStatsCard";
export const ChangeModelModal = ({
variant,
onClose,
}: {
variant: PromptVariant;
onClose: () => void;
}) => {
const originalModel = lookupModel(variant.modelProvider, variant.model);
const [selectedModel, setSelectedModel] = useState({
provider: variant.modelProvider,
model: variant.model,
} as ProviderModel);
const [convertedModel, setConvertedModel] = useState<ProviderModel | undefined>();
const visibleScenarios = useVisibleScenarioIds();
const utils = api.useContext();
const experiment = useExperiment();
const { mutateAsync: getModifiedPromptMutateAsync, data: modifiedPromptFn } =
api.promptVariants.getModifiedPromptFn.useMutation();
const [getModifiedPromptFn, modificationInProgress] = useHandledAsyncCallback(async () => {
if (!experiment) return;
await getModifiedPromptMutateAsync({
id: variant.id,
newModel: selectedModel,
});
setConvertedModel(selectedModel);
}, [getModifiedPromptMutateAsync, onClose, experiment, variant, selectedModel]);
const replaceVariantMutation = api.promptVariants.replaceVariant.useMutation();
const [replaceVariant, replacementInProgress] = useHandledAsyncCallback(async () => {
if (
!variant.experimentId ||
!modifiedPromptFn ||
(isObject(modifiedPromptFn) && "status" in modifiedPromptFn)
)
return;
await replaceVariantMutation.mutateAsync({
id: variant.id,
promptConstructor: modifiedPromptFn,
streamScenarios: visibleScenarios,
});
await utils.promptVariants.list.invalidate();
onClose();
}, [replaceVariantMutation, variant, onClose, modifiedPromptFn]);
const originalLabel = modelLabel(variant.modelProvider, variant.model);
const selectedLabel = modelLabel(selectedModel.provider, selectedModel.model);
const convertedLabel =
convertedModel && modelLabel(convertedModel.provider, convertedModel.model);
return (
<Modal
isOpen
onClose={onClose}
size={{ base: "xl", sm: "2xl", md: "3xl", lg: "5xl", xl: "7xl" }}
>
<ModalOverlay />
<ModalContent w={1200}>
<ModalHeader>
<HStack>
<Icon as={RiExchangeFundsFill} />
<Text>Change Model</Text>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody maxW="unset">
<VStack spacing={8}>
<ModelStatsCard label="Original Model" model={originalModel} />
{originalLabel !== selectedLabel && (
<ModelStatsCard
label="New Model"
model={lookupModel(selectedModel.provider, selectedModel.model)}
/>
)}
<ModelSearch selectedModel={selectedModel} setSelectedModel={setSelectedModel} />
{isString(modifiedPromptFn) && (
<CompareFunctions
originalFunction={variant.promptConstructor}
newFunction={modifiedPromptFn}
leftTitle={originalLabel}
rightTitle={convertedLabel}
/>
)}
</VStack>
</ModalBody>
<ModalFooter>
<HStack>
<Button
colorScheme="gray"
onClick={getModifiedPromptFn}
minW={24}
isDisabled={originalLabel === selectedLabel || modificationInProgress}
>
{modificationInProgress ? <Spinner boxSize={4} /> : <Text>Convert</Text>}
</Button>
<Button
colorScheme="blue"
onClick={replaceVariant}
minW={24}
isDisabled={!convertedModel || modificationInProgress || replacementInProgress}
>
{replacementInProgress ? <Spinner boxSize={4} /> : <Text>Accept</Text>}
</Button>
</HStack>
</ModalFooter>
</ModalContent>
</Modal>
);
};

View File

@@ -0,0 +1,36 @@
import { Text, VStack } from "@chakra-ui/react";
import { type LegacyRef } from "react";
import Select from "react-select";
import { useElementDimensions } from "~/utils/hooks";
import { flatMap } from "lodash-es";
import frontendModelProviders from "~/modelProviders/frontendModelProviders";
import { type ProviderModel } from "~/modelProviders/types";
import { modelLabel } from "~/utils/utils";
const modelOptions = flatMap(Object.entries(frontendModelProviders), ([providerId, provider]) =>
Object.entries(provider.models).map(([modelId]) => ({
provider: providerId,
model: modelId,
})),
) as ProviderModel[];
export const ModelSearch = (props: {
selectedModel: ProviderModel;
setSelectedModel: (model: ProviderModel) => void;
}) => {
const [containerRef, containerDimensions] = useElementDimensions();
return (
<VStack ref={containerRef as LegacyRef<HTMLDivElement>} w="full" fontFamily="inconsolata">
<Text fontWeight="bold">Browse Models</Text>
<Select<ProviderModel>
styles={{ control: (provided) => ({ ...provided, width: containerDimensions?.width }) }}
getOptionLabel={(data) => modelLabel(data.provider, data.model)}
getOptionValue={(data) => modelLabel(data.provider, data.model)}
options={modelOptions}
onChange={(option) => option && props.setSelectedModel(option)}
/>
</VStack>
);
};

View File

@@ -0,0 +1,117 @@
import {
GridItem,
HStack,
Link,
SimpleGrid,
Text,
VStack,
type StackProps,
} from "@chakra-ui/react";
import { type lookupModel } from "~/utils/utils";
export const ModelStatsCard = ({
label,
model,
}: {
label: string;
model: ReturnType<typeof lookupModel>;
}) => {
if (!model) return null;
return (
<VStack w="full" align="start">
<Text fontWeight="bold" fontSize="sm" textTransform="uppercase">
{label}
</Text>
<VStack
w="full"
spacing={6}
borderWidth={1}
borderColor="gray.300"
p={4}
borderRadius={8}
fontFamily="inconsolata"
>
<HStack w="full" align="flex-start">
<VStack flex={1} fontSize="lg" alignItems="flex-start">
<Text as="span" fontWeight="bold" color="gray.900">
{model.name}
</Text>
<Text as="span" color="gray.600" fontSize="sm">
Provider: {model.provider}
</Text>
</VStack>
<Link
href={model.learnMoreUrl}
isExternal
color="blue.500"
fontWeight="bold"
fontSize="sm"
ml={2}
>
Learn More
</Link>
</HStack>
<SimpleGrid
w="full"
justifyContent="space-between"
alignItems="flex-start"
fontSize="sm"
columns={{ base: 2, md: 4 }}
>
<SelectedModelLabeledInfo label="Context Window" info={model.contextWindow} />
{model.promptTokenPrice && (
<SelectedModelLabeledInfo
label="Input"
info={
<Text>
${(model.promptTokenPrice * 1000).toFixed(3)}
<Text color="gray.500"> / 1K tokens</Text>
</Text>
}
/>
)}
{model.completionTokenPrice && (
<SelectedModelLabeledInfo
label="Output"
info={
<Text>
${(model.completionTokenPrice * 1000).toFixed(3)}
<Text color="gray.500"> / 1K tokens</Text>
</Text>
}
/>
)}
{model.pricePerSecond && (
<SelectedModelLabeledInfo
label="Price"
info={
<Text>
${model.pricePerSecond.toFixed(4)}
<Text color="gray.500"> / second</Text>
</Text>
}
/>
)}
<SelectedModelLabeledInfo label="Speed" info={<Text>{model.speed}</Text>} />
</SimpleGrid>
</VStack>
</VStack>
);
};
const SelectedModelLabeledInfo = ({
label,
info,
...props
}: {
label: string;
info: string | number | React.ReactElement;
} & StackProps) => (
<GridItem>
<VStack alignItems="flex-start" {...props}>
<Text fontWeight="bold">{label}</Text>
<Text>{info}</Text>
</VStack>
</GridItem>
);

View File

@@ -0,0 +1,51 @@
import { HStack, Icon, IconButton, Tooltip, Text, type StackProps } from "@chakra-ui/react";
import { useState } from "react";
import { MdContentCopy } from "react-icons/md";
import { useHandledAsyncCallback } from "~/utils/hooks";
const CopiableCode = ({ code, ...rest }: { code: string } & StackProps) => {
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"
alignItems="flex-start"
{...rest}
>
<Text
fontFamily="inconsolata"
fontWeight="bold"
letterSpacing={0.5}
overflowX="auto"
whiteSpace="pre-wrap"
>
{code}
{/* Necessary for trailing newline to actually be displayed */}
{code.endsWith("\n") ? "\n" : ""}
</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;

View File

@@ -0,0 +1,86 @@
import {
Button,
Spinner,
InputGroup,
InputRightElement,
Icon,
HStack,
type InputGroupProps,
} from "@chakra-ui/react";
import { IoMdSend } from "react-icons/io";
import AutoResizeTextArea from "./AutoResizeTextArea";
export const CustomInstructionsInput = ({
instructions,
setInstructions,
loading,
onSubmit,
placeholder = "Send custom instructions",
...props
}: {
instructions: string;
setInstructions: (instructions: string) => void;
loading: boolean;
onSubmit: () => void;
placeholder?: string;
} & InputGroupProps) => {
return (
<InputGroup
size="md"
w="full"
maxW="600"
boxShadow="0 0 40px 4px rgba(0, 0, 0, 0.1);"
borderRadius={8}
alignItems="center"
colorScheme="orange"
{...props}
>
<AutoResizeTextArea
value={instructions}
onChange={(e) => setInstructions(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.metaKey && !e.ctrlKey && !e.shiftKey) {
e.preventDefault();
e.currentTarget.blur();
onSubmit();
}
}}
placeholder={placeholder}
py={4}
pl={4}
pr={12}
colorScheme="orange"
borderColor="gray.300"
borderWidth={1}
_hover={{
borderColor: "gray.300",
}}
_focus={{
borderColor: "gray.300",
}}
isDisabled={loading}
/>
<HStack></HStack>
<InputRightElement width="8" height="full">
<Button
h="8"
w="8"
minW="unset"
size="sm"
onClick={() => onSubmit()}
variant={instructions ? "solid" : "ghost"}
mr={4}
borderRadius="8"
bgColor={instructions ? "orange.400" : "transparent"}
colorScheme="orange"
>
{loading ? (
<Spinner boxSize={4} />
) : (
<Icon as={IoMdSend} color={instructions ? "white" : "gray.500"} boxSize={5} />
)}
</Button>
</InputRightElement>
</InputGroup>
);
};

View File

@@ -0,0 +1,74 @@
import {
Button,
Icon,
AlertDialog,
AlertDialogBody,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogContent,
AlertDialogOverlay,
useDisclosure,
Text,
} from "@chakra-ui/react";
import { useRouter } from "next/router";
import { useRef } from "react";
import { BsTrash } from "react-icons/bs";
import { useAppStore } from "~/state/store";
import { api } from "~/utils/api";
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
export const DeleteButton = () => {
const experiment = useExperiment();
const mutation = api.experiments.delete.useMutation();
const utils = api.useContext();
const router = useRouter();
const closeDrawer = useAppStore((s) => s.closeDrawer);
const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = useRef<HTMLButtonElement>(null);
const [onDeleteConfirm] = useHandledAsyncCallback(async () => {
if (!experiment.data?.id) return;
await mutation.mutateAsync({ id: experiment.data.id });
await utils.experiments.list.invalidate();
await router.push({ pathname: "/experiments" });
closeDrawer();
onClose();
}, [mutation, experiment.data?.id, router]);
return (
<>
<Button size="sm" variant="ghost" colorScheme="red" fontWeight="normal" onClick={onOpen}>
<Icon as={BsTrash} boxSize={4} />
<Text ml={2}>Delete Experiment</Text>
</Button>
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
Delete Experiment
</AlertDialogHeader>
<AlertDialogBody>
If you delete this experiment all the associated prompts and scenarios will be deleted
as well. Are you sure?
</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="red" onClick={onDeleteConfirm} ml={3}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
);
};

View File

@@ -6,13 +6,14 @@ import {
DrawerHeader,
DrawerOverlay,
Heading,
Stack,
VStack,
} from "@chakra-ui/react";
import EditScenarioVars from "./EditScenarioVars";
import EditEvaluations from "./EditEvaluations";
import EditScenarioVars from "../OutputsTable/EditScenarioVars";
import EditEvaluations from "../OutputsTable/EditEvaluations";
import { useAppStore } from "~/state/store";
import { DeleteButton } from "./DeleteButton";
export default function SettingsDrawer() {
export default function ExperimentSettingsDrawer() {
const isOpen = useAppStore((state) => state.drawerOpen);
const closeDrawer = useAppStore((state) => state.closeDrawer);
@@ -22,13 +23,16 @@ export default function SettingsDrawer() {
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>
<Heading size="md">Settings</Heading>
<Heading size="md">Experiment Settings</Heading>
</DrawerHeader>
<DrawerBody>
<Stack spacing={6}>
<EditScenarioVars />
<EditEvaluations />
</Stack>
<DrawerBody h="full" pb={4}>
<VStack h="full" justifyContent="space-between">
<VStack spacing={6}>
<EditScenarioVars />
<EditEvaluations />
</VStack>
<DeleteButton />
</VStack>
</DrawerBody>
</DrawerContent>
</Drawer>

View File

@@ -8,8 +8,8 @@ export default function Favicon() {
<link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png" />
<link rel="manifest" href="/favicons/site.webmanifest" />
<link rel="shortcut icon" href="/favicons/favicon.ico" />
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="msapplication-config" content="/favicons/browserconfig.xml" />
<meta name="theme-color" content="#ffffff" />
</Head>
);

View File

@@ -0,0 +1,80 @@
import {
Input,
InputGroup,
InputRightElement,
Icon,
Popover,
PopoverTrigger,
PopoverContent,
VStack,
HStack,
Button,
Text,
useDisclosure,
} from "@chakra-ui/react";
import { FiChevronDown } from "react-icons/fi";
import { BiCheck } from "react-icons/bi";
type InputDropdownProps<T> = {
options: ReadonlyArray<T>;
selectedOption: T;
onSelect: (option: T) => void;
};
const InputDropdown = <T,>({ options, selectedOption, onSelect }: InputDropdownProps<T>) => {
const popover = useDisclosure();
return (
<Popover placement="bottom-start" {...popover}>
<PopoverTrigger>
<InputGroup cursor="pointer" w={(selectedOption as string).length * 14 + 180}>
<Input
value={selectedOption as string}
// eslint-disable-next-line @typescript-eslint/no-empty-function -- controlled input requires onChange
onChange={() => {}}
cursor="pointer"
borderColor={popover.isOpen ? "blue.500" : undefined}
_hover={popover.isOpen ? { borderColor: "blue.500" } : undefined}
contentEditable={false}
// disable focus
onFocus={(e) => {
e.target.blur();
}}
/>
<InputRightElement>
<Icon as={FiChevronDown} />
</InputRightElement>
</InputGroup>
</PopoverTrigger>
<PopoverContent boxShadow="0 0 40px 4px rgba(0, 0, 0, 0.1);" minW={0} w="auto">
<VStack spacing={0}>
{options?.map((option, index) => (
<HStack
key={index}
as={Button}
onClick={() => {
onSelect(option);
popover.onClose();
}}
w="full"
variant="ghost"
justifyContent="space-between"
fontWeight="semibold"
borderRadius={0}
colorScheme="blue"
color="black"
fontSize="sm"
borderBottomWidth={1}
>
<Text mr={16}>{option as string}</Text>
{option === selectedOption && <Icon as={BiCheck} color="blue.500" boxSize={5} />}
</HStack>
))}
</VStack>
</PopoverContent>
</Popover>
);
};
export default InputDropdown;

View File

@@ -0,0 +1,43 @@
import { Box, Flex, Icon, Spinner } from "@chakra-ui/react";
import { BsPlus } from "react-icons/bs";
import { Text } from "@chakra-ui/react";
import { api } from "~/utils/api";
import {
useExperiment,
useExperimentAccess,
useHandledAsyncCallback,
useVisibleScenarioIds,
} from "~/utils/hooks";
import { cellPadding } from "../constants";
import { ActionButton } from "./ScenariosHeader";
export default function AddVariantButton() {
const experiment = useExperiment();
const mutation = api.promptVariants.create.useMutation();
const utils = api.useContext();
const visibleScenarios = useVisibleScenarioIds();
const [onClick, loading] = useHandledAsyncCallback(async () => {
if (!experiment.data) return;
await mutation.mutateAsync({
experimentId: experiment.data.id,
streamScenarios: visibleScenarios,
});
await utils.promptVariants.list.invalidate();
}, [mutation]);
const { canModify } = useExperimentAccess();
if (!canModify) return <Box w={cellPadding.x} />;
return (
<Flex w="100%" justifyContent="flex-end">
<ActionButton
onClick={onClick}
py={7}
leftIcon={<Icon as={loading ? Spinner : BsPlus} boxSize={6} mr={loading ? 1 : 0} />}
>
<Text display={{ base: "none", md: "flex" }}>Add Variant</Text>
</ActionButton>
</Flex>
);
}

View File

@@ -11,14 +11,17 @@ import {
FormLabel,
Select,
FormHelperText,
Code,
IconButton,
} from "@chakra-ui/react";
import { type Evaluation, EvaluationMatchType } from "@prisma/client";
import { type Evaluation, EvalType } from "@prisma/client";
import { useCallback, useState } from "react";
import { BsPencil, BsX } from "react-icons/bs";
import { api } from "~/utils/api";
import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
import AutoResizeTextArea from "../AutoResizeTextArea";
type EvalValues = Pick<Evaluation, "name" | "matchString" | "matchType">;
type EvalValues = Pick<Evaluation, "label" | "value" | "evalType">;
export function EvaluationEditor(props: {
evaluation: Evaluation | null;
@@ -27,35 +30,35 @@ export function EvaluationEditor(props: {
onCancel: () => void;
}) {
const [values, setValues] = useState<EvalValues>({
name: props.evaluation?.name ?? props.defaultName ?? "",
matchString: props.evaluation?.matchString ?? "",
matchType: props.evaluation?.matchType ?? "CONTAINS",
label: props.evaluation?.label ?? props.defaultName ?? "",
value: props.evaluation?.value ?? "",
evalType: props.evaluation?.evalType ?? "CONTAINS",
});
return (
<VStack borderTopWidth={1} borderColor="gray.200" py={4}>
<HStack w="100%">
<FormControl flex={1}>
<FormLabel fontSize="sm">Evaluation Name</FormLabel>
<FormLabel fontSize="sm">Eval Name</FormLabel>
<Input
size="sm"
value={values.name}
onChange={(e) => setValues((values) => ({ ...values, name: e.target.value }))}
value={values.label}
onChange={(e) => setValues((values) => ({ ...values, label: e.target.value }))}
/>
</FormControl>
<FormControl flex={1}>
<FormLabel fontSize="sm">Match Type</FormLabel>
<FormLabel fontSize="sm">Eval Type</FormLabel>
<Select
size="sm"
value={values.matchType}
value={values.evalType}
onChange={(e) =>
setValues((values) => ({
...values,
matchType: e.target.value as EvaluationMatchType,
evalType: e.target.value as EvalType,
}))
}
>
{Object.values(EvaluationMatchType).map((type) => (
{Object.values(EvalType).map((type) => (
<option key={type} value={type}>
{type}
</option>
@@ -63,17 +66,37 @@ export function EvaluationEditor(props: {
</Select>
</FormControl>
</HStack>
<FormControl>
<FormLabel fontSize="sm">Match String</FormLabel>
<Input
size="sm"
value={values.matchString}
onChange={(e) => setValues((values) => ({ ...values, matchString: e.target.value }))}
/>
<FormHelperText>
This string will be interpreted as a regex and checked against each model output.
</FormHelperText>
</FormControl>
{["CONTAINS", "DOES_NOT_CONTAIN"].includes(values.evalType) && (
<FormControl>
<FormLabel fontSize="sm">Match String</FormLabel>
<Input
size="sm"
value={values.value}
onChange={(e) => setValues((values) => ({ ...values, value: e.target.value }))}
/>
<FormHelperText>
This string will be interpreted as a regex and checked against each model output. You
can include scenario variables using <Code>{"{{curly_braces}}"}</Code>
</FormHelperText>
</FormControl>
)}
{values.evalType === "GPT4_EVAL" && (
<FormControl pt={2}>
<FormLabel fontSize="sm">GPT4 Instructions</FormLabel>
<AutoResizeTextArea
size="sm"
value={values.value}
onChange={(e) => setValues((values) => ({ ...values, value: e.target.value }))}
minRows={3}
/>
<FormHelperText>
Give instructions to GPT-4 for how to evaluate your prompt. It will have access to the
full scenario as well as the output it is evaluating. It will <strong>not</strong> have
access to the specific prompt variant, so be sure to be clear about the task you want it
to perform.
</FormHelperText>
</FormControl>
)}
<HStack alignSelf="flex-end">
<Button size="sm" onClick={props.onCancel} colorScheme="gray">
Cancel
@@ -125,6 +148,7 @@ export default function EditEvaluations() {
}
await utils.evaluations.list.invalidate();
await utils.promptVariants.stats.invalidate();
await utils.scenarioVariantCells.get.invalidate();
}, []);
const onCancel = useCallback(() => {
@@ -156,50 +180,41 @@ export default function EditEvaluations() {
align="center"
key={evaluation.id}
>
<Text fontWeight="bold">{evaluation.name}</Text>
<Text fontWeight="bold">{evaluation.label}</Text>
<Text flex={1}>
{evaluation.matchType}: &quot;{evaluation.matchString}&quot;
{evaluation.evalType}: &quot;{evaluation.value}&quot;
</Text>
<Button
<IconButton
aria-label="Edit"
variant="unstyled"
color="gray.400"
height="unset"
width="unset"
minW="unset"
color="gray.400"
onClick={() => setEditingId(evaluation.id)}
_hover={{
color: "gray.800",
cursor: "pointer",
}}
>
<Icon as={BsPencil} boxSize={4} />
</Button>
<Button
_hover={{ color: "gray.800", cursor: "pointer" }}
icon={<Icon as={BsPencil} />}
/>
<IconButton
aria-label="Delete"
variant="unstyled"
color="gray.400"
height="unset"
width="unset"
minW="unset"
color="gray.400"
onClick={() => onDelete(evaluation.id)}
_hover={{
color: "gray.800",
cursor: "pointer",
}}
>
<Icon as={BsX} boxSize={6} />
</Button>
_hover={{ color: "gray.800", cursor: "pointer" }}
icon={<Icon as={BsX} boxSize={6} />}
/>
</HStack>
),
)}
{editingId == null && (
<Button
onClick={() => setEditingId("new")}
alignSelf="flex-start"
alignSelf="end"
size="sm"
mt={4}
colorScheme="blue"
>
Add Evaluation
New Evaluation
</Button>
)}
{editingId == "new" && (

View File

@@ -0,0 +1,185 @@
import { Text, Button, HStack, Heading, Icon, IconButton, Stack, VStack } from "@chakra-ui/react";
import { type TemplateVariable } from "@prisma/client";
import { useEffect, useState } from "react";
import { BsPencil, BsX } from "react-icons/bs";
import { api } from "~/utils/api";
import { useExperiment, useHandledAsyncCallback, useScenarioVars } from "~/utils/hooks";
import { maybeReportError } from "~/utils/errorHandling/maybeReportError";
import { FloatingLabelInput } from "./FloatingLabelInput";
export const ScenarioVar = ({
variable,
isEditing,
setIsEditing,
}: {
variable: Pick<TemplateVariable, "id" | "label">;
isEditing: boolean;
setIsEditing: (isEditing: boolean) => void;
}) => {
const utils = api.useContext();
const [label, setLabel] = useState(variable.label);
useEffect(() => {
setLabel(variable.label);
}, [variable.label]);
const renameVarMutation = api.scenarioVars.rename.useMutation();
const [onRename] = useHandledAsyncCallback(async () => {
const resp = await renameVarMutation.mutateAsync({ id: variable.id, label });
if (maybeReportError(resp)) return;
setIsEditing(false);
await utils.scenarioVars.list.invalidate();
await utils.scenarios.list.invalidate();
}, [label, variable.id]);
const deleteMutation = api.scenarioVars.delete.useMutation();
const [onDeleteVar] = useHandledAsyncCallback(async () => {
await deleteMutation.mutateAsync({ id: variable.id });
await utils.scenarioVars.list.invalidate();
}, [variable.id]);
if (isEditing) {
return (
<HStack w="full">
<FloatingLabelInput
flex={1}
label="Renamed Variable"
value={label}
onChange={(e) => setLabel(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
onRename();
}
// If the user types a space, replace it with an underscore
if (e.key === " ") {
e.preventDefault();
setLabel((label) => label && `${label}_`);
}
}}
/>
<Button size="sm" onClick={() => setIsEditing(false)}>
Cancel
</Button>
<Button size="sm" colorScheme="blue" onClick={onRename}>
Save
</Button>
</HStack>
);
} else {
return (
<HStack w="full" borderTopWidth={1} borderColor="gray.200">
<Text flex={1}>{variable.label}</Text>
<IconButton
aria-label="Edit"
variant="unstyled"
minW="unset"
color="gray.400"
onClick={() => setIsEditing(true)}
_hover={{ color: "gray.800", cursor: "pointer" }}
icon={<Icon as={BsPencil} />}
/>
<IconButton
aria-label="Delete"
variant="unstyled"
minW="unset"
color="gray.400"
onClick={onDeleteVar}
_hover={{ color: "gray.800", cursor: "pointer" }}
icon={<Icon as={BsX} boxSize={6} />}
/>
</HStack>
);
}
};
export default function EditScenarioVars() {
const experiment = useExperiment();
const vars = useScenarioVars();
const [currentlyEditingId, setCurrentlyEditingId] = useState<string | null>(null);
const [newVariable, setNewVariable] = useState<string>("");
const newVarIsValid = newVariable?.length ?? 0 > 0;
const utils = api.useContext();
const addVarMutation = api.scenarioVars.create.useMutation();
const [onAddVar] = useHandledAsyncCallback(async () => {
if (!experiment.data?.id) return;
if (!newVariable) return;
const resp = await addVarMutation.mutateAsync({
experimentId: experiment.data.id,
label: newVariable,
});
if (maybeReportError(resp)) return;
await utils.scenarioVars.list.invalidate();
setNewVariable("");
}, [addVarMutation, experiment.data?.id, newVarIsValid, newVariable]);
return (
<Stack>
<Heading size="sm">Scenario Variables</Heading>
<VStack spacing={4}>
<Text fontSize="sm">
Scenario variables can be used in your prompt variants as well as evaluations.
</Text>
<VStack spacing={0} w="full">
{vars.data?.map((variable) => (
<ScenarioVar
variable={variable}
key={variable.id}
isEditing={currentlyEditingId === variable.id}
setIsEditing={(isEditing) => {
if (isEditing) {
setCurrentlyEditingId(variable.id);
} else {
setCurrentlyEditingId(null);
}
}}
/>
))}
</VStack>
{currentlyEditingId !== "new" && (
<Button
colorScheme="blue"
size="sm"
onClick={() => setCurrentlyEditingId("new")}
alignSelf="end"
>
New Variable
</Button>
)}
{currentlyEditingId === "new" && (
<HStack w="full">
<FloatingLabelInput
flex={1}
label="New Variable"
value={newVariable}
onChange={(e) => setNewVariable(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
onAddVar();
}
// If the user types a space, replace it with an underscore
if (e.key === " ") {
e.preventDefault();
setNewVariable((v) => v && `${v}_`);
}
}}
/>
<Button size="sm" onClick={() => setCurrentlyEditingId(null)}>
Cancel
</Button>
<Button size="sm" colorScheme="blue" onClick={onAddVar}>
Save
</Button>
</HStack>
)}
</VStack>
</Stack>
);
}

View File

@@ -0,0 +1,46 @@
import { FormLabel, FormControl, type TextareaProps } from "@chakra-ui/react";
import { useState } from "react";
import AutoResizeTextArea from "../AutoResizeTextArea";
export const FloatingLabelInput = ({
label,
value,
...props
}: { label: string; value: string } & TextareaProps) => {
const [isFocused, setIsFocused] = useState(false);
return (
<FormControl position="relative">
<FormLabel
position="absolute"
left="10px"
top={isFocused || !!value ? 0 : 3}
transform={isFocused || !!value ? "translateY(-50%)" : "translateY(0)"}
fontSize={isFocused || !!value ? "12px" : "16px"}
transition="all 0.15s"
zIndex="5"
bg="white"
px={1}
lineHeight="1"
pointerEvents="none"
color={isFocused ? "blue.500" : "gray.500"}
>
{label}
</FormLabel>
<AutoResizeTextArea
px={3}
pt={3}
pb={2}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
borderRadius="md"
borderColor={isFocused ? "blue.500" : "gray.400"}
autoComplete="off"
value={value}
overflowY="auto"
overflowX="hidden"
{...props}
/>
</FormControl>
);
};

View File

@@ -0,0 +1,53 @@
import { HStack, Icon, IconButton, Spinner, Tooltip, useDisclosure } from "@chakra-ui/react";
import { BsArrowClockwise, BsInfoCircle } from "react-icons/bs";
import { useExperimentAccess } from "~/utils/hooks";
import PromptModal 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>
<PromptModal 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>
);
};

View File

@@ -0,0 +1,29 @@
import { type StackProps, VStack } from "@chakra-ui/react";
import { type RouterOutputs } from "~/utils/api";
import { type Scenario } from "../types";
import { CellOptions } from "./CellOptions";
import { OutputStats } from "./OutputStats";
const CellWrapper: React.FC<
StackProps & {
cell: RouterOutputs["scenarioVariantCells"]["get"] | undefined;
hardRefetching: boolean;
hardRefetch: () => void;
mostRecentResponse:
| NonNullable<RouterOutputs["scenarioVariantCells"]["get"]>["modelResponses"][0]
| undefined;
scenario: Scenario;
}
> = ({ children, cell, hardRefetching, hardRefetch, mostRecentResponse, scenario, ...props }) => (
<VStack w="full" alignItems="flex-start" {...props} px={2} py={2} h="100%">
{cell && (
<CellOptions refetchingOutput={hardRefetching} refetchOutput={hardRefetch} cell={cell} />
)}
<VStack w="full" alignItems="flex-start" maxH={500} overflowY="auto" flex={1}>
{children}
</VStack>
{mostRecentResponse && <OutputStats modelResponse={mostRecentResponse} scenario={scenario} />}
</VStack>
);
export default CellWrapper;

View File

@@ -0,0 +1,195 @@
import { Text } from "@chakra-ui/react";
import stringify from "json-stringify-pretty-compact";
import { Fragment, useEffect, useState, type ReactElement } from "react";
import SyntaxHighlighter from "react-syntax-highlighter";
import { docco } from "react-syntax-highlighter/dist/cjs/styles/hljs";
import frontendModelProviders from "~/modelProviders/frontendModelProviders";
import { api } from "~/utils/api";
import { useHandledAsyncCallback, useScenarioVars } from "~/utils/hooks";
import useSocket from "~/utils/useSocket";
import { type PromptVariant, type Scenario } from "../types";
import CellWrapper from "./CellWrapper";
import { ResponseLog } from "./ResponseLog";
import { RetryCountdown } from "./RetryCountdown";
const WAITING_MESSAGE_INTERVAL = 20000;
export default function OutputCell({
scenario,
variant,
}: {
scenario: Scenario;
variant: PromptVariant;
}): ReactElement | null {
const utils = api.useContext();
const vars = useScenarioVars().data;
const scenarioVariables = scenario.variableValues as Record<string, string>;
const templateHasVariables =
vars?.length === 0 || vars?.some((v) => scenarioVariables[v.label] !== undefined);
let disabledReason: string | null = null;
if (!templateHasVariables) disabledReason = "Add a value to the scenario variables to see output";
const [refetchInterval, setRefetchInterval] = useState<number | false>(false);
const { data: cell, isLoading: queryLoading } = api.scenarioVariantCells.get.useQuery(
{ scenarioId: scenario.id, variantId: variant.id },
{ refetchInterval },
);
const provider =
frontendModelProviders[variant.modelProvider as keyof typeof frontendModelProviders];
type OutputSchema = Parameters<typeof provider.normalizeOutput>[0];
const { mutateAsync: hardRefetchMutate } = api.scenarioVariantCells.forceRefetch.useMutation();
const [hardRefetch, hardRefetching] = useHandledAsyncCallback(async () => {
await hardRefetchMutate({ scenarioId: scenario.id, variantId: variant.id });
await utils.scenarioVariantCells.get.invalidate({
scenarioId: scenario.id,
variantId: variant.id,
});
await utils.promptVariants.stats.invalidate({
variantId: variant.id,
});
}, [hardRefetchMutate, scenario.id, variant.id]);
const fetchingOutput = queryLoading || hardRefetching;
const awaitingOutput =
!cell ||
!cell.evalsComplete ||
cell.retrievalStatus === "PENDING" ||
cell.retrievalStatus === "IN_PROGRESS" ||
hardRefetching;
useEffect(() => setRefetchInterval(awaitingOutput ? 1000 : false), [awaitingOutput]);
// TODO: disconnect from socket if we're not streaming anymore
const streamedMessage = useSocket<OutputSchema>(cell?.id);
const mostRecentResponse = cell?.modelResponses[cell.modelResponses.length - 1];
const wrapperProps: Parameters<typeof CellWrapper>[0] = {
cell,
hardRefetching,
hardRefetch,
mostRecentResponse,
scenario,
};
if (!vars) return null;
if (!cell && !fetchingOutput)
return (
<CellWrapper {...wrapperProps}>
<Text color="gray.500">Error retrieving output</Text>
</CellWrapper>
);
if (cell && cell.errorMessage) {
return (
<CellWrapper {...wrapperProps}>
<Text color="red.500">{cell.errorMessage}</Text>
</CellWrapper>
);
}
if (disabledReason) return <Text color="gray.500">{disabledReason}</Text>;
const showLogs = !streamedMessage && !mostRecentResponse?.respPayload;
if (showLogs)
return (
<CellWrapper
{...wrapperProps}
alignItems="flex-start"
fontFamily="inconsolata, monospace"
spacing={0}
>
{cell?.jobQueuedAt && <ResponseLog time={cell.jobQueuedAt} title="Job queued" />}
{cell?.jobStartedAt && <ResponseLog time={cell.jobStartedAt} title="Job started" />}
{cell?.modelResponses?.map((response) => {
let numWaitingMessages = 0;
const relativeWaitingTime = response.receivedAt
? response.receivedAt.getTime()
: Date.now();
if (response.requestedAt) {
numWaitingMessages = Math.min(
Math.floor(
(relativeWaitingTime - response.requestedAt.getTime()) / WAITING_MESSAGE_INTERVAL,
),
// Don't try to render more than 15, it'll use too much CPU and
// break the page
15,
);
}
return (
<Fragment key={response.id}>
{response.requestedAt && (
<ResponseLog time={response.requestedAt} title="Request sent to API" />
)}
{response.requestedAt &&
Array.from({ length: numWaitingMessages }, (_, i) => (
<ResponseLog
key={`waiting-${i}`}
time={
new Date(
(response.requestedAt?.getTime?.() ?? 0) +
(i + 1) * WAITING_MESSAGE_INTERVAL,
)
}
title="Waiting for response..."
/>
))}
{response.receivedAt && (
<ResponseLog
time={response.receivedAt}
title="Response received from API"
message={`statusCode: ${response.statusCode ?? ""}\n ${
response.errorMessage ?? ""
}`}
/>
)}
</Fragment>
);
}) ?? null}
{mostRecentResponse?.retryTime && (
<RetryCountdown retryTime={mostRecentResponse.retryTime} />
)}
</CellWrapper>
);
const normalizedOutput = mostRecentResponse?.respPayload
? provider.normalizeOutput(mostRecentResponse?.respPayload)
: streamedMessage
? provider.normalizeOutput(streamedMessage)
: null;
if (mostRecentResponse?.respPayload && normalizedOutput?.type === "json") {
return (
<CellWrapper {...wrapperProps}>
<SyntaxHighlighter
customStyle={{ overflowX: "unset", width: "100%", flex: 1 }}
language="json"
style={docco}
lineProps={{
style: { wordBreak: "break-all", whiteSpace: "pre-wrap" },
}}
wrapLines
>
{stringify(normalizedOutput.value, { maxLength: 40 })}
</SyntaxHighlighter>
</CellWrapper>
);
}
const contentToDisplay = (normalizedOutput?.type === "text" && normalizedOutput.value) || "";
return (
<CellWrapper {...wrapperProps}>
<Text whiteSpace="pre-wrap">{contentToDisplay}</Text>
</CellWrapper>
);
}

View File

@@ -0,0 +1,76 @@
import { type Scenario } from "../types";
import { type RouterOutputs } from "~/utils/api";
import { HStack, Icon, Text, Tooltip } from "@chakra-ui/react";
import { BsCheck, BsClock, BsCurrencyDollar, BsX } from "react-icons/bs";
import { CostTooltip } from "~/components/tooltip/CostTooltip";
const SHOW_TIME = true;
export const OutputStats = ({
modelResponse,
}: {
modelResponse: NonNullable<
NonNullable<RouterOutputs["scenarioVariantCells"]["get"]>["modelResponses"][0]
>;
scenario: Scenario;
}) => {
const timeToComplete =
modelResponse.receivedAt && modelResponse.requestedAt
? modelResponse.receivedAt.getTime() - modelResponse.requestedAt.getTime()
: 0;
const inputTokens = modelResponse.inputTokens;
const outputTokens = modelResponse.outputTokens;
return (
<HStack
w="full"
align="center"
color="gray.500"
fontSize="2xs"
mt={{ base: 0, md: 1 }}
alignItems="flex-end"
>
<HStack flex={1} flexWrap="wrap">
{modelResponse.outputEvaluations.map((evaluation) => {
const passed = evaluation.result > 0.5;
return (
<Tooltip
isDisabled={!evaluation.details}
label={evaluation.details}
key={evaluation.id}
shouldWrapChildren
>
<HStack spacing={0}>
<Text>{evaluation.evaluation.label}</Text>
<Icon
as={passed ? BsCheck : BsX}
color={passed ? "green.500" : "red.500"}
boxSize={6}
/>
</HStack>
</Tooltip>
);
})}
</HStack>
{modelResponse.cost && (
<CostTooltip
inputTokens={inputTokens}
outputTokens={outputTokens}
cost={modelResponse.cost}
>
<HStack spacing={0}>
<Icon as={BsCurrencyDollar} />
<Text mr={1}>{modelResponse.cost.toFixed(3)}</Text>
</HStack>
</CostTooltip>
)}
{SHOW_TIME && (
<HStack spacing={0.5}>
<Icon as={BsClock} />
<Text>{(timeToComplete / 1000).toFixed(2)}s</Text>
</HStack>
)}
</HStack>
);
};

View File

@@ -0,0 +1,109 @@
import {
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
VStack,
Text,
Box,
type UseDisclosureReturn,
Link,
} from "@chakra-ui/react";
import { api, type RouterOutputs } from "~/utils/api";
import { JSONTree } from "react-json-tree";
import CopiableCode from "~/components/CopiableCode";
const theme = {
scheme: "chalk",
author: "chris kempson (http://chriskempson.com)",
base00: "transparent",
base01: "#202020",
base02: "#303030",
base03: "#505050",
base04: "#b0b0b0",
base05: "#d0d0d0",
base06: "#e0e0e0",
base07: "#f5f5f5",
base08: "#fb9fb1",
base09: "#eda987",
base0A: "#ddb26f",
base0B: "#acc267",
base0C: "#12cfc0",
base0D: "#6fc2ef",
base0E: "#e1a3ee",
base0F: "#deaf8f",
};
export default function PromptModal(props: {
cell: NonNullable<RouterOutputs["scenarioVariantCells"]["get"]>;
disclosure: UseDisclosureReturn;
}) {
const { data } = api.scenarioVariantCells.getTemplatedPromptMessage.useQuery(
{
cellId: props.cell.id,
},
{
enabled: props.disclosure.isOpen,
},
);
return (
<Modal isOpen={props.disclosure.isOpen} onClose={props.disclosure.onClose} size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>Prompt Details</ModalHeader>
<ModalCloseButton />
<ModalBody>
<VStack py={4} w="">
<VStack w="full" alignItems="flex-start">
<Text fontWeight="bold">Full Prompt</Text>
<Box
w="full"
p={4}
alignItems="flex-start"
backgroundColor="blackAlpha.800"
borderRadius={4}
>
<JSONTree
data={props.cell.prompt}
theme={theme}
shouldExpandNodeInitially={() => true}
getItemString={() => ""}
hideRoot
/>
</Box>
</VStack>
{data?.templatedPrompt && (
<VStack w="full" mt={4} alignItems="flex-start">
<Text fontWeight="bold">Templated prompt message:</Text>
<CopiableCode
w="full"
// bgColor="gray.100"
p={4}
borderWidth={1}
whiteSpace="pre-wrap"
code={data.templatedPrompt}
/>
</VStack>
)}
{data?.learnMoreUrl && (
<Link
href={data.learnMoreUrl}
isExternal
color="blue.500"
fontWeight="bold"
fontSize="sm"
mt={4}
alignSelf="flex-end"
>
Learn More
</Link>
)}
</VStack>
</ModalBody>
</ModalContent>
</Modal>
);
}

View File

@@ -0,0 +1,22 @@
import { HStack, VStack, Text } from "@chakra-ui/react";
import dayjs from "dayjs";
export const ResponseLog = ({
time,
title,
message,
}: {
time: Date;
title: string;
message?: string;
}) => {
return (
<VStack spacing={0} alignItems="flex-start">
<HStack>
<Text>{dayjs(time).format("HH:mm:ss")}</Text>
<Text>{title}</Text>
</HStack>
{message && <Text pl={4}>{message}</Text>}
</VStack>
);
};

View File

@@ -1,21 +1,12 @@
import { type ScenarioVariantCell } from "@prisma/client";
import { VStack, Text } from "@chakra-ui/react";
import { Text } from "@chakra-ui/react";
import { useEffect, useState } from "react";
import pluralize from "pluralize";
export const ErrorHandler = ({
cell,
refetchOutput,
}: {
cell: ScenarioVariantCell;
refetchOutput: () => void;
}) => {
export const RetryCountdown = ({ retryTime }: { retryTime: Date }) => {
const [msToWait, setMsToWait] = useState(0);
useEffect(() => {
if (!cell.retryTime) return;
const initialWaitTime = cell.retryTime.getTime() - Date.now();
const initialWaitTime = retryTime.getTime() - Date.now();
const msModuloOneSecond = initialWaitTime % 1000;
let remainingTime = initialWaitTime - msModuloOneSecond;
setMsToWait(remainingTime);
@@ -36,18 +27,13 @@ export const ErrorHandler = ({
clearInterval(interval);
clearTimeout(timeout);
};
}, [cell.retryTime, cell.statusCode, setMsToWait, refetchOutput]);
}, [retryTime]);
if (msToWait <= 0) return null;
return (
<VStack w="full">
<Text color="red.600" wordBreak="break-word">
{cell.errorMessage}
</Text>
{msToWait > 0 && (
<Text color="red.600" fontSize="sm">
Retrying in {pluralize("second", Math.ceil(msToWait / 1000), true)}...
</Text>
)}
</VStack>
<Text color="red.600" fontSize="sm">
Retrying in {pluralize("second", Math.ceil(msToWait / 1000), true)}...
</Text>
);
};

View File

@@ -0,0 +1,211 @@
import { isEqual } from "lodash-es";
import { useEffect, useState, type DragEvent } from "react";
import { api } from "~/utils/api";
import { useExperimentAccess, useHandledAsyncCallback, useScenarioVars } 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 vars = useScenarioVars();
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"}
>
{
<VStack spacing={4} flex={1} py={2}>
<HStack justifyContent="space-between" w="100%" align="center" spacing={0}>
<Text flex={1}>Scenario</Text>
{variableLabels.length && (
<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.length === 0 ? (
<Box color="gray.500">
{vars.data ? "No scenario variables configured" : "Loading..."}
</Box>
) : (
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)}
/>
)}
</>
);
}

View File

@@ -0,0 +1,123 @@
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.scenarioVars.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: "4xl", xl: "5xl" }}
>
<ModalOverlay />
<ModalContent w={1200}>
<ModalHeader>Edit Scenario</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>
);
};

View File

@@ -0,0 +1,16 @@
import { type StackProps } from "@chakra-ui/react";
import { useScenarios } from "~/utils/hooks";
import Paginator from "../Paginator";
const ScenarioPaginator = (props: StackProps) => {
const { data } = useScenarios();
if (!data) return null;
const { count } = data;
return <Paginator count={count} condense {...props} />;
};
export default ScenarioPaginator;

View File

@@ -1,14 +1,17 @@
import { Box, GridItem } from "@chakra-ui/react";
import { GridItem } from "@chakra-ui/react";
import React, { useState } from "react";
import { cellPadding } from "../constants";
import OutputCell from "./OutputCell/OutputCell";
import ScenarioEditor from "./ScenarioEditor";
import type { PromptVariant, Scenario } from "./types";
import { borders } from "./styles";
const ScenarioRow = (props: {
scenario: Scenario;
variants: PromptVariant[];
canHide: boolean;
rowStart: number;
isFirst: boolean;
isLast: boolean;
}) => {
const [isHovered, setIsHovered] = useState(false);
@@ -20,20 +23,32 @@ const ScenarioRow = (props: {
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
sx={isHovered ? highlightStyle : undefined}
bgColor="white"
rowStart={props.rowStart}
colStart={1}
borderLeftWidth={1}
borderTopWidth={props.isFirst ? 1 : 0}
borderTopLeftRadius={props.isFirst ? 8 : 0}
borderBottomLeftRadius={props.isLast ? 8 : 0}
{...borders}
>
<ScenarioEditor scenario={props.scenario} hovered={isHovered} canHide={props.canHide} />
</GridItem>
{props.variants.map((variant) => (
{props.variants.map((variant, i) => (
<GridItem
key={variant.id}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
sx={isHovered ? highlightStyle : undefined}
bgColor="white"
rowStart={props.rowStart}
colStart={i + 2}
borderTopWidth={props.isFirst ? 1 : 0}
borderTopRightRadius={props.isFirst && i === props.variants.length - 1 ? 8 : 0}
borderBottomRightRadius={props.isLast && i === props.variants.length - 1 ? 8 : 0}
{...borders}
>
<Box h="100%" w="100%" px={cellPadding.x} py={cellPadding.y}>
<OutputCell key={variant.id} scenario={props.scenario} variant={variant} />
</Box>
<OutputCell key={variant.id} scenario={props.scenario} variant={variant} />
</GridItem>
))}
</>

View File

@@ -0,0 +1,87 @@
import {
Button,
type ButtonProps,
HStack,
Text,
Icon,
Menu,
MenuButton,
MenuList,
MenuItem,
IconButton,
Spinner,
} from "@chakra-ui/react";
import { cellPadding } from "../constants";
import {
useExperiment,
useExperimentAccess,
useHandledAsyncCallback,
useScenarios,
} from "~/utils/hooks";
import { BsGear, BsPencil, BsPlus, BsStars } from "react-icons/bs";
import { useAppStore } from "~/state/store";
import { api } from "~/utils/api";
export const ActionButton = (props: ButtonProps) => (
<Button size="sm" variant="ghost" color="gray.600" {...props} />
);
export const ScenariosHeader = () => {
const openDrawer = useAppStore((s) => s.openDrawer);
const { canModify } = useExperimentAccess();
const scenarios = useScenarios();
const experiment = useExperiment();
const createScenarioMutation = api.scenarios.create.useMutation();
const utils = api.useContext();
const [onAddScenario, loading] = useHandledAsyncCallback(
async (autogenerate: boolean) => {
if (!experiment.data) return;
await createScenarioMutation.mutateAsync({
experimentId: experiment.data.id,
autogenerate,
});
await utils.scenarios.list.invalidate();
},
[createScenarioMutation],
);
return (
<HStack w="100%" py={cellPadding.y} px={cellPadding.x} align="center" spacing={0}>
<Text fontSize={16} fontWeight="bold">
Scenarios ({scenarios.data?.count})
</Text>
{canModify && (
<Menu>
<MenuButton
as={IconButton}
mt={1}
ml={2}
variant="ghost"
aria-label="Edit Scenarios"
icon={<Icon as={loading ? Spinner : BsGear} />}
maxW={8}
minW={8}
minH={8}
maxH={8}
/>
<MenuList fontSize="md" zIndex="dropdown" mt={-1}>
<MenuItem
icon={<Icon as={BsPlus} boxSize={6} mx="-5px" />}
onClick={() => onAddScenario(false)}
>
Add Scenario
</MenuItem>
<MenuItem icon={<BsStars />} onClick={() => onAddScenario(true)}>
Autogenerate Scenario
</MenuItem>
<MenuItem icon={<BsPencil />} onClick={openDrawer}>
Add or Remove Variables
</MenuItem>
</MenuList>
</Menu>
)}
</HStack>
);
};

Some files were not shown because too many files have changed in this diff Show More