diff --git a/.flox/env/manifest.lock b/.flox/env/manifest.lock index eb5c6199..fdf4acca 100644 --- a/.flox/env/manifest.lock +++ b/.flox/env/manifest.lock @@ -439,4 +439,4 @@ "priority": 5 } ] -} \ No newline at end of file +} diff --git a/package.json b/package.json index 231003c9..df9cd399 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@ai-sdk/anthropic": "^3.0.29", + "@ai-sdk/react": "^3.0.65", "@aws-sdk/client-s3": "^3.956.0", "@aws-sdk/s3-request-presigner": "^3.956.0", "@dagrejs/dagre": "^1.1.5", @@ -44,6 +46,7 @@ "@tanstack/react-table": "^8.21.3", "@tanstack/router-plugin": "^1.139.10", "@xyflow/react": "^12.9.1", + "ai": "^6.0.59", "better-auth": "^1.4.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4a2812a..f786b723 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@ai-sdk/anthropic': + specifier: ^3.0.29 + version: 3.0.29(zod@4.1.13) + '@ai-sdk/react': + specifier: ^3.0.65 + version: 3.0.65(react@19.2.1)(zod@4.1.13) '@aws-sdk/client-s3': specifier: ^3.956.0 version: 3.956.0 @@ -89,6 +95,9 @@ importers: '@xyflow/react': specifier: ^12.9.1 version: 12.9.1(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + ai: + specifier: ^6.0.59 + version: 6.0.59(zod@4.1.13) better-auth: specifier: ^1.4.5 version: 1.4.5(@tanstack/react-start@1.139.14(crossws@0.4.1(srvx@0.8.16))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(vite@7.1.12(@types/node@22.18.13)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -228,6 +237,50 @@ importers: packages: + '@ai-sdk/anthropic@3.0.29': + resolution: {integrity: sha512-Yo+LG0WZuv2QXgpeh95zDeFBy/D02yEgWFCLYE1y72XZzuZ6M/g8tcMyTFOVyfM4yULRE7wShIq2VUYTwi1ZKw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/gateway@3.0.27': + resolution: {integrity: sha512-Pr+ApS9k6/jcR3kNltJNxo60OdYvnVU4DeRhzVtxUAYTXCHx4qO+qTMG9nNRn+El1acJnNRA//Su47srjXkT/w==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/gateway@3.0.29': + resolution: {integrity: sha512-zf6yXT+7DcVGWG7ntxVCYC48X/opsWlO5ePvgH8W9DaEVUtkemqKUEzBqowQ778PkZo8sqMnRfD0+fi9HamRRQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.10': + resolution: {integrity: sha512-VeDAiCH+ZK8Xs4hb9Cw7pHlujWNL52RKe8TExOkrw6Ir1AmfajBZTb9XUdKOZO08RwQElIKA8+Ltm+Gqfo8djQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.11': + resolution: {integrity: sha512-y/WOPpcZaBjvNaogy83mBsCRPvbtaK0y1sY9ckRrrbTGMvG2HC/9Y/huqNXKnLAxUIME2PGa2uvF2CDwIsxoXQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@3.0.5': + resolution: {integrity: sha512-2Xmoq6DBJqmSl80U6V9z5jJSJP7ehaJJQMy2iFUqTay06wdCqTnPVBBQbtEL8RCChenL+q5DC5H5WzU3vV3v8w==} + engines: {node: '>=18'} + + '@ai-sdk/provider@3.0.6': + resolution: {integrity: sha512-hSfoJtLtpMd7YxKM+iTqlJ0ZB+kJ83WESMiWuWrNVey3X8gg97x0OdAAaeAeclZByCX3UdPOTqhvJdK8qYA3ww==} + engines: {node: '>=18'} + + '@ai-sdk/react@3.0.65': + resolution: {integrity: sha512-gUb3BWqsPoJjNkpk1ihkw3jK53M/ml4kGIgcDkBOARg/lDzvjBwn5T6zwjlo9V8YBEKZBfrMVCNUB0Ur87WwKg==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 + '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -906,78 +959,92 @@ packages: resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.3': resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.3': resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.3': resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.3': resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.3': resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.3': resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.4': resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.4': resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.4': resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.4': resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.4': resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.4': resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.4': resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.4': resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==} @@ -1313,36 +1380,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-wasm@2.5.1': resolution: {integrity: sha512-RJxlQQLkaMMIuWRozy+z2vEqbaQlCuaCgVZIUCzQLYggY22LZbP5Y1+ia+FD724Ids9e+XIyOLXLrLgQSHIthw==} @@ -1856,56 +1929,67 @@ packages: resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.52.5': resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.52.5': resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.52.5': resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.52.5': resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.52.5': resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.52.5': resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.52.5': resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.52.5': resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.52.5': resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.52.5': resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.52.5': resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} @@ -2157,6 +2241,9 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@stylistic/eslint-plugin@5.5.0': resolution: {integrity: sha512-IeZF+8H0ns6prg4VrkhgL+yrvDXWDH2cKchrbh80ejG9dQgZWp10epHMbgRuQvgchLII/lfh6Xn3lu6+6L86Hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2215,24 +2302,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.16': resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.16': resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.16': resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.16': resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==} @@ -2679,41 +2770,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -2740,6 +2839,10 @@ packages: engines: {node: '>=18'} hasBin: true + '@vercel/oidc@3.1.0': + resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} + engines: {node: '>= 20'} + '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2854,6 +2957,18 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ai@6.0.59: + resolution: {integrity: sha512-9SfCvcr4kVk4t8ZzIuyHpuL1hFYKsYMQfBSbBq3dipXPa+MphARvI8wHEjNaRqYl3JOsJbWxEBIMqHL0L92mUA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ai@6.0.63: + resolution: {integrity: sha512-kTGUTm5FZoWZBBBaKd5LAEJ5y5OFiGxThKX8ZW1jhXBuhiR5FQVtKGXv8fFJOq/6sLhsgu+zZGvU+3oByRQdEg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + ajv-errors@3.0.0: resolution: {integrity: sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==} peerDependencies: @@ -3779,6 +3894,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -4319,6 +4438,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -4411,24 +4533,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -5573,6 +5699,11 @@ packages: svix@1.76.1: resolution: {integrity: sha512-CRuDWBTgYfDnBLRaZdKp9VuoPcNUq9An14c/k+4YJ15Qc5Grvf66vp0jvTltd4t7OIRj+8lM1DAgvSgvf7hdLw==} + swr@2.3.8: + resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -5608,6 +5739,10 @@ packages: text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -6228,6 +6363,58 @@ packages: snapshots: + '@ai-sdk/anthropic@3.0.29(zod@4.1.13)': + dependencies: + '@ai-sdk/provider': 3.0.5 + '@ai-sdk/provider-utils': 4.0.10(zod@4.1.13) + zod: 4.1.13 + + '@ai-sdk/gateway@3.0.27(zod@4.1.13)': + dependencies: + '@ai-sdk/provider': 3.0.5 + '@ai-sdk/provider-utils': 4.0.10(zod@4.1.13) + '@vercel/oidc': 3.1.0 + zod: 4.1.13 + + '@ai-sdk/gateway@3.0.29(zod@4.1.13)': + dependencies: + '@ai-sdk/provider': 3.0.6 + '@ai-sdk/provider-utils': 4.0.11(zod@4.1.13) + '@vercel/oidc': 3.1.0 + zod: 4.1.13 + + '@ai-sdk/provider-utils@4.0.10(zod@4.1.13)': + dependencies: + '@ai-sdk/provider': 3.0.5 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 4.1.13 + + '@ai-sdk/provider-utils@4.0.11(zod@4.1.13)': + dependencies: + '@ai-sdk/provider': 3.0.6 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 4.1.13 + + '@ai-sdk/provider@3.0.5': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/provider@3.0.6': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@3.0.65(react@19.2.1)(zod@4.1.13)': + dependencies: + '@ai-sdk/provider-utils': 4.0.11(zod@4.1.13) + ai: 6.0.63(zod@4.1.13) + react: 19.2.1 + swr: 2.3.8(react@19.2.1) + throttleit: 2.1.0 + transitivePeerDependencies: + - zod + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -8751,6 +8938,8 @@ snapshots: '@standard-schema/spec@1.0.0': {} + '@standard-schema/spec@1.1.0': {} + '@stylistic/eslint-plugin@5.5.0(eslint@9.36.0(jiti@2.6.1))': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.1)) @@ -9446,6 +9635,8 @@ snapshots: - rollup - supports-color + '@vercel/oidc@3.1.0': {} + '@vitejs/plugin-react@4.7.0(vite@7.1.12(@types/node@22.18.13)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.5 @@ -9606,6 +9797,22 @@ snapshots: agent-base@7.1.4: {} + ai@6.0.59(zod@4.1.13): + dependencies: + '@ai-sdk/gateway': 3.0.27(zod@4.1.13) + '@ai-sdk/provider': 3.0.5 + '@ai-sdk/provider-utils': 4.0.10(zod@4.1.13) + '@opentelemetry/api': 1.9.0 + zod: 4.1.13 + + ai@6.0.63(zod@4.1.13): + dependencies: + '@ai-sdk/gateway': 3.0.29(zod@4.1.13) + '@ai-sdk/provider': 3.0.6 + '@ai-sdk/provider-utils': 4.0.11(zod@4.1.13) + '@opentelemetry/api': 1.9.0 + zod: 4.1.13 + ajv-errors@3.0.0(ajv@8.17.1): dependencies: ajv: 8.17.1 @@ -10303,7 +10510,7 @@ snapshots: effect@3.18.4: dependencies: - '@standard-schema/spec': 1.0.0 + '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 electron-to-chromium@1.5.243: {} @@ -10538,6 +10745,8 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.6: {} + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -11079,6 +11288,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} @@ -12481,6 +12692,12 @@ snapshots: url-parse: 1.5.10 uuid: 10.0.0 + swr@2.3.8(react@19.2.1): + dependencies: + dequal: 2.0.3 + react: 19.2.1 + use-sync-external-store: 1.6.0(react@19.2.1) + symbol-tree@3.2.4: {} system-architecture@0.1.0: {} @@ -12524,6 +12741,8 @@ snapshots: text-hex@1.0.0: {} + throttleit@2.1.0: {} + tiny-invariant@1.3.3: {} tiny-warning@1.0.3: {} diff --git a/prisma/migrations/20260130235612_make_agent_optional/migration.sql b/prisma/migrations/20260130235612_make_agent_optional/migration.sql new file mode 100644 index 00000000..b2edac9a --- /dev/null +++ b/prisma/migrations/20260130235612_make_agent_optional/migration.sql @@ -0,0 +1,91 @@ +-- CreateEnum +CREATE TYPE "AgentMessageRole" AS ENUM ('user', 'assistant', 'system', 'tool'); + +-- AlterEnum +ALTER TYPE "AuditLogEntityType" ADD VALUE 'AGENT_ACTION'; + +-- CreateTable +CREATE TABLE "Agent" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "description" TEXT, + "systemPrompt" TEXT NOT NULL, + "initialPrompt" TEXT, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdById" TEXT NOT NULL, + + CONSTRAINT "Agent_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AgentTool" ( + "id" TEXT NOT NULL, + "agentId" TEXT NOT NULL, + "toolName" TEXT NOT NULL, + "isEnabled" BOOLEAN NOT NULL DEFAULT true, + "config" JSONB, + + CONSTRAINT "AgentTool_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AgentConversation" ( + "id" TEXT NOT NULL, + "agentId" TEXT, + "userId" TEXT NOT NULL, + "title" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "AgentConversation_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AgentMessage" ( + "id" TEXT NOT NULL, + "conversationId" TEXT NOT NULL, + "role" "AgentMessageRole" NOT NULL, + "content" TEXT NOT NULL, + "toolCalls" JSONB, + "toolResults" JSONB, + "metadata" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "AgentMessage_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Agent_slug_key" ON "Agent"("slug"); + +-- CreateIndex +CREATE INDEX "AgentTool_agentId_idx" ON "AgentTool"("agentId"); + +-- CreateIndex +CREATE UNIQUE INDEX "AgentTool_agentId_toolName_key" ON "AgentTool"("agentId", "toolName"); + +-- CreateIndex +CREATE INDEX "AgentConversation_agentId_idx" ON "AgentConversation"("agentId"); + +-- CreateIndex +CREATE INDEX "AgentConversation_userId_idx" ON "AgentConversation"("userId"); + +-- CreateIndex +CREATE INDEX "AgentMessage_conversationId_idx" ON "AgentMessage"("conversationId"); + +-- AddForeignKey +ALTER TABLE "Agent" ADD CONSTRAINT "Agent_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AgentTool" ADD CONSTRAINT "AgentTool_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agent"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AgentConversation" ADD CONSTRAINT "AgentConversation_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agent"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AgentConversation" ADD CONSTRAINT "AgentConversation_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AgentMessage" ADD CONSTRAINT "AgentMessage_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "AgentConversation"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20260204232717_remove_agent_models/migration.sql b/prisma/migrations/20260204232717_remove_agent_models/migration.sql new file mode 100644 index 00000000..02de77fd --- /dev/null +++ b/prisma/migrations/20260204232717_remove_agent_models/migration.sql @@ -0,0 +1,28 @@ +/* + Warnings: + + - You are about to drop the column `agentId` on the `AgentConversation` table. All the data in the column will be lost. + - You are about to drop the `Agent` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `AgentTool` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "public"."Agent" DROP CONSTRAINT "Agent_createdById_fkey"; + +-- DropForeignKey +ALTER TABLE "public"."AgentConversation" DROP CONSTRAINT "AgentConversation_agentId_fkey"; + +-- DropForeignKey +ALTER TABLE "public"."AgentTool" DROP CONSTRAINT "AgentTool_agentId_fkey"; + +-- DropIndex +DROP INDEX "public"."AgentConversation_agentId_idx"; + +-- AlterTable +ALTER TABLE "AgentConversation" DROP COLUMN "agentId"; + +-- DropTable +DROP TABLE "public"."Agent"; + +-- DropTable +DROP TABLE "public"."AgentTool"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9bac21e1..e69f301b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -183,6 +183,7 @@ model User { filesUploaded File[] @relation("FileUploadedBy") feedbackGiven PerformanceProgramFeedback[] @relation("FeedbackGivenBy") auditLogsCreated AuditLog[] @relation("AuditLogActor") + agentConversations AgentConversation[] @relation("UserConversations") @@unique([email]) @@map("user") @@ -368,6 +369,7 @@ enum AuditLogEntityType { USER_ROLE MANAGER TEAM + AGENT_ACTION } model AuditLog { @@ -401,3 +403,37 @@ model CartaOptionGrant { vestedQuantity Int expiredQuantity Int } + +// AI Agents +model AgentConversation { + id String @id @default(cuid()) + userId String + user User @relation("UserConversations", fields: [userId], references: [id]) + title String? + messages AgentMessage[] + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + @@index([userId]) +} + +model AgentMessage { + id String @id @default(cuid()) + conversationId String + conversation AgentConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) + role AgentMessageRole + content String @db.Text + toolCalls Json? + toolResults Json? + metadata Json? + createdAt DateTime @default(now()) + + @@index([conversationId]) +} + +enum AgentMessageRole { + user + assistant + system + tool +} diff --git a/src/components/agents/AgentChat.tsx b/src/components/agents/AgentChat.tsx new file mode 100644 index 00000000..8f38e057 --- /dev/null +++ b/src/components/agents/AgentChat.tsx @@ -0,0 +1,154 @@ +import { useChat } from '@ai-sdk/react' +import { DefaultChatTransport } from 'ai' +import { useState, useRef, useEffect, type ChangeEvent, useMemo } from 'react' +import { useQuery, useQueryClient } from '@tanstack/react-query' +import { AgentChatMessages } from './AgentChatMessages' +import { AgentChatInput } from './AgentChatInput' +import { getConversationMessages } from '@/lib/agents/server-functions' + +interface AgentConfig { + id: string + name: string + slug: string + description?: string + systemPrompt: string + initialPrompt?: string +} + +interface AgentChatProps { + agent: AgentConfig + conversationId?: string + onConversationCreated?: (id: string) => void +} + +export function AgentChat({ + agent, + conversationId: propConversationId, +}: AgentChatProps) { + const queryClient = useQueryClient() + const [conversationId, setConversationId] = useState(propConversationId) + + // Sync conversationId from props + useEffect(() => { + if (propConversationId !== conversationId) { + setConversationId(propConversationId) + } + }, [propConversationId]) + const [input, setInput] = useState('') + + const messagesEndRef = useRef(null) + + // Load existing messages + const { data: conversation } = useQuery({ + queryKey: ['agentConversation', conversationId], + queryFn: () => + getConversationMessages({ data: { conversationId: conversationId! } }), + enabled: !!conversationId, + }) + + // Create transport instance - always create it since we always have conversationId now + const transport = useMemo( + () => + new DefaultChatTransport({ + api: '/api/agents/chat', + prepareSendMessagesRequest: async (options) => { + return { + ...options, + body: { + messages: options.messages, + conversationId, + }, + } + }, + }), + [conversationId], + ) + + // AI SDK useChat hook + const chatHook = useChat({ + transport, + onFinish: () => { + queryClient.invalidateQueries({ queryKey: ['agentConversations'] }) + }, + }) + + const messages = chatHook.messages + const sendMessage = chatHook.sendMessage + const setMessages = chatHook.setMessages + const error = chatHook.error + const status = chatHook.status + const isLoading = status === 'streaming' + + const handleInputChange = ( + e: ChangeEvent | ChangeEvent, + ) => { + setInput(e.target.value) + } + + // Sync messages from server when conversation loads + useEffect(() => { + if (conversation?.messages && setMessages) { + setMessages( + conversation.messages.map((m) => ({ + id: m.id, + role: m.role as 'user' | 'assistant', + parts: [{ type: 'text' as const, text: m.content }], + })), + ) + } + }, [conversation?.messages, setMessages]) + + // Auto-scroll to bottom + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) + }, [messages]) + + const handleSend = async (e: React.FormEvent) => { + e.preventDefault() + + if (!input.trim() || !conversationId) return + + sendMessage?.({ text: input }) + setInput('') + } + + return ( +
+ {/* Header */} +
+

{agent.name}

+ {agent.description && ( +

{agent.description}

+ )} +
+ + {/* Messages */} +
+ {agent.initialPrompt && messages.length === 0 && ( +
+ {agent.initialPrompt} +
+ )} + +
+
+ + {/* Input */} + + + {/* Error display */} + {error && ( +
+ {error.message} +
+ )} +
+ ) +} diff --git a/src/components/agents/AgentChatInput.tsx b/src/components/agents/AgentChatInput.tsx new file mode 100644 index 00000000..b98ad6cf --- /dev/null +++ b/src/components/agents/AgentChatInput.tsx @@ -0,0 +1,68 @@ +import { Button } from '@/components/ui/button' +import { Send } from 'lucide-react' +import { useEffect, useRef, type ChangeEvent, FormEvent } from 'react' + +interface AgentChatInputProps { + input?: string + onChange: (e: ChangeEvent | ChangeEvent) => void + onSubmit: (e: FormEvent) => void + isLoading: boolean + disabled?: boolean + placeholder?: string +} + +export function AgentChatInput({ + input = '', + onChange, + onSubmit, + isLoading, + disabled, + placeholder = 'Type your message…', +}: AgentChatInputProps) { + const textareaRef = useRef(null) + + useEffect(() => { + // Autofocus the input when component mounts or when it becomes enabled + if (!disabled && !isLoading) { + textareaRef.current?.focus() + } + }, [disabled, isLoading]) + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + if (input?.trim() && !isLoading && !disabled) { + onSubmit(e as unknown as FormEvent) + } + } + } + + return ( +
+
+