diff --git a/.env.example b/.env.example index 72e4f0a..f670030 100644 --- a/.env.example +++ b/.env.example @@ -21,3 +21,8 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:55679 OTEL_EXPORTER_OTLP_INSECURE=true OTEL_EXPORTER_OTLP_PROTOCOL=grpc NEXT_OTEL_VERBOSE=0 + +# Inference +INFERENCE_URL=http://127.0.0.1:8001 +INFERENCE_MODEL_NAME=summarizer_medical_journals_qa +INFERENCE_MODEL_VERSION=120240905190000 \ No newline at end of file diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 80d9dc5..19d12a3 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -39,5 +39,6 @@ jobs: - name: Check format run: pnpm run format:check - - name: Test - run: pnpm run test + # Bring back when we have actual tests + # - name: Test + # run: pnpm run test diff --git a/package.json b/package.json index c2a4f3c..9b17ad5 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev -p 3100", "build": "next build", "start": "next start", "lint": "next lint", @@ -15,8 +15,11 @@ "@auth0/nextjs-auth0": "^3.5.0", "@clinia-ui/icons": "^0.1.8", "@clinia-ui/react": "^0.1.8", + "@clinia/client-common": "1.0.10-hgs-84d73454.0", + "@clinia/client-datapartition": "1.0.10-hgs-84d73454.0", "@clinia/search-sdk-core": "^0.1.0", "@clinia/search-sdk-react": "^0.1.0", + "@clinia/tritonclient": "^1.0.0", "@uidotdev/usehooks": "^2.4.1", "@vercel/otel": "^1.5.0", "clsx": "^2.1.0", @@ -29,7 +32,9 @@ "react-dom": "^18", "react-hook-form": "7.48.2", "react-markdown": "^9.0.1", - "tailwind-merge": "^2.2.1" + "sanitize-html": "^2.13.0", + "tailwind-merge": "^2.2.1", + "uuid": "^10.0.0" }, "devDependencies": { "@testing-library/react": "^14.2.1", @@ -38,6 +43,8 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/sanitize-html": "^2.13.0", + "@types/uuid": "^10.0.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.0.1", "eslint": "^8", @@ -52,4 +59,4 @@ "typescript": "^5", "vitest": "^1.3.1" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0152e75..8de89f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,12 +14,21 @@ dependencies: '@clinia-ui/react': specifier: ^0.1.8 version: 0.1.8(@types/react-dom@18.2.19)(@types/react@18.2.61) + '@clinia/client-common': + specifier: 1.0.10-hgs-84d73454.0 + version: 1.0.10-hgs-84d73454.0 + '@clinia/client-datapartition': + specifier: 1.0.10-hgs-84d73454.0 + version: 1.0.10-hgs-84d73454.0(postcss@8.4.35)(typescript@5.3.3) '@clinia/search-sdk-core': specifier: ^0.1.0 version: 0.1.0 '@clinia/search-sdk-react': specifier: ^0.1.0 version: 0.1.0(react@18.2.0) + '@clinia/tritonclient': + specifier: ^1.0.0 + version: 1.0.0(@bufbuild/protobuf@1.10.0) '@uidotdev/usehooks': specifier: ^2.4.1 version: 2.4.1(react-dom@18.2.0)(react@18.2.0) @@ -56,9 +65,15 @@ dependencies: react-markdown: specifier: ^9.0.1 version: 9.0.1(@types/react@18.2.61)(react@18.2.0) + sanitize-html: + specifier: ^2.13.0 + version: 2.13.0 tailwind-merge: specifier: ^2.2.1 version: 2.2.1 + uuid: + specifier: ^10.0.0 + version: 10.0.0 devDependencies: '@testing-library/react': @@ -79,6 +94,12 @@ devDependencies: '@types/react-dom': specifier: ^18 version: 18.2.19 + '@types/sanitize-html': + specifier: ^2.13.0 + version: 2.13.0 + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 '@vitejs/plugin-react': specifier: ^4.2.1 version: 4.2.1(vite@5.1.4) @@ -392,6 +413,10 @@ packages: '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 + /@bufbuild/protobuf@1.10.0: + resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} + dev: false + /@clinia-ui/icons@0.1.8(react@18.2.0): resolution: {integrity: sha512-C0IWMJNaNcK77oMejE78PN+7QVkMFj+57RGQO7gw23Aw+aW3DCZfFXwcy4VVHuEVXWf/J2RHkAh4nDo0UA8ygg==, tarball: https://npm.pkg.github.com/download/@clinia-ui/icons/0.1.8/ec4e78aa87f4a497324800a505decb841fc9ab4b} peerDependencies: @@ -455,11 +480,34 @@ packages: bundledDependencies: - '@clinia-ui/react' + /@clinia/client-common@1.0.10-hgs-84d73454.0: + resolution: {integrity: sha512-Sj/QyO4PpSrvU3S2mUVwC/qGNKwDKKN63ETulpSUtQF/MLAkYieiARk3L00HvewgpTi1kvYOQS0UpvX/qqaA6A==, tarball: https://npm.pkg.github.com/download/@clinia/client-common/1.0.10-hgs-84d73454.0/1cfc97209a44e69f9a829beccaf2007e90934e22} + dependencies: + typescript: 5.5.4 + dev: false + /@clinia/client-common@1.0.8-edge-174a580.0: resolution: {integrity: sha512-q8eI0+LlAWmT0v3Ch6/6rauKHNbLcpXrn59bAmfTmIMISWxzVLB56rAbab9a8b+8mcFp8+CODwvp1CV5oRhksA==, tarball: https://npm.pkg.github.com/download/@clinia/client-common/1.0.8-edge-174a580.0/cf1a278ceaa0741f5f3103ed5dd258589f32ba93} dependencies: - typescript: 5.4.2 + typescript: 5.5.4 + dev: false + + /@clinia/client-datapartition@1.0.10-hgs-84d73454.0(postcss@8.4.35)(typescript@5.3.3): + resolution: {integrity: sha512-CysHBnL8pAsMzpqAn1yBf34vkxyS/S/7aNDaUB1V8yV5aAZbZU3I9Hxz7UdLKG6M4wsLt10yVdCq1HKRuPSFoA==, tarball: https://npm.pkg.github.com/download/@clinia/client-datapartition/1.0.10-hgs-84d73454.0/be45a25c5d9b7871bf2acbe842b12d3541e696e8} + engines: {node: '>= 14.0.0'} + dependencies: + tsup: 6.7.0(postcss@8.4.35)(typescript@5.3.3) + transitivePeerDependencies: + - '@swc/core' + - postcss + - supports-color + - ts-node + - typescript dev: false + bundledDependencies: + - '@clinia/client-common' + - '@clinia/requester-browser-xhr' + - '@clinia/requester-node-http' /@clinia/search-sdk-core@0.1.0: resolution: {integrity: sha512-UxmRdwlD1yB+m3Kn4OfGPy1eXZEptDVGqnh6ht2fpCblnTcJBDd1fXyDfFxiQoaJBPzFlkB1vmWTLtCEY5K7lQ==, tarball: https://npm.pkg.github.com/download/@clinia/search-sdk-core/0.1.0/834b6672567b1cae9ef97272ead4de96e96c18a7} @@ -483,6 +531,17 @@ packages: rxjs: 7.8.1 dev: false + /@clinia/tritonclient@1.0.0(@bufbuild/protobuf@1.10.0): + resolution: {integrity: sha512-ov5CS8RpnsBBSXUtDsrWm+Ble3Vv888pPKFVqbPPZlEG4lRlcbhsHfvIqloG1yY4FzmkgYVyqOplQQCL6xLlVg==, tarball: https://npm.pkg.github.com/download/@clinia/tritonclient/1.0.0/f0f2dc8a45125eb400fd57c41a1dcd1a5c8f8edc} + dependencies: + '@connectrpc/connect': 1.4.0(@bufbuild/protobuf@1.10.0) + '@connectrpc/connect-node': 1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0) + '@connectrpc/connect-web': 1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0) + uuid: 10.0.0 + transitivePeerDependencies: + - '@bufbuild/protobuf' + dev: false + /@clinia/util-react@0.1.0(react@18.2.0): resolution: {integrity: sha512-KUT4gyLXKFB/Zuv8FPzBWCL98GESRIIWAgem/Uy742QijMrStjgEWxlPfN3iL6ct14wJkvVq9otrz6nIPSLCzQ==, tarball: https://npm.pkg.github.com/download/@clinia/util-react/0.1.0/f7721e7c71bd700e70b48d110cf0c0fc09a2bdb8} peerDependencies: @@ -496,6 +555,36 @@ packages: resolution: {integrity: sha512-IKQSSKC1Xw8OQtpOoP804azhnoWjkPcJFZlmqxUZPViY7bHl93hQdnCmjMPlYvMmjEKxxNOBrGP40DFN8MOtYw==, tarball: https://npm.pkg.github.com/download/@clinia/util/0.1.0/5ed8df2c1bcc9e2fd4ad0e28d1b5ed2a9c96b087} dev: false + /@connectrpc/connect-node@1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0): + resolution: {integrity: sha512-0ANnrr6SvsjevsWEgdzHy7BaHkluZyS6s4xNoVt7RBHFR5V/kT9lPokoIbYUOU9JHzdRgTaS3x5595mwUsu15g==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@bufbuild/protobuf': ^1.4.2 + '@connectrpc/connect': 1.4.0 + dependencies: + '@bufbuild/protobuf': 1.10.0 + '@connectrpc/connect': 1.4.0(@bufbuild/protobuf@1.10.0) + undici: 5.28.4 + dev: false + + /@connectrpc/connect-web@1.4.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0): + resolution: {integrity: sha512-13aO4psFbbm7rdOFGV0De2Za64DY/acMspgloDlcOKzLPPs0yZkhp1OOzAQeiAIr7BM/VOHIA3p8mF0inxCYTA==} + peerDependencies: + '@bufbuild/protobuf': ^1.4.2 + '@connectrpc/connect': 1.4.0 + dependencies: + '@bufbuild/protobuf': 1.10.0 + '@connectrpc/connect': 1.4.0(@bufbuild/protobuf@1.10.0) + dev: false + + /@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0): + resolution: {integrity: sha512-vZeOkKaAjyV4+RH3+rJZIfDFJAfr+7fyYr6sLDKbYX3uuTVszhFe9/YKf5DNqrDb5cKdKVlYkGn6DTDqMitAnA==} + peerDependencies: + '@bufbuild/protobuf': ^1.4.2 + dependencies: + '@bufbuild/protobuf': 1.10.0 + dev: false + /@esbuild/aix-ppc64@0.19.12: resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} engines: {node: '>=12'} @@ -505,6 +594,15 @@ packages: dev: true optional: true + /@esbuild/android-arm64@0.17.19: + resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + /@esbuild/android-arm64@0.19.12: resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} @@ -514,6 +612,15 @@ packages: dev: true optional: true + /@esbuild/android-arm@0.17.19: + resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + /@esbuild/android-arm@0.19.12: resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} @@ -523,6 +630,15 @@ packages: dev: true optional: true + /@esbuild/android-x64@0.17.19: + resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: false + optional: true + /@esbuild/android-x64@0.19.12: resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} @@ -532,6 +648,15 @@ packages: dev: true optional: true + /@esbuild/darwin-arm64@0.17.19: + resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@esbuild/darwin-arm64@0.19.12: resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} @@ -541,6 +666,15 @@ packages: dev: true optional: true + /@esbuild/darwin-x64@0.17.19: + resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@esbuild/darwin-x64@0.19.12: resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} @@ -550,6 +684,15 @@ packages: dev: true optional: true + /@esbuild/freebsd-arm64@0.17.19: + resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + /@esbuild/freebsd-arm64@0.19.12: resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} @@ -559,6 +702,15 @@ packages: dev: true optional: true + /@esbuild/freebsd-x64@0.17.19: + resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + /@esbuild/freebsd-x64@0.19.12: resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} @@ -568,6 +720,15 @@ packages: dev: true optional: true + /@esbuild/linux-arm64@0.17.19: + resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-arm64@0.19.12: resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} @@ -577,6 +738,15 @@ packages: dev: true optional: true + /@esbuild/linux-arm@0.17.19: + resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-arm@0.19.12: resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} @@ -586,6 +756,15 @@ packages: dev: true optional: true + /@esbuild/linux-ia32@0.17.19: + resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-ia32@0.19.12: resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} @@ -595,6 +774,15 @@ packages: dev: true optional: true + /@esbuild/linux-loong64@0.17.19: + resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-loong64@0.19.12: resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} @@ -604,6 +792,15 @@ packages: dev: true optional: true + /@esbuild/linux-mips64el@0.17.19: + resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-mips64el@0.19.12: resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} @@ -613,6 +810,15 @@ packages: dev: true optional: true + /@esbuild/linux-ppc64@0.17.19: + resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-ppc64@0.19.12: resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} @@ -622,6 +828,15 @@ packages: dev: true optional: true + /@esbuild/linux-riscv64@0.17.19: + resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-riscv64@0.19.12: resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} @@ -631,6 +846,15 @@ packages: dev: true optional: true + /@esbuild/linux-s390x@0.17.19: + resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-s390x@0.19.12: resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} @@ -640,6 +864,15 @@ packages: dev: true optional: true + /@esbuild/linux-x64@0.17.19: + resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-x64@0.19.12: resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} @@ -649,6 +882,15 @@ packages: dev: true optional: true + /@esbuild/netbsd-x64@0.17.19: + resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: false + optional: true + /@esbuild/netbsd-x64@0.19.12: resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} @@ -658,6 +900,15 @@ packages: dev: true optional: true + /@esbuild/openbsd-x64@0.17.19: + resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: false + optional: true + /@esbuild/openbsd-x64@0.19.12: resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} @@ -667,6 +918,15 @@ packages: dev: true optional: true + /@esbuild/sunos-x64@0.17.19: + resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: false + optional: true + /@esbuild/sunos-x64@0.19.12: resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} @@ -676,6 +936,15 @@ packages: dev: true optional: true + /@esbuild/win32-arm64@0.17.19: + resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@esbuild/win32-arm64@0.19.12: resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} @@ -685,6 +954,15 @@ packages: dev: true optional: true + /@esbuild/win32-ia32@0.17.19: + resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@esbuild/win32-ia32@0.19.12: resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} @@ -694,6 +972,15 @@ packages: dev: true optional: true + /@esbuild/win32-x64@0.17.19: + resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@esbuild/win32-x64@0.19.12: resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} @@ -740,6 +1027,11 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@fastify/busboy@2.1.1: + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + dev: false + /@floating-ui/core@1.6.0: resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} dependencies: @@ -4378,6 +4670,12 @@ packages: '@types/scheduler': 0.16.8 csstype: 3.1.3 + /@types/sanitize-html@2.13.0: + resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==} + dependencies: + htmlparser2: 8.0.2 + dev: true + /@types/scheduler@0.16.8: resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} @@ -4393,6 +4691,10 @@ packages: resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} dev: false + /@types/uuid@10.0.0: + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + dev: true + /@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -4690,7 +4992,6 @@ packages: /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - dev: true /array.prototype.filter@1.0.3: resolution: {integrity: sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==} @@ -4865,6 +5166,16 @@ packages: ieee754: 1.2.1 dev: false + /bundle-require@4.2.1(esbuild@0.17.19): + resolution: {integrity: sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.17' + dependencies: + esbuild: 0.17.19 + load-tsconfig: 0.2.5 + dev: false + /busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -4875,7 +5186,6 @@ packages: /cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - dev: true /call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} @@ -5168,6 +5478,11 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: false + /define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -5218,7 +5533,6 @@ packages: engines: {node: '>=8'} dependencies: path-type: 4.0.0 - dev: true /dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -5241,6 +5555,29 @@ packages: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dev: true + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + + /domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -5264,7 +5601,6 @@ packages: /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - dev: true /es-abstract@1.22.5: resolution: {integrity: sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==} @@ -5388,6 +5724,36 @@ packages: is-symbol: 1.0.4 dev: true + /esbuild@0.17.19: + resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.17.19 + '@esbuild/android-arm64': 0.17.19 + '@esbuild/android-x64': 0.17.19 + '@esbuild/darwin-arm64': 0.17.19 + '@esbuild/darwin-x64': 0.17.19 + '@esbuild/freebsd-arm64': 0.17.19 + '@esbuild/freebsd-x64': 0.17.19 + '@esbuild/linux-arm': 0.17.19 + '@esbuild/linux-arm64': 0.17.19 + '@esbuild/linux-ia32': 0.17.19 + '@esbuild/linux-loong64': 0.17.19 + '@esbuild/linux-mips64el': 0.17.19 + '@esbuild/linux-ppc64': 0.17.19 + '@esbuild/linux-riscv64': 0.17.19 + '@esbuild/linux-s390x': 0.17.19 + '@esbuild/linux-x64': 0.17.19 + '@esbuild/netbsd-x64': 0.17.19 + '@esbuild/openbsd-x64': 0.17.19 + '@esbuild/sunos-x64': 0.17.19 + '@esbuild/win32-arm64': 0.17.19 + '@esbuild/win32-ia32': 0.17.19 + '@esbuild/win32-x64': 0.17.19 + dev: false + /esbuild@0.19.12: resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} engines: {node: '>=12'} @@ -5430,7 +5796,6 @@ packages: /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - dev: true /eslint-config-next@14.1.0(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-SBX2ed7DoRFXC6CQSLc/SbLY9Ut6HxNB2wPTcoIWjUMd7aF7O/SIE7111L8FdZ9TXsNV4pulUDnfthpyPtbFUg==} @@ -5740,6 +6105,21 @@ packages: engines: {node: '>=0.8.x'} dev: false + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: false + /execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -5918,6 +6298,11 @@ packages: engines: {node: '>=6'} dev: false + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: false + /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -6000,7 +6385,6 @@ packages: ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 - dev: true /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} @@ -6096,6 +6480,14 @@ packages: resolution: {integrity: sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==} dev: false + /htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + /http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -6127,6 +6519,11 @@ packages: - supports-color dev: true + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: false + /human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -6146,7 +6543,6 @@ packages: /ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} - dev: true /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -6354,6 +6750,11 @@ packages: engines: {node: '>=12'} dev: false + /is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + dev: false + /is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: true @@ -6377,6 +6778,11 @@ packages: call-bind: 1.0.7 dev: true + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: false + /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6467,6 +6873,11 @@ packages: resolution: {integrity: sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==} dev: false + /joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6596,6 +7007,11 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + /load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: false + /local-pkg@0.5.0: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} @@ -6618,6 +7034,10 @@ packages: /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + /lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + dev: false + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true @@ -6784,7 +7204,6 @@ packages: /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -6984,6 +7403,11 @@ packages: mime-db: 1.52.0 dev: true + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: false + /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} @@ -7115,6 +7539,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: false + /npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -7230,6 +7661,13 @@ packages: wrappy: 1.0.2 dev: true + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: false + /onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -7299,6 +7737,10 @@ packages: is-hexadecimal: 2.0.1 dev: false + /parse-srcset@1.0.2: + resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==} + dev: false + /parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} dependencies: @@ -7337,7 +7779,6 @@ packages: /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - dev: true /pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -7423,6 +7864,23 @@ packages: camelcase-css: 2.0.1 postcss: 8.4.35 + /postcss-load-config@3.1.4(postcss@8.4.35): + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + postcss: 8.4.35 + yaml: 1.10.2 + dev: false + /postcss-load-config@4.0.2(postcss@8.4.35): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} @@ -7604,7 +8062,6 @@ packages: /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - dev: true /querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} @@ -7937,6 +8394,11 @@ packages: engines: {node: '>=4'} dev: true + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: false + /resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} dev: true @@ -7969,6 +8431,14 @@ packages: glob: 7.2.3 dev: true + /rollup@3.29.4: + resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.3 + dev: false + /rollup@4.12.0: resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -8039,6 +8509,17 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true + /sanitize-html@2.13.0: + resolution: {integrity: sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==} + dependencies: + deepmerge: 4.3.1 + escape-string-regexp: 4.0.0 + htmlparser2: 8.0.2 + is-plain-object: 5.0.0 + parse-srcset: 1.0.2 + postcss: 8.4.35 + dev: false + /saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -8112,6 +8593,10 @@ packages: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} dev: true + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: false + /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -8119,7 +8604,6 @@ packages: /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - dev: true /sonic-boom@3.8.0: resolution: {integrity: sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA==} @@ -8136,6 +8620,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + dependencies: + whatwg-url: 7.1.0 + dev: false + /space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} dev: false @@ -8250,6 +8741,11 @@ packages: engines: {node: '>=4'} dev: true + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: false + /strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -8427,6 +8923,12 @@ packages: url-parse: 1.5.10 dev: true + /tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + dependencies: + punycode: 2.3.1 + dev: false + /tr46@5.0.0: resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} engines: {node: '>=18'} @@ -8434,6 +8936,11 @@ packages: punycode: 2.3.1 dev: true + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: false + /trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} dev: false @@ -8467,6 +8974,43 @@ packages: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: false + /tsup@6.7.0(postcss@8.4.35)(typescript@5.3.3): + resolution: {integrity: sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==} + engines: {node: '>=14.18'} + hasBin: true + peerDependencies: + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.1.0' + peerDependenciesMeta: + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + dependencies: + bundle-require: 4.2.1(esbuild@0.17.19) + cac: 6.7.14 + chokidar: 3.6.0 + debug: 4.3.4 + esbuild: 0.17.19 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss: 8.4.35 + postcss-load-config: 3.1.4(postcss@8.4.35) + resolve-from: 5.0.0 + rollup: 3.29.4 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tree-kill: 1.2.2 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + - ts-node + dev: false + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -8532,10 +9076,9 @@ packages: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} hasBin: true - dev: true - /typescript@5.4.2: - resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} + /typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true dev: false @@ -8556,6 +9099,13 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /undici@5.28.4: + resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} + engines: {node: '>=14.0'} + dependencies: + '@fastify/busboy': 2.1.1 + dev: false + /unified@11.0.4: resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} dependencies: @@ -8684,6 +9234,11 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + /uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + dev: false + /vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} dependencies: @@ -8820,6 +9375,10 @@ packages: xml-name-validator: 5.0.0 dev: true + /webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + dev: false + /webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -8845,6 +9404,14 @@ packages: webidl-conversions: 7.0.0 dev: true + /whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + dev: false + /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -8962,6 +9529,11 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: false + /yaml@2.4.0: resolution: {integrity: sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==} engines: {node: '>= 14'} diff --git a/src/__tests__/page.test.tsx b/src/__tests__/page.test.tsx index 8f07a2b..09b631c 100644 --- a/src/__tests__/page.test.tsx +++ b/src/__tests__/page.test.tsx @@ -4,7 +4,5 @@ import Page from '../app/[locale]/page'; test('Page', () => { render(); - expect( - screen.getByRole('heading', { level: 1 }) - ).toBeDefined(); + expect(screen.getByRole('heading', { level: 1 })).toBeDefined(); }); diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 1efa2fd..892cba8 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -28,8 +28,8 @@ export default function LocaleLayout({ className={cn('bg-background font-sans antialiased', fontSans.variable)} > - -
+ {/* */} +
{children}
diff --git a/src/app/[locale]/search/page.tsx b/src/app/[locale]/search/page.tsx index 2367176..b1e48c3 100644 --- a/src/app/[locale]/search/page.tsx +++ b/src/app/[locale]/search/page.tsx @@ -1,6 +1,5 @@ import { Assistant } from '@/components/assistant'; import { Hits } from '@/components/hits'; -import { QuestionsResult } from '@/components/questions'; import { SearchProvider } from '@/components/search-provider'; import { SearchBox } from '@/components/searchbox'; @@ -9,14 +8,13 @@ export default async function Search() {
-
- {/* */} -
+ {/*
+
*/}
- + {/* */}
diff --git a/src/app/api/assistant/route.ts b/src/app/api/assistant/route.ts new file mode 100644 index 0000000..b8a04da --- /dev/null +++ b/src/app/api/assistant/route.ts @@ -0,0 +1,103 @@ +import type { NextRequest } from 'next/server'; +import { SummarizerClient } from '@clinia/tritonclient'; + +export const dynamic = 'force-dynamic'; + +const INFERENCE_URL = process.env.INFERENCE_URL ?? 'http://127.0.0.1:8001'; +const MODEL_NAME = + process.env.INFERENCE_MODEL_NAME ?? 'summarizer_medical_journals_qa'; +const MODEL_VERSION = process.env.INFERENCE_MODEL_VERSION ?? '120240905190000'; +const client = new SummarizerClient(INFERENCE_URL); + +type InferParameter = { + parameterChoice: { + value: boolean; + }; +}; +const colors = [ + '\x1b[31m', // Red + '\x1b[32m', // Green + '\x1b[33m', // Yellow + '\x1b[34m', // Blue + '\x1b[35m', // Magenta + '\x1b[36m', // Cyan + '\x1b[91m', // Bright Red + '\x1b[92m', // Bright Green + '\x1b[93m', // Bright Yellow + '\x1b[94m', // Bright Blue +]; +const resetColor = '\x1b[0m'; // Reset color + +function getRandomColor(): string { + const randomIndex = Math.floor(Math.random() * colors.length); + return colors[randomIndex]; +} + +function shrinkText(text: string, maxLength: number): string { + return text.length > maxLength ? text.substring(0, maxLength) + '...' : text; +} + +export async function POST(request: NextRequest) { + const { query, articles, mode } = (await request.json()) as { + query: string; + articles: string[]; + mode?: string; + }; + const maxChars = 25; + const start = Date.now(); + const color = getRandomColor(); + console.log( + `Received request with query: ${color}${shrinkText(query, maxChars)}${resetColor}` + ); + const logEnd = () => { + console.log( + `Inference took ${'\x1b[36m'}${Date.now() - start}ms${resetColor} for query ${color}${shrinkText(query, maxChars)}${resetColor}` + ); + }; + + let responseStream = new TransformStream(); + const writer = responseStream.writable.getWriter(); + + const answerStream = client.streamAnswer( + MODEL_NAME, + MODEL_VERSION, + query, + articles, + mode ?? 'answer' + ); + + (async () => { + for await (const chunk of answerStream) { + // Assuming the text is in a property called 'text' in the chunk + const isFinished: InferParameter | undefined = chunk.inferResponse + ?.parameters['triton_final_response'] as InferParameter | undefined; + if (isFinished?.parameterChoice.value) { + logEnd(); + await writer.close(); + return; + } + + const bytes = chunk.inferResponse?.rawOutputContents?.[0]; + if (bytes === undefined || bytes.length <= 4) { + await writer.close(); + return; + } + + // Uncomment for debugging + // const text = new TextDecoder().decode(bytes?.slice(4)); + // console.log(`Got chunk = ${text}`); + writer.write(bytes?.slice(4)); + } + + logEnd(); + await writer.close(); + })(); + + return new Response(responseStream.readable, { + headers: { + 'Content-Type': 'text/event-stream', + Connection: 'keep-alive', + 'Cache-Control': 'no-cache, no-transform', + }, + }); +} diff --git a/src/components/article-drawer.tsx b/src/components/article-drawer.tsx index ee7b522..e5ee3f2 100644 --- a/src/components/article-drawer.tsx +++ b/src/components/article-drawer.tsx @@ -1,15 +1,38 @@ 'use client'; +import { HitsHighlight } from '@/lib/client'; +import { getHighlightText } from '@/lib/client/util'; import { X } from 'lucide-react'; -import Markdown from 'react-markdown'; -import Link from 'next/link'; +import { useMemo } from 'react'; import { Button } from '@clinia-ui/react'; -import { PassageHighlight } from './highlight'; +import { HtmlDisplay } from './html-display'; import { useSearchLayout } from './search-layout'; export const ArticleDrawer = () => { const searchLayout = useSearchLayout(); + const hitsToDisplay = useMemo((): string[] => { + const allhighlights = Object.values( + searchLayout.hit?.highlighting ?? {} + ).flat(); + if (allhighlights.length === 0) { + return []; + } + + const hitsHighlights = allhighlights.filter( + (highlight): highlight is HitsHighlight => + 'type' in highlight && highlight.type === 'hits' + ); + if (hitsHighlights.length === 0) { + return allhighlights.map(getHighlightText); + } + + // We display the hits by score + return hitsHighlights + .sort((a, b) => b.score - a.score) + .map(getHighlightText); + }, [searchLayout.hit?.highlighting]); + if (!searchLayout.hit) { return null; } @@ -33,37 +56,16 @@ export const ArticleDrawer = () => {

- {searchLayout.hit.resource.title} + {searchLayout.hit.resource.data.title}

- {searchLayout.hit.highlight.map((highlight, idx) => ( - ( -

- ), - h2: (props) => ( -

- ), - h3: (props) => ( -

- ), - }} + + {hitsToDisplay.map((highlight, idx) => ( + - {highlight.match} - - // + html={highlight} + /> ))} -
- -

diff --git a/src/components/article-hit.tsx b/src/components/article-hit.tsx index 6981dd9..e6dee3d 100644 --- a/src/components/article-hit.tsx +++ b/src/components/article-hit.tsx @@ -1,11 +1,35 @@ 'use client'; -import { Article, Hit } from '@/lib/client'; -import { PassageHighlight } from './highlight'; +import { Article, Hit, HitsHighlight } from '@/lib/client'; +import { useMemo } from 'react'; +import { getHighlightText } from '../lib/client/util'; +import { HtmlDisplay } from './html-display'; import { useSearchLayout } from './search-layout'; export const ArticleHit = ({ hit }: { hit: Hit
}) => { - const passageHighlight = hit.highlight.find((h) => h.type === 'passage'); + // TODO: should take the highlighing from 'abstract.passages.vector' | 'content.text.passages.vector' + // We will take all the highlights from all the keys `abstract.passages.vector`, `content.text.passages.vector`, etc. + // Then we will display only the hit with the highest score as the single paragraph below. + const highestHitsHighlight = useMemo(() => { + const allHighlights = Object.values(hit.highlighting ?? {}).flat(); + if (allHighlights.length === 0) { + return undefined; + } + const hitsHighlights = allHighlights.filter( + (highlight): highlight is HitsHighlight => + 'type' in highlight && highlight.type === 'hits' + ); + if (hitsHighlights.length === 0) { + // We fallback to displaying the first text highlight + return getHighlightText(allHighlights[0]); + } + + return getHighlightText( + hitsHighlights + // We sort the hits by score and take the highest one + .sort((a, b) => b.score - a.score)[0] + ); + }, [hit]); const searchLayout = useSearchLayout(); return ( @@ -16,9 +40,12 @@ export const ArticleHit = ({ hit }: { hit: Hit
}) => { }} >

- {hit.resource.title} + {hit.resource.data.title}

- {passageHighlight && } +

+ {highestHitsHighlight && } +

+ {/* {passageHighlight && } */}
); }; diff --git a/src/components/assistant.tsx b/src/components/assistant.tsx index c25977d..78939f7 100644 --- a/src/components/assistant.tsx +++ b/src/components/assistant.tsx @@ -2,22 +2,20 @@ import { Sparkles } from 'lucide-react'; import { twMerge } from 'tailwind-merge'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import Markdown from 'react-markdown'; +import { V1Hit } from '@clinia/client-common'; +import { useHits, useQuery } from '@clinia/search-sdk-react'; import styles from './assistant.module.css'; -import { useEventSource, useEventSourceListener } from './use-event-source'; -import { useMeta } from './use-meta'; +import { useStreamRequest } from './use-stream-request'; export type AssistantProps = { className?: string; }; export const Assistant = ({ className }: AssistantProps) => { - const meta = useMeta(); - - if (meta.queryIntent !== 'QUESTION' || !meta.queryId) { - return null; - } + const hits = useHits(); + const [query] = useQuery(); return (
@@ -26,7 +24,7 @@ export const Assistant = ({ className }: AssistantProps) => {

Assistant

- +
@@ -34,29 +32,53 @@ export const Assistant = ({ className }: AssistantProps) => { }; type AssistantListenerProps = { - queryId: string; + query: string; + hits: V1Hit[]; }; -const AssistantListener = ({ queryId }: AssistantListenerProps) => { +const AssistantListener = ({ hits, query }: AssistantListenerProps) => { const [summary, setSummary] = useState(''); + const queryRef = useRef(query); - // Reset summary every time the query ID change - useEffect(() => setSummary(''), [queryId]); + useEffect(() => { + // We store the query in a ref so that we only refetch the assistant when new articles are coming. + // This avoids doing a double-query in between the request-response from the query API. + queryRef.current = query; + setSummary(''); + }, [query]); - const [eventSource, eventSourceStatus] = useEventSource( - `/api/query/${queryId}/answer`, - true - ); - useEventSourceListener( - eventSource, - ['message'], - (evt) => { - setSummary((s) => s + evt.data); - }, - [setSummary] + const { refetch, status } = useStreamRequest( + useCallback( + (chunk: string) => { + setSummary((s) => s + chunk); + }, + [setSummary] + ) ); + // Reset summary every time the query changes + useEffect(() => { + if (hits.length === 0) return; + const passages = hits.flatMap((h) => + (h.highlighting?.['abstract.passages'] ?? []).slice(0, 1).map((x) => + JSON.stringify({ + id: h.resource.id, + text: '', + title: h.resource.data.title, + passages: [x.highlight], + }) + ) + ); + refetch(`/api/assistant`, { + method: 'POST', + body: JSON.stringify({ + query: queryRef.current, + articles: passages.slice(0, 3), + }), + }); + }, [hits, refetch]); + const classnames = []; - if (eventSourceStatus === 'open' || eventSourceStatus === 'init') { + if (status === 'loading' || status === 'idle') { classnames.push(styles.type); } @@ -75,5 +97,4 @@ const AssistantListener = ({ queryId }: AssistantListenerProps) => { {summary} ); - // return

{summary}

; }; diff --git a/src/components/combobox.tsx b/src/components/combobox.tsx index 9d730b3..d8b2476 100644 --- a/src/components/combobox.tsx +++ b/src/components/combobox.tsx @@ -30,25 +30,25 @@ export const ComboBox = ({ const router = useRouter(); const pathname = usePathname(); - const groups = useMemo( - () => [ - { - heading: t('searchbox.groups.ask.heading'), - icon: , - items: [ - 'How long to prepare for ACLR?', - 'Recovery time for ACLR', - 'How to treat a torn meniscus?', - ], - }, - { - heading: t('searchbox.groups.search.heading'), - icon: , - items: ['ACL recovery'], - }, - ], - [t] - ); + // const groups = useMemo( + // () => [ + // { + // heading: t('searchbox.groups.ask.heading'), + // icon: , + // items: [ + // 'How long to prepare for ACLR?', + // 'Recovery time for ACLR', + // 'How to treat a torn meniscus?', + // ], + // }, + // { + // heading: t('searchbox.groups.search.heading'), + // icon: , + // items: ['ACL recovery'], + // }, + // ], + // [t] + // ); const ref = useClickAway(() => setOpen(false)); @@ -71,7 +71,7 @@ export const ComboBox = ({ }} /> - + {/* {open && ( <> No results found. @@ -98,7 +98,7 @@ export const ComboBox = ({ ))} )} - + */} ); }; diff --git a/src/components/highlight.tsx b/src/components/highlight.tsx index eed69cf..2241855 100644 --- a/src/components/highlight.tsx +++ b/src/components/highlight.tsx @@ -1,40 +1,40 @@ -import { Highlight } from '@/lib/client'; +// import { Highlight } from '@/lib/client'; -export const PassageHighlight = ({ highlight }: { highlight: Highlight }) => { - if (highlight.type !== 'passage') { - return null; - } +// export const PassageHighlight = ({ highlight }: { highlight: Highlight }) => { +// if (highlight.type !== 'passage') { +// return null; +// } - const sentenceHighlight = highlight.highlight; - if (sentenceHighlight?.type === 'sentence') { - return ( - - ); - } +// const sentenceHighlight = highlight.highlight; +// if (sentenceHighlight?.type === 'sentence') { +// return ( +// +// ); +// } - return

{highlight.match}

; -}; +// return

{highlight.match}

; +// }; -export const SentenceHighlight = ({ - highlight, - passage, -}: { - highlight: Highlight; - passage: Highlight; -}) => { - // We get the start offset of the sentence with respect to the passage - const startOffset = highlight.startOffset - passage.startOffset; - const start = passage.match.slice(passage.startOffset, startOffset); +// export const SentenceHighlight = ({ +// highlight, +// passage, +// }: { +// highlight: Highlight; +// passage: Highlight; +// }) => { +// // We get the start offset of the sentence with respect to the passage +// const startOffset = highlight.startOffset - passage.startOffset; +// const start = passage.match.slice(passage.startOffset, startOffset); - // We get the end offset of the sentence with respect to the passage - const endOffset = highlight.endOffset - passage.startOffset; - const end = passage.match.slice(endOffset); +// // We get the end offset of the sentence with respect to the passage +// const endOffset = highlight.endOffset - passage.startOffset; +// const end = passage.match.slice(endOffset); - return ( -

- {start} - {highlight.match} - {end} -

- ); -}; +// return ( +//

+// {start} +// {highlight.match} +// {end} +//

+// ); +// }; diff --git a/src/components/html-display.tsx b/src/components/html-display.tsx new file mode 100644 index 0000000..bd9724b --- /dev/null +++ b/src/components/html-display.tsx @@ -0,0 +1,20 @@ +'use client'; + +import sanitize from 'sanitize-html'; +import { useMemo } from 'react'; + +type HtmlDisplayProps = { + className?: string; + html: string; +}; + +export const HtmlDisplay = ({ className, html }: HtmlDisplayProps) => { + const sanitizedHtml = useMemo(() => sanitize(html), [html]); + + return ( +
+ ); +}; diff --git a/src/components/questions.tsx b/src/components/questions.tsx index 45e5c94..9a3fe37 100644 --- a/src/components/questions.tsx +++ b/src/components/questions.tsx @@ -1,9 +1,7 @@ 'use client'; import { twMerge } from 'tailwind-merge'; -import { useTranslations } from 'next-intl'; import { useRouter } from 'next/navigation'; -import { useMeta } from './use-meta'; type QuestionsProps = { className?: string; @@ -34,15 +32,15 @@ export const Questions = ({ title, questions, className }: QuestionsProps) => { ); }; -export const QuestionsResult = () => { - const meta = useMeta(); - const t = useTranslations(); +// export const QuestionsResult = () => { +// const meta = useMeta(); +// const t = useTranslations(); - if (!meta?.questions || meta.questions.length === 0) { - return null; - } +// if (!meta?.questions || meta.questions.length === 0) { +// return null; +// } - return ( - - ); -}; +// return ( +// +// ); +// }; diff --git a/src/components/search-provider.tsx b/src/components/search-provider.tsx index aeef2f8..ad70b5b 100644 --- a/src/components/search-provider.tsx +++ b/src/components/search-provider.tsx @@ -1,8 +1,8 @@ 'use client'; import { SearchRequest, SearchResponse } from '@/lib/client'; -import { client } from '@/lib/info-poc-client'; import { PropsWithChildren, use, useCallback, useEffect, useMemo } from 'react'; +import client from '@clinia/client-datapartition'; import { SearchParameters, type SearchSDKOptions, @@ -17,19 +17,57 @@ type SearchProviderProps = PropsWithChildren<{ }; }>; +const datapartitionClient = client( + 'clinia', + { + mode: 'BearerToken', + bearerToken: '', + }, + { + hosts: [ + { + url: 'localhost:3100/api', + protocol: 'http', + accept: 'readWrite', + }, + ], + } +); + export const SearchProvider = ({ children, state }: SearchProviderProps) => { const search: SearchSDKOptions['search'] = async (_collection, params) => { - const resp = await client.search({ query: params.query ?? '' }); - return { - hits: resp.hits, - meta: { - numPages: 1, - page: 1, - perPage: 10, - total: resp.hits.length, - ...resp.meta, + const resp = await datapartitionClient.searchClient.query< + Record + >({ + partitionKey: 'clinia', + collectionKey: 'articles', + v1SearchParameters: { + page: 0, + perPage: 5, + query: { + or: [ + { + match: { + 'abstract.passages': { + value: params.query ?? '', + type: 'word', + }, + }, + }, + { + knn: { + 'abstract.passages.vector': { + value: params.query ?? '', + }, + }, + }, + ], + }, + highlighting: ['abstract.passages'], }, - }; + }); + // const resp = await client.search({ query: params.query ?? '' }); + return resp; }; const searchForFacets: SearchSDKOptions['searchForFacets'] = diff --git a/src/components/use-event-source.ts b/src/components/use-event-source.ts index 4bca698..afb5d64 100644 --- a/src/components/use-event-source.ts +++ b/src/components/use-event-source.ts @@ -11,7 +11,7 @@ export type EventSourceStatus = 'init' | 'open' | 'closed' | 'error'; export type EventSourceEvent = Event & { data: string }; export function useEventSource( - url: string, + url?: string, withCredentials?: boolean, ESClass: EventSourceConstructor = EventSource ) { diff --git a/src/components/use-stream-request.ts b/src/components/use-stream-request.ts new file mode 100644 index 0000000..85826e6 --- /dev/null +++ b/src/components/use-stream-request.ts @@ -0,0 +1,56 @@ +import { useCallback, useRef, useState } from 'react'; + +type StreamRequestStatus = 'idle' | 'loading' | 'error' | 'success'; + +export function useStreamRequest(onData: (data: string) => void) { + const [status, setStatus] = useState('idle'); + const controllerRef = useRef(null); + + const refetch = useCallback( + async (url: string, request: RequestInit) => { + setStatus('loading'); + if (controllerRef.current) { + console.warn('Aborting previous request'); + controllerRef.current.abort(); + } + const controller = new AbortController(); + controllerRef.current = controller; + + try { + const response = await fetch(url, { + signal: controller.signal, + headers: { + Accept: 'text/event-stream', + }, + ...request, + }); + + if (!response.body) { + throw new Error('ReadableStream not supported in this environment.'); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + + let done = false; + while (!done) { + const { value, done: readerDone } = await reader.read(); + done = readerDone; + if (value) { + const chunk = decoder.decode(value, { stream: true }); + onData(chunk); + } + } + + setStatus('success'); + } catch (error) { + if ((error as Error)?.name !== 'AbortError') { + setStatus('error'); + } + } + }, + [onData] + ); + + return { refetch, status }; +} diff --git a/src/lib/client/index.ts b/src/lib/client/index.ts index 541a3ad..d860d06 100644 --- a/src/lib/client/index.ts +++ b/src/lib/client/index.ts @@ -1,2 +1,2 @@ export * from './client'; -export * from './types'; \ No newline at end of file +export * from './types'; diff --git a/src/lib/client/types.ts b/src/lib/client/types.ts index f991794..81ede1c 100644 --- a/src/lib/client/types.ts +++ b/src/lib/client/types.ts @@ -10,36 +10,46 @@ export type SearchResponse = { hits: Hit[]; meta: { queryId: string; - queryIntent: 'QUESTION'; - questions: string[]; }; }; export type Hit = { resource: T; - highlight: Highlight[]; - enrichers: Enrichers; + highlighting?: Record; }; export type Resource = { id: string; - [key: string]: any; }; export type Article = Resource & { - title: string; - text: string; + data: { + title: string; + abstract: string; + content: [ + { + title: string; + text: string; + }, + ]; + }; }; -export type Highlight = { - match: string; - startOffset: number; - endOffset: number; - type: 'passage' | 'sentence'; - highlight?: Highlight; - score: number; -}; +// Display a dumb fallback. We would ideally show `data` but if it's not respecting that shape let's fallback to `highlight`. +export type Highlight = + | { + highlight: string; + } + | { + type: 'text'; + highlight: string; + } + | HitsHighlight; -export type Enrichers = { - questions: string[]; +export type HitsHighlight = { + type: 'hits'; + score: number; + data: string; + // content.0.passages.0 + path: string; }; diff --git a/src/lib/client/util.ts b/src/lib/client/util.ts new file mode 100644 index 0000000..c7d3328 --- /dev/null +++ b/src/lib/client/util.ts @@ -0,0 +1,13 @@ +import type { Highlight } from './types'; + +export const getHighlightText = (highlight: Highlight): string => { + if ('highlight' in highlight) { + return highlight.highlight; + } + + if (highlight.type === 'hits' || 'data' in highlight) { + return highlight.data; + } + + return ''; +}; diff --git a/src/pages/api/[...path].ts b/src/pages/api/[...path].ts index 659a6f6..bf52060 100644 --- a/src/pages/api/[...path].ts +++ b/src/pages/api/[...path].ts @@ -23,7 +23,7 @@ async function proxy(req: NextApiRequest, res: NextApiResponse) { req, res, { - target: process.env.API_URL ?? 'http://localhost:7999', + target: process.env.API_URL ?? 'http://localhost:3000', // Atlas }, (err: Error | null | undefined) => { if (err) {