Compare commits
1 Commits
aiassistan
...
projectpag
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ef0b27c50 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -46,5 +46,4 @@ yarn-error.log*
|
|||||||
.idea
|
.idea
|
||||||
# clerk configuration (can include secrets)
|
# clerk configuration (can include secrets)
|
||||||
/.clerk/
|
/.clerk/
|
||||||
.worktrees
|
|
||||||
.claudesession
|
.claudesession
|
||||||
|
|||||||
34
README.md
34
README.md
@@ -1,13 +1,29 @@
|
|||||||
# My Personal Website
|
# Create T3 App
|
||||||
|
|
||||||
## Using:
|
This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`.
|
||||||
|
|
||||||
- nextjs
|
## What's next? How do I make an app with this?
|
||||||
- trpc
|
|
||||||
- neon
|
|
||||||
- uploadthing
|
|
||||||
- drizzle
|
|
||||||
- gsap
|
|
||||||
- openai
|
|
||||||
|
|
||||||
|
We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary.
|
||||||
|
|
||||||
|
If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help.
|
||||||
|
|
||||||
|
- [Next.js](https://nextjs.org)
|
||||||
|
- [NextAuth.js](https://next-auth.js.org)
|
||||||
|
- [Prisma](https://prisma.io)
|
||||||
|
- [Drizzle](https://orm.drizzle.team)
|
||||||
|
- [Tailwind CSS](https://tailwindcss.com)
|
||||||
|
- [tRPC](https://trpc.io)
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources:
|
||||||
|
|
||||||
|
- [Documentation](https://create.t3.gg/)
|
||||||
|
- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials
|
||||||
|
|
||||||
|
You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## How do I deploy this?
|
||||||
|
|
||||||
|
Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information.
|
||||||
|
|||||||
112
bun.lock
112
bun.lock
@@ -5,8 +5,6 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "gregorlohaus.com",
|
"name": "gregorlohaus.com",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^3.0.41",
|
|
||||||
"@ai-sdk/react": "^3.0.118",
|
|
||||||
"@clerk/nextjs": "^7.0.2",
|
"@clerk/nextjs": "^7.0.2",
|
||||||
"@electric-sql/pglite": "^0.3.16",
|
"@electric-sql/pglite": "^0.3.16",
|
||||||
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
||||||
@@ -51,8 +49,6 @@
|
|||||||
"@trpc/react-query": "^11.12.0",
|
"@trpc/react-query": "^11.12.0",
|
||||||
"@trpc/server": "^11.12.0",
|
"@trpc/server": "^11.12.0",
|
||||||
"@uiw/react-md-editor": "^4.0.11",
|
"@uiw/react-md-editor": "^4.0.11",
|
||||||
"@uploadthing/react": "^7.3.3",
|
|
||||||
"ai": "^6.0.116",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -62,7 +58,6 @@
|
|||||||
"drizzle-zod": "^0.8.3",
|
"drizzle-zod": "^0.8.3",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"glazejs": "^2.0.1",
|
"glazejs": "^2.0.1",
|
||||||
"googleapis": "^171.4.0",
|
|
||||||
"gsap": "^3.14.2",
|
"gsap": "^3.14.2",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
@@ -85,7 +80,6 @@
|
|||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss-motion": "^1.1.1",
|
"tailwindcss-motion": "^1.1.1",
|
||||||
"type-fest": "^5.4.4",
|
"type-fest": "^5.4.4",
|
||||||
"uploadthing": "^7.7.4",
|
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
},
|
},
|
||||||
@@ -124,16 +118,6 @@
|
|||||||
|
|
||||||
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
|
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
|
||||||
|
|
||||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.66", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A=="],
|
|
||||||
|
|
||||||
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-IZ42A+FO+vuEQCVNqlnAPYQnnUpUfdJIwn1BEDOBywiEHa23fw7PahxVtlX9zm3/zMvTW4JKPzWyvAgDu+SQ2A=="],
|
|
||||||
|
|
||||||
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
|
||||||
|
|
||||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],
|
|
||||||
|
|
||||||
"@ai-sdk/react": ["@ai-sdk/react@3.0.118", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.19", "ai": "6.0.116", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-fBAix8Jftxse6/2YJnOFkwW1/O6EQK4DK68M9DlFmZGAzBmsaHXEPVS77sVIlkaOWCy11bE7434NAVXRY+3OsQ=="],
|
|
||||||
|
|
||||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||||
|
|
||||||
"@antfu/ni": ["@antfu/ni@25.0.0", "", { "dependencies": { "ansis": "^4.0.0", "fzf": "^0.5.2", "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" }, "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs", "nup": "bin/nup.mjs" } }, "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA=="],
|
"@antfu/ni": ["@antfu/ni@25.0.0", "", { "dependencies": { "ansis": "^4.0.0", "fzf": "^0.5.2", "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" }, "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs", "nup": "bin/nup.mjs" } }, "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA=="],
|
||||||
@@ -288,8 +272,6 @@
|
|||||||
|
|
||||||
"@ecies/ciphers": ["@ecies/ciphers@0.2.5", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A=="],
|
"@ecies/ciphers": ["@ecies/ciphers@0.2.5", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A=="],
|
||||||
|
|
||||||
"@effect/platform": ["@effect/platform@0.90.3", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.33.0", "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.17.7" } }, "sha512-XvQ37yzWQKih4Du2CYladd1i/MzqtgkTPNCaN6Ku6No4CK83hDtXIV/rP03nEoBg2R3Pqgz6gGWmE2id2G81HA=="],
|
|
||||||
|
|
||||||
"@electric-sql/pglite": ["@electric-sql/pglite@0.3.16", "", {}, "sha512-mZkZfOd9OqTMHsK+1cje8OSzfAQcpD7JmILXTl5ahdempjUDdmg4euf1biDex5/LfQIDJ3gvCu6qDgdnDxfJmA=="],
|
"@electric-sql/pglite": ["@electric-sql/pglite@0.3.16", "", {}, "sha512-mZkZfOd9OqTMHsK+1cje8OSzfAQcpD7JmILXTl5ahdempjUDdmg4euf1biDex5/LfQIDJ3gvCu6qDgdnDxfJmA=="],
|
||||||
|
|
||||||
"@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
"@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||||
@@ -496,18 +478,6 @@
|
|||||||
|
|
||||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
|
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
|
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="],
|
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="],
|
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="],
|
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="],
|
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="],
|
|
||||||
|
|
||||||
"@mswjs/interceptors": ["@mswjs/interceptors@0.41.3", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA=="],
|
"@mswjs/interceptors": ["@mswjs/interceptors@0.41.3", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA=="],
|
||||||
|
|
||||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||||
@@ -550,10 +520,6 @@
|
|||||||
|
|
||||||
"@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="],
|
"@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="],
|
||||||
|
|
||||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
|
|
||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
|
||||||
|
|
||||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||||
|
|
||||||
"@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
|
"@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
|
||||||
@@ -742,7 +708,7 @@
|
|||||||
|
|
||||||
"@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="],
|
"@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="],
|
||||||
|
|
||||||
"@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.4", "", {}, "sha512-d3IxtzLo7P1oZ8s8YNvxzBUXRXojSut8pbPrTYtzsc5sn4+53jVqbk66pQerSZbZSJZQux6LkclB/+8IDordHg=="],
|
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
||||||
|
|
||||||
@@ -972,14 +938,6 @@
|
|||||||
|
|
||||||
"@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="],
|
"@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="],
|
||||||
|
|
||||||
"@uploadthing/mime-types": ["@uploadthing/mime-types@0.3.6", "", {}, "sha512-t3tTzgwFV9+1D7lNDYc7Lr7kBwotHaX0ZsvoCGe7xGnXKo9z0jG2Sjl/msll12FeoLj77nyhsxevXyGpQDBvLg=="],
|
|
||||||
|
|
||||||
"@uploadthing/react": ["@uploadthing/react@7.3.3", "", { "dependencies": { "@uploadthing/shared": "7.1.10", "file-selector": "0.6.0" }, "peerDependencies": { "next": "*", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "uploadthing": "^7.2.0" }, "optionalPeers": ["next"] }, "sha512-GhKbK42jL2Qs7OhRd2Z6j0zTLsnJTRJH31nR7RZnUYVoRh2aS/NabMAnHBNqfunIAGXVaA717Pvzq7vtxuPTmQ=="],
|
|
||||||
|
|
||||||
"@uploadthing/shared": ["@uploadthing/shared@7.1.10", "", { "dependencies": { "@uploadthing/mime-types": "0.3.6", "effect": "3.17.7", "sqids": "^0.3.0" } }, "sha512-R/XSA3SfCVnLIzFpXyGaKPfbwlYlWYSTuGjTFHuJhdAomuBuhopAHLh2Ois5fJibAHzi02uP1QCKbgTAdmArqg=="],
|
|
||||||
|
|
||||||
"@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
|
|
||||||
|
|
||||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.4", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA=="],
|
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.4", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA=="],
|
||||||
|
|
||||||
"@vitest/coverage-v8": ["@vitest/coverage-v8@4.0.18", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.0.18", "ast-v8-to-istanbul": "^0.3.10", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.1", "obug": "^2.1.1", "std-env": "^3.10.0", "tinyrainbow": "^3.0.3" }, "peerDependencies": { "@vitest/browser": "4.0.18", "vitest": "4.0.18" }, "optionalPeers": ["@vitest/browser"] }, "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg=="],
|
"@vitest/coverage-v8": ["@vitest/coverage-v8@4.0.18", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.0.18", "ast-v8-to-istanbul": "^0.3.10", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.1", "obug": "^2.1.1", "std-env": "^3.10.0", "tinyrainbow": "^3.0.3" }, "peerDependencies": { "@vitest/browser": "4.0.18", "vitest": "4.0.18" }, "optionalPeers": ["@vitest/browser"] }, "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg=="],
|
||||||
@@ -1006,8 +964,6 @@
|
|||||||
|
|
||||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||||
|
|
||||||
"ai": ["ai@6.0.116", "", { "dependencies": { "@ai-sdk/gateway": "3.0.66", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA=="],
|
|
||||||
|
|
||||||
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||||
|
|
||||||
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||||
@@ -1050,16 +1006,12 @@
|
|||||||
|
|
||||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||||
|
|
||||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
|
||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
|
||||||
|
|
||||||
"bcp-47-match": ["bcp-47-match@2.0.3", "", {}, "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="],
|
"bcp-47-match": ["bcp-47-match@2.0.3", "", {}, "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="],
|
||||||
|
|
||||||
"bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
|
"bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
|
||||||
|
|
||||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
|
||||||
|
|
||||||
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||||
|
|
||||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||||
@@ -1072,8 +1024,6 @@
|
|||||||
|
|
||||||
"bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
|
"bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
|
||||||
|
|
||||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
|
||||||
|
|
||||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||||
|
|
||||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||||
@@ -1260,14 +1210,10 @@
|
|||||||
|
|
||||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||||
|
|
||||||
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
|
||||||
|
|
||||||
"eciesjs": ["eciesjs@0.4.18", "", { "dependencies": { "@ecies/ciphers": "^0.2.5", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ=="],
|
"eciesjs": ["eciesjs@0.4.18", "", { "dependencies": { "@ecies/ciphers": "^0.2.5", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ=="],
|
||||||
|
|
||||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||||
|
|
||||||
"effect": ["effect@3.17.7", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-dpt0ONUn3zzAuul6k4nC/coTTw27AL5nhkORXgTi6NfMPzqWYa1M05oKmOMTxpVSTKepqXVcW9vIwkuaaqx9zA=="],
|
|
||||||
|
|
||||||
"electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="],
|
"electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="],
|
||||||
|
|
||||||
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
|
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
|
||||||
@@ -1336,8 +1282,6 @@
|
|||||||
|
|
||||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||||
|
|
||||||
"fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
|
|
||||||
|
|
||||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||||
|
|
||||||
"fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="],
|
"fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="],
|
||||||
@@ -1360,14 +1304,10 @@
|
|||||||
|
|
||||||
"figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
|
"figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
|
||||||
|
|
||||||
"file-selector": ["file-selector@0.6.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw=="],
|
|
||||||
|
|
||||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||||
|
|
||||||
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
||||||
|
|
||||||
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
|
|
||||||
|
|
||||||
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||||
|
|
||||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||||
@@ -1392,10 +1332,6 @@
|
|||||||
|
|
||||||
"fzf": ["fzf@0.5.2", "", {}, "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q=="],
|
"fzf": ["fzf@0.5.2", "", {}, "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q=="],
|
||||||
|
|
||||||
"gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
|
|
||||||
|
|
||||||
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
|
|
||||||
|
|
||||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||||
|
|
||||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||||
@@ -1428,14 +1364,6 @@
|
|||||||
|
|
||||||
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
||||||
|
|
||||||
"google-auth-library": ["google-auth-library@10.6.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "7.1.3", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-5awwuLrzNol+pFDmKJd0dKtZ0fPLAtoA5p7YO4ODsDu6ONJUVqbYwvv8y2ZBO5MBNp9TJXigB19710kYpBPdtA=="],
|
|
||||||
|
|
||||||
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
|
|
||||||
|
|
||||||
"googleapis": ["googleapis@171.4.0", "", { "dependencies": { "google-auth-library": "^10.2.0", "googleapis-common": "^8.0.0" } }, "sha512-xybFL2SmmUgIifgsbsRQYRdNrSAYwxWZDmkZTGjUIaRnX5jPqR8el/cEvo6rCqh7iaZx6MfEPS/lrDgZ0bymkg=="],
|
|
||||||
|
|
||||||
"googleapis-common": ["googleapis-common@8.0.1", "", { "dependencies": { "extend": "^3.0.2", "gaxios": "^7.0.0-rc.4", "google-auth-library": "^10.1.0", "qs": "^6.7.0", "url-template": "^2.0.8" } }, "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A=="],
|
|
||||||
|
|
||||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
@@ -1660,12 +1588,8 @@
|
|||||||
|
|
||||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||||
|
|
||||||
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
|
||||||
|
|
||||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
|
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
|
||||||
|
|
||||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
|
||||||
|
|
||||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
||||||
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
|
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
|
||||||
@@ -1680,10 +1604,6 @@
|
|||||||
|
|
||||||
"jsonify": ["jsonify@0.0.1", "", {}, "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg=="],
|
"jsonify": ["jsonify@0.0.1", "", {}, "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg=="],
|
||||||
|
|
||||||
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
|
||||||
|
|
||||||
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
|
|
||||||
|
|
||||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||||
|
|
||||||
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
||||||
@@ -1866,14 +1786,8 @@
|
|||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
"msgpackr": ["msgpackr@1.11.9", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw=="],
|
|
||||||
|
|
||||||
"msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="],
|
|
||||||
|
|
||||||
"msw": ["msw@2.12.10", "", { "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.2", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", "graphql": "^16.12.0", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^5.2.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-G3VUymSE0/iegFnuipujpwyTM2GuZAKXNeerUSrG2+Eg391wW63xFs5ixWsK9MWzr1AGoSkYGmyAzNgbR3+urw=="],
|
"msw": ["msw@2.12.10", "", { "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.2", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", "graphql": "^16.12.0", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^5.2.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-G3VUymSE0/iegFnuipujpwyTM2GuZAKXNeerUSrG2+Eg391wW63xFs5ixWsK9MWzr1AGoSkYGmyAzNgbR3+urw=="],
|
||||||
|
|
||||||
"multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
|
|
||||||
|
|
||||||
"mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="],
|
"mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="],
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
@@ -1896,8 +1810,6 @@
|
|||||||
|
|
||||||
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||||
|
|
||||||
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
|
|
||||||
|
|
||||||
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
|
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
|
||||||
|
|
||||||
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
|
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
|
||||||
@@ -2022,7 +1934,7 @@
|
|||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
|
"pure-rand": ["pure-rand@7.0.1", "", {}, "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ=="],
|
||||||
|
|
||||||
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
|
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
|
||||||
|
|
||||||
@@ -2124,8 +2036,6 @@
|
|||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
|
|
||||||
|
|
||||||
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
|
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
|
||||||
|
|
||||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||||
@@ -2136,8 +2046,6 @@
|
|||||||
|
|
||||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||||
|
|
||||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
"saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
|
"saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
|
||||||
@@ -2192,8 +2100,6 @@
|
|||||||
|
|
||||||
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
||||||
|
|
||||||
"sqids": ["sqids@0.3.0", "", {}, "sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw=="],
|
|
||||||
|
|
||||||
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
|
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
|
||||||
|
|
||||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||||
@@ -2238,8 +2144,6 @@
|
|||||||
|
|
||||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||||
|
|
||||||
"swr": ["swr@2.4.1", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA=="],
|
|
||||||
|
|
||||||
"symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
|
"symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
|
||||||
|
|
||||||
"synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="],
|
"synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="],
|
||||||
@@ -2256,8 +2160,6 @@
|
|||||||
|
|
||||||
"test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
|
"test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
|
||||||
|
|
||||||
"throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="],
|
|
||||||
|
|
||||||
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||||
|
|
||||||
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||||
@@ -2338,10 +2240,6 @@
|
|||||||
|
|
||||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||||
|
|
||||||
"uploadthing": ["uploadthing@7.7.4", "", { "dependencies": { "@effect/platform": "0.90.3", "@standard-schema/spec": "1.0.0-beta.4", "@uploadthing/mime-types": "0.3.6", "@uploadthing/shared": "7.1.10", "effect": "3.17.7" }, "peerDependencies": { "express": "*", "h3": "*", "tailwindcss": "^3.0.0 || ^4.0.0-beta.0" }, "optionalPeers": ["express", "h3", "tailwindcss"] }, "sha512-rlK/4JWHW5jP30syzWGBFDDXv3WJDdT8gn9OoxRJmXLoXi94hBmyyjxihGlNrKhBc81czyv8TkzMioe/OuKGfA=="],
|
|
||||||
|
|
||||||
"url-template": ["url-template@2.0.8", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="],
|
|
||||||
|
|
||||||
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
|
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
|
||||||
|
|
||||||
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
|
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
|
||||||
@@ -2544,8 +2442,6 @@
|
|||||||
|
|
||||||
"@uiw/react-markdown-preview/rehype-prism-plus": ["rehype-prism-plus@2.0.0", "", { "dependencies": { "hast-util-to-string": "^3.0.0", "parse-numeric-range": "^1.3.0", "refractor": "^4.8.0", "rehype-parse": "^9.0.0", "unist-util-filter": "^5.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ=="],
|
"@uiw/react-markdown-preview/rehype-prism-plus": ["rehype-prism-plus@2.0.0", "", { "dependencies": { "hast-util-to-string": "^3.0.0", "parse-numeric-range": "^1.3.0", "refractor": "^4.8.0", "rehype-parse": "^9.0.0", "unist-util-filter": "^5.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ=="],
|
||||||
|
|
||||||
"@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
|
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
|
||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
@@ -2566,8 +2462,6 @@
|
|||||||
|
|
||||||
"cssstyle/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
"cssstyle/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
||||||
|
|
||||||
"effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||||
|
|
||||||
"glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
|
"glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
|
||||||
@@ -2586,8 +2480,6 @@
|
|||||||
|
|
||||||
"jest-circus/pretty-format": ["pretty-format@30.3.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ=="],
|
"jest-circus/pretty-format": ["pretty-format@30.3.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ=="],
|
||||||
|
|
||||||
"jest-circus/pure-rand": ["pure-rand@7.0.1", "", {}, "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ=="],
|
|
||||||
|
|
||||||
"jest-config/pretty-format": ["pretty-format@30.3.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ=="],
|
"jest-config/pretty-format": ["pretty-format@30.3.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ=="],
|
||||||
|
|
||||||
"jest-diff/pretty-format": ["pretty-format@30.3.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ=="],
|
"jest-diff/pretty-format": ["pretty-format@30.3.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ=="],
|
||||||
|
|||||||
@@ -19,8 +19,6 @@
|
|||||||
"test": "vitest --typecheck"
|
"test": "vitest --typecheck"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^3.0.41",
|
|
||||||
"@ai-sdk/react": "^3.0.118",
|
|
||||||
"@clerk/nextjs": "^7.0.2",
|
"@clerk/nextjs": "^7.0.2",
|
||||||
"@electric-sql/pglite": "^0.3.16",
|
"@electric-sql/pglite": "^0.3.16",
|
||||||
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
||||||
@@ -65,8 +63,6 @@
|
|||||||
"@trpc/react-query": "^11.12.0",
|
"@trpc/react-query": "^11.12.0",
|
||||||
"@trpc/server": "^11.12.0",
|
"@trpc/server": "^11.12.0",
|
||||||
"@uiw/react-md-editor": "^4.0.11",
|
"@uiw/react-md-editor": "^4.0.11",
|
||||||
"@uploadthing/react": "^7.3.3",
|
|
||||||
"ai": "^6.0.116",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -76,7 +72,6 @@
|
|||||||
"drizzle-zod": "^0.8.3",
|
"drizzle-zod": "^0.8.3",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"glazejs": "^2.0.1",
|
"glazejs": "^2.0.1",
|
||||||
"googleapis": "^171.4.0",
|
|
||||||
"gsap": "^3.14.2",
|
"gsap": "^3.14.2",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
@@ -99,7 +94,6 @@
|
|||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss-motion": "^1.1.1",
|
"tailwindcss-motion": "^1.1.1",
|
||||||
"type-fest": "^5.4.4",
|
"type-fest": "^5.4.4",
|
||||||
"uploadthing": "^7.7.4",
|
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { useRouter } from 'next/navigation'
|
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui/dialog'
|
|
||||||
import ChatInterface from '~/app/chat/_components/ChatInterface'
|
|
||||||
import { useMessages } from '~/app/_providers/MessagesProvider';
|
|
||||||
import { Spinner } from '~/components/ui/spinner';
|
|
||||||
|
|
||||||
export default function ChatModal() {
|
|
||||||
const router = useRouter()
|
|
||||||
const {messages,session,isLoading,error} = useMessages()
|
|
||||||
return (
|
|
||||||
<Dialog modal={true} open onOpenChange={() => router.back()}>
|
|
||||||
<DialogContent className="w-full max-w-full rounded-none sm:max-w-full h-[100svh] lg:max-w-3xl lg:rounded-xl lg:h-[80vh] flex flex-col p-0 gap-0">
|
|
||||||
<DialogHeader className="p-4 border-b shrink-0">
|
|
||||||
<DialogTitle>Talk To My AI-Assistant</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="flex-1 overflow-hidden min-h-0">
|
|
||||||
{!isLoading &&
|
|
||||||
<ChatInterface sessionId={session?.id} dbMessages={messages ?? []}/>
|
|
||||||
}
|
|
||||||
{isLoading &&
|
|
||||||
<><Spinner/> Loading Messages...</>
|
|
||||||
}
|
|
||||||
{error &&
|
|
||||||
<div> {error} </div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import ChatModal from './_components/ChatModal'
|
|
||||||
|
|
||||||
export default function AssistantModalPage() {
|
|
||||||
return (
|
|
||||||
<ChatModal/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { useGSAP } from "@gsap/react";
|
|
||||||
import { useRef, type HTMLAttributes, type ReactNode } from "react";
|
|
||||||
import { useGsapContext } from "~/app/_providers/GsapProvicer";
|
|
||||||
import { SplitText } from "gsap/SplitText";
|
|
||||||
import gsap from 'gsap'
|
|
||||||
import { cn } from "~/lib/utils";
|
|
||||||
const AnimateTextIn = ({
|
|
||||||
children,
|
|
||||||
animation = "type",
|
|
||||||
position = 0,
|
|
||||||
tlId = undefined,
|
|
||||||
speed = 1,
|
|
||||||
scrollOnly = false,
|
|
||||||
className
|
|
||||||
}: {
|
|
||||||
children: ReactNode,
|
|
||||||
animation?: "type" | "slide",
|
|
||||||
position?: gsap.Position,
|
|
||||||
tlId?: string,
|
|
||||||
scrollOnly?: boolean,
|
|
||||||
speed?: number,
|
|
||||||
className?: HTMLAttributes<HTMLDivElement>['className']
|
|
||||||
}) => {
|
|
||||||
const el = useRef<HTMLDivElement>(null)
|
|
||||||
const gsapContext = useGsapContext();
|
|
||||||
useGSAP(() => {
|
|
||||||
const rect = el.current?.getBoundingClientRect()
|
|
||||||
const scroller = gsapContext?.getScroller()
|
|
||||||
console.log(scroller)
|
|
||||||
let viewportTop = 0
|
|
||||||
let viewportBottom = window.innerHeight
|
|
||||||
if (scroller && scroller instanceof Element) {
|
|
||||||
const scrollerRect = scroller.getBoundingClientRect()
|
|
||||||
viewportTop = scrollerRect.top
|
|
||||||
viewportBottom = scrollerRect.top + scrollerRect.height
|
|
||||||
}
|
|
||||||
const isInView = rect && rect.bottom > viewportTop && rect.top < viewportBottom
|
|
||||||
console.log(isInView)
|
|
||||||
const chars = new SplitText(el.current, { type: 'chars' })
|
|
||||||
gsapContext?.addAnimation(gsap.to(el.current, { opacity: 100, duration: 0 }), 0, tlId)
|
|
||||||
const fromVars = animation === "slide"
|
|
||||||
? { opacity: 0, x: -10, duration: 0.2 * speed, stagger: { each: 0.08 * speed }, ease: 'bounce.inOut', onComplete: () => chars.revert() }
|
|
||||||
: { opacity: 0, duration: 0.01 * speed, stagger: { each: 0.04 * speed }, ease: 'bounce.inOut', onComplete: () => chars.revert() }
|
|
||||||
if (isInView && !scrollOnly) {
|
|
||||||
gsapContext?.addAnimation(gsap.from(chars.chars, fromVars), position, tlId)
|
|
||||||
} else {
|
|
||||||
gsap.from(chars.chars,
|
|
||||||
{
|
|
||||||
...fromVars,
|
|
||||||
scrollTrigger: {
|
|
||||||
trigger: el.current,
|
|
||||||
start: 'top bottom',
|
|
||||||
end: 'bottom top',
|
|
||||||
toggleActions: "play reverse play reverse",
|
|
||||||
scroller
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, { dependencies: [] })
|
|
||||||
return (
|
|
||||||
<div ref={el} className={cn(className, "opacity-0")}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AnimateTextIn;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { type HTMLAttributes, type ReactNode } from "react";
|
|
||||||
import AnimatedDiv from "./AnimatedDiv";
|
|
||||||
import { cn } from "~/lib/utils";
|
|
||||||
const AnimatePopUp = ({
|
|
||||||
children,
|
|
||||||
position,
|
|
||||||
className,
|
|
||||||
duration=1,
|
|
||||||
ease='elastic'
|
|
||||||
}:{
|
|
||||||
children:ReactNode
|
|
||||||
position:gsap.Position,
|
|
||||||
className?:HTMLAttributes<HTMLDivElement>['className']
|
|
||||||
duration?:number,
|
|
||||||
ease?:gsap.EaseString|gsap.EaseFunction
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<AnimatedDiv children={children} position={position} className={cn(className,'h-0 translate-y-[50] overflow-hidden')} height='auto' y={0} overflow='' ease={ease} duration={duration} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AnimatePopUp;
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useRef, useEffect, useCallback, useState } from "react";
|
|
||||||
import { useGSAP } from "@gsap/react";
|
|
||||||
import gsap from "gsap";
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
|
|
||||||
/* ─────────────────────────────────────────────
|
|
||||||
* Config — grayscale palettes
|
|
||||||
* ───────────────────────────────────────────── */
|
|
||||||
const PALETTES = {
|
|
||||||
dark: {
|
|
||||||
base: "#0a0a0a",
|
|
||||||
particles: [
|
|
||||||
"rgba(255,255,255,0.70)",
|
|
||||||
"rgba(255,255,255,0.45)",
|
|
||||||
"rgba(180,180,180,0.50)",
|
|
||||||
"rgba(200,200,200,0.35)",
|
|
||||||
"rgba(255,255,255,0.22)",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
light: {
|
|
||||||
base: "#f5f5f5",
|
|
||||||
particles: [
|
|
||||||
"rgba(0,0,0,0.55)",
|
|
||||||
"rgba(0,0,0,0.35)",
|
|
||||||
"rgba(60,60,60,0.40)",
|
|
||||||
"rgba(80,80,80,0.25)",
|
|
||||||
"rgba(0,0,0,0.18)",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
/* ─────────────────────────────────────────────
|
|
||||||
* Helpers
|
|
||||||
* ───────────────────────────────────────────── */
|
|
||||||
const isMobileDevice = (): boolean => {
|
|
||||||
if (typeof window === "undefined") return false;
|
|
||||||
return window.matchMedia("(pointer: coarse)").matches || window.innerWidth < 768;
|
|
||||||
};
|
|
||||||
|
|
||||||
const rand = (min: number, max: number) => Math.random() * (max - min) + min;
|
|
||||||
|
|
||||||
/* ─────────────────────────────────────────────
|
|
||||||
* Particle
|
|
||||||
* ───────────────────────────────────────────── */
|
|
||||||
interface Particle {
|
|
||||||
angle: number;
|
|
||||||
radius: number;
|
|
||||||
speed: number;
|
|
||||||
size: number;
|
|
||||||
colorIndex: number;
|
|
||||||
wobbleAmp: number;
|
|
||||||
wobbleSpeed: number;
|
|
||||||
wobblePhase: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const spawnParticle = (): Particle => ({
|
|
||||||
angle: rand(0, Math.PI * 2),
|
|
||||||
radius: rand(30, 240),
|
|
||||||
speed: rand(0.003, 0.002) * (Math.random() > 0.5 ? 1 : -1),
|
|
||||||
size: rand(1.2, 4),
|
|
||||||
colorIndex: Math.floor(rand(0, 5)),
|
|
||||||
wobbleAmp: rand(6, 30),
|
|
||||||
wobbleSpeed: rand(0.008, 0.035),
|
|
||||||
wobblePhase: rand(0, Math.PI * 2),
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ─────────────────────────────────────────────
|
|
||||||
* Component
|
|
||||||
* ───────────────────────────────────────────── */
|
|
||||||
interface AnimatedBackgroundContainerProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
/** Number of orbiting particles. Default 60 */
|
|
||||||
particleCount?: number;
|
|
||||||
/** Max orbit radius in px — controls how far particles spread from the cursor. Default 240 */
|
|
||||||
orbitRadius?: number;
|
|
||||||
/** How quickly particles catch up to cursor (0–1). Default 0.06 */
|
|
||||||
followSpeed?: number;
|
|
||||||
/** Speed multiplier for mobile random anchor drift. Default 1 */
|
|
||||||
mobileSpeed?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AnimatedBackgroundContainer({
|
|
||||||
children,
|
|
||||||
className = "",
|
|
||||||
particleCount = 60,
|
|
||||||
orbitRadius = 240,
|
|
||||||
followSpeed = 0.06,
|
|
||||||
mobileSpeed = 1,
|
|
||||||
}: AnimatedBackgroundContainerProps) {
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const mousePos = useRef({ x: 0, y: 0 });
|
|
||||||
const smoothMouse = useRef({ x: 0, y: 0 });
|
|
||||||
const mobileAnchor = useRef({ x: 0, y: 0 });
|
|
||||||
const mobileTarget = useRef({ x: 0, y: 0 });
|
|
||||||
const isMobile = useRef(false);
|
|
||||||
const particles = useRef<Particle[]>([]);
|
|
||||||
const frame = useRef(0);
|
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
|
|
||||||
const { resolvedTheme } = useTheme();
|
|
||||||
let isDark = resolvedTheme === "dark";
|
|
||||||
if (resolvedTheme == undefined) {
|
|
||||||
isDark = true;
|
|
||||||
}
|
|
||||||
const palette = isDark ? PALETTES.dark : PALETTES.light;
|
|
||||||
|
|
||||||
/* Spawn particles */
|
|
||||||
useEffect(() => {
|
|
||||||
const minR = Math.max(10, orbitRadius * 0.12);
|
|
||||||
particles.current = Array.from({ length: particleCount }, () => ({
|
|
||||||
...spawnParticle(),
|
|
||||||
radius: rand(minR, orbitRadius),
|
|
||||||
wobbleAmp: rand(orbitRadius * 0.025, orbitRadius * 0.12),
|
|
||||||
}));
|
|
||||||
}, [particleCount, orbitRadius]);
|
|
||||||
|
|
||||||
/* Detect mobile & seed positions */
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
isMobile.current = isMobileDevice();
|
|
||||||
if (containerRef.current) {
|
|
||||||
const cx = containerRef.current.clientWidth / 2;
|
|
||||||
const cy = containerRef.current.clientHeight / 2;
|
|
||||||
mousePos.current = { x: cx, y: cy };
|
|
||||||
smoothMouse.current = { x: cx, y: cy };
|
|
||||||
mobileAnchor.current = { x: cx, y: cy };
|
|
||||||
mobileTarget.current = {
|
|
||||||
x: rand(cx * 0.4, cx * 1.6),
|
|
||||||
y: rand(cy * 0.4, cy * 1.6),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
/* Resize canvas to match container */
|
|
||||||
useEffect(() => {
|
|
||||||
const resize = () => {
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
const container = containerRef.current;
|
|
||||||
if (!canvas || !container) return;
|
|
||||||
const dpr = window.devicePixelRatio || 1;
|
|
||||||
const w = container.clientWidth;
|
|
||||||
const h = container.clientHeight;
|
|
||||||
canvas.width = w * dpr;
|
|
||||||
canvas.height = h * dpr;
|
|
||||||
canvas.style.width = `${w}px`;
|
|
||||||
canvas.style.height = `${h}px`;
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
if (ctx) ctx.scale(dpr, dpr);
|
|
||||||
};
|
|
||||||
resize();
|
|
||||||
window.addEventListener("resize", resize);
|
|
||||||
return () => window.removeEventListener("resize", resize);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
/* Mouse tracking (desktop only) */
|
|
||||||
const handleMouseMove = useCallback((e: MouseEvent) => {
|
|
||||||
if (!containerRef.current || isMobile.current) return;
|
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
|
||||||
mousePos.current = {
|
|
||||||
x: e.clientX - rect.left,
|
|
||||||
y: e.clientY - rect.top,
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const el = containerRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
el.addEventListener("mousemove", handleMouseMove, { passive: true });
|
|
||||||
return () => el.removeEventListener("mousemove", handleMouseMove);
|
|
||||||
}, [handleMouseMove]);
|
|
||||||
|
|
||||||
/* ── GSAP ticker — draw loop ── */
|
|
||||||
useGSAP(
|
|
||||||
() => {
|
|
||||||
if (!mounted) return;
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
const container = containerRef.current;
|
|
||||||
if (!canvas || !container) return;
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
if (!ctx) return;
|
|
||||||
|
|
||||||
const tick = () => {
|
|
||||||
const w = container.clientWidth;
|
|
||||||
const h = container.clientHeight;
|
|
||||||
frame.current++;
|
|
||||||
|
|
||||||
/* Anchor: smooth-follow cursor or drift on mobile */
|
|
||||||
if (isMobile.current) {
|
|
||||||
mobileAnchor.current.x +=
|
|
||||||
(mobileTarget.current.x - mobileAnchor.current.x) * 0.008 * mobileSpeed;
|
|
||||||
mobileAnchor.current.y +=
|
|
||||||
(mobileTarget.current.y - mobileAnchor.current.y) * 0.008 * mobileSpeed;
|
|
||||||
|
|
||||||
const dx = mobileTarget.current.x - mobileAnchor.current.x;
|
|
||||||
const dy = mobileTarget.current.y - mobileAnchor.current.y;
|
|
||||||
if (Math.sqrt(dx * dx + dy * dy) < 30) {
|
|
||||||
mobileTarget.current = {
|
|
||||||
x: rand(w * 0.15, w * 0.85),
|
|
||||||
y: rand(h * 0.15, h * 0.85),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
smoothMouse.current.x = mobileAnchor.current.x;
|
|
||||||
smoothMouse.current.y = mobileAnchor.current.y;
|
|
||||||
} else {
|
|
||||||
smoothMouse.current.x +=
|
|
||||||
(mousePos.current.x - smoothMouse.current.x) * followSpeed;
|
|
||||||
smoothMouse.current.y +=
|
|
||||||
(mousePos.current.y - smoothMouse.current.y) * followSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cx = smoothMouse.current.x;
|
|
||||||
const cy = smoothMouse.current.y;
|
|
||||||
|
|
||||||
/* Clear frame */
|
|
||||||
ctx.clearRect(0, 0, w, h);
|
|
||||||
|
|
||||||
/* Draw each particle */
|
|
||||||
particles.current.forEach((p) => {
|
|
||||||
p.angle += p.speed;
|
|
||||||
|
|
||||||
const wobble =
|
|
||||||
Math.sin(frame.current * p.wobbleSpeed + p.wobblePhase) * p.wobbleAmp;
|
|
||||||
const r = p.radius + wobble;
|
|
||||||
|
|
||||||
const x = cx + Math.cos(p.angle) * r;
|
|
||||||
const y = cy + Math.sin(p.angle) * r;
|
|
||||||
|
|
||||||
/* Soft fade near viewport edges */
|
|
||||||
const edgeFade = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(x / 80, (w - x) / 80, y / 80, (h - y) / 80, 1),
|
|
||||||
);
|
|
||||||
if (edgeFade <= 0) return;
|
|
||||||
|
|
||||||
ctx.globalAlpha = edgeFade;
|
|
||||||
ctx.fillStyle = palette.particles[p.colorIndex];
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(x, y, p.size, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.globalAlpha = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
gsap.ticker.add(tick);
|
|
||||||
return () => {
|
|
||||||
gsap.ticker.remove(tick);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scope: containerRef,
|
|
||||||
dependencies: [mounted, isDark, followSpeed, mobileSpeed, orbitRadius, palette],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/* ── Render ── */
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={containerRef}
|
|
||||||
className={className}
|
|
||||||
style={{
|
|
||||||
position: "relative",
|
|
||||||
minHeight: "100vh",
|
|
||||||
width: "100%",
|
|
||||||
overflow: "hidden",
|
|
||||||
transition: "background-color 0.6s ease",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<canvas
|
|
||||||
ref={canvasRef}
|
|
||||||
aria-hidden
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
inset: 0,
|
|
||||||
zIndex: 0,
|
|
||||||
pointerEvents: "none",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Grain texture */}
|
|
||||||
<div
|
|
||||||
aria-hidden
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
inset: 0,
|
|
||||||
zIndex: 1,
|
|
||||||
opacity: isDark ? 0.05 : 0.03,
|
|
||||||
pointerEvents: "none",
|
|
||||||
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`,
|
|
||||||
backgroundRepeat: "repeat",
|
|
||||||
backgroundSize: "180px 180px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div style={{ position: "relative", zIndex: 2 }}>{children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import gsap from "gsap";
|
|
||||||
import { type HTMLAttributes,
|
|
||||||
type ReactNode, useLayoutEffect, useRef } from "react";
|
|
||||||
import { useGsapContext } from "~/app/_providers/GsapProvicer";
|
|
||||||
const AnimatedDiv = (
|
|
||||||
{
|
|
||||||
children,
|
|
||||||
position,
|
|
||||||
className,
|
|
||||||
animationMode='to',
|
|
||||||
...tweenVars
|
|
||||||
}:
|
|
||||||
gsap.TweenVars & {
|
|
||||||
children:ReactNode,
|
|
||||||
position:gsap.Position,
|
|
||||||
animationMode?: 'from'|'to',
|
|
||||||
className?:HTMLAttributes<HTMLDivElement>['className']
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
const div = useRef<HTMLDivElement>(null);
|
|
||||||
const gsapContext = useGsapContext()
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
let tween:gsap.core.Tween;
|
|
||||||
switch(animationMode) {
|
|
||||||
case 'from':
|
|
||||||
tween = gsap.from(div.current,tweenVars);
|
|
||||||
break;
|
|
||||||
case 'to':
|
|
||||||
tween = gsap.to(div.current,tweenVars);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
gsapContext?.addAnimation(tween,position)
|
|
||||||
},[])
|
|
||||||
return (
|
|
||||||
<div ref={div} className={className}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AnimatedDiv;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { useGSAP } from "@gsap/react"; import { useEffect, useLayoutEffect, useRef,type ReactNode } from "react";
|
|
||||||
import { useGsapContext } from "~/app/_providers/GsapProvicer";
|
|
||||||
import { SplitText } from "gsap/SplitText";
|
|
||||||
import gsap from 'gsap'
|
|
||||||
const AnimatedPageTitle = (
|
|
||||||
{ children, position }: { children: ReactNode, position:gsap.Position }
|
|
||||||
) => {
|
|
||||||
const el = useRef<HTMLHeadingElement>(null)
|
|
||||||
const gsapContext = useGsapContext();
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const split = new SplitText(el.current, { type: "lines,chars", autoSplit:true })
|
|
||||||
gsapContext?.addAnimation(gsap.to(el.current, { opacity: 100 }),position)
|
|
||||||
gsapContext?.addAnimation(gsap.from(split.chars, { id: 'titlesplit',
|
|
||||||
stagger: 0.05, rotate: -90, opacity: 0, x: -10, onComplete: () => {split.revert()}
|
|
||||||
}),'>')
|
|
||||||
},[])
|
|
||||||
return (
|
|
||||||
<h1 className="text-4xl break-keep opacity-0 font-bold text-balance w-full" ref={el}> {children} </h1>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AnimatedPageTitle;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { MessageCircle } from 'lucide-react'
|
|
||||||
import { Button } from '~/components/ui/button'
|
|
||||||
import { usePathname } from 'next/navigation'
|
|
||||||
export default function ChatFAB() {
|
|
||||||
const pathName = usePathname()
|
|
||||||
const isChat = pathName.indexOf('\/chat') > -1
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!isChat &&
|
|
||||||
<div className="fixed bottom-6 right-6 z-50">
|
|
||||||
<Button asChild size="icon" className="h-14 w-14 rounded-full shadow-lg">
|
|
||||||
<Link href="/assistant">
|
|
||||||
<MessageCircle className="h-6 w-6" />
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -4,18 +4,10 @@ import { Moon, Sun } from "lucide-react"
|
|||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
type Props = {activeTheme:string|undefined}
|
type Props = {activeTheme:string|undefined}
|
||||||
const ThemeIcon = (props:Props) => {
|
const ThemeIcon = (props:Props) => {
|
||||||
return (
|
if (props.activeTheme == "dark") {
|
||||||
<>
|
return (<Sun/>)
|
||||||
{props.activeTheme && props.activeTheme == 'dark' &&
|
} else {
|
||||||
<Sun/>
|
return (<Moon/>)
|
||||||
}
|
}
|
||||||
{props.activeTheme && props.activeTheme == 'light' &&
|
|
||||||
<Moon/>
|
|
||||||
}
|
|
||||||
{!props.activeTheme &&
|
|
||||||
<Sun/>
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
export default ThemeIcon;
|
export default ThemeIcon;
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ import ThemeIcon from "./ThemeIcon"
|
|||||||
|
|
||||||
export function ThemeSwitch() {
|
export function ThemeSwitch() {
|
||||||
const { setTheme, theme } = useTheme()
|
const { setTheme, theme } = useTheme()
|
||||||
if (!theme) {
|
|
||||||
setTheme('dark')
|
|
||||||
}
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
setTheme(theme == "dark" ? "light" : "dark")
|
setTheme(theme == "dark" ? "light" : "dark")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ThemeSwitch } from "./ThemeSwitch"
|
|||||||
|
|
||||||
export default function TopNav() {
|
export default function TopNav() {
|
||||||
return (
|
return (
|
||||||
<div className="fixed backdrop-blur-md lg:w-full right-0 z-50">
|
<div className="fixed lg:w-full right-0 z-50 lg:bg-background">
|
||||||
<nav className="flex flex-col-reverse lg:flex-row flex-wrap w-20 lg:w-full outline-1 lg:h-10 h-full">
|
<nav className="flex flex-col-reverse lg:flex-row flex-wrap w-20 lg:w-full outline-1 lg:h-10 h-full">
|
||||||
<div className="flex flex-wrap lg:h-full w-20 lg:w-fit lg:flex-row">
|
<div className="flex flex-wrap lg:h-full w-20 lg:w-fit lg:flex-row">
|
||||||
<Button className="flex h-10 lg:h-full w-full lg:w-20" asChild variant="outline">
|
<Button className="flex h-10 lg:h-full w-full lg:w-20" asChild variant="outline">
|
||||||
@@ -19,14 +19,6 @@ export default function TopNav() {
|
|||||||
<Button asChild className="flex h-10 lg:h-full w-full lg:w-20" variant="outline">
|
<Button asChild className="flex h-10 lg:h-full w-full lg:w-20" variant="outline">
|
||||||
<Link href={"/projects"}> Projects </Link>
|
<Link href={"/projects"}> Projects </Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild className="flex h-10 lg:h-full w-full lg:w-20" variant="outline">
|
|
||||||
<Link href={"/music"}> Music </Link>
|
|
||||||
</Button>
|
|
||||||
<Show when="signed-in">
|
|
||||||
<Button asChild className="flex h-10 lg:h-full w-full lg:w-20" variant="outline">
|
|
||||||
<Link href="/chat"> Chat </Link>
|
|
||||||
</Button>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col-reverse flex-wrap lg:h-full w-20 lg:w-fit lg:flex-row lg:ml-auto">
|
<div className="flex flex-col-reverse flex-wrap lg:h-full w-20 lg:w-fit lg:flex-row lg:ml-auto">
|
||||||
<AdminWrap>
|
<AdminWrap>
|
||||||
@@ -52,13 +44,7 @@ export default function TopNav() {
|
|||||||
<Show when="signed-in">
|
<Show when="signed-in">
|
||||||
<Button asChild className="flex h-10 lg:h-full cursor-pointer lg:w-20 content-center" variant={"outline"}>
|
<Button asChild className="flex h-10 lg:h-full cursor-pointer lg:w-20 content-center" variant={"outline"}>
|
||||||
<div>
|
<div>
|
||||||
<UserButton
|
<UserButton />
|
||||||
userProfileProps={{
|
|
||||||
additionalOAuthScopes: {
|
|
||||||
google: ['https://www.googleapis.com/auth/calendar'],
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,98 +1,18 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useGSAP } from '@gsap/react'
|
import { useGSAP } from '@gsap/react'
|
||||||
import gsap from 'gsap'
|
import gsap from 'gsap'
|
||||||
import { SplitText } from 'gsap/SplitText'
|
import { createContext, useContext, type ReactNode } from 'react'
|
||||||
import { ScrollTrigger, GSDevTools } from 'gsap/all'
|
|
||||||
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useRef, type ReactNode } from 'react'
|
|
||||||
|
|
||||||
gsap.registerPlugin(useGSAP)
|
gsap.registerPlugin(useGSAP)
|
||||||
gsap.registerPlugin(ScrollTrigger)
|
const GsapContext = createContext<typeof globalThis.gsap | null>(null)
|
||||||
gsap.registerPlugin(SplitText)
|
|
||||||
gsap.registerPlugin(GSDevTools)
|
|
||||||
const GsapContext = createContext<{
|
|
||||||
addAnimation: (
|
|
||||||
animation: gsap.core.TimelineChild,
|
|
||||||
position: gsap.Position
|
|
||||||
) => void,
|
|
||||||
resetTimeline: () => void,
|
|
||||||
resumeTimeline: () => void,
|
|
||||||
getScroller: () => Element | Window | null
|
|
||||||
} | null>(null)
|
|
||||||
|
|
||||||
export function useGsapContext() {
|
export function useGsapContext() {
|
||||||
return useContext(GsapContext)
|
return useContext(GsapContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTimeLine = (dep:any,all?:boolean) => {
|
export default function GsapProvider({children}:{children:ReactNode}) {
|
||||||
const gsapContext = useGsapContext()
|
|
||||||
useEffect(() => {
|
|
||||||
if (dep instanceof Array && all) {
|
|
||||||
let acc = true;
|
|
||||||
let allDepsSatisfied = dep.reduce((p,c) => c !== undefined && p ,acc )
|
|
||||||
if (allDepsSatisfied) {
|
|
||||||
gsapContext?.resumeTimeline()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (dep) {
|
|
||||||
gsapContext?.resumeTimeline()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},[dep])
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
return () => {
|
|
||||||
gsapContext?.resetTimeline()
|
|
||||||
}
|
|
||||||
},[])
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function GsapProvider({ children }: { children: ReactNode }) {
|
|
||||||
const tl = useRef<gsap.core.Timeline | null>(null)
|
|
||||||
const scrollerRef = useRef<Element | Window | null>(null)
|
|
||||||
const getScroller = useCallback(() => {
|
|
||||||
// const cached = scrollerRef.current
|
|
||||||
// if (!cached || (cached instanceof Element && !document.contains(cached))) {
|
|
||||||
let scrollers = document.querySelectorAll('[data-slot="scroll-area-viewport"]')
|
|
||||||
if (scrollers.length < 1) {
|
|
||||||
scrollerRef.current = window
|
|
||||||
} else {
|
|
||||||
let scrollerArray = Array.from(scrollers.values()).sort((a,b) => {
|
|
||||||
const s1 = a as HTMLDivElement;
|
|
||||||
const s2 = b as HTMLDivElement;
|
|
||||||
// using bitwise not (~~) to coerce NaN values to 0
|
|
||||||
const aPriority = ~~Number(s1.dataset?.scrollerPriority)
|
|
||||||
const bPriority = ~~Number(s2.dataset?.scrollerPriority)
|
|
||||||
return aPriority - bPriority;
|
|
||||||
})
|
|
||||||
let prioScroller = scrollerArray.pop();
|
|
||||||
scrollerRef.current = prioScroller || window;
|
|
||||||
}
|
|
||||||
|
|
||||||
// }
|
|
||||||
return scrollerRef.current
|
|
||||||
}, [])
|
|
||||||
useGSAP(() => {
|
|
||||||
if (!tl.current) {
|
|
||||||
tl.current = gsap.timeline({ paused: true })
|
|
||||||
}
|
|
||||||
return () => { console.log("gsap cleanup") }
|
|
||||||
})
|
|
||||||
|
|
||||||
const addAnimation = useCallback((animation: gsap.core.TimelineChild, position: gsap.Position) => {
|
|
||||||
console.log("add animation to:", position, tl.current !== undefined)
|
|
||||||
tl.current?.add(animation, position);
|
|
||||||
},[])
|
|
||||||
const resetTimeline = useCallback(() => {
|
|
||||||
tl.current?.kill()
|
|
||||||
tl.current?.revert()
|
|
||||||
ScrollTrigger.getAll().forEach(st => st.kill())
|
|
||||||
tl.current = gsap.timeline({paused:true})
|
|
||||||
},[])
|
|
||||||
const resumeTimeline = useCallback(() => {
|
|
||||||
console.log("resuming timeline:",tl.current)
|
|
||||||
tl.current?.resume()
|
|
||||||
},[])
|
|
||||||
return (
|
return (
|
||||||
<GsapContext.Provider value={{ addAnimation, resetTimeline, resumeTimeline, getScroller }}>
|
<GsapContext.Provider value={gsap}>
|
||||||
{children}
|
{children}
|
||||||
</GsapContext.Provider>
|
</GsapContext.Provider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { inferRouterOutputs } from '@trpc/server';
|
|
||||||
import { useUser } from '@clerk/nextjs'
|
|
||||||
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
|
|
||||||
import { trpc } from '~/app/_trpc/Client'
|
|
||||||
import { type ChatRouter } from '~/server/routers/chat'
|
|
||||||
const MessageContext = createContext<{
|
|
||||||
session?: inferRouterOutputs<ChatRouter>['getSession']
|
|
||||||
messages?: inferRouterOutputs<ChatRouter>['getMessages']
|
|
||||||
refetchMessages: () => void
|
|
||||||
clearChat: (callback?: () => void) => void
|
|
||||||
error: string|null
|
|
||||||
isLoading: boolean
|
|
||||||
clearingChat: boolean
|
|
||||||
clearedChat: boolean
|
|
||||||
}>({
|
|
||||||
session: undefined,
|
|
||||||
messages: undefined,
|
|
||||||
refetchMessages: () => undefined,
|
|
||||||
clearChat: () => undefined,
|
|
||||||
error: null,
|
|
||||||
isLoading: true,
|
|
||||||
clearingChat: false,
|
|
||||||
clearedChat: false
|
|
||||||
})
|
|
||||||
export const useMessages = () => useContext(MessageContext)
|
|
||||||
export const MessagesProvider = ({children}:{children:ReactNode}) => {
|
|
||||||
const [error,setError] = useState<string|null>(null)
|
|
||||||
const [isLoading,setIsLoading] = useState<boolean>(true)
|
|
||||||
const { isLoaded, isSignedIn } = useUser()
|
|
||||||
const { data: session,error:sessionError,isLoading:sessionLoading} = trpc.chat.getSession.useQuery(undefined, {
|
|
||||||
enabled: isSignedIn === true,
|
|
||||||
})
|
|
||||||
const { data: messages, refetch, error:messageError, isLoading:messagesLoading } = trpc.chat.getMessages.useQuery(session?.id ? session.id : "", {
|
|
||||||
enabled: isSignedIn === true && session?.id != undefined,
|
|
||||||
})
|
|
||||||
const { mutate ,isPending:clearingChat,isSuccess:clearedChat } = trpc.chat.clearChat.useMutation()
|
|
||||||
const utils = trpc.useUtils()
|
|
||||||
const refetchMessages = () => {
|
|
||||||
if (!isSignedIn) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
utils.chat.getMessages.invalidate()
|
|
||||||
refetch()
|
|
||||||
}
|
|
||||||
const clearChat = (callback?: () => void) => {
|
|
||||||
if (!isSignedIn) {
|
|
||||||
if (callback) {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mutate(undefined,{onSuccess: () => {
|
|
||||||
if (callback) {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
utils.chat.getMessages.invalidate()
|
|
||||||
}})
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
if (isSignedIn !== true) {
|
|
||||||
setError(null)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
messageError && setError(messageError.message)
|
|
||||||
sessionError && setError(sessionError.message)
|
|
||||||
},[messageError,sessionError,isSignedIn])
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isLoaded) {
|
|
||||||
setIsLoading(true)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isSignedIn !== true) {
|
|
||||||
setIsLoading(false)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setIsLoading(sessionLoading || messagesLoading)
|
|
||||||
},[isLoaded,isSignedIn,sessionLoading,messagesLoading])
|
|
||||||
return (
|
|
||||||
<MessageContext.Provider value={
|
|
||||||
{
|
|
||||||
session: isSignedIn === true ? session : undefined,
|
|
||||||
messages: isSignedIn === true ? messages : undefined,
|
|
||||||
refetchMessages,
|
|
||||||
clearChat,
|
|
||||||
error,
|
|
||||||
isLoading,
|
|
||||||
clearingChat,
|
|
||||||
clearedChat
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
{children}
|
|
||||||
</MessageContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,23 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
import * as React from "react"
|
||||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||||
|
|
||||||
export default function ThemeProvider({children}:{children: React.ReactNode}) {
|
export default function ThemeProvider({children}:{children: React.ReactNode}) {
|
||||||
|
const [mounted,setMounted] = React.useState(false)
|
||||||
|
React.useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
})
|
||||||
|
if (mounted) {
|
||||||
return (
|
return (
|
||||||
<NextThemesProvider disableTransitionOnChange attribute="class" defaultTheme="dark">
|
<NextThemesProvider disableTransitionOnChange nonce="test" attribute="class" defaultTheme="dark">
|
||||||
{children}
|
{children}
|
||||||
</NextThemesProvider>
|
</NextThemesProvider>
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,5 @@ import { env } from "~/env"
|
|||||||
|
|
||||||
export async function isAdmin() {
|
export async function isAdmin() {
|
||||||
const userid = (await auth()).userId
|
const userid = (await auth()).userId
|
||||||
console.log(userid)
|
|
||||||
return (userid == env.ADMIN_USER_CLERK_ID)
|
return (userid == env.ADMIN_USER_CLERK_ID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
export default function currentTime() {
|
|
||||||
let now = Date.now();
|
|
||||||
console.log(now);
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
time: now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
'use server'
|
|
||||||
import { clerkClient, auth } from '@clerk/nextjs/server'
|
|
||||||
import { google } from 'googleapis'
|
|
||||||
import { env } from '~/env'
|
|
||||||
|
|
||||||
export async function scheduleMeeting({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
dateTime,
|
|
||||||
durationMinutes,
|
|
||||||
attendeeEmail,
|
|
||||||
attendeeName,
|
|
||||||
}: {
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
dateTime: string
|
|
||||||
durationMinutes: number
|
|
||||||
attendeeEmail?: string
|
|
||||||
attendeeName?: string
|
|
||||||
}) {
|
|
||||||
try {
|
|
||||||
const clerk = await clerkClient()
|
|
||||||
const userAuth = await auth()
|
|
||||||
const user = await clerk.users.getUser(userAuth.userId?userAuth.userId:"")
|
|
||||||
// Get admin's Google OAuth token to create the event on Gregor's calendar
|
|
||||||
const adminTokenResponse = await clerk.users.getUserOauthAccessToken(
|
|
||||||
env.ADMIN_USER_CLERK_ID,
|
|
||||||
'oauth_google',
|
|
||||||
)
|
|
||||||
const adminToken = adminTokenResponse.data[0]
|
|
||||||
|
|
||||||
if (!adminToken?.token) {
|
|
||||||
return { success: false, error: 'Admin Google Calendar not connected. Ensure the admin account is linked with Google and has calendar scope enabled.' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to resolve visitor's Google email for the invite
|
|
||||||
let visitorEmail: string | undefined = attendeeEmail
|
|
||||||
if (!visitorEmail) {
|
|
||||||
visitorEmail = user?.emailAddresses.at(0)?.emailAddress ?? undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const oAuth2Client = new google.auth.OAuth2()
|
|
||||||
oAuth2Client.setCredentials({ access_token: adminToken.token })
|
|
||||||
const calendar = google.calendar({ version: 'v3', auth: oAuth2Client })
|
|
||||||
|
|
||||||
const startTime = new Date(dateTime)
|
|
||||||
const endTime = new Date(startTime.getTime() + durationMinutes * 60 * 1000)
|
|
||||||
|
|
||||||
const attendees: { email: string; displayName?: string }[] = []
|
|
||||||
if (visitorEmail) {
|
|
||||||
attendees.push({ email: visitorEmail, displayName: attendeeName })
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = await calendar.events.insert({
|
|
||||||
calendarId: 'primary',
|
|
||||||
sendUpdates: 'all',
|
|
||||||
requestBody: {
|
|
||||||
summary: title,
|
|
||||||
description,
|
|
||||||
start: { dateTime: startTime.toISOString(), timeZone: 'UTC' },
|
|
||||||
end: { dateTime: endTime.toISOString(), timeZone: 'UTC' },
|
|
||||||
attendees,
|
|
||||||
},
|
|
||||||
sendNotifications: true
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
eventId: event.data.id,
|
|
||||||
htmlLink: event.data.htmlLink,
|
|
||||||
message: `Meeting "${title}" scheduled for ${startTime.toLocaleString()}${visitorEmail ? `. Invite sent to ${visitorEmail}.` : '.'}`,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to schedule meeting:', error)
|
|
||||||
return { success: false, error: 'Failed to schedule meeting. Please try again.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import Link from "next/link";
|
|||||||
import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger } from "~/components/ui/sidebar";
|
import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger } from "~/components/ui/sidebar";
|
||||||
import SimpleSidebarGroup from "~/components/ui/simple-sidebar-group";
|
import SimpleSidebarGroup from "~/components/ui/simple-sidebar-group";
|
||||||
|
|
||||||
export default function AdminSideBar() {
|
export default async function AdminSideBar() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
@@ -20,15 +20,9 @@ export default function AdminSideBar() {
|
|||||||
<Link href={"/admin/project/techStack/create"}> Create Stack </Link>
|
<Link href={"/admin/project/techStack/create"}> Create Stack </Link>
|
||||||
<Link href={"/admin/project/list"}> Project List </Link>
|
<Link href={"/admin/project/list"}> Project List </Link>
|
||||||
</SimpleSidebarGroup>
|
</SimpleSidebarGroup>
|
||||||
<SimpleSidebarGroup lable="Music">
|
|
||||||
<Link href={"/admin/music"}> Manage Music </Link>
|
|
||||||
</SimpleSidebarGroup>
|
|
||||||
<SimpleSidebarGroup lable="Blog">
|
<SimpleSidebarGroup lable="Blog">
|
||||||
<Link href={"/"}> Some Blog Action </Link>
|
<Link href={"/"}> Some Blog Action </Link>
|
||||||
</SimpleSidebarGroup>
|
</SimpleSidebarGroup>
|
||||||
<SimpleSidebarGroup lable="Chat">
|
|
||||||
<Link href={"/admin/chat"}> System Prompt </Link>
|
|
||||||
</SimpleSidebarGroup>
|
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { useState } from 'react'
|
|
||||||
import { Textarea } from '~/components/ui/textarea'
|
|
||||||
import { Button } from '~/components/ui/button'
|
|
||||||
import { trpc } from '~/app/_trpc/Client'
|
|
||||||
|
|
||||||
export default function SystemPromptForm({ initialValue }: { initialValue: string }) {
|
|
||||||
const [value, setValue] = useState(initialValue)
|
|
||||||
const [saved, setSaved] = useState(false)
|
|
||||||
|
|
||||||
const mutation = trpc.chat.updateSystemPrompt.useMutation({
|
|
||||||
onSuccess: () => setSaved(true),
|
|
||||||
})
|
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
|
||||||
e.preventDefault()
|
|
||||||
setSaved(false)
|
|
||||||
mutation.mutate({ prompt: value })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4 w-full">
|
|
||||||
<Textarea
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => { setValue(e.target.value); setSaved(false) }}
|
|
||||||
rows={16}
|
|
||||||
className="font-mono text-sm resize-y"
|
|
||||||
placeholder="Enter the system prompt for the AI recruiter..."
|
|
||||||
/>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Button type="submit" disabled={mutation.isPending}>
|
|
||||||
{mutation.isPending ? 'Saving…' : 'Save'}
|
|
||||||
</Button>
|
|
||||||
{saved && <span className="text-sm text-muted-foreground">Saved</span>}
|
|
||||||
{mutation.error && <span className="text-sm text-destructive">{mutation.error.message}</span>}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { isAdmin } from '~/app/actions'
|
|
||||||
import { redirect } from 'next/navigation'
|
|
||||||
import { servTrpc } from '~/app/_trpc/ServerClient'
|
|
||||||
import SystemPromptForm from './_components/SystemPromptForm'
|
|
||||||
|
|
||||||
export default async function SystemPromptPage() {
|
|
||||||
if (!(await isAdmin())) redirect('/admin')
|
|
||||||
|
|
||||||
const prompt = await servTrpc.chat.getSystemPrompt()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full max-w-2xl p-6 flex flex-col gap-4">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-lg font-semibold">AI System Prompt</h1>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
This prompt is sent to the model on every chat request.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<SystemPromptForm initialValue={prompt} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -14,13 +14,13 @@ import { SelectItem } from '~/components/ui/select';
|
|||||||
import {FormMutationContextProvider} from '~/app/_components/Form/Components/MutationProvider';
|
import {FormMutationContextProvider} from '~/app/_components/Form/Components/MutationProvider';
|
||||||
export default function CreateUpdateCvCategoryForm(params: { className?: string, entity?: IterableElement<RouterOutputs['category']['select']> }) {
|
export default function CreateUpdateCvCategoryForm(params: { className?: string, entity?: IterableElement<RouterOutputs['category']['select']> }) {
|
||||||
const schemas = entitySchemas('cvCategory')
|
const schemas = entitySchemas('cvCategory')
|
||||||
const [id, setId] = useState<string | undefined>(params.entity?.id)
|
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
|
||||||
const form = useForm<z.infer<typeof schemas.insert>>({
|
const form = useForm<z.infer<typeof schemas.insert>>({
|
||||||
resolver: zodResolver(schemas.insert),
|
resolver: zodResolver(schemas.insert),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: params.entity?.id || crypto.randomUUID(),
|
id: params.entity ? params.entity.id : crypto.randomUUID(),
|
||||||
name: params.entity?.name || "",
|
name: params.entity ? params.entity.name : "",
|
||||||
layoutPosition: params.entity?.layoutPosition || "col1"
|
layoutPosition: params.entity ? params.entity.layoutPosition : "col1"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
let path = usePathname()
|
let path = usePathname()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
import AdminSideBar from "./_components/AdminSideBar";
|
import AdminSideBar from "./_components/AdminSideBar";
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
export default async function Admin({children}: Readonly<{children: React.ReactNode}>) {
|
||||||
|
|
||||||
export default function Admin({children}: Readonly<{children: React.ReactNode}>) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AdminSideBar/>
|
<AdminSideBar/>
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { trpc } from "~/app/_trpc/Client";
|
|
||||||
import { UploadDropzone } from "~/lib/uploadthing";
|
|
||||||
import { Label } from "~/components/ui/label";
|
|
||||||
import { FormScaffold } from "~/app/_components/Form/Components";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import type { RouterOutputs } from "~/server/routers/_app";
|
|
||||||
import type { IterableElement } from "type-fest";
|
|
||||||
import { Toaster } from "~/components/ui/sonner";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { FormMutationContextProvider } from "~/app/_components/Form/Components/MutationProvider";
|
|
||||||
import { TextInputFormField } from "~/app/_components/Form/Fields";
|
|
||||||
import { createMusicInputSchema } from "~/lib/trpc/music/schemas";
|
|
||||||
export default function CreateUpdateMusicForm(props: {
|
|
||||||
entity?: IterableElement<RouterOutputs['music']['list']>,
|
|
||||||
className?: string
|
|
||||||
}) {
|
|
||||||
const entity = props.entity;
|
|
||||||
const [id, setId] = useState<string | undefined>(entity?.id)
|
|
||||||
const utils = trpc.useUtils();
|
|
||||||
const form = useForm<z.infer<typeof createMusicInputSchema>>({
|
|
||||||
resolver: zodResolver(createMusicInputSchema),
|
|
||||||
defaultValues: {
|
|
||||||
id: entity?.id || crypto.randomUUID(),
|
|
||||||
title: entity?.title || "",
|
|
||||||
description: entity?.description || "",
|
|
||||||
fileUrl: entity?.fileUrl,
|
|
||||||
fileKey: entity?.fileKey,
|
|
||||||
fileName: entity?.fileName,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const createMutation = trpc.music.create.useMutation({
|
|
||||||
onSuccess: (values) => {
|
|
||||||
setId(values?.id);
|
|
||||||
utils.music.list.invalidate();
|
|
||||||
},
|
|
||||||
onError: (e) => {
|
|
||||||
toast(e.message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const updateMutation = trpc.music.update.useMutation({
|
|
||||||
onSuccess: (_) => {
|
|
||||||
utils.music.list.invalidate();
|
|
||||||
},
|
|
||||||
onError: (e) => {
|
|
||||||
toast(e.message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const deleteMutation = trpc.music.delete.useMutation({
|
|
||||||
onSuccess: (_) => {
|
|
||||||
utils.music.list.invalidate();
|
|
||||||
},
|
|
||||||
onError: (e) => {
|
|
||||||
toast(e.message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function onSubmit(values: z.infer<typeof createMusicInputSchema>) {
|
|
||||||
id ?
|
|
||||||
updateMutation.mutate(values) :
|
|
||||||
createMutation.mutate(values)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Toaster />
|
|
||||||
<FormMutationContextProvider value={{
|
|
||||||
createMutation: createMutation,
|
|
||||||
updateMutation: updateMutation,
|
|
||||||
deleteMutation: deleteMutation
|
|
||||||
}}>
|
|
||||||
<FormScaffold form={form} onSubmit={onSubmit} title='Music' id={id} className={props.className}>
|
|
||||||
<TextInputFormField control={form.control} name='title' label='Title'/>
|
|
||||||
<TextInputFormField control={form.control} name='description' label='Description'/>
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<Label>Audio File</Label>
|
|
||||||
<UploadDropzone
|
|
||||||
endpoint="musicUploader"
|
|
||||||
config={{mode: 'auto'}}
|
|
||||||
onUploadError={(e) => {
|
|
||||||
toast(e.message)
|
|
||||||
}}
|
|
||||||
onClientUploadComplete={(res) => {
|
|
||||||
console.log(res)
|
|
||||||
if (res[0]) {
|
|
||||||
form.setValue('fileKey',res[0].serverData.fileKey);
|
|
||||||
form.setValue('fileName',res[0].serverData.fileName);
|
|
||||||
form.setValue('title',res[0].serverData.fileName);
|
|
||||||
form.setValue('description',res[0].serverData.fileName);
|
|
||||||
form.setValue('fileUrl',res[0].serverData.fileUrl);
|
|
||||||
}
|
|
||||||
console.log(form.getValues());
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</FormScaffold>
|
|
||||||
</FormMutationContextProvider>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { trpc } from "~/app/_trpc/Client";
|
|
||||||
import * as Card from "~/components/ui/card";
|
|
||||||
import UploadMusicForm from "./_components/UploadMusicForm";
|
|
||||||
import { CollapsibleForm } from "~/app/_components/Form/Components";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
export default function AdminMusicPage() {
|
|
||||||
const { data: tracks } = trpc.music.list.useQuery();
|
|
||||||
useEffect(() => {console.log(tracks)}, [tracks])
|
|
||||||
return (
|
|
||||||
<div className="w-5/6 lg:w-1/2 flex flex-col gap-3">
|
|
||||||
{tracks && <>
|
|
||||||
{tracks.map((t) => (
|
|
||||||
<Card.Card key={t.id}>
|
|
||||||
<Card.CardContent>
|
|
||||||
<UploadMusicForm entity={t} className="w-full"/>
|
|
||||||
</Card.CardContent>
|
|
||||||
</Card.Card>
|
|
||||||
))}
|
|
||||||
</>}
|
|
||||||
<CollapsibleForm entityName="Track" form={UploadMusicForm}/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
import { auth } from '@clerk/nextjs/server'
|
|
||||||
import { createOpenAI } from '@ai-sdk/openai'
|
|
||||||
import { streamText, tool, convertToModelMessages, stepCountIs, type UIMessage } from 'ai'
|
|
||||||
import { success, z } from 'zod'
|
|
||||||
import { eq, and } from 'drizzle-orm'
|
|
||||||
import { env } from '~/env'
|
|
||||||
import { db } from '~/server/db'
|
|
||||||
import { chatSession, chatMessage } from '~/server/dbschema/schema'
|
|
||||||
import { servTrpc } from '~/app/_trpc/ServerClient'
|
|
||||||
import { scheduleMeeting } from '~/app/actions/scheduleMeeting'
|
|
||||||
import currentTime from '~/app/actions/currentTime';
|
|
||||||
|
|
||||||
const openai = createOpenAI({ apiKey: env.OPENAI_API_KEY })
|
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
|
||||||
const { userId } = await auth()
|
|
||||||
if (userId == null) return new Response('Unauthorized', { status: 401 })
|
|
||||||
|
|
||||||
const { messages, sessionId } = (await req.json()) as {
|
|
||||||
messages: UIMessage[]
|
|
||||||
sessionId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify this session belongs to the authenticated user
|
|
||||||
const session = await db
|
|
||||||
.select()
|
|
||||||
.from(chatSession)
|
|
||||||
.where(and(eq(chatSession.id, sessionId), eq(chatSession.userId, userId)))
|
|
||||||
.limit(1)
|
|
||||||
.then((r) => r[0])
|
|
||||||
|
|
||||||
if (!session) return new Response('Session not found', { status: 404 })
|
|
||||||
|
|
||||||
const systemPrompt = await servTrpc.chat.getSystemPrompt() || 'You are an AI recruiter assistant.'
|
|
||||||
|
|
||||||
// Save the latest user message
|
|
||||||
const lastMessage = messages[messages.length - 1]
|
|
||||||
if (lastMessage?.role === 'user') {
|
|
||||||
const content = lastMessage.parts
|
|
||||||
.filter((p): p is { type: 'text'; text: string } => p.type === 'text')
|
|
||||||
.map((p) => p.text)
|
|
||||||
.join('')
|
|
||||||
if (content) {
|
|
||||||
await db.insert(chatMessage).values({ sessionId, role: 'user', content })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = streamText({
|
|
||||||
model: openai('gpt-5-mini'),
|
|
||||||
system: systemPrompt,
|
|
||||||
messages: await convertToModelMessages(messages),
|
|
||||||
tools: {
|
|
||||||
scheduleMeeting: tool({
|
|
||||||
description: 'Schedule a meeting with Gregor Lohaus and add it to his Google Calendar',
|
|
||||||
inputSchema: z.object({
|
|
||||||
title: z.string().describe('Meeting title, make something up if not provided'),
|
|
||||||
description: z.string().describe('Meeting description / agenda, make something up if not provided'),
|
|
||||||
dateTime: z
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
'ISO 8601 datetime for the meeting start, e.g. 2025-04-01T10:00:00',
|
|
||||||
),
|
|
||||||
durationMinutes: z
|
|
||||||
.number()
|
|
||||||
.int()
|
|
||||||
.min(15)
|
|
||||||
.max(120)
|
|
||||||
.describe('Duration of the meeting in minutes, if none provided ask if 20 minutes is ok'),
|
|
||||||
attendeeEmail: z
|
|
||||||
.string()
|
|
||||||
.email()
|
|
||||||
.optional()
|
|
||||||
.describe('Optional Email of the visitor to invite (if provided)'),
|
|
||||||
attendeeName: z.string().optional().describe('Name of the visitor'),
|
|
||||||
}),
|
|
||||||
execute: async (input) => scheduleMeeting({ ...input }),
|
|
||||||
}),
|
|
||||||
getCurrentUnixTime: tool({
|
|
||||||
description: 'Get the current unix time to reference for meeting dates',
|
|
||||||
inputSchema: z.object({
|
|
||||||
none: z.string().optional().describe("no inputs are needed")
|
|
||||||
}),
|
|
||||||
execute: async () => currentTime()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
stopWhen: stepCountIs(5),
|
|
||||||
onFinish: async ({ text, finishReason }) => {
|
|
||||||
if (text && finishReason === 'stop') {
|
|
||||||
await db.insert(chatMessage).values({
|
|
||||||
sessionId,
|
|
||||||
role: 'assistant',
|
|
||||||
content: text,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return result.toUIMessageStreamResponse()
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createRouteHandler } from "uploadthing/next";
|
|
||||||
import { fileRouter } from "~/server/uploadthing";
|
|
||||||
|
|
||||||
export const { GET, POST } = createRouteHandler({
|
|
||||||
router: fileRouter,
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { redirect } from 'next/navigation'
|
|
||||||
|
|
||||||
export default function AssistantPage() {
|
|
||||||
redirect('/chat')
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import type { UIMessage } from "ai";
|
|
||||||
import Markdown from "react-markdown";
|
|
||||||
import { cn } from "~/lib/utils";
|
|
||||||
|
|
||||||
export const AssistantMessage = (props: { message: UIMessage }) => {
|
|
||||||
let message = props.message;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={message.id}
|
|
||||||
className='flex justify-start'
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className=
|
|
||||||
'max-w-[80%] px-4 py-2 text-sm space-y-2 bg-muted'
|
|
||||||
>
|
|
||||||
{message.parts.map((part, i) => {
|
|
||||||
if (part.type === 'text') {
|
|
||||||
return (
|
|
||||||
<Markdown>
|
|
||||||
{part.text}
|
|
||||||
</Markdown>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (part.type === 'tool-scheduleMeeting') {
|
|
||||||
const toolPart = part as unknown as {
|
|
||||||
type: 'tool-scheduleMeeting'
|
|
||||||
state: string
|
|
||||||
input: unknown
|
|
||||||
output?: { success: boolean; message?: string; htmlLink?: string; error?: string }
|
|
||||||
}
|
|
||||||
if (toolPart.state === 'input-available' || toolPart.state === 'input-streaming') {
|
|
||||||
return (
|
|
||||||
<p key={i} className="text-xs opacity-70 italic">
|
|
||||||
Scheduling meeting…
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (toolPart.state === 'output-available' && toolPart.output) {
|
|
||||||
const result = toolPart.output
|
|
||||||
return (
|
|
||||||
<div key={i} className="text-xs mt-1 p-2 bg-background/20 rounded">
|
|
||||||
{result.success ? (
|
|
||||||
<span>
|
|
||||||
✓ {result.message}{' '}
|
|
||||||
{result.htmlLink && (
|
|
||||||
<a
|
|
||||||
href={result.htmlLink}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
View event
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>✗ {result.error}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { useChat } from '@ai-sdk/react'
|
|
||||||
import { DefaultChatTransport, type UIMessage } from 'ai'
|
|
||||||
import { Button } from '~/components/ui/button'
|
|
||||||
import { Textarea } from '~/components/ui/textarea'
|
|
||||||
import { SignInButton } from '@clerk/nextjs'
|
|
||||||
import {
|
|
||||||
useGsapContext,
|
|
||||||
} from '~/app/_providers/GsapProvicer';
|
|
||||||
import Messages from './Messages'
|
|
||||||
import { DeleteIcon } from 'lucide-react';
|
|
||||||
import { Spinner } from '~/components/ui/spinner';
|
|
||||||
import { useMessages } from '~/app/_providers/MessagesProvider';
|
|
||||||
interface DBMessage {
|
|
||||||
id: string
|
|
||||||
role: 'user' | 'assistant'
|
|
||||||
content: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChatInterfaceProps {
|
|
||||||
sessionId?: string,
|
|
||||||
dbMessages: DBMessage[],
|
|
||||||
}
|
|
||||||
|
|
||||||
function SignInChatPrompt() {
|
|
||||||
return (
|
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center gap-4 text-center">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h2 className="text-xl font-semibold">Sign in to use the chat</h2>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
You need to be signed in before you can talk to Gregor's AI assistant.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<SignInButton mode="modal">
|
|
||||||
<Button type="button">Sign in</Button>
|
|
||||||
</SignInButton>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toUIMessages(dbMessages: DBMessage[]): UIMessage[] {
|
|
||||||
return dbMessages.map((m) => ({
|
|
||||||
id: m.id,
|
|
||||||
role: m.role,
|
|
||||||
parts: [{ type: 'text' as const, text: m.content }],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
function addInitMessage(messageArray: UIMessage[]) {
|
|
||||||
if (messageArray.at(0)?.id != 'init') {
|
|
||||||
messageArray.unshift({
|
|
||||||
id: "init",
|
|
||||||
role: 'assistant',
|
|
||||||
parts: [{
|
|
||||||
type: 'text',
|
|
||||||
text: "Hi im gregors ai assistant,you can ask me to provide general information or to schedule a meeting."
|
|
||||||
}],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function AuthenticatedChatInterface({ dbMessages, sessionId }: ChatInterfaceProps & { sessionId: string }) {
|
|
||||||
const [input, setInput] = useState('')
|
|
||||||
const { clearingChat, clearChat, refetchMessages } = useMessages();
|
|
||||||
const initialMessages = toUIMessages(dbMessages)
|
|
||||||
addInitMessage(initialMessages)
|
|
||||||
const { messages, sendMessage, status, error, clearError, setMessages } = useChat({
|
|
||||||
transport: new DefaultChatTransport({
|
|
||||||
api: '/api/chat', body: { sessionId },
|
|
||||||
}),
|
|
||||||
messages: initialMessages,
|
|
||||||
})
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
refetchMessages()
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
const handleSend = () => {
|
|
||||||
const text = input.trim()
|
|
||||||
if (!text || status != 'ready' || clearingChat) return
|
|
||||||
setInput('')
|
|
||||||
sendMessage({ text })
|
|
||||||
}
|
|
||||||
const gsapContext = useGsapContext()
|
|
||||||
useEffect(() => {
|
|
||||||
let scroller = gsapContext?.getScroller()
|
|
||||||
if (scroller instanceof Window) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(scroller?.scrollHeight)
|
|
||||||
scroller?.scrollTo({ behavior: 'smooth', top: scroller.scrollHeight })
|
|
||||||
}, [messages])
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col h-full">
|
|
||||||
{messages &&
|
|
||||||
<Messages status={status} messages={messages} />
|
|
||||||
}
|
|
||||||
{error && (
|
|
||||||
<div className="mx-4 mb-2 flex items-start gap-2 rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
|
||||||
<span className="flex-1">
|
|
||||||
{error.message.includes('quota') || error.message.includes('429')
|
|
||||||
? 'OpenAI quota exceeded. Please try again later.'
|
|
||||||
: `Error: ${error.message}`}
|
|
||||||
</span>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={clearError}
|
|
||||||
className="shrink-0 opacity-60 hover:opacity-100"
|
|
||||||
variant='destructive'
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="p-4 border-t flex flex-row gap-2">
|
|
||||||
<Textarea
|
|
||||||
name='message'
|
|
||||||
value={input}
|
|
||||||
onChange={(e) => setInput(e.target.value)}
|
|
||||||
placeholder="Ask about Gregor's experience or schedule a meeting…"
|
|
||||||
className="resize-none"
|
|
||||||
rows={2}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
|
||||||
e.preventDefault()
|
|
||||||
handleSend()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className='flex flex-col gap-2'>
|
|
||||||
<Button
|
|
||||||
onClick={handleSend}
|
|
||||||
disabled={status != "ready" || !input.trim()}
|
|
||||||
>
|
|
||||||
Send
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant='destructive'
|
|
||||||
onClick={() => {
|
|
||||||
clearChat(() => {
|
|
||||||
let messages: UIMessage[] = [];
|
|
||||||
addInitMessage(messages);
|
|
||||||
setMessages(messages)
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
disabled={status != "ready" || clearingChat}
|
|
||||||
>
|
|
||||||
{clearingChat ?
|
|
||||||
<Spinner /> :
|
|
||||||
"Clear Chat"
|
|
||||||
}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChatInterface({ dbMessages, sessionId }: ChatInterfaceProps) {
|
|
||||||
if (sessionId == undefined) {
|
|
||||||
return <SignInChatPrompt />
|
|
||||||
}
|
|
||||||
return <AuthenticatedChatInterface sessionId={sessionId} dbMessages={dbMessages} />
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { type ChatStatus, type UIMessage } from 'ai'
|
|
||||||
import * as Card from "~/components/ui/card"
|
|
||||||
import { UserMessage } from './UserMessage';
|
|
||||||
import { AssistantMessage } from './AssistantMessage';
|
|
||||||
import { ScrollArea } from '~/components/ui/scroll-area';
|
|
||||||
import { memo } from 'react';
|
|
||||||
const Messages = memo(({messages,status}: { messages: UIMessage[],status:ChatStatus}) => {
|
|
||||||
return (
|
|
||||||
<ScrollArea data-scroller-priority='1' className="w-full h-[90%] max-w-4xl mx-auto">
|
|
||||||
{messages.map((message, i) => (
|
|
||||||
<Card.AnimatedCard scrollOnly={true} key={i}>
|
|
||||||
<Card.CardContent>
|
|
||||||
{message.role == 'assistant' && <AssistantMessage message={message} />}
|
|
||||||
{message.role == 'user' && <UserMessage message={message} />}
|
|
||||||
</Card.CardContent>
|
|
||||||
</Card.AnimatedCard>
|
|
||||||
))}
|
|
||||||
{status == 'submitted' &&
|
|
||||||
<Card.AnimatedCard scrollOnly={true}>
|
|
||||||
<Card.CardContent>
|
|
||||||
<AssistantMessage message={{
|
|
||||||
id:"",
|
|
||||||
role:"assistant",
|
|
||||||
parts:[{
|
|
||||||
type:'text',
|
|
||||||
text:'Thinking ...'
|
|
||||||
}]
|
|
||||||
}}/>
|
|
||||||
</Card.CardContent>
|
|
||||||
</Card.AnimatedCard>
|
|
||||||
|
|
||||||
}
|
|
||||||
</ScrollArea>)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default Messages;
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import type { UIMessage } from "ai"
|
|
||||||
|
|
||||||
export const UserMessage = (props:{message: UIMessage}) => {
|
|
||||||
let message = props.message.parts.reduce((acc, part) => {
|
|
||||||
if (part.type == 'text') {
|
|
||||||
return acc + part.text
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
},"");
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={props.message.id}
|
|
||||||
className='flex justify-end'
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className=
|
|
||||||
'max-w-[80%] px-4 py-2 text-sm space-y-2 bg-primary'
|
|
||||||
>
|
|
||||||
{message}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import ChatInterface from './_components/ChatInterface'
|
|
||||||
import AnimatedPageTitle from '../_components/Animated/AnimatedPageTitle';
|
|
||||||
import { useTimeLine } from '../_providers/GsapProvicer';
|
|
||||||
import { useMessages } from '../_providers/MessagesProvider';
|
|
||||||
import { Spinner } from '~/components/ui/spinner';
|
|
||||||
|
|
||||||
export default function ChatPage() {
|
|
||||||
const {messages,session,isLoading,error} = useMessages()
|
|
||||||
useTimeLine(messages)
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col px-10 lg:px-0 w-full h-full max-w-4xl mx-auto pt-10">
|
|
||||||
<AnimatedPageTitle position={0}>
|
|
||||||
<span>Talk To My </span> <span> AI-Assistant</span>
|
|
||||||
</AnimatedPageTitle>
|
|
||||||
<div className='flex items-center h-[80%] w-full my-auto w-full'>
|
|
||||||
{!isLoading &&
|
|
||||||
<ChatInterface sessionId={session?.id} dbMessages={messages ?? []}/>
|
|
||||||
}
|
|
||||||
{isLoading &&
|
|
||||||
<><Spinner/> Loading Messages...</>
|
|
||||||
}
|
|
||||||
{error &&
|
|
||||||
<div> {error} </div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useGSAP } from "@gsap/react";
|
import { useGSAP } from "@gsap/react";
|
||||||
import { useGsapContext,useTimeLine } from "../_providers/GsapProvicer";
|
import { useGsapContext } from "../_providers/GsapProvicer";
|
||||||
import { trpc } from "../_trpc/Client";
|
import { trpc } from "../_trpc/Client";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { SidebarContent, SidebarProvider, Sidebar } from "~/components/ui/sidebar";
|
import { SidebarContent, SidebarProvider, Sidebar } from "~/components/ui/sidebar";
|
||||||
import SidebarTriggerDisappearsOnMobile from "./_components/SidebarTriggerDisappearsOnMobile";
|
import SidebarTriggerDisappearsOnMobile from "./_components/SidebarTriggerDisappearsOnMobile";
|
||||||
import CvCategory from "./_components/CvCategory";
|
import CvCategory from "./_components/CvCategory";
|
||||||
import gsap from 'gsap'
|
|
||||||
export default function CvPage() {
|
export default function CvPage() {
|
||||||
const sidebarCategories = trpc.categoryv2.listByLayoutPosition.useQuery("sidebar");
|
const sidebarCategories = trpc.categoryv2.listByLayoutPosition.useQuery("sidebar");
|
||||||
const col1Categories = trpc.categoryv2.listByLayoutPosition.useQuery("col1");
|
const col1Categories = trpc.categoryv2.listByLayoutPosition.useQuery("col1");
|
||||||
const headerCategories = trpc.categoryv2.listByLayoutPosition.useQuery("header");
|
const headerCategories = trpc.categoryv2.listByLayoutPosition.useQuery("header");
|
||||||
const col2Categories = trpc.categoryv2.listByLayoutPosition.useQuery("col2");
|
const col2Categories = trpc.categoryv2.listByLayoutPosition.useQuery("col2");
|
||||||
const gsapContext = useGsapContext()
|
const gsap = useGsapContext()
|
||||||
const container = useRef<HTMLDivElement>(null)
|
const container = useRef<HTMLDivElement>(null)
|
||||||
enum Direction {
|
enum Direction {
|
||||||
Left = 1,
|
Left = 1,
|
||||||
@@ -32,12 +31,12 @@ export default function CvPage() {
|
|||||||
return { y: 100, opacity: 0, duration: 0.5 }
|
return { y: 100, opacity: 0, duration: 0.5 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
useTimeLine(col2Categories)
|
|
||||||
useGSAP(() => {
|
useGSAP(() => {
|
||||||
const items = gsap?.utils.toArray<GSAPTweenTarget>('.gsapan');
|
const items = gsap?.utils.toArray<GSAPTweenTarget>('.gsapan');
|
||||||
|
const tl = gsap?.timeline();
|
||||||
let dir = Direction.Left;
|
let dir = Direction.Left;
|
||||||
items?.forEach(item => {
|
items?.forEach(item => {
|
||||||
gsapContext?.addAnimation(gsap.from(item, nextGsapConf(dir)),0)
|
tl?.from(item, nextGsapConf(dir))
|
||||||
if (dir == Direction.Down) {
|
if (dir == Direction.Down) {
|
||||||
dir = Direction.Left
|
dir = Direction.Left
|
||||||
} else {
|
} else {
|
||||||
@@ -48,7 +47,7 @@ export default function CvPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SidebarProvider ref={container}>
|
<SidebarProvider ref={container}>
|
||||||
{sidebarCategories.data &&
|
{(sidebarCategories.data?.length ? sidebarCategories.data?.length : 0) > 0 ?
|
||||||
<>
|
<>
|
||||||
<SidebarTriggerDisappearsOnMobile />
|
<SidebarTriggerDisappearsOnMobile />
|
||||||
<Sidebar className="gsapan ">
|
<Sidebar className="gsapan ">
|
||||||
@@ -62,7 +61,8 @@ export default function CvPage() {
|
|||||||
})}
|
})}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
</>
|
</> :
|
||||||
|
<></>
|
||||||
}
|
}
|
||||||
<div className="h-full w-full flex flex-wrap flex-row p-4 pt-8 ">
|
<div className="h-full w-full flex flex-wrap flex-row p-4 pt-8 ">
|
||||||
<div id="mainwrap" className="flex w-full flex-col gap-4 lg:px-[15vw]">
|
<div id="mainwrap" className="flex w-full flex-col gap-4 lg:px-[15vw]">
|
||||||
|
|||||||
@@ -5,16 +5,13 @@ import { ClerkProvider } from "@clerk/nextjs";
|
|||||||
import { config } from "@fortawesome/fontawesome-svg-core"
|
import { config } from "@fortawesome/fontawesome-svg-core"
|
||||||
import "@fortawesome/fontawesome-svg-core/styles.css"
|
import "@fortawesome/fontawesome-svg-core/styles.css"
|
||||||
import TopNav from "./_components/TopNav";
|
import TopNav from "./_components/TopNav";
|
||||||
import ChatFAB from "./_components/ChatFAB";
|
|
||||||
import TrpcProvider from "./_trpc/TrpcProvider";
|
import TrpcProvider from "./_trpc/TrpcProvider";
|
||||||
// import dynamic from "next/dynamic";
|
// import dynamic from "next/dynamic";
|
||||||
// const ThemeProvider = dynamic(() => import("./_providers/ThemeProvider"),{ssr:true})
|
// const ThemeProvider = dynamic(() => import("./_providers/ThemeProvider"),{ssr:true})
|
||||||
import ThemeProvider from './_providers/ThemeProvider'
|
import ThemeProvider from './_providers/ThemeProvider'
|
||||||
import GsapProvider from "./_providers/GsapProvicer";
|
import GsapProvider from "./_providers/GsapProvicer";
|
||||||
import {MessagesProvider} from "./_providers/MessagesProvider";
|
|
||||||
import { CodeHighlightStyle } from "./_components/CodeHighlightSyle";
|
import { CodeHighlightStyle } from "./_components/CodeHighlightSyle";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import AnimatedBackGroundContainer from "./_components/Animated/AnimatedBackGroundContainer";
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'], variable: '--font-sans' });
|
const inter = Inter({ subsets: ['latin'], variable: '--font-sans' });
|
||||||
|
|
||||||
@@ -31,6 +28,7 @@ const geist = Geist({
|
|||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default async function RootLayout({
|
||||||
children,
|
children,
|
||||||
modal
|
modal
|
||||||
@@ -46,16 +44,11 @@ export default async function RootLayout({
|
|||||||
</head>
|
</head>
|
||||||
<body className="flex flex-col bg-background text-foreground">
|
<body className="flex flex-col bg-background text-foreground">
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<MessagesProvider>
|
|
||||||
<AnimatedBackGroundContainer followSpeed={0.003} particleCount={100} orbitRadius={2000}>
|
|
||||||
<TopNav />
|
<TopNav />
|
||||||
<main className="absolute lg:top-10 h-screen lg:h-[calc(100vh-var(--spacing)*10)] w-screen">
|
<main className="absolute lg:top-10 h-screen w-screen">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
{modal}
|
{modal}
|
||||||
</AnimatedBackGroundContainer>
|
|
||||||
<ChatFAB />
|
|
||||||
</MessagesProvider>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { trpc } from "~/app/_trpc/Client";
|
|
||||||
import * as Card from "~/components/ui/card";
|
|
||||||
import { useTimeLine } from "../_providers/GsapProvicer";
|
|
||||||
import AnimatedPageTitle from "../_components/Animated/AnimatedPageTitle";
|
|
||||||
import { Spinner } from "~/components/ui/spinner";
|
|
||||||
import AnimateTextIn from "../_components/Animated/AnimateIn";
|
|
||||||
import { ScrollArea } from "~/components/ui/scroll-area";
|
|
||||||
import AnimatePopUp from "../_components/Animated/AnimatePopUp";
|
|
||||||
export default function MusicPage() {
|
|
||||||
const { data: tracks, isLoading } = trpc.music.list.useQuery();
|
|
||||||
useTimeLine(tracks)
|
|
||||||
return (
|
|
||||||
<ScrollArea className="px-10 lg:px-0 w-full h-full max-w-4xl mx-auto pt-10">
|
|
||||||
<AnimatedPageTitle position={0}><span>Just Some </span> <span>Music I Made</span> </AnimatedPageTitle>
|
|
||||||
<div className="flex flex-wrap h-fit content-center">
|
|
||||||
<AnimateTextIn className="flex flex-wrap mr-[1em]" position={0.5}>
|
|
||||||
<div><p className="break-after-avoid mr-[1em]">All works on this page are licensed under:</p></div>
|
|
||||||
<div><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></div>
|
|
||||||
</AnimateTextIn>
|
|
||||||
<AnimatePopUp position={2} className="items-center content-center">
|
|
||||||
<div className="flex flex-row">
|
|
||||||
<img className="max-w-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="" />
|
|
||||||
<img className="max-w-[1em] ml-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="" />
|
|
||||||
<img className="max-w-[1em] ml-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg" alt="" />
|
|
||||||
<img className="max-w-[1em] ml-[1em]" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" alt="" />
|
|
||||||
</div>
|
|
||||||
</AnimatePopUp>
|
|
||||||
</div>
|
|
||||||
<div className="pt-10" />
|
|
||||||
{tracks && tracks.map((track, i) => (
|
|
||||||
<div key={track.id}>
|
|
||||||
<Card.AnimatedCard position={i + 1}>
|
|
||||||
<Card.CardHeader>
|
|
||||||
<AnimateTextIn position={i + 1.2} animation="slide">
|
|
||||||
<Card.CardTitle>{track.title}</Card.CardTitle>
|
|
||||||
</AnimateTextIn>
|
|
||||||
</Card.CardHeader>
|
|
||||||
<Card.CardContent className="flex flex-col gap-3">
|
|
||||||
{track.description && (
|
|
||||||
<p className="text-sm text-muted-foreground gsapant">{track.description}</p>
|
|
||||||
)}
|
|
||||||
<AnimatePopUp position={i + 1.3}>
|
|
||||||
<audio controls className="w-full player" src={track.fileUrl}>
|
|
||||||
Your browser does not support the audio element.
|
|
||||||
</audio>
|
|
||||||
</AnimatePopUp>
|
|
||||||
</Card.CardContent>
|
|
||||||
</Card.AnimatedCard>
|
|
||||||
<div className="pt-5" />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{!isLoading && !tracks?.length &&
|
|
||||||
<div className="flex justify-center items-center text-muted-foreground">
|
|
||||||
No music yet.
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{isLoading && <div className="w-full h-full items-center flex flex-row content-center gap-4 justify-center">
|
|
||||||
<Spinner /> Loading Tracks
|
|
||||||
</div>}
|
|
||||||
</ScrollArea>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -5,16 +5,10 @@ import * as Card from "~/components/ui/card";
|
|||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
import { StackBadge } from "~/components/StackBadge";
|
import { StackBadge } from "~/components/StackBadge";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import { ScrollArea } from "~/components/ui/scroll-area";
|
|
||||||
import AnimatedPageTitle from "../_components/Animated/AnimatedPageTitle";
|
|
||||||
import AnimateTextIn from "../_components/Animated/AnimateIn";
|
|
||||||
import { useTimeLine } from "../_providers/GsapProvicer";
|
|
||||||
import AnimatePopUp from "../_components/Animated/AnimatePopUp";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
|
|
||||||
export default function ProjectsPage() {
|
export default function ProjectsPage() {
|
||||||
const { data: projects, isLoading } = trpc.projectv2.listWithStack.useQuery();
|
const { data: projects, isLoading } = trpc.projectv2.listWithStack.useQuery();
|
||||||
useTimeLine(projects)
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center min-h-[200px] text-muted-foreground">
|
<div className="flex justify-center items-center min-h-[200px] text-muted-foreground">
|
||||||
@@ -32,22 +26,17 @@ export default function ProjectsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea className="px-10 lg:px-0 w-full h-full max-w-4xl mx-auto pt-10">
|
<div className="w-full max-w-4xl mx-auto px-4 py-8 flex flex-col gap-4">
|
||||||
<AnimatedPageTitle position={0}><span>Project I've Been</span><span> Working on</span> </AnimatedPageTitle>
|
{projects.map((project) => (
|
||||||
<div className="pt-10" />
|
<Card.Card key={project.id}>
|
||||||
{projects.map((project, i) => (
|
|
||||||
<div key={i}>
|
|
||||||
<Card.AnimatedCard position={i + 1.2} key={project.id}>
|
|
||||||
<Card.CardHeader>
|
<Card.CardHeader>
|
||||||
<div className="flex items-start justify-between gap-2 flex-wrap">
|
<div className="flex items-start justify-between gap-2 flex-wrap">
|
||||||
<AnimateTextIn position={i + 1.4} animation="slide"><Card.CardTitle>{project.title}</Card.CardTitle></AnimateTextIn>
|
<Card.CardTitle>{project.title}</Card.CardTitle>
|
||||||
<div className="flex gap-2 flex-wrap">
|
<div className="flex gap-2 flex-wrap">
|
||||||
{project.sourceType && (
|
{project.sourceType && (
|
||||||
<AnimatePopUp position={i + 2} duration={2}>
|
|
||||||
<Badge variant={project.sourceType === "open" ? "secondary" : "outline"}>
|
<Badge variant={project.sourceType === "open" ? "secondary" : "outline"}>
|
||||||
{project.sourceType === "open" ? "Open Source" : "Closed Source"}
|
{project.sourceType === "open" ? "Open Source" : "Closed Source"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</AnimatePopUp>
|
|
||||||
)}
|
)}
|
||||||
{project.releaseStatus && (
|
{project.releaseStatus && (
|
||||||
<Badge variant={project.releaseStatus === "released" ? "default" : "outline"}>
|
<Badge variant={project.releaseStatus === "released" ? "default" : "outline"}>
|
||||||
@@ -61,54 +50,44 @@ export default function ProjectsPage() {
|
|||||||
<Card.CardContent className="flex flex-col gap-3">
|
<Card.CardContent className="flex flex-col gap-3">
|
||||||
{project.description && (
|
{project.description && (
|
||||||
<div className="prose prose-sm dark:prose-invert max-w-none text-muted-foreground">
|
<div className="prose prose-sm dark:prose-invert max-w-none text-muted-foreground">
|
||||||
<AnimatePopUp position={i + 1.4} duration={project.description.length / 20}>
|
<Markdown>{project.description}</Markdown>
|
||||||
<AnimateTextIn position={i + 1.5} animation="slide"><Markdown>{project.description}</Markdown></AnimateTextIn></AnimatePopUp>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex flex-row">
|
|
||||||
{project.techStack?.stackItems && project.techStack.stackItems.length > 0 && (
|
|
||||||
<div className="flex flex-wrap gap-1.5">
|
|
||||||
{project.techStack.stackItems.map((item, k) => (
|
|
||||||
<AnimatePopUp key={k} position={(i + 2) + k * 0.5}> <StackBadge key={item} item={item} /> </AnimatePopUp>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(project.sourceLink || project.releaseLink) && (
|
{(project.sourceLink || project.releaseLink) && (
|
||||||
<div className="ml-auto flex-col lg:flex-row justify-center gap-5">
|
<div className="flex gap-3 flex-wrap">
|
||||||
{project.sourceLink &&
|
{project.sourceLink && (
|
||||||
<Button variant='outline' className="cursor-pointer mb-3 lg:mb-0 lg:mr-3 min-w-18">
|
|
||||||
<a
|
<a
|
||||||
href={project.sourceLink}
|
href={project.sourceLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className='items-center'
|
className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4 transition-colors"
|
||||||
>
|
>
|
||||||
Source
|
Source
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
)}
|
||||||
}
|
{project.releaseLink && (
|
||||||
{project.releaseLink &&
|
|
||||||
<Button variant='default' className="cursor-pointer min-w-18 items-center">
|
|
||||||
<a
|
<a
|
||||||
href={project.releaseLink}
|
href={project.releaseLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className='items-center'
|
className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4 transition-colors"
|
||||||
>
|
>
|
||||||
Live
|
Live
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
|
||||||
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{project.techStack?.stackItems && project.techStack.stackItems.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{project.techStack.stackItems.map((item) => (
|
||||||
|
<StackBadge key={item} item={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Card.CardContent>
|
</Card.CardContent>
|
||||||
)}
|
)}
|
||||||
</Card.AnimatedCard>
|
</Card.Card>
|
||||||
<div className="pt-5" />
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</ScrollArea>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { useGSAP } from "@gsap/react"; import * as React from "react"
|
import * as React from "react"
|
||||||
import { useRef } from "react";
|
|
||||||
import { useGsapContext } from "~/app/_providers/GsapProvicer";
|
|
||||||
import gsap from 'gsap'
|
|
||||||
import { cn } from "~/lib/utils"
|
import { cn } from "~/lib/utils"
|
||||||
|
|
||||||
function Card({
|
function Card({
|
||||||
@@ -14,60 +12,7 @@ function Card({
|
|||||||
data-slot="card"
|
data-slot="card"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl bg-opacity-60 backdrop-blur-sm",
|
"group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function AnimatedCard({
|
|
||||||
className,
|
|
||||||
position = 0,
|
|
||||||
size = "default",
|
|
||||||
scrollOnly = false,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"div"> & { size?: "default" | "sm", position?: gsap.Position, scrollOnly?: boolean }) {
|
|
||||||
const gsapContext = useGsapContext()
|
|
||||||
const ref = useRef<HTMLDivElement | null>(null)
|
|
||||||
useGSAP(() => {
|
|
||||||
const rect = ref.current?.getBoundingClientRect()
|
|
||||||
const scroller = gsapContext?.getScroller()
|
|
||||||
console.log(scroller)
|
|
||||||
let viewportTop = 0
|
|
||||||
let viewportBottom = window.innerHeight
|
|
||||||
if (scroller && scroller instanceof Element) {
|
|
||||||
const scrollerRect = scroller.getBoundingClientRect()
|
|
||||||
viewportTop = scrollerRect.top
|
|
||||||
viewportBottom = scrollerRect.top + scrollerRect.height
|
|
||||||
}
|
|
||||||
const isInView = rect && rect.bottom > viewportTop && rect.top < viewportBottom
|
|
||||||
console.log(isInView)
|
|
||||||
const fromVars = { x: -100, opacity: 0, duration: 0.5 }
|
|
||||||
if (isInView && !scrollOnly) {
|
|
||||||
gsapContext?.addAnimation(gsap.from(ref.current, fromVars), position)
|
|
||||||
} else {
|
|
||||||
gsap.from(ref.current,
|
|
||||||
{
|
|
||||||
...fromVars,
|
|
||||||
scrollTrigger: {
|
|
||||||
trigger: ref.current,
|
|
||||||
start: 'top bottom',
|
|
||||||
end: 'bottom top',
|
|
||||||
toggleActions: "play reverse play reverse",
|
|
||||||
scroller
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, { dependencies: [] })
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={ref}
|
|
||||||
data-slot="card"
|
|
||||||
data-size={size}
|
|
||||||
className={cn(
|
|
||||||
"group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl bg-opacity-60 backdrop-blur-sm",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -155,5 +100,4 @@ export {
|
|||||||
CardAction,
|
CardAction,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardContent,
|
CardContent,
|
||||||
AnimatedCard
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import { cn } from "~/lib/utils"
|
|
||||||
import { Loader2Icon } from "lucide-react"
|
|
||||||
|
|
||||||
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
|
||||||
return (
|
|
||||||
<Loader2Icon role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Spinner }
|
|
||||||
@@ -27,8 +27,6 @@ export const env = createEnv({
|
|||||||
|
|
||||||
CLERK_SECRET_KEY: z.string(),
|
CLERK_SECRET_KEY: z.string(),
|
||||||
ADMIN_USER_CLERK_ID: z.string(),
|
ADMIN_USER_CLERK_ID: z.string(),
|
||||||
UPLOADTHING_TOKEN: z.string(),
|
|
||||||
OPENAI_API_KEY: z.string(),
|
|
||||||
NODE_ENV: z
|
NODE_ENV: z
|
||||||
.enum(["development", "test", "production"])
|
.enum(["development", "test", "production"])
|
||||||
.default("development"),
|
.default("development"),
|
||||||
@@ -66,8 +64,6 @@ export const env = createEnv({
|
|||||||
POSTGRES_URL_NO_SSL: process.env.POSTGRES_URL_NO_SSL,
|
POSTGRES_URL_NO_SSL: process.env.POSTGRES_URL_NO_SSL,
|
||||||
POSTGRES_PRISMA_URL: process.env.POSTGRES_PRISMA_URL,
|
POSTGRES_PRISMA_URL: process.env.POSTGRES_PRISMA_URL,
|
||||||
ADMIN_USER_CLERK_ID: process.env.ADMIN_USER_CLERK_ID,
|
ADMIN_USER_CLERK_ID: process.env.ADMIN_USER_CLERK_ID,
|
||||||
UPLOADTHING_TOKEN: process.env.UPLOADTHING_TOKEN,
|
|
||||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
|
||||||
NEXT_PUBLIC_ADMIN_USER_CLERK_ID: process.env.NEXT_PUBLIC_ADMIN_USER_CLERK_ID,
|
NEXT_PUBLIC_ADMIN_USER_CLERK_ID: process.env.NEXT_PUBLIC_ADMIN_USER_CLERK_ID,
|
||||||
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
|
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
|
||||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { UseTRPCQueryResult } from "node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs"
|
import type { UseTRPCQueryResult } from "node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs"
|
||||||
import { useEffect, useRef, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
function useRelationShipSuccess<T extends Record<string,any> & {id:string},K extends keyof T>(
|
function useRelationShipSuccess<T extends Record<string,any> & {id:string},K extends keyof T>(
|
||||||
relationShipData: T[] | undefined,
|
relationShipData: T[] | undefined,
|
||||||
@@ -56,10 +56,3 @@ export function makeUseRelationShipWithNameIndex<K extends string>(key:K) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePrevious<T>(value:T,initialValue:T) {
|
|
||||||
const ref = useRef(initialValue)
|
|
||||||
useEffect(() => {
|
|
||||||
ref.current = value
|
|
||||||
})
|
|
||||||
return ref.current;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import z from "zod"
|
|
||||||
|
|
||||||
export const createMusicInputSchema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
title: z.string().min(1).max(100),
|
|
||||||
description: z.string().optional(),
|
|
||||||
fileUrl: z.string(),
|
|
||||||
fileKey: z.string(),
|
|
||||||
fileName: z.string(),
|
|
||||||
})
|
|
||||||
export const updateMusicInputSchema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
title: z.string().min(1).max(100).optional(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
fileUrl: z.string().optional(),
|
|
||||||
fileKey: z.string().optional(),
|
|
||||||
fileName: z.string().optional(),
|
|
||||||
})
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { generateUploadButton, generateUploadDropzone } from "@uploadthing/react";
|
|
||||||
import type { FileRouter } from "~/server/uploadthing";
|
|
||||||
|
|
||||||
export const UploadButton = generateUploadButton<FileRouter>();
|
|
||||||
export const UploadDropzone = generateUploadDropzone<FileRouter>();
|
|
||||||
@@ -85,62 +85,3 @@ export const techStack = createTable(
|
|||||||
stackItems: stackItemEnum().array()
|
stackItems: stackItemEnum().array()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
export const music = createTable(
|
|
||||||
"music",
|
|
||||||
(d) => ({
|
|
||||||
id: d.uuid().primaryKey().notNull(),
|
|
||||||
title: d.varchar({ length: 100 }).notNull(),
|
|
||||||
description: d.text(),
|
|
||||||
fileUrl: d.varchar("file_url", { length: 500 }).notNull(),
|
|
||||||
fileKey: d.varchar("file_key", { length: 200 }).notNull(),
|
|
||||||
fileName: d.varchar("file_name", { length: 200 }).notNull(),
|
|
||||||
createdAt: d
|
|
||||||
.timestamp({ withTimezone: true })
|
|
||||||
.default(sql`CURRENT_TIMESTAMP`)
|
|
||||||
.notNull()
|
|
||||||
.$type<Date>(),
|
|
||||||
updatedAt: d.timestamp({ withTimezone: true }).$onUpdate(() => new Date()).$type<Date>(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export const messageRoleEnum = pgEnum('message_role', ['user', 'assistant'])
|
|
||||||
|
|
||||||
export const chatSession = createTable(
|
|
||||||
"chat_session",
|
|
||||||
(d) => ({
|
|
||||||
id: d.uuid().primaryKey().defaultRandom(),
|
|
||||||
userId: d.varchar({ length: 255 }).notNull(),
|
|
||||||
createdAt: d.timestamp({ withTimezone: true }).default(sql`CURRENT_TIMESTAMP`).notNull().$type<Date>(),
|
|
||||||
updatedAt: d.timestamp({ withTimezone: true }).$onUpdate(() => new Date()).$type<Date>(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export const chatSessionRelations = relations(chatSession, ({ many }) => ({
|
|
||||||
messages: many(chatMessage),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const chatMessage = createTable(
|
|
||||||
"chat_message",
|
|
||||||
(d) => ({
|
|
||||||
id: d.uuid().primaryKey().defaultRandom(),
|
|
||||||
sessionId: d.uuid('session_id').notNull(),
|
|
||||||
role: messageRoleEnum().notNull(),
|
|
||||||
content: d.text().notNull(),
|
|
||||||
createdAt: d.timestamp({ withTimezone: true }).default(sql`CURRENT_TIMESTAMP`).notNull().$type<Date>(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export const chatMessageRelations = relations(chatMessage, ({ one }) => ({
|
|
||||||
session: one(chatSession, {
|
|
||||||
fields: [chatMessage.sessionId],
|
|
||||||
references: [chatSession.id],
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const systemSettings = createTable(
|
|
||||||
"systemSetting",
|
|
||||||
(d) => ({
|
|
||||||
systemPropmt: d.text()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ import { projectRouter } from "./project";
|
|||||||
import { techStackRouter } from "./techStack";
|
import { techStackRouter } from "./techStack";
|
||||||
import { cvCategoryRouter } from "./cvCategory";
|
import { cvCategoryRouter } from "./cvCategory";
|
||||||
import { cvEntryRouter } from "./cvEntry";
|
import { cvEntryRouter } from "./cvEntry";
|
||||||
import { musicRouter } from "./music";
|
|
||||||
import { trpcCrudRouterFromDrizzleEntity } from "../lib";
|
import { trpcCrudRouterFromDrizzleEntity } from "../lib";
|
||||||
import { cvCategory } from "../dbschema/schema";
|
import { cvCategory } from "../dbschema/schema";
|
||||||
import { chatRouter } from "./chat";
|
|
||||||
|
|
||||||
export const trpcRouter = router({
|
export const trpcRouter = router({
|
||||||
project: trpcCrudRouterFromDrizzleEntity('project').router,
|
project: trpcCrudRouterFromDrizzleEntity('project').router,
|
||||||
@@ -19,8 +17,6 @@ export const trpcRouter = router({
|
|||||||
categoryv2: cvCategoryRouter,
|
categoryv2: cvCategoryRouter,
|
||||||
entry: trpcCrudRouterFromDrizzleEntity('cvEntry').router,
|
entry: trpcCrudRouterFromDrizzleEntity('cvEntry').router,
|
||||||
entryv2: cvEntryRouter,
|
entryv2: cvEntryRouter,
|
||||||
music: musicRouter,
|
|
||||||
chat: chatRouter
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TrpcRouter = typeof trpcRouter;
|
export type TrpcRouter = typeof trpcRouter;
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
import { publicProcedure, router } from "../trpc";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { db } from '~/server/db'
|
|
||||||
import { chatMessage,
|
|
||||||
chatSession, systemSettings } from "../dbschema/schema";
|
|
||||||
import { isAdmin } from '~/app/actions';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { eq } from 'drizzle-orm';
|
|
||||||
import { clerkClient, auth } from '@clerk/nextjs/server'
|
|
||||||
export const chatRouter = router({
|
|
||||||
getSession: publicProcedure.query(async () => {
|
|
||||||
const clerk = await clerkClient()
|
|
||||||
const { userId } = await auth();
|
|
||||||
const user = await clerk.users.getUser(userId?userId:"")
|
|
||||||
if (user == undefined) {
|
|
||||||
throw new TRPCError({ message: "chat is only available to signed in users", code: 'UNAUTHORIZED' });
|
|
||||||
}
|
|
||||||
let session = await db.query.chatSession.findFirst({
|
|
||||||
where(fields, operators) {
|
|
||||||
return operators.eq(fields.userId, user.id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (session !== undefined) {
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
let newSession = await db.insert(chatSession).values({ userId: user.id}).returning().execute().then((r) => r.at(0)); if (newSession == undefined) {
|
|
||||||
throw new TRPCError({ message: "failed to create session", code: "INTERNAL_SERVER_ERROR" });
|
|
||||||
}
|
|
||||||
session = await db.query.chatSession.findFirst({
|
|
||||||
where(fields, operators) {
|
|
||||||
return operators.eq(fields.userId, user.id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (session == undefined) {
|
|
||||||
throw new TRPCError({ message: "session not found", code: "NOT_FOUND" });
|
|
||||||
}
|
|
||||||
if (session !== undefined) {
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
getMessages: publicProcedure.input(z.string()).query(async ({input}) => {
|
|
||||||
let res = await db.query.chatMessage.findMany({
|
|
||||||
where(fields,operators) {
|
|
||||||
return operators.eq(fields.sessionId,input)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return res;
|
|
||||||
}),
|
|
||||||
clearChat: publicProcedure.mutation(async () => {
|
|
||||||
console.log("deleting session")
|
|
||||||
const { userId } = await auth();
|
|
||||||
if (userId == null) {
|
|
||||||
throw new TRPCError({ message: "chat is only available to signed in users", code: 'UNAUTHORIZED' });
|
|
||||||
}
|
|
||||||
let session = await db.query.chatSession.findFirst({
|
|
||||||
with: {
|
|
||||||
messages: true
|
|
||||||
},
|
|
||||||
where(fields, operators) {
|
|
||||||
return operators.eq(fields.userId, userId)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (session != undefined) {
|
|
||||||
db.delete(chatMessage).where(eq(chatMessage.sessionId,session.id)).execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
}),
|
|
||||||
getSystemPrompt: publicProcedure.query(async () => {
|
|
||||||
const row = await db.select().from(systemSettings).limit(1).then((r) => r[0])
|
|
||||||
return row?.systemPropmt ?? ''
|
|
||||||
}),
|
|
||||||
updateSystemPrompt: publicProcedure.input(z.object({ prompt: z.string() })).mutation(async ({ input }) => {
|
|
||||||
if (!(await isAdmin())) throw new TRPCError({ code: 'FORBIDDEN' })
|
|
||||||
await db.delete(systemSettings)
|
|
||||||
await db.insert(systemSettings).values({ systemPropmt: input.prompt })
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
export type ChatRouter = typeof chatRouter;
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { publicProcedure, router } from "~/server/trpc";
|
|
||||||
import { db } from "~/server/db";
|
|
||||||
import { music } from "~/server/dbschema/schema";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { isAdmin } from "~/app/actions";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { createMusicInputSchema, updateMusicInputSchema } from "~/lib/trpc/music/schemas";
|
|
||||||
import { utapi } from "../uploadthing";
|
|
||||||
export const musicRouter = router({
|
|
||||||
list: publicProcedure.query(async () => {
|
|
||||||
let res = await db.select().from(music).orderBy(music.createdAt);
|
|
||||||
console.log(res);
|
|
||||||
return res;
|
|
||||||
}),
|
|
||||||
create: publicProcedure
|
|
||||||
.input(
|
|
||||||
createMusicInputSchema
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
const admin = await isAdmin();
|
|
||||||
if (!admin) throw new TRPCError({ code: "FORBIDDEN", message: "Access denied" });
|
|
||||||
let res = await db.insert(music).values(input).returning();
|
|
||||||
return res.at(0);
|
|
||||||
}),
|
|
||||||
update: publicProcedure
|
|
||||||
.input(
|
|
||||||
updateMusicInputSchema
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
const admin = await isAdmin();
|
|
||||||
if (!admin) throw new TRPCError({ code: "FORBIDDEN", message: "Access denied" });
|
|
||||||
const { id, ...data } = input;
|
|
||||||
return db.update(music).set(data).where(eq(music.id, id)).returning();
|
|
||||||
}),
|
|
||||||
delete: publicProcedure
|
|
||||||
.input(z.object({id:z.string().uuid()}))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
const admin = await isAdmin();
|
|
||||||
if (!admin) throw new TRPCError({ code: "FORBIDDEN", message: "Access denied" });
|
|
||||||
let res = await db.delete(music).where(eq(music.id, input.id)).returning();
|
|
||||||
let ret = res.at(0)
|
|
||||||
if (ret) {
|
|
||||||
utapi.deleteFiles(ret.fileKey)
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { createUploadthing, type FileRouter as UploadThingFileRouter } from "uploadthing/next";
|
|
||||||
import { UTApi } from 'uploadthing/server'
|
|
||||||
import { isAdmin } from "~/app/actions";
|
|
||||||
|
|
||||||
const f = createUploadthing();
|
|
||||||
|
|
||||||
export const fileRouter = {
|
|
||||||
musicUploader: f({ audio: { maxFileSize: "64MB", maxFileCount: 1 } })
|
|
||||||
.middleware(async () => {
|
|
||||||
const admin = await isAdmin();
|
|
||||||
if (!admin) throw new Error("Unauthorized");
|
|
||||||
return {};
|
|
||||||
})
|
|
||||||
.onUploadComplete(async ({ file }) => {
|
|
||||||
console.log(file)
|
|
||||||
return { fileUrl: file.ufsUrl, fileKey: file.key, fileName: file.name };
|
|
||||||
}),
|
|
||||||
} satisfies UploadThingFileRouter ;
|
|
||||||
|
|
||||||
export type FileRouter = typeof fileRouter;
|
|
||||||
|
|
||||||
export const utapi = new UTApi();
|
|
||||||
@@ -140,7 +140,3 @@
|
|||||||
-ms-overflow-style: none; /* IE and Edge */
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
scrollbar-width: none; /* Firefox */
|
scrollbar-width: none; /* Firefox */
|
||||||
}
|
}
|
||||||
|
|
||||||
.cl-button__google {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user