From 030e9909bde6f47d0283e5155b53775b2d9dc3fc Mon Sep 17 00:00:00 2001 From: Tafita Mathieu <199898191+Mathieu-bot@users.noreply.github.com> Date: Mon, 29 Jun 2026 12:28:04 +0300 Subject: [PATCH] feat: ai embeddings * chore: add @xenova/transformers, natural, compromise * feat: add AI embeddings service * feat: add AI DTO types * feat: add semantic search API route * feat: integrate semantic search into events page * feat: add session recommendations feature * feat: add duplicate question detection API route * feat: add duplicate detection to live Q&A panel * feat: add startDate, endDate, isOnline to SearchResultDto for client-side filtering * fix: lint errors in AI service and Q&A panel * fix: batch pipeline call in generateBatchEmbeddings, reset modelLoadPromise on failure * fix: use generateBatchEmbeddings in recommendations and QA check routes * fix: replace Map cache with lru-cache for recommendations * fix: raise recommendations threshold to 0.4, remove redundant bestIndex check * feat: warmup AI model at server startup via instrumentation.ts * chore: remove unused natural and compromise dependencies * fix: pass filters to search API for server-side filtering * fix: lint errors from review fixes (prefer-const, unnecessary dep) * fix: replace framer-motion import with motion/react and wrap ProgressBar state in startTransition - Nav.tsx: import { motion } from 'motion/react' (motion v12 replaces framer-motion) - ProgressBar.tsx: wrap setVisible/setProgress in startTransition to avoid React 19 'useInsertionEffect must not schedule updates' warning * fix: pnpm-workspace.yaml - replace allowBuilds placeholders with onlyBuiltDependencies * fix: use dynamic import for AI warmup to avoid Edge compilation error * fix: replace startTransition with queueMicrotask in ProgressBar * fix: raise search similarity threshold from 0.15 to 0.3 * refactor: delete unused file --------- Co-authored-by: Mathieu-bot --- package.json | 2 + pnpm-lock.yaml | 619 +++++++++++++++++- pnpm-workspace.yaml | 12 +- src/ai/embeddings.ts | 99 +++ src/ai/index.ts | 8 + .../events/[eventId]/recommendations/route.ts | 95 +++ src/app/api/ai/questions/check/route.ts | 104 +++ src/app/api/ai/search/route.ts | 155 +++++ src/app/events/[eventId]/page.tsx | 41 ++ src/app/events/page.tsx | 40 +- src/app/sessions/[sessionId]/page.tsx | 1 + .../features/SessionPlayer/qa-panel.tsx | 78 ++- src/components/layouts/Nav.tsx | 2 +- src/components/ui/ProgressBar.tsx | 6 +- src/instrumentation.ts | 11 + src/lib/api.ts | 21 +- src/lib/hooks/useEvents.ts | 17 +- src/types/dto/index.ts | 21 + 18 files changed, 1272 insertions(+), 60 deletions(-) create mode 100644 src/ai/embeddings.ts create mode 100644 src/ai/index.ts create mode 100644 src/app/api/ai/events/[eventId]/recommendations/route.ts create mode 100644 src/app/api/ai/questions/check/route.ts create mode 100644 src/app/api/ai/search/route.ts create mode 100644 src/instrumentation.ts diff --git a/package.json b/package.json index 9e105a1..c49b012 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,11 @@ "@tanstack/react-query": "^5.100.7", "@vercel/analytics": "^2.0.1", "@vercel/speed-insights": "^2.0.0", + "@xenova/transformers": "^2.17.2", "clsx": "^2.1.1", "dotenv": "^17.4.2", "gsap": "^3.15.0", + "lru-cache": "^11.5.1", "lucide-react": "^1.16.0", "motion": "^12.40.0", "next": "16.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b642772..f697e35 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: '@vercel/speed-insights': specifier: ^2.0.0 version: 2.0.0(next@16.2.4(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + '@xenova/transformers': + specifier: ^2.17.2 + version: 2.17.2 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -47,6 +50,9 @@ importers: gsap: specifier: ^3.15.0 version: 3.15.0 + lru-cache: + specifier: ^11.5.1 + version: 11.5.1 lucide-react: specifier: ^1.16.0 version: 1.17.0(react@19.2.4) @@ -531,6 +537,10 @@ packages: peerDependencies: hono: ^4 + '@huggingface/jinja@0.2.2': + resolution: {integrity: sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==} + engines: {node: '>=18'} + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -581,105 +591,89 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -759,28 +753,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@next/swc-linux-arm64-musl@16.2.4': resolution: {integrity: sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@next/swc-linux-x64-gnu@16.2.4': resolution: {integrity: sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@next/swc-linux-x64-musl@16.2.4': resolution: {integrity: sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@next/swc-win32-arm64-msvc@16.2.4': resolution: {integrity: sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==} @@ -873,6 +863,36 @@ packages: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@radix-ui/number@1.1.2': resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==} @@ -1318,28 +1338,24 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.3.0': resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.3.0': resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.3.0': resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.3.0': resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} @@ -1395,6 +1411,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/long@4.0.2': + resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + '@types/node@20.19.40': resolution: {integrity: sha512-xxx6M2IpSTnnKcR0cMvIiohkiCx20/oRPtWGbenFygKCGl3zqUzdNjQ/1V4solq1LU+dgv0nQzeGOuqkqZGg0Q==} @@ -1510,49 +1529,41 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] - libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -1629,6 +1640,9 @@ packages: vue-router: optional: true + '@xenova/transformers@2.17.2': + resolution: {integrity: sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1755,6 +1769,14 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1762,6 +1784,50 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + bare-events@2.9.1: + resolution: {integrity: sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.7.2: + resolution: {integrity: sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.9.2: + resolution: {integrity: sha512-h530JsrkYi8518ZfR57GHaLoI5YzXkGGEV0Y+mf4KYPBn4OnNajiznwkDq7FgE+Vnmyss9Utnzi44y7sowiAXA==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.1: + resolution: {integrity: sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==} + + bare-stream@2.13.3: + resolution: {integrity: sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.4.5: + resolution: {integrity: sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.10.28: resolution: {integrity: sha512-Ic44hnOtFIgravCunj1ifSoQPSUrkNiJuH9Mf6jr2jjoA74icqV8wU0KuadXeOR8zuIJMOoTv0GuQjZ9ZYNMeA==} engines: {node: '>=6.0.0'} @@ -1770,6 +1836,9 @@ packages: better-result@2.9.2: resolution: {integrity: sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + body-parser@2.3.0: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} @@ -1790,6 +1859,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -1841,6 +1913,9 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -1866,6 +1941,13 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -1968,6 +2050,10 @@ packages: supports-color: optional: true + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dedent@1.7.2: resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} peerDependencies: @@ -1976,6 +2062,10 @@ packages: babel-plugin-macros: optional: true + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2075,6 +2165,9 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.21.2: resolution: {integrity: sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==} engines: {node: '>=10.13.0'} @@ -2271,6 +2364,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + eventsource-parser@3.1.0: resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} engines: {node: '>=18.0.0'} @@ -2287,6 +2383,10 @@ packages: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + express-rate-limit@8.5.2: resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} engines: {node: '>= 16'} @@ -2307,6 +2407,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -2364,6 +2467,9 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} + flatbuffers@1.12.0: + resolution: {integrity: sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==} + flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} @@ -2397,6 +2503,9 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@11.3.5: resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} engines: {node: '>=14.14'} @@ -2472,6 +2581,9 @@ packages: resolution: {integrity: sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==} hasBin: true + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2508,6 +2620,9 @@ packages: gsap@3.15.0: resolution: {integrity: sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==} + guid-typescript@1.0.9: + resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -2564,6 +2679,9 @@ packages: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2583,6 +2701,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -2602,6 +2723,9 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -2897,28 +3021,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -2954,6 +3074,9 @@ packages: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} + long@4.0.0: + resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -2961,6 +3084,10 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -3019,6 +3146,10 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -3029,6 +3160,9 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + motion-dom@12.42.0: resolution: {integrity: sha512-M63h4n8R+quJdNhBwuLlgxM+OLYa9+I/T2pzDRboB9fLXRdbou+Gw7Zury+SkpaCyACP1JHSjHgZ1EgTkBr30w==} @@ -3065,6 +3199,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -3098,6 +3235,13 @@ packages: sass: optional: true + node-abi@3.92.0: + resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} + engines: {node: '>=10'} + + node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + node-exports-info@1.6.0: resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} engines: {node: '>= 0.4'} @@ -3167,6 +3311,19 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + onnx-proto@4.0.4: + resolution: {integrity: sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==} + + onnxruntime-common@1.14.0: + resolution: {integrity: sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew==} + + onnxruntime-node@1.14.0: + resolution: {integrity: sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w==} + os: [win32, darwin, linux] + + onnxruntime-web@1.14.0: + resolution: {integrity: sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw==} + open@11.0.0: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} @@ -3310,6 +3467,9 @@ packages: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -3354,6 +3514,12 @@ packages: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -3385,10 +3551,17 @@ packages: proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + protobufjs@6.11.6: + resolution: {integrity: sha512-k8BHqgPBOtrlougZZqF2uUk5Z7bN8f0wj+3e8M3hvtSv0NBAz4VBy5f6R5Nxq/l+i7mRFTgNZb2trxqTpHNY/A==} + hasBin: true + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -3414,6 +3587,10 @@ packages: rc9@3.0.1: resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-dom@19.2.4: resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: @@ -3456,6 +3633,10 @@ packages: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readdirp@5.0.0: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} @@ -3518,6 +3699,9 @@ packages: resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -3572,6 +3756,10 @@ packages: engines: {node: '>=20.18.1'} hasBin: true + sharp@0.32.6: + resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} + engines: {node: '>=14.15.0'} + sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3611,6 +3799,15 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -3648,6 +3845,9 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + streamx@2.28.0: + resolution: {integrity: sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -3675,6 +3875,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-object@5.0.0: resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==} engines: {node: '>=14.16'} @@ -3699,6 +3902,10 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -3740,6 +3947,25 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + tar-fs@2.1.5: + resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} + + tar-fs@3.1.3: + resolution: {integrity: sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.2.0: + resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -3779,6 +4005,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -4412,6 +4641,8 @@ snapshots: dependencies: hono: 4.12.18 + '@huggingface/jinja@0.2.2': {} + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -4718,6 +4949,28 @@ snapshots: transitivePeerDependencies: - '@types/react-dom' + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + '@radix-ui/number@1.1.2': {} '@radix-ui/primitive@1.1.3': {} @@ -5189,6 +5442,8 @@ snapshots: '@types/json5@0.0.29': {} + '@types/long@4.0.2': {} + '@types/node@20.19.40': dependencies: undici-types: 6.21.0 @@ -5369,6 +5624,18 @@ snapshots: next: 16.2.4(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 + '@xenova/transformers@2.17.2': + dependencies: + '@huggingface/jinja': 0.2.2 + onnxruntime-web: 1.14.0 + sharp: 0.32.6 + optionalDependencies: + onnxruntime-node: 1.14.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -5507,14 +5774,57 @@ snapshots: axobject-query@4.1.0: {} + b4a@1.8.1: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} + bare-events@2.9.1: {} + + bare-fs@4.7.2: + dependencies: + bare-events: 2.9.1 + bare-path: 3.0.1 + bare-stream: 2.13.3(bare-events@2.9.1) + bare-url: 2.4.5 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-os@3.9.2: {} + + bare-path@3.0.1: + dependencies: + bare-os: 3.9.2 + + bare-stream@2.13.3(bare-events@2.9.1): + dependencies: + b4a: 1.8.1 + streamx: 2.28.0 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.9.1 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.4.5: + dependencies: + bare-path: 3.0.1 + + base64-js@1.5.1: {} + baseline-browser-mapping@2.10.28: {} better-result@2.9.2: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + body-parser@2.3.0: dependencies: bytes: 3.1.2 @@ -5550,6 +5860,11 @@ snapshots: node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + bundle-name@4.1.0: dependencies: run-applescript: 7.1.0 @@ -5607,6 +5922,8 @@ snapshots: dependencies: readdirp: 5.0.0 + chownr@1.1.4: {} + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -5625,6 +5942,16 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + commander@11.1.0: {} commander@14.0.3: {} @@ -5714,8 +6041,14 @@ snapshots: dependencies: ms: 2.1.3 + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + dedent@1.7.2: {} + deep-extend@0.6.0: {} + deep-is@0.1.4: {} deepmerge-ts@7.1.5: {} @@ -5792,6 +6125,10 @@ snapshots: encodeurl@2.0.0: {} + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enhanced-resolve@5.21.2: dependencies: graceful-fs: 4.2.11 @@ -6155,6 +6492,12 @@ snapshots: etag@1.8.1: {} + events-universal@1.0.1: + dependencies: + bare-events: 2.9.1 + transitivePeerDependencies: + - bare-abort-controller + eventsource-parser@3.1.0: {} eventsource@3.0.7: @@ -6188,6 +6531,8 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.2 + expand-template@2.0.3: {} + express-rate-limit@8.5.2(express@5.2.1): dependencies: express: 5.2.1 @@ -6234,6 +6579,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.1: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6301,6 +6648,8 @@ snapshots: flatted: 3.4.2 keyv: 4.5.4 + flatbuffers@1.12.0: {} + flatted@3.4.2: {} for-each@0.3.5: @@ -6325,6 +6674,8 @@ snapshots: fresh@2.0.0: {} + fs-constants@1.0.0: {} + fs-extra@11.3.5: dependencies: graceful-fs: 4.2.11 @@ -6402,6 +6753,8 @@ snapshots: giget@3.2.0: {} + github-from-package@0.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -6429,6 +6782,8 @@ snapshots: gsap@3.15.0: {} + guid-typescript@1.0.9: {} + has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -6477,6 +6832,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -6490,6 +6847,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -6508,6 +6867,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.4: {} + is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -6800,12 +7161,16 @@ snapshots: chalk: 5.6.2 is-unicode-supported: 1.3.0 + long@4.0.0: {} + long@5.3.2: {} loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 + lru-cache@11.5.1: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -6847,6 +7212,8 @@ snapshots: mimic-function@5.0.1: {} + mimic-response@3.1.0: {} + minimatch@10.2.5: dependencies: brace-expansion: 5.0.6 @@ -6857,6 +7224,8 @@ snapshots: minimist@1.2.8: {} + mkdirp-classic@0.5.3: {} + motion-dom@12.42.0: dependencies: motion-utils: 12.39.0 @@ -6891,6 +7260,8 @@ snapshots: nanoid@3.3.12: {} + napi-build-utils@2.0.0: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -6921,6 +7292,12 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-abi@3.92.0: + dependencies: + semver: 7.8.0 + + node-addon-api@6.1.0: {} + node-exports-info@1.6.0: dependencies: array.prototype.flatmap: 1.3.3 @@ -7001,6 +7378,26 @@ snapshots: dependencies: mimic-function: 5.0.1 + onnx-proto@4.0.4: + dependencies: + protobufjs: 6.11.6 + + onnxruntime-common@1.14.0: {} + + onnxruntime-node@1.14.0: + dependencies: + onnxruntime-common: 1.14.0 + optional: true + + onnxruntime-web@1.14.0: + dependencies: + flatbuffers: 1.12.0 + guid-typescript: 1.0.9 + long: 4.0.0 + onnx-proto: 4.0.4 + onnxruntime-common: 1.14.0 + platform: 1.3.6 + open@11.0.0: dependencies: default-browser: 5.5.0 @@ -7147,6 +7544,8 @@ snapshots: dependencies: find-up: 3.0.0 + platform@1.3.6: {} + possible-typed-array-names@1.1.0: {} postcss-selector-parser@7.1.4: @@ -7182,6 +7581,21 @@ snapshots: powershell-utils@0.1.0: {} + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.92.0 + pump: 3.0.4 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.5 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} pretty-ms@9.3.0: @@ -7222,11 +7636,32 @@ snapshots: retry: 0.12.0 signal-exit: 3.0.7 + protobufjs@6.11.6: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/long': 4.0.2 + '@types/node': 20.19.40 + long: 4.0.0 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -7252,6 +7687,13 @@ snapshots: defu: 6.1.7 destr: 2.0.5 + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 @@ -7288,6 +7730,12 @@ snapshots: react@19.2.4: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@5.0.0: {} recast@0.23.11: @@ -7368,6 +7816,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -7478,6 +7928,21 @@ snapshots: - supports-color - typescript + sharp@0.32.6: + dependencies: + color: 4.2.3 + detect-libc: 2.1.2 + node-addon-api: 6.1.0 + prebuild-install: 7.1.3 + semver: 7.8.0 + simple-get: 4.0.1 + tar-fs: 3.1.3 + tunnel-agent: 0.6.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + sharp@0.34.5: dependencies: '@img/colour': 1.1.0 @@ -7556,6 +8021,18 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + sisteransi@1.0.5: {} source-map-js@1.2.1: {} @@ -7579,6 +8056,15 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + streamx@2.28.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -7635,6 +8121,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + stringify-object@5.0.0: dependencies: get-own-enumerable-keys: 1.0.0 @@ -7655,6 +8145,8 @@ snapshots: strip-final-newline@4.0.0: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.4): @@ -7678,6 +8170,57 @@ snapshots: tapable@2.3.3: {} + tar-fs@2.1.5: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-fs@3.1.3: + dependencies: + pump: 3.0.4 + tar-stream: 3.2.0 + optionalDependencies: + bare-fs: 4.7.2 + bare-path: 3.0.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar-stream@3.2.0: + dependencies: + b4a: 1.8.1 + bare-fs: 4.7.2 + fast-fifo: 1.3.2 + streamx: 2.28.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + teex@1.0.1: + dependencies: + streamx: 2.28.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.1 + transitivePeerDependencies: + - react-native-b4a + tiny-invariant@1.3.3: {} tinyglobby@0.2.16: @@ -7721,6 +8264,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b431131..606e749 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,6 @@ -allowBuilds: - '@prisma/engines': set this to true or false - esbuild: set this to true or false - prisma: set this to true or false - sharp: set this to true or false - unrs-resolver: set this to true or false +onlyBuiltDependencies: + - '@prisma/engines' + - esbuild + - prisma + - sharp + - unrs-resolver diff --git a/src/ai/embeddings.ts b/src/ai/embeddings.ts new file mode 100644 index 0000000..51e5b15 --- /dev/null +++ b/src/ai/embeddings.ts @@ -0,0 +1,99 @@ +import { env, pipeline } from "@xenova/transformers"; + +env.localModelPath = ""; +env.allowRemoteModels = true; + +type EmbeddingFn = ( + texts: string | string[], + options?: { pooling?: string; normalize?: boolean }, +) => Promise<{ data: Float32Array }>; + +let embeddingModel: EmbeddingFn | null = null; +let modelLoadPromise: Promise | null = null; + +export const EMBEDDING_DIMENSION = 384; + +async function getModel(): Promise { + if (embeddingModel) return embeddingModel; + + if (!modelLoadPromise) { + modelLoadPromise = pipeline( + "feature-extraction", + "Xenova/all-MiniLM-L6-v2", + ) + .then((p) => { + const fn = p as unknown as EmbeddingFn; + embeddingModel = fn; + return fn; + }) + .catch((err) => { + modelLoadPromise = null; + throw err; + }); + } + + return modelLoadPromise; +} + +export async function warmup(): Promise { + await getModel(); +} + +export async function generateEmbedding(text: string): Promise { + const pipe = await getModel(); + const result = await pipe(text, { pooling: "mean", normalize: true }); + return result.data as Float32Array; +} + +export async function generateBatchEmbeddings( + texts: string[], +): Promise { + if (texts.length === 0) return []; + + const pipe = await getModel(); + const result = await pipe(texts, { pooling: "mean", normalize: true }); + const flat = result.data as Float32Array; + const dim = flat.length / texts.length; + + const embeddings: Float32Array[] = []; + for (let i = 0; i < texts.length; i++) { + embeddings.push(flat.slice(i * dim, (i + 1) * dim)); + } + return embeddings; +} + +export function cosineSimilarity( + a: Float32Array | number[], + b: Float32Array | number[], +): number { + if (a.length !== b.length) return 0; + + let dot = 0; + let na = 0; + let nb = 0; + + for (let i = 0; i < a.length; i++) { + dot += a[i] * b[i]; + na += a[i] * a[i]; + nb += b[i] * b[i]; + } + + const mag = Math.sqrt(na) * Math.sqrt(nb); + return mag === 0 ? 0 : dot / mag; +} + +export function findMostSimilar( + queryVec: Float32Array, + candidates: Float32Array[], + minScore = 0, +): Array<{ index: number; score: number }> { + const scored: Array<{ index: number; score: number }> = []; + + for (let i = 0; i < candidates.length; i++) { + const score = cosineSimilarity(queryVec, candidates[i]); + if (score >= minScore) scored.push({ index: i, score }); + } + + scored.sort((a, b) => b.score - a.score); + return scored; +} diff --git a/src/ai/index.ts b/src/ai/index.ts new file mode 100644 index 0000000..54f0925 --- /dev/null +++ b/src/ai/index.ts @@ -0,0 +1,8 @@ +export { + generateEmbedding, + generateBatchEmbeddings, + cosineSimilarity, + findMostSimilar, + warmup, + EMBEDDING_DIMENSION, +} from "./embeddings"; diff --git a/src/app/api/ai/events/[eventId]/recommendations/route.ts b/src/app/api/ai/events/[eventId]/recommendations/route.ts new file mode 100644 index 0000000..20c492a --- /dev/null +++ b/src/app/api/ai/events/[eventId]/recommendations/route.ts @@ -0,0 +1,95 @@ +import { NextResponse } from "next/server"; +import { LRUCache } from "lru-cache"; +import prisma from "@/lib/db/prisma"; +import { generateEmbedding, generateBatchEmbeddings, findMostSimilar } from "@/ai"; +import type { SearchResultDto } from "@/types/dto"; + +const RECOMMENDATION_CACHE = new LRUCache({ + max: 100, + ttl: 10 * 60 * 1000, +}); + +export async function GET( + _: Request, + { params }: { params: Promise<{ eventId: string }> }, +): Promise> { + try { + const { eventId } = await params; + + const event = await prisma.event.findUnique({ + where: { id: eventId }, + include: { venue: true }, + }); + + if (!event) { + return NextResponse.json( + { error: "Event not found" }, + { status: 404 }, + ); + } + + const cached = RECOMMENDATION_CACHE.get(eventId); + if (cached) { + return NextResponse.json({ data: cached }, { status: 200 }); + } + + const sourceText = [event.title, event.description] + .filter(Boolean) + .join(" "); + const sourceVec = await generateEmbedding(sourceText); + const otherEvents = await prisma.event.findMany({ + where: { id: { not: eventId } }, + include: { venue: true }, + }); + + const otherTexts = otherEvents.map((e) => + [e.title, e.description, e.venue?.name ?? ""] + .filter(Boolean) + .join(" "), + ); + + const otherVecs = await generateBatchEmbeddings(otherTexts); + + const scored = findMostSimilar(sourceVec, otherVecs, 0.4).slice(0, 5); + + const results: SearchResultDto[] = scored.map((s) => { + const e = otherEvents[s.index]; + const v = e.venue; + return { + id: e.id, + title: e.title, + description: e.description, + startDate: e.startDate.toISOString(), + endDate: e.endDate.toISOString(), + isOnline: e.isOnline, + type: "event", + score: Math.round(s.score * 1000) / 1000, + match: { + venue: v + ? { + id: v.id, + name: v.name, + city: v.city, + neighborhood: v.neighborhood, + totalRooms: v.totalRooms, + } + : null, + speakers: [], + }, + }; + }); + + RECOMMENDATION_CACHE.set(eventId, results); + + return NextResponse.json({ data: results }, { status: 200 }); + } catch (err) { + console.error( + "GET /api/ai/events/[eventId]/recommendations error:", + err, + ); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} diff --git a/src/app/api/ai/questions/check/route.ts b/src/app/api/ai/questions/check/route.ts new file mode 100644 index 0000000..82edbd9 --- /dev/null +++ b/src/app/api/ai/questions/check/route.ts @@ -0,0 +1,104 @@ +import { NextRequest, NextResponse } from "next/server"; +import prisma from "@/lib/db/prisma"; +import { generateEmbedding, generateBatchEmbeddings, cosineSimilarity } from "@/ai"; +import type { DuplicateCheckResponseDto } from "@/types/dto"; + +const SIMILARITY_THRESHOLD = 0.85; + +export async function POST( + request: NextRequest, +): Promise< + NextResponse<{ data: DuplicateCheckResponseDto } | { error: string }> +> { + try { + const body = await request.json(); + const { sessionId, content } = body; + + if (!sessionId || typeof sessionId !== "string") { + return NextResponse.json( + { error: "'sessionId' is required" }, + { status: 400 }, + ); + } + if (!content || typeof content !== "string" || content.trim().length === 0) { + return NextResponse.json( + { error: "'content' is required" }, + { status: 400 }, + ); + } + + const trimmed = content.trim(); + + const existingQuestions = await prisma.question.findMany({ + where: { eventSessionId: sessionId }, + orderBy: { createdAt: "desc" }, + take: 50, + }); + + if (existingQuestions.length === 0) { + return NextResponse.json( + { + data: { + isDuplicate: false, + similarity: 0, + similarQuestion: null, + }, + }, + { status: 200 }, + ); + } + + const queryVec = await generateEmbedding(trimmed); + + const existingTexts = existingQuestions.map((q) => q.content); + const existingVecs = await generateBatchEmbeddings(existingTexts); + + let bestScore = 0; + let bestIndex = -1; + + for (let i = 0; i < existingVecs.length; i++) { + const score = cosineSimilarity(queryVec, existingVecs[i]); + if (score > bestScore) { + bestScore = score; + bestIndex = i; + } + } + + if (bestScore >= SIMILARITY_THRESHOLD) { + const match = existingQuestions[bestIndex]; + return NextResponse.json( + { + data: { + isDuplicate: true, + similarity: Math.round(bestScore * 1000) / 1000, + similarQuestion: { + id: match.id, + content: match.content, + authorName: match.authorName, + upvotes: match.upvotes, + createdAt: match.createdAt.toISOString(), + }, + }, + }, + { status: 200 }, + ); + } + + return NextResponse.json( + { + data: { + isDuplicate: false, + similarity: Math.round(bestScore * 1000) / 1000, + similarQuestion: null, + }, + }, + { status: 200 }, + ); + } catch (err) { + console.error("POST /api/ai/questions/check error:", err); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} diff --git a/src/app/api/ai/search/route.ts b/src/app/api/ai/search/route.ts new file mode 100644 index 0000000..6a347a4 --- /dev/null +++ b/src/app/api/ai/search/route.ts @@ -0,0 +1,155 @@ +import { NextRequest, NextResponse } from "next/server"; +import prisma from "@/lib/db/prisma"; +import { generateEmbedding, generateBatchEmbeddings, cosineSimilarity } from "@/ai"; +import type { SearchResultDto } from "@/types/dto"; + +interface CachedEventEmbedding { + embedding: Float32Array; + event: { + id: string; + title: string; + description: string | null; + startDate: Date; + endDate: Date; + isOnline: boolean; + venue: { + id: string; + name: string; + city: string; + neighborhood: string; + totalRooms: number; + } | null; + }; +} + +let cachedEmbeddings: CachedEventEmbedding[] | null = null; +let cachedAt = 0; +const CACHE_TTL = 5 * 60 * 1000; + +export async function GET( + request: NextRequest, +): Promise< + NextResponse<{ data: SearchResultDto[] } | { error: string }> +> { + try { + const { searchParams } = new URL(request.url); + const query = searchParams.get("q")?.trim(); + const limit = Math.min(50, parseInt(searchParams.get("limit") || "10")); + const city = searchParams.get("city"); + const dateFrom = searchParams.get("dateFrom"); + const dateTo = searchParams.get("dateTo"); + const status = searchParams.get("status"); + + if (!query) { + return NextResponse.json( + { error: "Query parameter 'q' is required" }, + { status: 400 }, + ); + } + + if (query.length > 500) { + return NextResponse.json( + { error: "Query too long (max 500 characters)" }, + { status: 400 }, + ); + } + + const now = Date.now(); + if (!cachedEmbeddings || now - cachedAt > CACHE_TTL) { + const events = await prisma.event.findMany({ + include: { venue: true }, + orderBy: { startDate: "asc" }, + }); + + const texts = events.map((e) => + [e.title, e.description, e.venue?.name ?? ""] + .filter(Boolean) + .join(" "), + ); + + const embeddings = await generateBatchEmbeddings(texts); + + cachedEmbeddings = events.map((event, i) => ({ + embedding: embeddings[i], + event: { + id: event.id, + title: event.title, + description: event.description, + startDate: event.startDate, + endDate: event.endDate, + isOnline: event.isOnline, + venue: event.venue + ? { + id: event.venue.id, + name: event.venue.name, + city: event.venue.city, + neighborhood: event.venue.neighborhood, + totalRooms: event.venue.totalRooms, + } + : null, + }, + })); + cachedAt = now; + } + + const queryVec = await generateEmbedding(query); + + const nowDate = new Date(); + + const scored = cachedEmbeddings + .filter((item) => { + if (city && item.event.venue?.city !== city) return false; + if (dateFrom && item.event.startDate < new Date(dateFrom)) return false; + if (dateTo && item.event.endDate > new Date(dateTo)) return false; + if (status === "live") { + if (!(item.event.startDate <= nowDate && item.event.endDate >= nowDate)) return false; + } else if (status === "upcoming") { + if (item.event.startDate <= nowDate) return false; + } else if (status === "ended") { + if (item.event.endDate >= nowDate) return false; + } + return true; + }) + .map((item) => ({ + item, + score: cosineSimilarity(queryVec, item.embedding), + })) + .filter((s) => s.score > 0.3) + .sort((a, b) => b.score - a.score) + .slice(0, limit); + + const data: SearchResultDto[] = scored.map((s) => { + const v = s.item.event.venue; + return { + id: s.item.event.id, + title: s.item.event.title, + description: s.item.event.description, + startDate: s.item.event.startDate.toISOString(), + endDate: s.item.event.endDate.toISOString(), + isOnline: s.item.event.isOnline, + type: "event", + score: Math.round(s.score * 1000) / 1000, + match: { + venue: v + ? { + id: v.id, + name: v.name, + city: v.city, + neighborhood: v.neighborhood, + totalRooms: v.totalRooms, + } + : null, + speakers: [], + }, + }; + }); + + return NextResponse.json({ data }, { status: 200 }); + } catch (err) { + console.error("GET /api/ai/search error:", err); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} diff --git a/src/app/events/[eventId]/page.tsx b/src/app/events/[eventId]/page.tsx index 5930d39..772cb78 100644 --- a/src/app/events/[eventId]/page.tsx +++ b/src/app/events/[eventId]/page.tsx @@ -3,7 +3,9 @@ import Link from "next/link"; import { useState, useRef, useCallback, useMemo } from "react"; import { useParams } from "next/navigation"; +import { useQuery } from "@tanstack/react-query"; import { useGetEvent } from "@/lib/hooks/useEvents"; +import { api } from "@/lib/api"; import { EventHero } from "@/components/events/EventHero"; import { EventInfoGrid } from "@/components/events/EventInfoGrid"; import { EventSchedule } from "@/components/features/EventSchedule"; @@ -30,6 +32,12 @@ function NotFound() { export default function EventDetailPage() { const { eventId } = useParams<{ eventId: string }>(); const { data: event, isLoading } = useGetEvent(eventId); + const { data: recsData } = useQuery({ + queryKey: ["recommendations", eventId], + queryFn: () => api.getRecommendations(eventId), + enabled: !!eventId, + staleTime: 10 * 60 * 1000, + }); const [selectedDay, setSelectedDay] = useState("all"); const [viewMode, setViewMode] = useState("table"); const [statusFilter, setStatusFilter] = useState("all"); @@ -252,6 +260,39 @@ export default function EventDetailPage() { )} + + {recsData?.data && recsData.data.length > 0 && ( +
+

