From 0a675cd7f7559ede53b6c13e75b1cd5d5241c6ab Mon Sep 17 00:00:00 2001 From: Kyle Corbitt Date: Tue, 27 Jun 2023 13:00:23 -0700 Subject: [PATCH] autogen scenarios --- package.json | 4 + pnpm-lock.yaml | 224 ++++++++++++++++++ .../OutputsTable/NewScenarioButton.tsx | 53 ++++- src/components/OutputsTable/OutputCell.tsx | 63 +++-- src/components/OutputsTable/index.tsx | 7 +- src/server/api/autogen.ts | 157 ++++++++++++ src/server/api/routers/scenarios.router.ts | 6 +- src/server/utils/openai.ts | 8 + 8 files changed, 472 insertions(+), 50 deletions(-) create mode 100644 src/server/api/autogen.ts create mode 100644 src/server/utils/openai.ts diff --git a/package.json b/package.json index 0b727aa..720f048 100644 --- a/package.json +++ b/package.json @@ -30,14 +30,17 @@ "dayjs": "^1.11.8", "dotenv": "^16.3.1", "framer-motion": "^10.12.17", + "json-stringify-pretty-compact": "^4.0.0", "lodash": "^4.17.21", "next": "^13.4.2", "next-auth": "^4.22.1", "nextjs-routes": "^2.0.1", + "openai": "^3.3.0", "posthog-js": "^1.68.4", "react": "18.2.0", "react-dom": "18.2.0", "react-icons": "^4.10.1", + "react-syntax-highlighter": "^15.5.0", "react-textarea-autosize": "^8.5.0", "superjson": "1.12.2", "tsx": "^3.12.7", @@ -50,6 +53,7 @@ "@types/node": "^18.16.0", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", + "@types/react-syntax-highlighter": "^15.5.7", "@typescript-eslint/eslint-plugin": "^5.59.6", "@typescript-eslint/parser": "^5.59.6", "eslint": "^8.40.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd86424..de2630e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ dependencies: framer-motion: specifier: ^10.12.17 version: 10.12.17(react-dom@18.2.0)(react@18.2.0) + json-stringify-pretty-compact: + specifier: ^4.0.0 + version: 4.0.0 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -71,6 +74,9 @@ dependencies: nextjs-routes: specifier: ^2.0.1 version: 2.0.1(next@13.4.2) + openai: + specifier: ^3.3.0 + version: 3.3.0 posthog-js: specifier: ^1.68.4 version: 1.68.4 @@ -83,6 +89,9 @@ dependencies: react-icons: specifier: ^4.10.1 version: 4.10.1(react@18.2.0) + react-syntax-highlighter: + specifier: ^15.5.0 + version: 15.5.0(react@18.2.0) react-textarea-autosize: specifier: ^8.5.0 version: 8.5.0(@types/react@18.2.6)(react@18.2.0) @@ -115,6 +124,9 @@ devDependencies: '@types/react-dom': specifier: ^18.2.4 version: 18.2.4 + '@types/react-syntax-highlighter': + specifier: ^15.5.7 + version: 15.5.7 '@typescript-eslint/eslint-plugin': specifier: ^5.59.6 version: 5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.40.0)(typescript@5.0.4) @@ -2033,6 +2045,12 @@ packages: resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} dev: true + /@types/hast@2.3.4: + resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==} + dependencies: + '@types/unist': 2.0.6 + dev: false + /@types/json-schema@7.0.12: resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} dev: true @@ -2071,6 +2089,12 @@ packages: '@types/react': 18.2.6 dev: true + /@types/react-syntax-highlighter@15.5.7: + resolution: {integrity: sha512-bo5fEO5toQeyCp0zVHBeggclqf5SQ/Z5blfFmjwO5dkMVGPgmiwZsJh9nu/Bo5L7IHTuGWrja6LxJVE2uB5ZrQ==} + dependencies: + '@types/react': 18.2.6 + dev: true + /@types/react@18.2.6: resolution: {integrity: sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==} dependencies: @@ -2085,6 +2109,10 @@ packages: resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} dev: true + /@types/unist@2.0.6: + resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} + dev: false + /@typescript-eslint/eslint-plugin@5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.40.0)(typescript@5.0.4): resolution: {integrity: sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2347,6 +2375,10 @@ packages: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} dev: true + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} @@ -2357,6 +2389,14 @@ packages: engines: {node: '>=4'} dev: true + /axios@0.26.1: + resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} + dependencies: + follow-redirects: 1.15.2 + transitivePeerDependencies: + - debug + dev: false + /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} dependencies: @@ -2459,6 +2499,18 @@ packages: supports-color: 7.2.0 dev: true + /character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + dev: false + + /character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + dev: false + + /character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + dev: false + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -2503,6 +2555,17 @@ packages: resolution: {integrity: sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w==} dev: false + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + + /comma-separated-tokens@1.0.8: + resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} + dev: false + /compute-scroll-into-view@1.0.20: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} dev: false @@ -2632,6 +2695,11 @@ packages: object-keys: 1.1.1 dev: true + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -3161,6 +3229,12 @@ packages: reusify: 1.0.4 dev: true + /fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + dependencies: + format: 0.2.2 + dev: false + /fflate@0.4.8: resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} dev: false @@ -3209,12 +3283,36 @@ packages: tslib: 2.5.3 dev: false + /follow-redirects@1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 dev: true + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + dev: false + /framer-motion@10.12.17(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-IR+aAYntsyu6ofyxqQV4QYotmOqzcuKxhqNpfc3DXJjNWOPpOeSyH0A+In3IEBu49Yx/+PNht+YMeZSdCNaYbw==} peerDependencies: @@ -3437,6 +3535,24 @@ packages: dependencies: function-bind: 1.1.1 + /hast-util-parse-selector@2.2.5: + resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} + dev: false + + /hastscript@6.0.0: + resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} + dependencies: + '@types/hast': 2.3.4 + comma-separated-tokens: 1.0.8 + hast-util-parse-selector: 2.2.5 + property-information: 5.6.0 + space-separated-tokens: 1.1.5 + dev: false + + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false + /hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} dependencies: @@ -3506,6 +3622,17 @@ packages: loose-envify: 1.4.0 dev: false + /is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + dev: false + + /is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + dependencies: + is-alphabetical: 1.0.4 + is-decimal: 1.0.4 + dev: false + /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: @@ -3556,6 +3683,10 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + dev: false + /is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -3578,6 +3709,10 @@ packages: dependencies: is-extglob: 2.1.1 + /is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + dev: false + /is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -3716,6 +3851,10 @@ packages: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true + /json-stringify-pretty-compact@4.0.0: + resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==} + dev: false + /json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -3778,6 +3917,13 @@ packages: dependencies: js-tokens: 4.0.0 + /lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + dev: false + /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -3801,6 +3947,18 @@ packages: picomatch: 2.3.1 dev: true + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + /mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -4063,6 +4221,15 @@ packages: is-wsl: 2.2.0 dev: true + /openai@3.3.0: + resolution: {integrity: sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==} + dependencies: + axios: 0.26.1 + form-data: 4.0.0 + transitivePeerDependencies: + - debug + dev: false + /openapi-typescript@5.4.1: resolution: {integrity: sha512-AGB2QiZPz4rE7zIwV3dRHtoUC/CWHhUjuzGXvtmMQN2AFV8xCTLKcZUHLcdPQmt/83i22nRE7+TxXOXkK+gf4Q==} engines: {node: '>= 14.0.0'} @@ -4117,6 +4284,17 @@ packages: dependencies: callsites: 3.1.0 + /parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + dependencies: + character-entities: 1.2.4 + character-entities-legacy: 1.1.4 + character-reference-invalid: 1.1.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 + is-hexadecimal: 1.0.4 + dev: false + /parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -4212,6 +4390,16 @@ packages: dependencies: '@prisma/engines': 4.14.0 + /prismjs@1.27.0: + resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} + engines: {node: '>=6'} + dev: false + + /prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + dev: false + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: false @@ -4223,6 +4411,12 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 + /property-information@5.6.0: + resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} + dependencies: + xtend: 4.0.2 + dev: false + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -4345,6 +4539,19 @@ packages: tslib: 2.5.3 dev: false + /react-syntax-highlighter@15.5.0(react@18.2.0): + resolution: {integrity: sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==} + peerDependencies: + react: '>= 0.14.0' + dependencies: + '@babel/runtime': 7.22.5 + highlight.js: 10.7.3 + lowlight: 1.20.0 + prismjs: 1.29.0 + react: 18.2.0 + refractor: 3.6.0 + dev: false + /react-textarea-autosize@8.5.0(@types/react@18.2.6)(react@18.2.0): resolution: {integrity: sha512-cp488su3U9RygmHmGpJp0KEt0i/+57KCK33XVPH+50swVRBhIZYh0fGduz2YLKXwl9vSKBZ9HUXcg9PQXUXqIw==} engines: {node: '>=10'} @@ -4394,6 +4601,14 @@ packages: picomatch: 2.3.1 dev: false + /refractor@3.6.0: + resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==} + dependencies: + hastscript: 6.0.0 + parse-entities: 2.0.0 + prismjs: 1.27.0 + dev: false + /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} @@ -4542,6 +4757,10 @@ packages: engines: {node: '>=0.10.0'} dev: false + /space-separated-tokens@1.1.5: + resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} + dev: false + /state-local@1.0.7: resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} dev: false @@ -4952,6 +5171,11 @@ packages: object-keys: 0.4.0 dev: false + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: false + /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} diff --git a/src/components/OutputsTable/NewScenarioButton.tsx b/src/components/OutputsTable/NewScenarioButton.tsx index ce3e9c2..1583b58 100644 --- a/src/components/OutputsTable/NewScenarioButton.tsx +++ b/src/components/OutputsTable/NewScenarioButton.tsx @@ -1,12 +1,27 @@ -import { Button } from "@chakra-ui/react"; +import { Button, type ButtonProps, Fade, HStack } from "@chakra-ui/react"; +import { useState } from "react"; import { BsPlus } from "react-icons/bs"; import { api } from "~/utils/api"; import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks"; +// Extracted Button styling into reusable component +const StyledButton = ({ children, onClick }: ButtonProps) => ( + +); + export default function NewScenarioButton() { const experiment = useExperiment(); const mutation = api.scenarios.create.useMutation(); const utils = api.useContext(); + const [hovering, setHovering] = useState(false); const [onClick] = useHandledAsyncCallback(async () => { if (!experiment.data) return; @@ -16,19 +31,31 @@ export default function NewScenarioButton() { await utils.scenarios.list.invalidate(); }, [mutation]); + const [onAutogenerate] = useHandledAsyncCallback(async () => { + if (!experiment.data) return; + await mutation.mutateAsync({ + experimentId: experiment.data.id, + autogenerate: true, + }); + await utils.scenarios.list.invalidate(); + }, [mutation]); + return ( - + + + Add Scenario + + + + + Autogenerate Scenario + + + ); } diff --git a/src/components/OutputsTable/OutputCell.tsx b/src/components/OutputsTable/OutputCell.tsx index bee52a7..a60ae1b 100644 --- a/src/components/OutputsTable/OutputCell.tsx +++ b/src/components/OutputsTable/OutputCell.tsx @@ -1,14 +1,12 @@ import { api } from "~/utils/api"; import { type PromptVariant, type Scenario } from "./types"; -import { Center, Spinner, Text } from "@chakra-ui/react"; +import { Spinner, Text, Box } from "@chakra-ui/react"; import { useExperiment } from "~/utils/hooks"; -import { cellPadding } from "../constants"; - -const CellShell = ({ children }: { children: React.ReactNode }) => ( -
- {children} -
-); +import { type CreateChatCompletionResponse } from "openai"; +import SyntaxHighlighter from "react-syntax-highlighter"; +import { docco } from "react-syntax-highlighter/dist/cjs/styles/hljs"; +import stringify from "json-stringify-pretty-compact"; +import { type ReactElement } from "react"; export default function OutputCell({ scenario, @@ -16,7 +14,7 @@ export default function OutputCell({ }: { scenario: Scenario; variant: PromptVariant; -}) { +}): ReactElement | null { const experiment = useExperiment(); const vars = api.templateVars.list.useQuery({ experimentId: experiment.data?.id ?? "" }).data; @@ -41,37 +39,34 @@ export default function OutputCell({ if (!vars) return null; - if (disabledReason) - return ( - - {disabledReason} - - ); + if (disabledReason) return {disabledReason}; - if (output.isLoading) - return ( - - - - ); + if (output.isLoading) return ; - if (!output.data) - return ( - - Error retrieving output - - ); + if (!output.data) return Error retrieving output; if (output.data.errorMessage) { + return Error: {output.data.errorMessage}; + } + + const response = output.data?.output as unknown as CreateChatCompletionResponse; + const message = response?.choices?.[0]?.message; + + if (message?.function_call) { return ( - - Error: {output.data.errorMessage} - + + + {stringify( + { + function: message.function_call.name, + args: JSON.parse(message.function_call.arguments ?? "null"), + }, + { maxLength: 40 } + )} + + ); } - return ( - // @ts-expect-error TODO proper typing and error checks - {output.data.output.choices[0].message.content} - ); + return {message?.content ?? JSON.stringify(output.data.output)}; } diff --git a/src/components/OutputsTable/index.tsx b/src/components/OutputsTable/index.tsx index b1c41fe..c5d56b4 100644 --- a/src/components/OutputsTable/index.tsx +++ b/src/components/OutputsTable/index.tsx @@ -1,4 +1,4 @@ -import { Grid, GridItem, type SystemStyleObject } from "@chakra-ui/react"; +import { Center, Grid, GridItem, type SystemStyleObject } from "@chakra-ui/react"; import React, { useState } from "react"; import { api } from "~/utils/api"; import NewScenarioButton from "./NewScenarioButton"; @@ -9,6 +9,7 @@ import VariantConfigEditor from "./VariantConfigEditor"; import VariantHeader from "./VariantHeader"; import type { Scenario, PromptVariant } from "./types"; import ScenarioHeader from "~/server/ScenarioHeader"; +import { cellPadding } from "../constants"; const stickyHeaderStyle: SystemStyleObject = { position: "sticky", @@ -41,7 +42,9 @@ const ScenarioRow = (props: { scenario: Scenario; variants: PromptVariant[] }) = onMouseLeave={() => setIsHovered(false)} sx={isHovered ? highlightStyle : undefined} > - +
+ +
))} diff --git a/src/server/api/autogen.ts b/src/server/api/autogen.ts new file mode 100644 index 0000000..ebe417c --- /dev/null +++ b/src/server/api/autogen.ts @@ -0,0 +1,157 @@ +import { type CreateChatCompletionRequest } from "openai"; +import { prisma } from "../db"; +import { openai } from "../utils/openai"; +import { pick } from "lodash"; + +function promptHasVariable(prompt: string, variableName: string) { + return prompt.includes(`{{${variableName}}}`); +} + +type AxiosError = { + response?: { + data?: { + error?: { + message?: string; + }; + }; + }; +}; + +function isAxiosError(error: unknown): error is AxiosError { + if (typeof error === "object" && error !== null) { + // Initial check + const err = error as AxiosError; + return err.response?.data?.error?.message !== undefined; // Check structure + } + return false; +} +export const autogenerateScenarioValues = async ( + experimentId: string +): Promise> => { + const [experiment, variables, existingScenarios, prompt] = await Promise.all([ + prisma.experiment.findUnique({ + where: { + id: experimentId, + }, + }), + prisma.templateVariable.findMany({ + where: { + experimentId, + }, + }), + prisma.testScenario.findMany({ + where: { + experimentId, + visible: true, + }, + orderBy: { + sortIndex: "asc", + }, + take: 10, + }), + prisma.promptVariant.findFirst({ + where: { + experimentId, + visible: true, + }, + orderBy: { + sortIndex: "asc", + }, + }), + ]); + + if (!experiment || !(variables?.length > 0) || !prompt) return {}; + + const messages: CreateChatCompletionRequest["messages"] = [ + { + role: "system", + content: + "The user is testing multiple scenarios against the same prompt. Attempt to generate a new scenario that is different from the others.", + }, + ]; + + const promptText = JSON.stringify(prompt.config); + if (variables.some((variable) => promptHasVariable(promptText, variable.label))) { + messages.push({ + role: "user", + content: `Prompt template:\n---\n${promptText}`, + }); + } + + existingScenarios + .map( + (scenario) => + pick( + scenario.variableValues, + variables.map((variable) => variable.label) + ) as Record + ) + .filter((vals) => Object.keys(vals ?? {}).length > 0) + .forEach((vals) => { + messages.push({ + role: "assistant", + // @ts-expect-error the openai type definition is wrong, the content field is required + content: null, + function_call: { + name: "add_scenario", + arguments: JSON.stringify(vals), + }, + }); + }); + + const variableProperties = variables.reduce((acc, variable) => { + acc[variable.label] = { type: "string" }; + return acc; + }, {} as Record); + + console.log({ + model: "gpt-3.5-turbo-0613", + messages, + functions: [ + { + name: "add_scenario", + parameters: { + type: "object", + properties: variableProperties, + }, + }, + ], + + function_call: { name: "add_scenario" }, + temperature: 0.5, + }); + + try { + const completion = await openai.createChatCompletion({ + model: "gpt-3.5-turbo-0613", + messages, + functions: [ + { + name: "add_scenario", + parameters: { + type: "object", + properties: variableProperties, + }, + }, + ], + + function_call: { name: "add_scenario" }, + temperature: 0.5, + }); + + const parsed = JSON.parse( + completion.data.choices[0]?.message?.function_call?.arguments ?? "{}" + ) as Record; + return parsed; + } catch (e) { + // If it's an axios error, try to get the error message + if (isAxiosError(e)) { + console.error(e?.response?.data?.error?.message); + } else { + console.error(e); + } + return {}; + } + + return {}; +}; diff --git a/src/server/api/routers/scenarios.router.ts b/src/server/api/routers/scenarios.router.ts index 3cc6f00..6747f44 100644 --- a/src/server/api/routers/scenarios.router.ts +++ b/src/server/api/routers/scenarios.router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; import { prisma } from "~/server/db"; +import { autogenerateScenarioValues } from "../autogen"; export const scenariosRouter = createTRPCRouter({ list: publicProcedure.input(z.object({ experimentId: z.string() })).query(async ({ input }) => { @@ -19,6 +20,7 @@ export const scenariosRouter = createTRPCRouter({ .input( z.object({ experimentId: z.string(), + autogenerate: z.boolean().optional(), }) ) .mutation(async ({ input }) => { @@ -38,7 +40,9 @@ export const scenariosRouter = createTRPCRouter({ data: { experimentId: input.experimentId, sortIndex: maxSortIndex + 1, - variableValues: {}, + variableValues: input.autogenerate + ? await autogenerateScenarioValues(input.experimentId) + : {}, }, }); }), diff --git a/src/server/utils/openai.ts b/src/server/utils/openai.ts new file mode 100644 index 0000000..fb507fa --- /dev/null +++ b/src/server/utils/openai.ts @@ -0,0 +1,8 @@ +import { Configuration, OpenAIApi } from "openai"; +import { env } from "~/env.mjs"; + +const configuration = new Configuration({ + apiKey: env.OPENAI_API_KEY, +}); + +export const openai = new OpenAIApi(configuration);