diff --git a/.vscode/settings.json b/.vscode/settings.json index 03182fcf..1e20aba9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ "cloudflarestorage", "cmdk", "CredentialsSignin", + "datetime", "Eterna", "exif", "exifr", @@ -20,9 +21,11 @@ "headlessui", "hgetall", "hset", + "ILIKE", "jpgs", "Lightbox", "Makernote", + "mitigations", "nanoids", "nextjs", "parameterizes", @@ -30,19 +33,22 @@ "Provia", "qaub", "QRSTUVWXYZ", + "ratelimit", + "ratelimiter", "Reala", "skippable", "sonner", + "Streamable", "thephotoblog", "trpc", "unnest", + "upstash", "UsKSGcbt", "Velvia", "WRHGZC", "wxyz", "zadd", - "zrange", - "datetime" + "zrange" ], "files.associations": { "*.css": "tailwindcss" diff --git a/README.md b/README.md index 1a462320..458d01bd 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,14 @@ https://photos.sambecker.com Features - +- Built-in auth - Photo upload with EXIF extraction - Organize photos by tag and camera model - Infinite scroll -- Built-in auth - Light/dark mode +- CMD-K menu with photo search - Automatic OG image generation +- Experimental support for AI-generated descriptions - Support for Fujifilm simulations OG Image Preview @@ -66,7 +68,25 @@ Installation 2. Click "Speed Insights" tab 3. Follow "Enable Speed Insights" instructions (`@vercel/speed-insights` already included) -### 7. Optional configuration +### 7. Add experimental AI text generation + +_⚠️ READ BEFORE PROCEEDING_ + +> _Usage of this feature will result in fees from OpenAI. When enabling AI text generation, follow all recommended mitigations in order to avoid unexpected charges and attacks. Make sure your OpenAI secret key environment variable is not prefixed with NEXT_PUBLIC._ + +1. Setup OpenAI + - If you don't already have one, create an [OpenAI](https://openai.com) account + - Generate an API key and store in environment variable `OPENAI_SECRET_KEY` + - Setup usage limits to avoid unexpected charges (_recommended_) +2. Add rate limiting (_recommended_) + - As an additional precaution, create a [Vercel KV](https://vercel.com/docs/storage/vercel-kv/quickstart#create-a-kv-database) store and link it to your project in order to enable rate limiting +3. Configure auto-generated fields (optional) + - Set which text fields should auto-generate when uploading a photo by storing a comma-separated list, e.g., `AI_TEXT_AUTO_GENERATED_FIELDS = title, semantic` + - Accepted values: title, caption, tags, description, all, or none (default is "all") + +### 8. Optional configuration + +Application behavior can be changed by configuring the following environment variables: - `NEXT_PUBLIC_PRO_MODE = 1` enables higher quality image storage for jpgs (will result in increased storage usage) - `NEXT_PUBLIC_BLUR_DISABLED = 1` prevents image blur data being stored and displayed (potentially useful for limiting Postgres usage) diff --git a/__tests__/ai.test.ts b/__tests__/ai.test.ts new file mode 100644 index 00000000..b70c93a0 --- /dev/null +++ b/__tests__/ai.test.ts @@ -0,0 +1,42 @@ +/* eslint-disable quotes */ +import { parseTitleAndCaption } from "@/photo/ai"; + +describe('AI text parses', () => { + it('titles and captions', () => { + // Complex case + expect(parseTitleAndCaption( + `'Title: "Ephemeral Beauty" Caption: "Roses bask in fleeting sunlight."'` + )).toStrictEqual({ + title: 'Ephemeral Beauty', + caption: 'Roses bask in fleeting sunlight', + }); + // Without surrounding single quotes + expect(parseTitleAndCaption( + `Title: "Ephemeral Beauty" Caption: "Roses bask in fleeting sunlight."` + )).toStrictEqual({ + title: 'Ephemeral Beauty', + caption: 'Roses bask in fleeting sunlight', + }); + // Without trailing period + expect(parseTitleAndCaption( + `Title: "Ephemeral Beauty" Caption: "Roses bask in fleeting sunlight"` + )).toStrictEqual({ + title: 'Ephemeral Beauty', + caption: 'Roses bask in fleeting sunlight', + }); + // Without and quotes + expect(parseTitleAndCaption( + `Title: Ephemeral Beauty Caption: Roses bask in fleeting sunlight` + )).toStrictEqual({ + title: 'Ephemeral Beauty', + caption: 'Roses bask in fleeting sunlight', + }); + // With single space + expect(parseTitleAndCaption( + `Title: Ephemeral Beauty Caption: Roses bask in fleeting sunlight` + )).toStrictEqual({ + title: 'Ephemeral Beauty', + caption: 'Roses bask in fleeting sunlight', + }); + }); +}); diff --git a/package.json b/package.json index 86939297..73431121 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,13 @@ "@types/react-dom": "18.2.21", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", + "@upstash/ratelimit": "^1.0.1", "@vercel/analytics": "^1.2.2", "@vercel/blob": "^0.22.1", + "@vercel/kv": "^1.0.1", "@vercel/postgres": "0.7.2", "@vercel/speed-insights": "^1.0.10", + "ai": "^3.0.13", "autoprefixer": "10.4.18", "camelcase-keys": "^9.1.3", "clsx": "^2.1.0", @@ -39,9 +42,10 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "nanoid": "^5.0.6", - "next": "14.1.3", + "next": "14.1.4", "next-auth": "5.0.0-beta.15", "next-themes": "^0.3.0", + "openai": "^4.29.2", "postcss": "8.4.35", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f59b382..70a1fae3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,18 +47,27 @@ dependencies: '@typescript-eslint/parser': specifier: ^7.2.0 version: 7.2.0(eslint@8.57.0)(typescript@5.4.2) + '@upstash/ratelimit': + specifier: ^1.0.1 + version: 1.0.1 '@vercel/analytics': specifier: ^1.2.2 - version: 1.2.2(next@14.1.3)(react@18.2.0) + version: 1.2.2(next@14.1.4)(react@18.2.0) '@vercel/blob': specifier: ^0.22.1 version: 0.22.1 + '@vercel/kv': + specifier: ^1.0.1 + version: 1.0.1 '@vercel/postgres': specifier: 0.7.2 version: 0.7.2 '@vercel/speed-insights': specifier: ^1.0.10 - version: 1.0.10(next@14.1.3)(react@18.2.0) + version: 1.0.10(next@14.1.4)(react@18.2.0)(svelte@4.2.12)(vue@3.4.21) + ai: + specifier: ^3.0.13 + version: 3.0.13(react@18.2.0)(solid-js@1.8.15)(svelte@4.2.12)(vue@3.4.21)(zod@3.22.4) autoprefixer: specifier: 10.4.18 version: 10.4.18(postcss@8.4.35) @@ -96,14 +105,17 @@ dependencies: specifier: ^5.0.6 version: 5.0.6 next: - specifier: 14.1.3 - version: 14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) + specifier: 14.1.4 + version: 14.1.4(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) next-auth: specifier: 5.0.0-beta.15 - version: 5.0.0-beta.15(next@14.1.3)(react@18.2.0) + version: 5.0.0-beta.15(next@14.1.4)(react@18.2.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0)(react@18.2.0) + openai: + specifier: ^4.29.2 + version: 4.29.2 postcss: specifier: 8.4.35 version: 8.4.35 @@ -1551,8 +1563,8 @@ packages: - utf-8-validate dev: false - /@next/env@14.1.3: - resolution: {integrity: sha512-VhgXTvrgeBRxNPjyfBsDIMvgsKDxjlpw4IAUsHCX8Gjl1vtHUYRT3+xfQ/wwvLPDd/6kqfLqk9Pt4+7gysuCKQ==} + /@next/env@14.1.4: + resolution: {integrity: sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ==} dev: false /@next/eslint-plugin-next@14.1.3: @@ -1561,8 +1573,8 @@ packages: glob: 10.3.10 dev: false - /@next/swc-darwin-arm64@14.1.3: - resolution: {integrity: sha512-LALu0yIBPRiG9ANrD5ncB3pjpO0Gli9ZLhxdOu6ZUNf3x1r3ea1rd9Q+4xxUkGrUXLqKVK9/lDkpYIJaCJ6AHQ==} + /@next/swc-darwin-arm64@14.1.4: + resolution: {integrity: sha512-ubmUkbmW65nIAOmoxT1IROZdmmJMmdYvXIe8211send9ZYJu+SqxSnJM4TrPj9wmL6g9Atvj0S/2cFmMSS99jg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1570,8 +1582,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@14.1.3: - resolution: {integrity: sha512-E/9WQeXxkqw2dfcn5UcjApFgUq73jqNKaE5bysDm58hEUdUGedVrnRhblhJM7HbCZNhtVl0j+6TXsK0PuzXTCg==} + /@next/swc-darwin-x64@14.1.4: + resolution: {integrity: sha512-b0Xo1ELj3u7IkZWAKcJPJEhBop117U78l70nfoQGo4xUSvv0PJSTaV4U9xQBLvZlnjsYkc8RwQN1HoH/oQmLlQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1579,8 +1591,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@14.1.3: - resolution: {integrity: sha512-USArX9B+3rZSXYLFvgy0NVWQgqh6LHWDmMt38O4lmiJNQcwazeI6xRvSsliDLKt+78KChVacNiwvOMbl6g6BBw==} + /@next/swc-linux-arm64-gnu@14.1.4: + resolution: {integrity: sha512-457G0hcLrdYA/u1O2XkRMsDKId5VKe3uKPvrKVOyuARa6nXrdhJOOYU9hkKKyQTMru1B8qEP78IAhf/1XnVqKA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1588,8 +1600,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@14.1.3: - resolution: {integrity: sha512-esk1RkRBLSIEp1qaQXv1+s6ZdYzuVCnDAZySpa62iFTMGTisCyNQmqyCTL9P+cLJ4N9FKCI3ojtSfsyPHJDQNw==} + /@next/swc-linux-arm64-musl@14.1.4: + resolution: {integrity: sha512-l/kMG+z6MB+fKA9KdtyprkTQ1ihlJcBh66cf0HvqGP+rXBbOXX0dpJatjZbHeunvEHoBBS69GYQG5ry78JMy3g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1597,8 +1609,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@14.1.3: - resolution: {integrity: sha512-8uOgRlYEYiKo0L8YGeS+3TudHVDWDjPVDUcST+z+dUzgBbTEwSSIaSgF/vkcC1T/iwl4QX9iuUyUdQEl0Kxalg==} + /@next/swc-linux-x64-gnu@14.1.4: + resolution: {integrity: sha512-BapIFZ3ZRnvQ1uWbmqEGJuPT9cgLwvKtxhK/L2t4QYO7l+/DxXuIGjvp1x8rvfa/x1FFSsipERZK70pewbtJtw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1606,8 +1618,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@14.1.3: - resolution: {integrity: sha512-DX2zqz05ziElLoxskgHasaJBREC5Y9TJcbR2LYqu4r7naff25B4iXkfXWfcp69uD75/0URmmoSgT8JclJtrBoQ==} + /@next/swc-linux-x64-musl@14.1.4: + resolution: {integrity: sha512-mqVxTwk4XuBl49qn2A5UmzFImoL1iLm0KQQwtdRJRKl21ylQwwGCxJtIYo2rbfkZHoSKlh/YgztY0qH3wG1xIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1615,8 +1627,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@14.1.3: - resolution: {integrity: sha512-HjssFsCdsD4GHstXSQxsi2l70F/5FsRTRQp8xNgmQs15SxUfUJRvSI9qKny/jLkY3gLgiCR3+6A7wzzK0DBlfA==} + /@next/swc-win32-arm64-msvc@14.1.4: + resolution: {integrity: sha512-xzxF4ErcumXjO2Pvg/wVGrtr9QQJLk3IyQX1ddAC/fi6/5jZCZ9xpuL9Tzc4KPWMFq8GGWFVDMshZOdHGdkvag==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1624,8 +1636,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@14.1.3: - resolution: {integrity: sha512-DRuxD5axfDM1/Ue4VahwSxl1O5rn61hX8/sF0HY8y0iCbpqdxw3rB3QasdHn/LJ6Wb2y5DoWzXcz3L1Cr+Thrw==} + /@next/swc-win32-ia32-msvc@14.1.4: + resolution: {integrity: sha512-WZiz8OdbkpRw6/IU/lredZWKKZopUMhcI2F+XiMAcPja0uZYdMTZQRoQ0WZcvinn9xZAidimE7tN9W5v9Yyfyw==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -1633,8 +1645,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@14.1.3: - resolution: {integrity: sha512-uC2DaDoWH7h1P/aJ4Fok3Xiw6P0Lo4ez7NbowW2VGNXw/Xv6tOuLUcxhBYZxsSUJtpeknCi8/fvnSpyCFp4Rcg==} + /@next/swc-win32-x64-msvc@14.1.4: + resolution: {integrity: sha512-4Rto21sPfw555sZ/XNLqfxDUNeLhNYGO2dlPqsnuCg8N8a2a9u1ltqBOPQ4vj1Gf7eJC0W2hHG2eYUHuiXgY2w==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2780,6 +2792,14 @@ packages: resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} dev: false + /@types/diff-match-patch@1.0.36: + resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} + dev: false + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: false + /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: @@ -2825,6 +2845,19 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: false + /@types/node-fetch@2.6.11: + resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} + dependencies: + '@types/node': 20.11.26 + form-data: 4.0.0 + dev: false + + /@types/node@18.19.24: + resolution: {integrity: sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==} + dependencies: + undici-types: 5.26.5 + dev: false + /@types/node@20.11.26: resolution: {integrity: sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==} dependencies: @@ -3083,7 +3116,32 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: false - /@vercel/analytics@1.2.2(next@14.1.3)(react@18.2.0): + /@upstash/core-analytics@0.0.7: + resolution: {integrity: sha512-lC2j5efqb1haX/fpTGaPUx1rue1WUkOZBVHDzCB7eMIVsRdFFp4xiHtyH/G9omiR1zj39fU5SCTWFiKJH3KOpw==} + engines: {node: '>=16.0.0'} + dependencies: + '@upstash/redis': 1.28.4 + dev: false + + /@upstash/ratelimit@1.0.1: + resolution: {integrity: sha512-G9LZ7idhlkuYknbUngCB3qzd7QnkK1xDkFG5jRtEJZuOUS5UKJ0UTKbhalCtp39eX2wu2Ubv8W7HCeaJQOWM0A==} + dependencies: + '@upstash/core-analytics': 0.0.7 + dev: false + + /@upstash/redis@1.25.1: + resolution: {integrity: sha512-ACj0GhJ4qrQyBshwFgPod6XufVEfKX2wcaihsEvSdLYnY+m+pa13kGt1RXm/yTHKf4TQi/Dy2A8z/y6WUEOmlg==} + dependencies: + crypto-js: 4.2.0 + dev: false + + /@upstash/redis@1.28.4: + resolution: {integrity: sha512-UalkSAny/dz1m8giEhD3Y5ru1o+CPHI32wFyS3MyzDzj2TRvEN+lTw+mPwi20ojk0H2gs8TBW3qsrvwuLLy+pA==} + dependencies: + crypto-js: 4.2.0 + dev: false + + /@vercel/analytics@1.2.2(next@14.1.4)(react@18.2.0): resolution: {integrity: sha512-X0rctVWkQV1e5Y300ehVNqpOfSOufo7ieA5PIdna8yX/U7Vjz0GFsGf4qvAhxV02uQ2CVt7GYcrFfddXXK2Y4A==} peerDependencies: next: '>= 13' @@ -3094,7 +3152,7 @@ packages: react: optional: true dependencies: - next: 14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) + next: 14.1.4(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 server-only: 0.0.1 dev: false @@ -3109,6 +3167,13 @@ packages: undici: 5.28.3 dev: false + /@vercel/kv@1.0.1: + resolution: {integrity: sha512-uTKddsqVYS2GRAM/QMNNXCTuw9N742mLoGRXoNDcyECaxEXvIHG0dEY+ZnYISV4Vz534VwJO+64fd9XeSggSKw==} + engines: {node: '>=14.6'} + dependencies: + '@upstash/redis': 1.25.1 + dev: false + /@vercel/postgres@0.7.2: resolution: {integrity: sha512-IqR/ZAvoPGcPaXl9eWWB5KaA+w/81RzZa/18P4izQRHpNBkTGt9HwGfYi9+wut5UgxNq4QSX9A7HIQR6QDvX2Q==} engines: {node: '>=14.6'} @@ -3119,7 +3184,7 @@ packages: ws: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) dev: false - /@vercel/speed-insights@1.0.10(next@14.1.3)(react@18.2.0): + /@vercel/speed-insights@1.0.10(next@14.1.4)(react@18.2.0)(svelte@4.2.12)(vue@3.4.21): resolution: {integrity: sha512-4uzdKB0RW6Ff2FkzshzjZ+RlJfLPxgm/00i0XXgxfMPhwnnsk92YgtqsxT9OcPLdJUyVU1DqFlSWWjIQMPkh0g==} requiresBuild: true peerDependencies: @@ -3143,8 +3208,83 @@ packages: vue-router: optional: true dependencies: - next: 14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) + next: 14.1.4(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 + svelte: 4.2.12 + vue: 3.4.21(typescript@5.4.2) + dev: false + + /@vue/compiler-core@3.4.21: + resolution: {integrity: sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==} + dependencies: + '@babel/parser': 7.23.9 + '@vue/shared': 3.4.21 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.0.2 + dev: false + + /@vue/compiler-dom@3.4.21: + resolution: {integrity: sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==} + dependencies: + '@vue/compiler-core': 3.4.21 + '@vue/shared': 3.4.21 + dev: false + + /@vue/compiler-sfc@3.4.21: + resolution: {integrity: sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==} + dependencies: + '@babel/parser': 7.23.9 + '@vue/compiler-core': 3.4.21 + '@vue/compiler-dom': 3.4.21 + '@vue/compiler-ssr': 3.4.21 + '@vue/shared': 3.4.21 + estree-walker: 2.0.2 + magic-string: 0.30.8 + postcss: 8.4.35 + source-map-js: 1.0.2 + dev: false + + /@vue/compiler-ssr@3.4.21: + resolution: {integrity: sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==} + dependencies: + '@vue/compiler-dom': 3.4.21 + '@vue/shared': 3.4.21 + dev: false + + /@vue/reactivity@3.4.21: + resolution: {integrity: sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==} + dependencies: + '@vue/shared': 3.4.21 + dev: false + + /@vue/runtime-core@3.4.21: + resolution: {integrity: sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==} + dependencies: + '@vue/reactivity': 3.4.21 + '@vue/shared': 3.4.21 + dev: false + + /@vue/runtime-dom@3.4.21: + resolution: {integrity: sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==} + dependencies: + '@vue/runtime-core': 3.4.21 + '@vue/shared': 3.4.21 + csstype: 3.1.3 + dev: false + + /@vue/server-renderer@3.4.21(vue@3.4.21): + resolution: {integrity: sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==} + peerDependencies: + vue: 3.4.21 + dependencies: + '@vue/compiler-ssr': 3.4.21 + '@vue/shared': 3.4.21 + vue: 3.4.21(typescript@5.4.2) + dev: false + + /@vue/shared@3.4.21: + resolution: {integrity: sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==} dev: false /abab@2.0.6: @@ -3152,6 +3292,13 @@ packages: deprecated: Use your platform's native atob() and btoa() methods instead dev: false + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: false + /acorn-globals@7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} dependencies: @@ -3187,6 +3334,50 @@ packages: - supports-color dev: false + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: false + + /ai@3.0.13(react@18.2.0)(solid-js@1.8.15)(svelte@4.2.12)(vue@3.4.21)(zod@3.22.4): + resolution: {integrity: sha512-fDrYnVTdMJuS/qYUq0T/CX3WDuTfcZFie9LkgnoQ2layfUG2Wzh/mpfkfYXFEq/mqnpep3xUtECOB1weyyvwUg==} + engines: {node: '>=14.6'} + peerDependencies: + react: ^18.2.0 + solid-js: ^1.7.7 + svelte: ^3.0.0 || ^4.0.0 + vue: ^3.3.4 + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + solid-js: + optional: true + svelte: + optional: true + vue: + optional: true + zod: + optional: true + dependencies: + eventsource-parser: 1.0.0 + jsondiffpatch: 0.6.0 + nanoid: 3.3.6 + react: 18.2.0 + solid-js: 1.8.15 + solid-swr-store: 0.10.7(solid-js@1.8.15)(swr-store@0.10.6) + sswr: 2.0.0(svelte@4.2.12) + svelte: 4.2.12 + swr: 2.2.0(react@18.2.0) + swr-store: 0.10.6 + swrv: 1.0.4(vue@3.4.21) + vue: 3.4.21(typescript@5.4.2) + zod: 3.22.4 + zod-to-json-schema: 3.22.4(zod@3.22.4) + dev: false + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -3424,6 +3615,12 @@ packages: dequal: 2.0.3 dev: false + /axobject-query@4.0.0: + resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} + dependencies: + dequal: 2.0.3 + dev: false + /babel-jest@29.7.0(@babel/core@7.23.9): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3500,6 +3697,10 @@ packages: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: false + /base-64@0.1.0: + resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==} + dev: false + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -3655,11 +3856,20 @@ packages: supports-color: 7.2.0 dev: false + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false + /char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} dev: false + /charenc@0.0.2: + resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + dev: false + /chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -3722,6 +3932,16 @@ packages: engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} dev: false + /code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + '@types/estree': 1.0.5 + acorn: 8.11.3 + estree-walker: 3.0.3 + periscopic: 3.1.0 + dev: false + /collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} dev: false @@ -3805,6 +4025,22 @@ packages: which: 2.0.2 dev: false + /crypt@0.0.2: + resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + dev: false + + /crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + dev: false + + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: false + /css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} dev: false @@ -3966,11 +4202,22 @@ packages: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: false + /diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + dev: false + /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: false + /digest-fetch@1.3.0: + resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==} + dependencies: + base-64: 0.1.0 + md5: 2.3.0 + dev: false + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -4508,11 +4755,31 @@ packages: engines: {node: '>=4.0'} dev: false + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: false + + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + dev: false + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} dev: false + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: false + + /eventsource-parser@1.0.0: + resolution: {integrity: sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==} + engines: {node: '>=14.18'} + dev: false + /execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -4647,6 +4914,10 @@ packages: signal-exit: 4.1.0 dev: false + /form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + dev: false + /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -4656,6 +4927,14 @@ packages: mime-types: 2.1.35 dev: false + /formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + dev: false + /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: false @@ -4930,6 +5209,12 @@ packages: engines: {node: '>=10.17.0'} dev: false + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: false + /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -5043,6 +5328,10 @@ packages: has-tostringtag: 1.0.2 dev: false + /is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + dev: false + /is-buffer@2.0.5: resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} engines: {node: '>=4'} @@ -5136,6 +5425,12 @@ packages: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: false + /is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + dependencies: + '@types/estree': 1.0.5 + dev: false + /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -5820,6 +6115,16 @@ packages: hasBin: true dev: false + /jsondiffpatch@0.6.0: + resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + '@types/diff-match-patch': 1.0.36 + chalk: 5.3.0 + diff-match-patch: 1.0.5 + dev: false + /jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -5879,6 +6184,10 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: false + /locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + dev: false + /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -5931,6 +6240,13 @@ packages: hasBin: true dev: false + /magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: false + /make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -5949,6 +6265,18 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: false + /md5@2.3.0: + resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: 1.1.6 + dev: false + + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: false + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: false @@ -6036,6 +6364,12 @@ packages: thenify-all: 1.6.0 dev: false + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + /nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -6052,7 +6386,7 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: false - /next-auth@5.0.0-beta.15(next@14.1.3)(react@18.2.0): + /next-auth@5.0.0-beta.15(next@14.1.4)(react@18.2.0): resolution: {integrity: sha512-UQggNq8CDu3/w8CYkihKLLnRPNXel98K0j7mtjj9a6XTNYo4Hni8xg/2h1YhElW6vXE8mgtvmH11rU8NKw86jQ==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 @@ -6069,7 +6403,7 @@ packages: optional: true dependencies: '@auth/core': 0.28.0 - next: 14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) + next: 14.1.4(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 dev: false @@ -6083,8 +6417,8 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /next@14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-oexgMV2MapI0UIWiXKkixF8J8ORxpy64OuJ/J9oVUmIthXOUCcuVEZX+dtpgq7wIfIqtBwQsKEDXejcjTsan9g==} + /next@14.1.4(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-1WTaXeSrUwlz/XcnhGTY7+8eiaFvdet5z9u3V2jb+Ek1vFo0VhHKSAIJvDWfQpttWjnyw14kBeq28TPq7bTeEQ==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -6098,7 +6432,7 @@ packages: sass: optional: true dependencies: - '@next/env': 14.1.3 + '@next/env': 14.1.4 '@swc/helpers': 0.5.2 busboy: 1.6.0 caniuse-lite: 1.0.30001591 @@ -6108,20 +6442,37 @@ packages: react-dom: 18.2.0(react@18.2.0) styled-jsx: 5.1.1(@babel/core@7.23.9)(react@18.2.0) optionalDependencies: - '@next/swc-darwin-arm64': 14.1.3 - '@next/swc-darwin-x64': 14.1.3 - '@next/swc-linux-arm64-gnu': 14.1.3 - '@next/swc-linux-arm64-musl': 14.1.3 - '@next/swc-linux-x64-gnu': 14.1.3 - '@next/swc-linux-x64-musl': 14.1.3 - '@next/swc-win32-arm64-msvc': 14.1.3 - '@next/swc-win32-ia32-msvc': 14.1.3 - '@next/swc-win32-x64-msvc': 14.1.3 + '@next/swc-darwin-arm64': 14.1.4 + '@next/swc-darwin-x64': 14.1.4 + '@next/swc-linux-arm64-gnu': 14.1.4 + '@next/swc-linux-arm64-musl': 14.1.4 + '@next/swc-linux-x64-gnu': 14.1.4 + '@next/swc-linux-x64-musl': 14.1.4 + '@next/swc-win32-arm64-msvc': 14.1.4 + '@next/swc-win32-ia32-msvc': 14.1.4 + '@next/swc-win32-x64-msvc': 14.1.4 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros dev: false + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: false + + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + /node-gyp-build@4.8.0: resolution: {integrity: sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==} hasBin: true @@ -6254,6 +6605,23 @@ packages: mimic-fn: 2.1.0 dev: false + /openai@4.29.2: + resolution: {integrity: sha512-cPkT6zjEcE4qU5OW/SoDDuXEsdOLrXlAORhzmaguj5xZSPlgKvLhi27sFWhLKj07Y6WKNWxcwIbzm512FzTBNQ==} + hasBin: true + dependencies: + '@types/node': 18.19.24 + '@types/node-fetch': 2.6.11 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + digest-fetch: 1.3.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + web-streams-polyfill: 3.3.3 + transitivePeerDependencies: + - encoding + dev: false + /opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -6359,6 +6727,14 @@ packages: engines: {node: '>=8'} dev: false + /periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + dependencies: + '@types/estree': 1.0.5 + estree-walker: 3.0.3 + is-reference: 3.0.2 + dev: false + /pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -6853,6 +7229,20 @@ packages: lru-cache: 6.0.0 dev: false + /seroval-plugins@1.0.5(seroval@1.0.5): + resolution: {integrity: sha512-8+pDC1vOedPXjKG7oz8o+iiHrtF2WswaMQJ7CKFpccvSYfrzmvKY9zOJWCg+881722wIHfwkdnRmiiDm9ym+zQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + dependencies: + seroval: 1.0.5 + dev: false + + /seroval@1.0.5: + resolution: {integrity: sha512-TM+Z11tHHvQVQKeNlOUonOWnsNM+2IBwZ4vwoi4j3zKzIpc5IDw8WPwCfcc8F17wy6cBcJGbZbFOR0UCuTZHQA==} + engines: {node: '>=10'} + dev: false + /server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} dev: false @@ -6927,6 +7317,25 @@ packages: engines: {node: '>=8'} dev: false + /solid-js@1.8.15: + resolution: {integrity: sha512-d0QP/efr3UVcwGgWVPveQQ0IHOH6iU7yUhc2piy8arNG8wxKmvUy1kFxyF8owpmfCWGB87usDKMaVnsNYZm+Vw==} + dependencies: + csstype: 3.1.3 + seroval: 1.0.5 + seroval-plugins: 1.0.5(seroval@1.0.5) + dev: false + + /solid-swr-store@0.10.7(solid-js@1.8.15)(swr-store@0.10.6): + resolution: {integrity: sha512-A6d68aJmRP471aWqKKPE2tpgOiR5fH4qXQNfKIec+Vap+MGQm3tvXlT8n0I8UgJSlNAsSAUuw2VTviH2h3Vv5g==} + engines: {node: '>=10'} + peerDependencies: + solid-js: ^1.2 + swr-store: ^0.10 + dependencies: + solid-js: 1.8.15 + swr-store: 0.10.6 + dev: false + /sonner@1.4.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-SArYlHbkjqRuLiR0iGY2ZSr09oOrxw081ZZkQPfXrs8aZQLIBOLOdzTYxGJB5yIZ7qL56UEPmrX1YqbODwG0Lw==} peerDependencies: @@ -6958,6 +7367,15 @@ packages: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: false + /sswr@2.0.0(svelte@4.2.12): + resolution: {integrity: sha512-mV0kkeBHcjcb0M5NqKtKVg/uTIYNlIIniyDfSGrSfxpEdM9C365jK0z55pl9K0xAkNTJi2OAOVFQpgMPUk+V0w==} + peerDependencies: + svelte: ^4.0.0 + dependencies: + svelte: 4.2.12 + swrev: 4.0.0 + dev: false + /stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -7145,6 +7563,54 @@ packages: engines: {node: '>= 0.4'} dev: false + /svelte@4.2.12: + resolution: {integrity: sha512-d8+wsh5TfPwqVzbm4/HCXC783/KPHV60NvwitJnyTA5lWn1elhXMNWhXGCJ7PwPa8qFUnyJNIyuIRt2mT0WMug==} + engines: {node: '>=16'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.22 + '@types/estree': 1.0.5 + acorn: 8.11.3 + aria-query: 5.3.0 + axobject-query: 4.0.0 + code-red: 1.0.4 + css-tree: 2.3.1 + estree-walker: 3.0.3 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.8 + periscopic: 3.1.0 + dev: false + + /swr-store@0.10.6: + resolution: {integrity: sha512-xPjB1hARSiRaNNlUQvWSVrG5SirCjk2TmaUyzzvk69SZQan9hCJqw/5rG9iL7xElHU784GxRPISClq4488/XVw==} + engines: {node: '>=10'} + dependencies: + dequal: 2.0.3 + dev: false + + /swr@2.2.0(react@18.2.0): + resolution: {integrity: sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + + /swrev@4.0.0: + resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==} + dev: false + + /swrv@1.0.4(vue@3.4.21): + resolution: {integrity: sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==} + peerDependencies: + vue: '>=3.2.26 < 4' + dependencies: + vue: 3.4.21(typescript@5.4.2) + dev: false + /symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: false @@ -7242,6 +7708,10 @@ packages: url-parse: 1.5.10 dev: false + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + /tr46@3.0.0: resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} engines: {node: '>=12'} @@ -7456,6 +7926,14 @@ packages: tslib: 2.6.2 dev: false + /use-sync-external-store@1.2.0(react@18.2.0): + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /utf-8-validate@6.0.3: resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==} engines: {node: '>=6.14.2'} @@ -7482,6 +7960,22 @@ packages: convert-source-map: 2.0.0 dev: false + /vue@3.4.21(typescript@5.4.2): + resolution: {integrity: sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@vue/compiler-dom': 3.4.21 + '@vue/compiler-sfc': 3.4.21 + '@vue/runtime-dom': 3.4.21 + '@vue/server-renderer': 3.4.21(vue@3.4.21) + '@vue/shared': 3.4.21 + typescript: 5.4.2 + dev: false + /w3c-xmlserializer@4.0.0: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} engines: {node: '>=14'} @@ -7495,6 +7989,20 @@ packages: makeerror: 1.0.12 dev: false + /web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + dev: false + + /web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + dev: false + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + /webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -7543,6 +8051,13 @@ packages: webidl-conversions: 7.0.0 dev: false + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -7725,3 +8240,15 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: false + + /zod-to-json-schema@3.22.4(zod@3.22.4): + resolution: {integrity: sha512-2Ed5dJ+n/O3cU383xSY28cuVi0BCQhF8nYqWU5paEpl7fVdqdAmiLdqLyfblbNdfOFwFfi/mqU4O1pwc60iBhQ==} + peerDependencies: + zod: ^3.22.4 + dependencies: + zod: 3.22.4 + dev: false + + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + dev: false diff --git a/src/app/admin/photos/[photoId]/edit/page.tsx b/src/app/admin/photos/[photoId]/edit/page.tsx index 2a5c2d6c..c9a5c68c 100644 --- a/src/app/admin/photos/[photoId]/edit/page.tsx +++ b/src/app/admin/photos/[photoId]/edit/page.tsx @@ -2,6 +2,7 @@ import { redirect } from 'next/navigation'; import { getPhotoNoStore, getUniqueTagsCached } from '@/photo/cache'; import { PATH_ADMIN } from '@/site/paths'; import PhotoEditPageClient from '@/photo/PhotoEditPageClient'; +import { AI_TEXT_GENERATION_ENABLED } from '@/site/config'; export default async function PhotoEditPage({ params: { photoId }, @@ -14,7 +15,13 @@ export default async function PhotoEditPage({ const uniqueTags = await getUniqueTagsCached(); + const hasAiTextGeneration = AI_TEXT_GENERATION_ENABLED; + return ( - + ); }; diff --git a/src/app/admin/uploads/[uploadPath]/page.tsx b/src/app/admin/uploads/[uploadPath]/page.tsx index b59ccc45..a2faee67 100644 --- a/src/app/admin/uploads/[uploadPath]/page.tsx +++ b/src/app/admin/uploads/[uploadPath]/page.tsx @@ -3,6 +3,10 @@ import { extractExifDataFromBlobPath } from '@/photo/server'; import { redirect } from 'next/navigation'; import { getUniqueTagsCached } from '@/photo/cache'; import UploadPageClient from '@/photo/UploadPageClient'; +import { + AI_TEXT_AUTO_GENERATED_FIELDS, + AI_TEXT_GENERATION_ENABLED, +} from '@/site/config'; interface Params { params: { uploadPath: string } @@ -14,11 +18,21 @@ export default async function UploadPage({ params: { uploadPath } }: Params) { photoFormExif, } = await extractExifDataFromBlobPath(uploadPath); - const uniqueTags = await getUniqueTagsCached(); - if (!photoFormExif) { redirect(PATH_ADMIN); } + const uniqueTags = await getUniqueTagsCached(); + + const hasAiTextGeneration = AI_TEXT_GENERATION_ENABLED; + + const textFieldsToAutoGenerate = AI_TEXT_AUTO_GENERATED_FIELDS; + return ( - + ); }; diff --git a/src/auth/index.ts b/src/auth/index.ts index 4ea60774..20ae70af 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -44,7 +44,7 @@ export const { }, }); -export const safelyRunServerAdminAction = async ( +export const safelyRunAdminServerAction = async ( callback: () => T, ): Promise => { const session = await auth(); diff --git a/src/components/AdminChildPage.tsx b/src/components/AdminChildPage.tsx index 6a96900e..a0b4861e 100644 --- a/src/components/AdminChildPage.tsx +++ b/src/components/AdminChildPage.tsx @@ -10,6 +10,7 @@ function AdminChildPage({ backPath, backLabel, breadcrumb, + breadcrumbEllipsis, accessory, isLoading, children, @@ -17,6 +18,7 @@ function AdminChildPage({ backPath?: string backLabel?: string breadcrumb?: ReactNode + breadcrumbEllipsis?: boolean accessory?: ReactNode isLoading?: boolean children: ReactNode, @@ -27,12 +29,14 @@ function AdminChildPage({
{(backPath || breadcrumb || accessory) &&
{backPath && - {backLabel || 'Back'} + + {backLabel || 'Back'} + } {breadcrumb && <> / - + {breadcrumb} } diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index fb4850d4..8a942928 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -7,6 +7,7 @@ export default function Badge({ highContrast, uppercase, interactive, + className, }: { children: React.ReactNode type?: 'large' | 'small' | 'text-only' @@ -14,6 +15,7 @@ export default function Badge({ highContrast?: boolean uppercase?: boolean interactive?: boolean + className?: string }) { const stylesForType = () => { switch (type) { @@ -44,6 +46,7 @@ export default function Badge({ 'leading-none', stylesForType(), uppercase && 'uppercase tracking-wider', + className, )}> {children} diff --git a/src/components/CanvasBlurCapture.tsx b/src/components/CanvasBlurCapture.tsx index a4541e04..cce1ed31 100644 --- a/src/components/CanvasBlurCapture.tsx +++ b/src/components/CanvasBlurCapture.tsx @@ -6,7 +6,9 @@ const RETRY_DELAY = 2000; export default function CanvasBlurCapture({ imageUrl, + onLoad, onCapture, + onError, width, height, hidden = true, @@ -15,7 +17,9 @@ export default function CanvasBlurCapture({ quality = 0.9, }: { imageUrl: string - onCapture: (blurData: string) => void + onLoad?: (imageData: string) => void + onCapture: (imageData: string) => void + onError?: (error: string) => void width: number height: number hidden?: boolean @@ -44,7 +48,18 @@ export default function CanvasBlurCapture({ canvas.style.height = `${height}px`; const context = refCanvas.current?.getContext('2d'); if (context) { + // Draw scaled image context.scale(scale, scale); + context.drawImage( + refImage.current, + -edgeCompensation, + -edgeCompensation, + width + edgeCompensation * 2, + width * refImage.current.height / refImage.current.width + + edgeCompensation * 2, + ); + onLoad?.(canvas.toDataURL('image/jpeg', quality)); + // Draw blurred image context.filter = 'contrast(1.2) saturate(1.2) ' + `blur(${scale * 10}px)`; @@ -56,17 +71,21 @@ export default function CanvasBlurCapture({ width * refImage.current.height / refImage.current.width + edgeCompensation * 2, ); - refTimeouts.current.forEach(clearTimeout); onCapture(canvas.toDataURL('image/jpeg', quality)); + onError?.(''); + refTimeouts.current.forEach(clearTimeout); refShouldCapture.current = false; } else { - console.error('Cannot get 2d context'); + console.error('Cannot get 2d context ... retrying'); + onError?.('Cannot get 2d context ... retrying'); // Retry capture in case canvas is not available refTimeouts.current.push(setTimeout(capture, RETRY_DELAY)); } } else { // eslint-disable-next-line max-len - console.error('Cannot generate blur data: canvas/image not ready'); + console.error('Cannot generate blur data: canvas/image not ready ... retrying'); + // eslint-disable-next-line max-len + onError?.('Cannot generate blur data: canvas/image not ready ... retrying'); // Retry capture in case canvas is not available refTimeouts.current.push(setTimeout(capture, RETRY_DELAY)); } @@ -92,6 +111,8 @@ export default function CanvasBlurCapture({ }, [ imageUrl, onCapture, + onLoad, + onError, width, height, edgeCompensation, diff --git a/src/components/Checklist.tsx b/src/components/Checklist.tsx index cb80dded..62a23bab 100644 --- a/src/components/Checklist.tsx +++ b/src/components/Checklist.tsx @@ -1,30 +1,36 @@ import { ReactNode } from 'react'; import { clsx } from 'clsx/lite'; +import ExperimentalBadge from './ExperimentalBadge'; +import Badge from './Badge'; export default function Checklist({ title, icon, optional, + experimental, children, }: { title: string icon?: ReactNode optional?: boolean + experimental?: boolean children: ReactNode }) { return (
- {icon} -
-
{title}
+ {icon} + + {title} {optional && -
(Optional)
} -
+ Optional} + {experimental && + } +
-
+
{title} + {experimental && + }
{children} diff --git a/src/components/CommandKClient.tsx b/src/components/CommandKClient.tsx index 105b1049..d0588f29 100644 --- a/src/components/CommandKClient.tsx +++ b/src/components/CommandKClient.tsx @@ -27,6 +27,7 @@ export type CommandKSection = { accessory?: ReactNode items: { label: string + keywords?: string[] annotation?: ReactNode annotationAria?: string accessory?: ReactNode @@ -157,8 +158,13 @@ export default function CommandKClient({ open={isOpen} onOpenChange={setIsOpen} label="Global Command Menu" - filter={(value, search) => - value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0} + filter={(value, search, keywords) => { + const searchFormatted = search.trim().toLocaleLowerCase(); + return ( + value.toLocaleLowerCase().includes(searchFormatted) || + keywords?.includes(searchFormatted) + ) ? 1 : 0 ; + }} loop > {items.map(({ - accessory, label, + keywords, annotation, annotationAria, + accessory, path, action, }) => + Experimental + + ); +} diff --git a/src/components/FieldSetWithStatus.tsx b/src/components/FieldSetWithStatus.tsx index 19e7ab01..3c7bdd7f 100644 --- a/src/components/FieldSetWithStatus.tsx +++ b/src/components/FieldSetWithStatus.tsx @@ -24,6 +24,7 @@ export default function FieldSetWithStatus({ capitalize, type = 'text', inputRef, + accessory, }: { id: string label: string @@ -41,6 +42,7 @@ export default function FieldSetWithStatus({ capitalize?: boolean type?: FieldSetType inputRef?: LegacyRef + accessory?: React.ReactNode }) { const { pending } = useFormStatus(); @@ -68,58 +70,76 @@ export default function FieldSetWithStatus({ } - {selectOptions - ? - : tagOptions - ? + {selectOptions + ? onChange?.(type === 'checkbox' - ? e.target.value === 'true' ? 'false' : 'true' - : e.target.value)} - type={type} - autoComplete="off" - autoCapitalize={!capitalize ? 'off' : undefined} - readOnly={readOnly || pending} + onChange={e => onChange?.(e.target.value)} className={clsx( - type === 'text' && 'w-full', - Boolean(error) && 'error', + 'w-full', + clsx(Boolean(error) && 'error'), + // Use special class because `select` can't be readonly + readOnly || pending && 'disabled-select', )} - />} + > + {selectOptionsDefaultLabel && + } + {selectOptions.map(({ value: optionValue, label: optionLabel }) => + )} + + : tagOptions + ? + : type === 'textarea' + ?