diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..bcae28a
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,3 @@
+OPENAI_API_KEY=""
+UPSTASH_REDIS_REST_URL=""
+UPSTASH_REDIS_REST_TOKEN=""
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 327c497..17883c0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,9 +8,15 @@
"name": "nicholasly.com",
"version": "0.1.0",
"dependencies": {
+ "@ai-sdk/openai": "^0.0.39",
+ "@upstash/ratelimit": "^2.0.1",
+ "@upstash/redis": "^1.33.0",
+ "ai": "^3.2.35",
+ "lucide-react": "^0.414.0",
"next": "14.2.5",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "sonner": "^1.5.0"
},
"devDependencies": {
"@types/node": "^20",
@@ -25,6 +31,186 @@
"typescript": "^5"
}
},
+ "node_modules/@ai-sdk/openai": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-0.0.39.tgz",
+ "integrity": "sha512-UNkT7394/aG7s2VRY53mRqKlv2tBBuRGvIOuqMcpgfVquf+NRYWsDWAYM4UxqcK5uigSKk4uV/lIJX+fRUXszg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/provider": "0.0.13",
+ "@ai-sdk/provider-utils": "1.0.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "zod": "^3.0.0"
+ }
+ },
+ "node_modules/@ai-sdk/provider": {
+ "version": "0.0.13",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.13.tgz",
+ "integrity": "sha512-RBstpG/3RqVBBJgTvjZpou/+1fNQMDIwB9enqQPXqr0MoCrJHscLD2Zsfr6cKM5HFfY1D4KhdSPTycCDebVGlQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "json-schema": "0.4.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@ai-sdk/provider-utils": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.4.tgz",
+ "integrity": "sha512-2TldHn54+HrvVHMCos9lrSMcDIp3cgnpphpYT/95RXcIzcGwAFM5MioDWnsrki3noplU5pVld6ylD3Vu/Xt3Vw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/provider": "0.0.13",
+ "eventsource-parser": "1.1.2",
+ "nanoid": "3.3.6",
+ "secure-json-parse": "2.7.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "zod": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/provider-utils/node_modules/nanoid": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/@ai-sdk/react": {
+ "version": "0.0.28",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-0.0.28.tgz",
+ "integrity": "sha512-wOYtnXKGWjgO8jEX059GFAa8Jys2vrA8CUKxvfO1dD3GTyb7oiRZ9eVjtGEM5wQ2fepX2xIQj8oDxJh0EjKiNA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/provider-utils": "1.0.4",
+ "@ai-sdk/ui-utils": "0.0.19",
+ "swr": "2.2.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19",
+ "zod": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/solid": {
+ "version": "0.0.21",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/solid/-/solid-0.0.21.tgz",
+ "integrity": "sha512-5uF4AGy2jk9j8sMu2cebfWregaXiebV1N/l/5wGW01Y+7NvGNHUuVC/aSVcF5BvXGZiKjwkVvoU7h5VQIq+IZw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/ui-utils": "0.0.19"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "solid-js": "^1.7.7"
+ },
+ "peerDependenciesMeta": {
+ "solid-js": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/svelte": {
+ "version": "0.0.22",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/svelte/-/svelte-0.0.22.tgz",
+ "integrity": "sha512-ij5rNA+QSSstcOit+p5EWvyujz368u73K6QrwCpMPMEUNoyxJbazVdU79PlMviFHRsrjA9I7WmEDIXH/YBKZCQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/provider-utils": "1.0.4",
+ "@ai-sdk/ui-utils": "0.0.19",
+ "sswr": "2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "svelte": "^3.0.0 || ^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/ui-utils": {
+ "version": "0.0.19",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.19.tgz",
+ "integrity": "sha512-nLCUNoQ4hY+v/5pMLz7mIe8ZUW6tR644Zaqh9gg8ScKVfc3EMvyxJ/AoAjcgD00iXOmbT1/xTsF6TkXRGExZ1Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/provider-utils": "1.0.4",
+ "secure-json-parse": "2.7.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "zod": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/vue": {
+ "version": "0.0.23",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-0.0.23.tgz",
+ "integrity": "sha512-01zCo6mJJSPwDYjjUvSqWebGAjqUNOb6qzd/ZVPB77mW2XEYVXxBoOSBv5ARPIH3cRwovj7YQVvgh9oKGgE+3A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/provider-utils": "1.0.4",
+ "@ai-sdk/ui-utils": "0.0.19",
+ "swrv": "1.0.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "vue": "^3.3.4"
+ },
+ "peerDependenciesMeta": {
+ "vue": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -38,6 +224,33 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.24.8",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz",
+ "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==",
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -187,7 +400,6 @@
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
@@ -202,7 +414,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -212,7 +423,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -222,14 +432,12 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -434,6 +642,15 @@
"node": ">= 8"
}
},
+ "node_modules/@opentelemetry/api": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
+ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -468,6 +685,19 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/diff-match-patch": {
+ "version": "1.0.36",
+ "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
+ "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -654,11 +884,163 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/@upstash/core-analytics": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/@upstash/core-analytics/-/core-analytics-0.0.10.tgz",
+ "integrity": "sha512-7qJHGxpQgQr9/vmeS1PktEwvNAF7TI4iJDi8Pu2CFZ9YUGHZH4fOP5TfYlZ4aVxfopnELiE4BS4FBjyK7V1/xQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@upstash/redis": "^1.28.3"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@upstash/ratelimit": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@upstash/ratelimit/-/ratelimit-2.0.1.tgz",
+ "integrity": "sha512-J+0hlkvWUjlVrjcBQhWx7gbaUGsvFF59i+GAx7YQk8L0E0MQ93xzCPu02uaXhGDJGkxiar7nRRPqj3hs+CdAJg==",
+ "license": "MIT",
+ "dependencies": {
+ "@upstash/core-analytics": "^0.0.10"
+ }
+ },
+ "node_modules/@upstash/redis": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.33.0.tgz",
+ "integrity": "sha512-5WOilc7AE0ITAdE3NCyMwgOq1n3RHcqW0OfmbotiAyfA+QAEe1R7kXin8L/Yladgdc5lkA0GcYyewqKfAw53jQ==",
+ "license": "MIT",
+ "dependencies": {
+ "crypto-js": "^4.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.34.tgz",
+ "integrity": "sha512-Z0izUf32+wAnQewjHu+pQf1yw00EGOmevl1kE+ljjjMe7oEfpQ+BI3/JNK7yMB4IrUsqLDmPecUrpj3mCP+yJQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/parser": "^7.24.7",
+ "@vue/shared": "3.4.34",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-core/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.34.tgz",
+ "integrity": "sha512-3PUOTS1h5cskdOJMExCu2TInXuM0j60DRPpSCJDqOCupCfUZCJoyQmKtRmA8EgDNZ5kcEE7vketamRZfrEuVDw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-core": "3.4.34",
+ "@vue/shared": "3.4.34"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.34.tgz",
+ "integrity": "sha512-x6lm0UrM03jjDXTPZgD9Ad8bIVD1ifWNit2EaWQIZB5CULr46+FbLQ5RpK7AXtDHGjx9rmvC7QRCTjsiGkAwRw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/parser": "^7.24.7",
+ "@vue/compiler-core": "3.4.34",
+ "@vue/compiler-dom": "3.4.34",
+ "@vue/compiler-ssr": "3.4.34",
+ "@vue/shared": "3.4.34",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.10",
+ "postcss": "^8.4.39",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-sfc/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.34.tgz",
+ "integrity": "sha512-8TDBcLaTrFm5rnF+Qm4BlliaopJgqJ28Nsrc80qazynm5aJO+Emu7y0RWw34L8dNnTRdcVBpWzJxhGYzsoVu4g==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-dom": "3.4.34",
+ "@vue/shared": "3.4.34"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.34.tgz",
+ "integrity": "sha512-ua+Lo+wBRlBEX9TtgPOShE2JwIO7p6BTZ7t1KZVPoaBRfqbC7N3c8Mpzicx173fXxx5VXeU6ykiHo7WgLzJQDA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vue/shared": "3.4.34"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.34.tgz",
+ "integrity": "sha512-PXhkiRPwcPGJ1BnyBZFI96GfInCVskd0HPNIAZn7i3YOmLbtbTZpB7/kDTwC1W7IqdGPkTVC63IS7J2nZs4Ebg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vue/reactivity": "3.4.34",
+ "@vue/shared": "3.4.34"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.34.tgz",
+ "integrity": "sha512-dXqIe+RqFAK2Euak4UsvbIupalrhc67OuQKpD7HJ3W2fv8jlqvI7szfBCsAEcE8o/wyNpkloxB6J8viuF/E3gw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vue/reactivity": "3.4.34",
+ "@vue/runtime-core": "3.4.34",
+ "@vue/shared": "3.4.34",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.34.tgz",
+ "integrity": "sha512-GeyEUfMVRZMD/mZcNONEqg7MiU10QQ1DB3O/Qr6+8uXpbwdlmVgQ5Qs1/ZUAFX1X2UUtqMoGrDRbxdWfOJFT7Q==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-ssr": "3.4.34",
+ "@vue/shared": "3.4.34"
+ },
+ "peerDependencies": {
+ "vue": "3.4.34"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.34.tgz",
+ "integrity": "sha512-x5LmiRLpRsd9KTjAB8MPKf0CDPMcuItjP0gbNqFCIgL1I8iYp4zglhj9w9FPCdIbHG2M91RVeIbArFfFTz9I3A==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
- "dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -677,6 +1059,73 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/ai": {
+ "version": "3.2.35",
+ "resolved": "https://registry.npmjs.org/ai/-/ai-3.2.35.tgz",
+ "integrity": "sha512-H4/gAuum6kL4NP22ZoBPKzp9/uCLUUW6r6L0+ll1WihgbNBUhH2ahxDG+y4p7H7DJOEoUwHlqQ9u/1evPxXtDQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/provider": "0.0.13",
+ "@ai-sdk/provider-utils": "1.0.4",
+ "@ai-sdk/react": "0.0.28",
+ "@ai-sdk/solid": "0.0.21",
+ "@ai-sdk/svelte": "0.0.22",
+ "@ai-sdk/ui-utils": "0.0.19",
+ "@ai-sdk/vue": "0.0.23",
+ "@opentelemetry/api": "1.9.0",
+ "eventsource-parser": "1.1.2",
+ "json-schema": "0.4.0",
+ "jsondiffpatch": "0.6.0",
+ "nanoid": "3.3.6",
+ "secure-json-parse": "2.7.0",
+ "zod-to-json-schema": "3.22.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "openai": "^4.42.0",
+ "react": "^18 || ^19",
+ "sswr": "^2.1.0",
+ "svelte": "^3.0.0 || ^4.0.0",
+ "zod": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "openai": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "sswr": {
+ "optional": true
+ },
+ "svelte": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ai/node_modules/nanoid": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -1165,6 +1614,20 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/code-red": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
+ "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@types/estree": "^1.0.1",
+ "acorn": "^8.10.0",
+ "estree-walker": "^3.0.3",
+ "periscopic": "^3.1.0"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1217,6 +1680,26 @@
"node": ">= 8"
}
},
+ "node_modules/crypto-js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+ "license": "MIT"
+ },
+ "node_modules/css-tree": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "mdn-data": "2.0.30",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -1234,7 +1717,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
@@ -1392,6 +1874,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -1399,6 +1891,12 @@
"dev": true,
"license": "Apache-2.0"
},
+ "node_modules/diff-match-patch": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
+ "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
+ "license": "Apache-2.0"
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -1460,6 +1958,19 @@
"node": ">=10.13.0"
}
},
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/es-abstract": {
"version": "1.23.3",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
@@ -2087,6 +2598,16 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -2097,6 +2618,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/eventsource-parser": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
+ "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.18"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2941,6 +3471,16 @@
"node": ">=8"
}
},
+ "node_modules/is-reference": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
+ "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
"node_modules/is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -3161,6 +3701,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json-schema": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
+ "license": "(AFL-2.1 OR BSD-3-Clause)"
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -3188,6 +3734,35 @@
"json5": "lib/cli.js"
}
},
+ "node_modules/jsondiffpatch": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
+ "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/diff-match-patch": "^1.0.36",
+ "chalk": "^5.3.0",
+ "diff-match-patch": "^1.0.5"
+ },
+ "bin": {
+ "jsondiffpatch": "bin/jsondiffpatch.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/jsondiffpatch/node_modules/chalk": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -3265,6 +3840,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/locate-character": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
+ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -3307,6 +3889,32 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/lucide-react": {
+ "version": "0.414.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.414.0.tgz",
+ "integrity": "sha512-Krr/MHg9AWoJc52qx8hyJ64X9++JNfS1wjaJviLM1EP/68VNB7Tv0VMldLCB1aUe6Ka9QxURPhQm/eB6cqOM3A==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.10",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
+ "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ }
+ },
+ "node_modules/mdn-data": {
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "license": "CC0-1.0",
+ "peer": true
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -3779,6 +4387,18 @@
"node": ">=8"
}
},
+ "node_modules/periscopic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
+ "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^3.0.0",
+ "is-reference": "^3.0.0"
+ }
+ },
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
@@ -3832,7 +4452,6 @@
"version": "8.4.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -4383,6 +5002,12 @@
"loose-envify": "^1.1.0"
}
},
+ "node_modules/secure-json-parse": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
+ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
@@ -4495,6 +5120,16 @@
"node": ">=8"
}
},
+ "node_modules/sonner": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.5.0.tgz",
+ "integrity": "sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
@@ -4504,6 +5139,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/sswr": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/sswr/-/sswr-2.1.0.tgz",
+ "integrity": "sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "swrev": "^4.0.0"
+ },
+ "peerDependencies": {
+ "svelte": "^4.0.0 || ^5.0.0-next.0"
+ }
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
@@ -4818,6 +5465,80 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/svelte": {
+ "version": "4.2.18",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz",
+ "integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/estree": "^1.0.1",
+ "acorn": "^8.9.0",
+ "aria-query": "^5.3.0",
+ "axobject-query": "^4.0.0",
+ "code-red": "^1.0.3",
+ "css-tree": "^2.3.1",
+ "estree-walker": "^3.0.3",
+ "is-reference": "^3.0.1",
+ "locate-character": "^3.0.0",
+ "magic-string": "^0.30.4",
+ "periscopic": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/svelte/node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/svelte/node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/swr": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz",
+ "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==",
+ "license": "MIT",
+ "dependencies": {
+ "client-only": "^0.0.1",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "peerDependencies": {
+ "react": "^16.11.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/swrev": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/swrev/-/swrev-4.0.0.tgz",
+ "integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==",
+ "license": "MIT"
+ },
+ "node_modules/swrv": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.0.4.tgz",
+ "integrity": "sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "vue": ">=3.2.26 < 4"
+ }
+ },
"node_modules/tailwindcss": {
"version": "3.4.6",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz",
@@ -5055,7 +5776,7 @@
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
- "dev": true,
+ "devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -5098,6 +5819,15 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
+ "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -5105,6 +5835,28 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/vue": {
+ "version": "3.4.34",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.34.tgz",
+ "integrity": "sha512-VZze05HWlA3ItreQ/ka7Sx7PoD0/3St8FEiSlSTVgb6l4hL+RjtP2/8g5WQBzZgyf8WG2f+g1bXzC7zggLhAJA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-dom": "3.4.34",
+ "@vue/compiler-sfc": "3.4.34",
+ "@vue/runtime-dom": "3.4.34",
+ "@vue/server-renderer": "3.4.34",
+ "@vue/shared": "3.4.34"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -5347,6 +6099,25 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "3.23.8",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
+ "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.22.5",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.22.5.tgz",
+ "integrity": "sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.22.4"
+ }
}
}
}
diff --git a/package.json b/package.json
index 3219837..331f8f0 100644
--- a/package.json
+++ b/package.json
@@ -11,9 +11,15 @@
"format": "npx prettier . --write"
},
"dependencies": {
+ "@ai-sdk/openai": "^0.0.39",
+ "@upstash/ratelimit": "^2.0.1",
+ "@upstash/redis": "^1.33.0",
+ "ai": "^3.2.35",
+ "lucide-react": "^0.414.0",
"next": "14.2.5",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "sonner": "^1.5.0"
},
"devDependencies": {
"@types/node": "^20",
diff --git a/public/favicon.ico b/public/favicon.ico
index 718d6fe..c25c8a3 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ
diff --git a/public/memoji.png b/public/memoji.png
new file mode 100644
index 0000000..2e19a36
Binary files /dev/null and b/public/memoji.png differ
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index e0c86a4..f9671aa 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,7 @@
import "@/styles/globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
+import { Toaster } from "sonner";
const inter = Inter({
subsets: ["latin"],
@@ -8,8 +9,9 @@ const inter = Inter({
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "Nicholas Ly",
+ description:
+ "Get to know Nicholas Ly and his work by chatting with his AI assistant!",
icons: [{ rel: "icon", url: "/favicon.ico" }],
};
@@ -20,6 +22,7 @@ export default function RootLayout({
}>) {
return (
+
{children}
);
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 41237d8..cb71211 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,7 +1,9 @@
+import Chat from "@/components/chat";
+
export default function Home() {
return (
-
- Hello world!
+
+
);
}
diff --git a/src/components/chat.tsx b/src/components/chat.tsx
new file mode 100644
index 0000000..d5a054e
--- /dev/null
+++ b/src/components/chat.tsx
@@ -0,0 +1,120 @@
+"use client";
+
+import { continueConversation } from "@/lib/ai";
+import { CoreMessage } from "ai";
+import { readStreamableValue } from "ai/rsc";
+import { ChangeEvent, FormEvent, useEffect, useRef, useState } from "react";
+import { toast } from "sonner";
+import MessageHeader from "./message-header";
+import MessageFeed from "./message-feed";
+import MessageForm from "./message-editor";
+
+export default function Chat() {
+ const [messages, setMessages] = useState([]);
+ const [input, setInput] = useState("");
+ const [loading, setLoading] = useState(false);
+ const scrollAnchor = useRef(null);
+
+ /**
+ * Updates the message editor `textarea` element height to fit the content of the element.
+ * If the `reset` option is set to `true`, the `textarea` content and user input state will be reset.
+ */
+ const resizeInput = ({ reset }: { reset: boolean }) => {
+ const editor = document.getElementById("editor") as HTMLTextAreaElement;
+
+ if (reset) {
+ setInput("");
+ editor.value = "";
+ }
+
+ editor.style.height = "auto";
+ editor.style.height = editor.scrollHeight + "px";
+ };
+
+ /**
+ * Updates the user input state and resizes the message editor.
+ * @param event A React `textarea` change event.
+ */
+ const handleChange = (event: ChangeEvent) => {
+ setInput(event.target.value);
+ resizeInput({ reset: false });
+ };
+
+ /**
+ * Form submission handler that resets the user input state and appends a user message to the conversation.
+ * @param event A React `form` event.
+ */
+ const handleSubmit = async (event?: FormEvent) => {
+ event?.preventDefault();
+
+ const newMessages: CoreMessage[] = [
+ ...messages,
+ { content: input, role: "user" },
+ ];
+
+ setLoading(true);
+ setMessages(newMessages);
+ resizeInput({ reset: true });
+
+ try {
+ const result = await continueConversation(newMessages);
+
+ for await (const content of readStreamableValue(result)) {
+ setMessages([
+ ...newMessages,
+ {
+ role: "assistant",
+ content: content as string,
+ },
+ ]);
+ }
+ } catch (error) {
+ handleMessageError(error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // Automatically scrolls message feed on changes so that new messages are visible.
+ useEffect(() => {
+ scrollAnchor.current?.scrollIntoView({ behavior: "smooth" });
+ }, [messages]);
+
+ return (
+
+ );
+}
+
+/**
+ * Displays a specific toast message for an expected error.
+ * @param error The error object caught by a `try-catch`.
+ */
+function handleMessageError(error: unknown) {
+ const isError = error instanceof Error;
+ if (!isError) throw error;
+
+ let message: string;
+ let description: string;
+
+ switch (error.message) {
+ case "TOO_MANY_REQUESTS":
+ message = "Rate limit exceeded!";
+ description = "Please wait a few moments then try again.";
+ break;
+ default:
+ message = "Uh oh!";
+ description = "Something went wrong!";
+ break;
+ }
+
+ toast.error(message, { description });
+}
diff --git a/src/components/message-editor.tsx b/src/components/message-editor.tsx
new file mode 100644
index 0000000..9f7181c
--- /dev/null
+++ b/src/components/message-editor.tsx
@@ -0,0 +1,54 @@
+import { ArrowUpIcon } from "lucide-react";
+import { ChangeEvent, FormEvent } from "react";
+
+interface MessageFormProps {
+ input: string;
+ handleChange: (event: ChangeEvent) => void;
+ handleSubmit: (event?: FormEvent) => Promise;
+ loading: boolean;
+}
+
+export default function MessageForm({
+ input,
+ handleChange,
+ handleSubmit,
+ loading,
+}: MessageFormProps) {
+ return (
+
+ );
+}
diff --git a/src/components/message-feed.tsx b/src/components/message-feed.tsx
new file mode 100644
index 0000000..93befcd
--- /dev/null
+++ b/src/components/message-feed.tsx
@@ -0,0 +1,49 @@
+import { CoreMessage } from "ai";
+import { AssistantMessage, UserMessage } from "./messages";
+import { RefObject } from "react";
+
+interface MessageFeedProps {
+ messages: CoreMessage[];
+ scrollAnchor?: RefObject;
+}
+
+export default function MessageFeed({
+ messages,
+ scrollAnchor,
+}: MessageFeedProps) {
+ return (
+
+
+
+
nicholasly.com
+
+ Today{" "}
+ {new Date().toLocaleTimeString([], {
+ hour: "numeric",
+ minute: "numeric",
+ })}
+
+
+
+ {messages.map((message, index) => {
+ switch (message.role) {
+ case "assistant":
+ return (
+
+ );
+ case "user":
+ return (
+
+ );
+ default:
+ return;
+ }
+ })}
+
+
+
+ );
+}
diff --git a/src/components/message-header.tsx b/src/components/message-header.tsx
new file mode 100644
index 0000000..a17f1c2
--- /dev/null
+++ b/src/components/message-header.tsx
@@ -0,0 +1,19 @@
+import Image from "next/image";
+
+export default function MessageHeader() {
+ return (
+
+
+
+
+ nick's assistant 🤖
+
+ );
+}
diff --git a/src/components/messages.tsx b/src/components/messages.tsx
new file mode 100644
index 0000000..96854aa
--- /dev/null
+++ b/src/components/messages.tsx
@@ -0,0 +1,19 @@
+interface MessageProps {
+ content: string;
+}
+
+export function UserMessage({ content }: MessageProps) {
+ return (
+
+ {content}
+
+ );
+}
+
+export function AssistantMessage({ content }: MessageProps) {
+ return (
+
+ {content}
+
+ );
+}
diff --git a/src/lib/ai.ts b/src/lib/ai.ts
new file mode 100644
index 0000000..9831d77
--- /dev/null
+++ b/src/lib/ai.ts
@@ -0,0 +1,56 @@
+"use server";
+
+import { openai } from "@ai-sdk/openai";
+import { Ratelimit } from "@upstash/ratelimit";
+import { Redis } from "@upstash/redis";
+import { CoreMessage, streamText } from "ai";
+import { createStreamableValue } from "ai/rsc";
+import { headers } from "next/headers";
+
+const ratelimit = new Ratelimit({
+ redis: Redis.fromEnv(),
+ limiter: Ratelimit.slidingWindow(10, "30s"),
+ analytics: true,
+});
+
+export async function continueConversation(messages: CoreMessage[]) {
+ const ip = headers().get("x-forwarded-for") ?? "ip";
+ const { success } = await ratelimit.limit(ip);
+ if (!success) throw new Error("TOO_MANY_REQUESTS");
+
+ const system = `
+ You are a helpful assistant working for Nicholas Ly.
+ Your job is be a resource for other people to learn about and connect with Nicholas Ly.
+
+ You will be given background information on Nicholas Ly in XML tags.
+ Read the background information carefully, since the user may ask you questions about Nicholas Ly.
+
+ Try to only respond using relevant information from Nicholas Ly's background.
+ If you are unable to help the user with only the background information, please tell the user you are unable to help.
+
+
+ Nicholas Ly is a 23 year old web developer from Glendale Heights, Illinois.
+
+
+ Speak casually and friendly, doing your best to speak like a young adult.
+ Prefer short and direct responses unless the user specifically asks for more detail.
+
+ No yapping.
+ `;
+
+ const startTime = performance.now();
+
+ const result = await streamText({
+ model: openai("gpt-4o-mini"),
+ system,
+ messages,
+ maxTokens: 1000,
+ onFinish: () => {
+ const duration = Math.round(performance.now() - startTime) / 1000;
+ console.log(`Finished streaming AI response in ${duration} seconds.`);
+ },
+ });
+
+ const stream = createStreamableValue(result.textStream);
+ return stream.value;
+}
diff --git a/src/styles/globals.css b/src/styles/globals.css
index b5c61c9..111d943 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -1,3 +1,16 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+@layer utilities {
+ /* Hide scrollbar for Chrome, Safari and Opera */
+ .no-scrollbar::-webkit-scrollbar {
+ display: none;
+ }
+
+ /* Hide scrollbar for IE, Edge and Firefox */
+ .no-scrollbar {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+ }
+}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index e9a0944..029026a 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -7,13 +7,7 @@ const config: Config = {
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
- extend: {
- backgroundImage: {
- "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
- "gradient-conic":
- "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
- },
- },
+ extend: {},
},
plugins: [],
};