ยง RECOMMENDED

+

+ You might also like +

+
+ {recsData.data.map((rec) => ( + +

+ {rec.title} +

+

+ {rec.description || "No description"} +

+
+ + {rec.match?.venue?.city ?? "Online"} + + + {Math.round(rec.score * 100)}% match + +
+ + ))} +
+
+ )} ); diff --git a/src/app/events/page.tsx b/src/app/events/page.tsx index 838cc25..dfbb7c5 100644 --- a/src/app/events/page.tsx +++ b/src/app/events/page.tsx @@ -3,11 +3,11 @@ import { useState, useMemo, useDeferredValue } from "react"; import { useQuery } from "@tanstack/react-query"; import dynamic from "next/dynamic"; -import { useGetEvents } from "@/lib/hooks/useEvents"; +import { useGetEvents, useSearchEvents } from "@/lib/hooks/useEvents"; import { api } from "@/lib/api"; import { Select } from "@/components/ui/Select"; import { Search, X } from "lucide-react"; -import type { EventSummaryDto } from "@/types/dto"; +import type { EventSummaryDto, SearchResultDto } from "@/types/dto"; import { BentoGrid } from "@/components/features/EventsList/event-cards"; const DateRangePicker = dynamic(() => @@ -80,19 +80,41 @@ export default function EventsPage() { const queryParams = useMemo(() => ({ page: 1, limit: 50, ...(status !== "all" && { status }), - ...(deferredSearch && { search: deferredSearch }), ...(city !== "all" && { city }), ...(dateFrom && { dateFrom }), ...(dateTo && { dateTo }), - }), [status, deferredSearch, city, dateFrom, dateTo]); + }), [status, city, dateFrom, dateTo]); - const { data: eventsData, isLoading } = useGetEvents(queryParams); - - const events = useMemo( - () => filterEvents(eventsData?.data || [], format), - [eventsData, format], + const { data: searchData, isLoading: isSearching } = useSearchEvents(deferredSearch, { + city: city !== "all" ? city : undefined, + dateFrom: dateFrom || undefined, + dateTo: dateTo || undefined, + status: status !== "all" ? status : undefined, + }); + const { data: eventsData, isLoading: isLoadingEvents } = useGetEvents( + deferredSearch ? { page: 1, limit: 50, ...(status !== "all" && { status }) } : queryParams, + { enabled: !deferredSearch }, ); + const events = useMemo(() => { + if (deferredSearch && searchData?.data) { + const results: EventSummaryDto[] = searchData.data.map((r: SearchResultDto) => ({ + id: r.id, + title: r.title, + description: r.description, + startDate: r.startDate, + endDate: r.endDate, + isOnline: r.isOnline, + venue: r.match?.venue || null, + eventSessionCount: 0, + })); + return filterEvents(results, format); + } + return filterEvents(eventsData?.data || [], format); + }, [deferredSearch, searchData, eventsData, format]); + + const isLoading = deferredSearch ? isSearching : isLoadingEvents; + const hasActiveFilters = status !== "all" || format !== "all" || city !== "all" || !!search || !!dateFrom || !!dateTo; const clearAll = () => { diff --git a/src/app/sessions/[sessionId]/page.tsx b/src/app/sessions/[sessionId]/page.tsx index 2fcd916..d49ea81 100644 --- a/src/app/sessions/[sessionId]/page.tsx +++ b/src/app/sessions/[sessionId]/page.tsx @@ -147,6 +147,7 @@ export default function SessionPlayerPage() { (null); + const [isChecking, setIsChecking] = useState(false); useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); @@ -85,6 +93,35 @@ export function QaPanel({ const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!questionText.trim()) return; + + if (duplicateWarning) { + setDuplicateWarning(null); + setIsSubmitting(true); + try { + await onSubmitQuestion(questionText, authorName); + setQuestionText(""); + } finally { + setIsSubmitting(false); + } + return; + } + + setIsChecking(true); + try { + const result = await api.checkDuplicateQuestion(sessionId, questionText); + if (result.data.isDuplicate && result.data.similarQuestion) { + setDuplicateWarning({ + similarQuestion: result.data.similarQuestion, + similarity: result.data.similarity, + }); + return; + } + } catch { + // If the check fails, submit anyway + } finally { + setIsChecking(false); + } + setIsSubmitting(true); try { await onSubmitQuestion(questionText, authorName); @@ -94,6 +131,11 @@ export function QaPanel({ } }; + const handleTextChange = (value: string) => { + setQuestionText(value); + if (duplicateWarning) setDuplicateWarning(null); + }; + const isEmptyQuestions = questions.length === 0; return ( @@ -160,22 +202,50 @@ export function QaPanel({ {isLive && (
+ {duplicateWarning && ( +
+
+ +

+ SIMILAR QUESTION EXISTS +

+
+

+ {'\u201C'}{duplicateWarning.similarQuestion.content}{'\u201D'} +

+
+ + {Math.round(duplicateWarning.similarity * 100)}% similar + + +
+
+ )}
setQuestionText(e.target.value)} + onChange={(e) => handleTextChange(e.target.value)} placeholder="Ask a question..." className="flex-1 px-3 py-2 text-xs bg-white/5 border border-white/10 text-ivory/80 placeholder-ivory/30 focus:outline-none focus:border-chartreuse/40 rounded-lg" required />
{ clearInterval(timerRef.current); clearTimeout(hideTimerRef.current); - setVisible(true); + queueMicrotask(() => { + setVisible(true); + setProgress(30); + }); progressRef.current = 30; - setProgress(30); timerRef.current = setInterval(() => { progressRef.current = Math.min( progressRef.current + (100 - progressRef.current) * 0.2, diff --git a/src/instrumentation.ts b/src/instrumentation.ts new file mode 100644 index 0000000..2c314c8 --- /dev/null +++ b/src/instrumentation.ts @@ -0,0 +1,11 @@ +export async function register() { + if (process.env.NEXT_RUNTIME === "nodejs") { + try { + const { warmup } = await import("@/ai"); + await warmup(); + console.log("[ai] model loaded on startup"); + } catch (err) { + console.error("[ai] model warmup failed:", err); + } + } +} diff --git a/src/lib/api.ts b/src/lib/api.ts index 5b15ca3..ed81284 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -10,7 +10,8 @@ import type { VenueDetailDto, QuestionDto, UpvoteResponseDto, - + SearchResultDto, + DuplicateCheckResponseDto, } from "@/types/dto"; import { API_BASE_URL } from "./constants"; @@ -163,6 +164,24 @@ export const api = { }), getLiveSessionCount: () => http<{ count: number }>("/api/live/count"), + + searchEvents: (q: string, params?: { city?: string; dateFrom?: string; dateTo?: string; status?: string }) => { + const searchParams = new URLSearchParams({ q }); + if (params?.city && params.city !== "all") searchParams.set("city", params.city); + if (params?.dateFrom) searchParams.set("dateFrom", params.dateFrom); + if (params?.dateTo) searchParams.set("dateTo", params.dateTo); + if (params?.status && params.status !== "all") searchParams.set("status", params.status); + return http<{ data: SearchResultDto[] }>(`/api/ai/search?${searchParams.toString()}`); + }, + + getRecommendations: (eventId: string) => + http<{ data: SearchResultDto[] }>(`/api/ai/events/${eventId}/recommendations`), + + checkDuplicateQuestion: (sessionId: string, content: string) => + http<{ data: DuplicateCheckResponseDto }>("/api/ai/questions/check", { + method: "POST", + body: JSON.stringify({ sessionId, content }), + }), }; export function isEventLive(event: { diff --git a/src/lib/hooks/useEvents.ts b/src/lib/hooks/useEvents.ts index 54b1218..d4a67be 100644 --- a/src/lib/hooks/useEvents.ts +++ b/src/lib/hooks/useEvents.ts @@ -1,6 +1,6 @@ import { useQuery, keepPreviousData, UseQueryOptions } from "@tanstack/react-query"; import { api, GetEventsParams, PaginatedResponse } from "@/lib/api"; -import type { EventSummaryDto, EventDetailDto } from "@/types/dto"; +import type { EventSummaryDto, EventDetailDto, SearchResultDto } from "@/types/dto"; export const eventKeys = { all: ["events"] as const, @@ -8,6 +8,7 @@ export const eventKeys = { list: (params?: GetEventsParams) => [...eventKeys.lists(), params] as const, details: () => [...eventKeys.all, "detail"] as const, detail: (id: string) => [...eventKeys.details(), id] as const, + search: (q: string) => [...eventKeys.all, "search", q] as const, }; export function useGetEvents( @@ -36,3 +37,17 @@ export function useGetEvent( ...options, }); } + +export function useSearchEvents( + query: string, + params?: { city?: string; dateFrom?: string; dateTo?: string; status?: string }, + options?: Omit, "queryKey" | "queryFn">, +) { + return useQuery({ + queryKey: eventKeys.search(`${query}|${JSON.stringify(params || {})}`), + queryFn: () => api.searchEvents(query, params), + enabled: query.length > 0, + staleTime: 30 * 1000, + ...options, + }); +} diff --git a/src/types/dto/index.ts b/src/types/dto/index.ts index b11cc09..d5d7dd8 100644 --- a/src/types/dto/index.ts +++ b/src/types/dto/index.ts @@ -130,6 +130,27 @@ export interface UpvoteResponseDto { upvotes: number; } +export interface SearchResultDto { + id: string; + title: string; + description: string | null; + startDate: string; + endDate: string; + isOnline: boolean; + type: "event" | "session" | "speaker"; + score: number; + match: { + venue: VenueDto | null; + speakers: SpeakerRefDto[]; + } | null; +} + +export interface DuplicateCheckResponseDto { + isDuplicate: boolean; + similarity: number; + similarQuestion: QuestionDto | null; +} + export interface PaginationDto { page: number; limit: number;