From d2b60ab8ac934e704e4d512b7178a355db69a233 Mon Sep 17 00:00:00 2001 From: Dylan Tientcheu Date: Thu, 27 Aug 2026 11:32:44 +0200 Subject: [PATCH 1/8] Replace Kapa with Algolia Agent Studio in the AI dock The AI panel now streams answers from Algolia Agent Studio's AG-UI completions endpoint via TanStack AI's useChat, replacing the Kapa SDK and its reCAPTCHA integration. Sources are extracted from the agent's Algolia search tool results, search activity renders inline in the answer flow, and thumbs feedback posts to the Agent Studio feedback endpoint. --- package.json | 2 +- pnpm-lock.yaml | 1092 +++++++++++++++++++++++++++++++++++-- src/components/AiDock.tsx | 900 ++++++++++++++++-------------- src/styles/app.css | 6 - src/utils/env.ts | 2 - vite.config.ts | 2 +- 6 files changed, 1543 insertions(+), 461 deletions(-) diff --git a/package.json b/package.json index 1493fc89c..727897346 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "@codemirror/state": "^6.7.1", "@fingerprintjs/fingerprintjs-pro-react": "^2.7.1", "@floating-ui/react": "^0.27.19", - "@kapaai/react-sdk": "^0.9.10", "@lezer/highlight": "^1.2.3", "@modelcontextprotocol/sdk": "^1.29.0", "@octokit/graphql": "^9.0.3", @@ -76,6 +75,7 @@ "@tanstack/ai-client": "^0.31.1", "@tanstack/ai-openai": "^0.22.6", "@tanstack/ai-openrouter": "^0.19.9", + "@tanstack/ai-react": "^0.24.1", "@tanstack/charts": "0.16.1", "@tanstack/create": "^0.70.0", "@tanstack/highlight": "^0.0.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 963956ba1..e2cdf47b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,9 +48,6 @@ importers: '@floating-ui/react': specifier: ^0.27.19 version: 0.27.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@kapaai/react-sdk': - specifier: ^0.9.10 - version: 0.9.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@lezer/highlight': specifier: ^1.2.3 version: 1.2.3 @@ -111,6 +108,9 @@ importers: '@tanstack/ai-openrouter': specifier: ^0.19.9 version: 0.19.9(@tanstack/ai@0.54.0(@opentelemetry/api@1.9.1)(zod@4.3.6)) + '@tanstack/ai-react': + specifier: ^0.24.1 + version: 0.24.1(@opentelemetry/api@1.9.1)(@tanstack/ai@0.54.0(@opentelemetry/api@1.9.1)(zod@4.3.6))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.3.6) '@tanstack/charts': specifier: 0.16.1 version: 0.16.1(lit@3.3.2)(octane@0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.0.16(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.9.0)))(preact@10.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.12) @@ -1446,15 +1446,6 @@ packages: peerDependencies: three: ^0.182.0 - '@hcaptcha/loader@2.3.0': - resolution: {integrity: sha512-i4lnNxKBe+COf3R1nFZEWaZoHIoJjvDgWqvcNrdZq8ehoSNMN6KVZ56dcQ02qKie2h3+BkbkwlJA9DOIuLlK/g==} - - '@hcaptcha/react-hcaptcha@1.17.4': - resolution: {integrity: sha512-rIvgesG1N7SS9sAYYHFoWm+nXqRrxq7RcA9z2pKkDWV+S1GdfmrTNYA1aPyVWVe3eowphTCwyDJvl97Swwy0mw==} - peerDependencies: - react: '>= 16.3.0' - react-dom: '>= 16.3.0' - '@hono/node-server@1.19.12': resolution: {integrity: sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==} engines: {node: '>=18.14.1'} @@ -1639,12 +1630,6 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@kapaai/react-sdk@0.9.10': - resolution: {integrity: sha512-osQyFgBJmhNM207MpB0aZbjs2kjNHjEJbNc5YEReJuyFE72Iydn9CoZGxox2GYRgMIlQ7wljHCzgQqkvR8lXqA==} - peerDependencies: - react: '>=17.0.0' - react-dom: '>=17.0.0' - '@lezer/common@1.5.2': resolution: {integrity: sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==} @@ -3423,6 +3408,20 @@ packages: peerDependencies: '@tanstack/ai': ^0.54.0 + '@tanstack/ai-react@0.24.1': + resolution: {integrity: sha512-kxxkyunUiacR79HlRpBxOLT/fGKqNP16W/UBOcgM+lqmqaM7TiwqOpHHhwg800opGAEETIFFedUDMyP8ls1Glw==} + peerDependencies: + '@mcp-ui/client': ^7 + '@tanstack/ai': ^0.54.0 + '@types/react': '>=18.0.0' + react: '>=18.0.0' + react-dom: '>=18.0.0' + peerDependenciesMeta: + '@mcp-ui/client': + optional: true + react-dom: + optional: true + '@tanstack/ai-utils@0.4.0': resolution: {integrity: sha512-xqvAgPJkIuGk1m+jZKyb7vBTQIaBmUD9EVzlPkDWWMp80n69uwL0TnBZCVk+OwL9goTQiFy648dnBfLsiJgl6A==} @@ -3938,6 +3937,9 @@ packages: '@types/d3@7.4.3': resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/dom-speech-recognition@0.0.1': resolution: {integrity: sha512-udCxb8DvjcDKfk1WTBzDsxFbLgYxmQGKrE/ricoMqHRNjSlSUCcamVTA5lIQqzY10mY5qCY0QDwBfFEwhfoDPw==} @@ -3962,6 +3964,12 @@ packages: '@types/hogan.js@3.0.5': resolution: {integrity: sha512-/uRaY3HGPWyLqOyhgvW9Aa43BNnLZrNeQxl2p8wqId4UHMfPKolSB+U7BlZyO1ng7MkLnyEAItsBzCG0SDhqrA==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} @@ -4017,12 +4025,18 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} '@types/webxr@0.5.24': resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + '@uploadthing/mime-types@0.3.6': resolution: {integrity: sha512-t3tTzgwFV9+1D7lNDYc7Lr7kBwotHaX0ZsvoCGe7xGnXKo9z0jG2Sjl/msll12FeoLj77nyhsxevXyGpQDBvLg==} @@ -4191,6 +4205,9 @@ packages: babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -4315,6 +4332,9 @@ packages: caniuse-lite@1.0.30001784: resolution: {integrity: sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -4327,6 +4347,18 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} @@ -4382,6 +4414,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -4648,6 +4683,9 @@ packages: supports-color: optional: true + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -4663,6 +4701,10 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-gpu@5.0.70: resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==} @@ -4676,6 +4718,9 @@ packages: devalue@5.9.0: resolution: {integrity: sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -4928,6 +4973,10 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -4941,6 +4990,9 @@ packages: '@typescript-eslint/types': optional: true + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -4980,6 +5032,9 @@ packages: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-check@3.23.2: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} @@ -5194,6 +5249,40 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-sanitize@5.0.2: + resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + highlight.js@11.11.2: + resolution: {integrity: sha512-oaXMACAU0kzOMXBjWpNcX+vlwSBCIAiZ9BHa7gA15NOTtT2L/l8OSZDuqS2XppOhZBPJ7hm4o8ep2kyuip2uEQ==} + engines: {node: '>=12.0.0'} + hls.js@1.6.15: resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==} @@ -5211,6 +5300,12 @@ packages: htm@3.1.1: resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==} + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} @@ -5259,6 +5354,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + instantsearch-ui-components@0.23.0: resolution: {integrity: sha512-ILqp/RAm0mBrsFuGozTk09tPpQMiODtcLVHQRudXBC23YcaTpt9qTB30nWy5yHODb15dBH5DoUw7gqyUo/PPkw==} @@ -5286,6 +5384,12 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -5321,6 +5425,9 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -5342,6 +5449,9 @@ packages: resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -5445,9 +5555,6 @@ packages: jose@6.2.2: resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} - js-cookie@3.0.8: - resolution: {integrity: sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==} - js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -5689,6 +5796,12 @@ packages: lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + lru-cache@11.2.7: resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} engines: {node: 20 || >=22} @@ -5705,6 +5818,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + markdown-to-jsx@7.7.17: resolution: {integrity: sha512-7mG/1feQ0TX5I7YyMZVDgCC/y2I3CiEhIRQIhyov9nGBP5eoVrOXXHuL5ZP8GRfxVZKRiXWJgwXkb9It+nQZfQ==} engines: {node: '>= 10'} @@ -5726,6 +5842,51 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} @@ -5749,6 +5910,90 @@ packages: meshoptimizer@1.0.1: resolution: {integrity: sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.2: + resolution: {integrity: sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} @@ -5964,6 +6209,9 @@ packages: pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-gitignore@2.0.0: resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} engines: {node: '>=14'} @@ -6144,6 +6392,9 @@ packages: promise-worker-transferable@1.0.4: resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -6196,6 +6447,12 @@ packages: react: '>= 16.8.0 < 20' react-dom: '>= 16.8.0 < 20' + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -6266,6 +6523,27 @@ packages: resolution: {integrity: sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==} engines: {node: '>=8'} + rehype-highlight@7.0.2: + resolution: {integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-sanitize@6.0.0: + resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + remove-accents@0.5.0: resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} @@ -6496,6 +6774,9 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -6565,6 +6846,9 @@ packages: string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -6584,6 +6868,12 @@ packages: style-mod@4.1.3: resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + stylis@4.3.6: resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} @@ -6673,13 +6963,6 @@ packages: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} - tldts-core@7.4.2: - resolution: {integrity: sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==} - - tldts@7.4.2: - resolution: {integrity: sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==} - hasBin: true - toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -6687,6 +6970,9 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + troika-three-text@0.52.4: resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==} peerDependencies: @@ -6700,6 +6986,9 @@ packages: troika-worker-utils@0.52.0: resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==} + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-algebra@2.0.0: resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} @@ -6783,6 +7072,27 @@ packages: resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} engines: {node: '>=20'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universal-user-agent@7.0.3: resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} @@ -6914,6 +7224,15 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-bundle-analyzer@1.3.6: resolution: {integrity: sha512-elFXkF9oGy4CJEeOdP1DSEHRDKWfmaEDfT9/GuF4jmyfmUF6nC//iN+ythqMEVvrtchOEHGKLumZwnu1NjoI0w==} hasBin: true @@ -6992,6 +7311,9 @@ packages: w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + webgl-constants@1.1.1: resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==} @@ -7184,6 +7506,9 @@ packages: use-sync-external-store: optional: true + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@ag-ui/core@0.1.1-canary.beta.0(zod@4.3.6)': @@ -8046,15 +8371,6 @@ snapshots: lit: 3.3.2 three: 0.183.2 - '@hcaptcha/loader@2.3.0': {} - - '@hcaptcha/react-hcaptcha@1.17.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': - dependencies: - '@babel/runtime': 7.29.2 - '@hcaptcha/loader': 2.3.0 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - '@hono/node-server@1.19.12(hono@4.12.9)': dependencies: hono: 4.12.9 @@ -8187,16 +8503,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@kapaai/react-sdk@0.9.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': - dependencies: - '@fingerprintjs/fingerprintjs-pro-react': 2.7.1 - '@fingerprintjs/fingerprintjs-pro-spa': 1.3.3 - '@hcaptcha/react-hcaptcha': 1.17.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - js-cookie: 3.0.8 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - tldts: 7.4.2 - '@lezer/common@1.5.2': {} '@lezer/css@1.3.6': @@ -9753,6 +10059,24 @@ snapshots: '@tanstack/ai': 0.54.0(@opentelemetry/api@1.9.1)(zod@4.3.6) '@tanstack/ai-utils': 0.4.0 + '@tanstack/ai-react@0.24.1(@opentelemetry/api@1.9.1)(@tanstack/ai@0.54.0(@opentelemetry/api@1.9.1)(zod@4.3.6))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.3.6)': + dependencies: + '@tanstack/ai': 0.54.0(@opentelemetry/api@1.9.1)(zod@4.3.6) + '@tanstack/ai-client': 0.31.1(@opentelemetry/api@1.9.1)(zod@4.3.6) + '@types/react': 19.2.14 + react: 19.2.3 + react-markdown: 10.1.0(@types/react@19.2.14)(react@19.2.3) + rehype-highlight: 7.0.2 + rehype-raw: 7.0.0 + rehype-sanitize: 6.0.0 + remark-gfm: 4.0.1 + optionalDependencies: + react-dom: 19.2.3(react@19.2.3) + transitivePeerDependencies: + - '@opentelemetry/api' + - supports-color + - zod + '@tanstack/ai-utils@0.4.0': {} '@tanstack/ai@0.54.0(@opentelemetry/api@1.9.1)(zod@4.3.6)': @@ -10436,6 +10760,10 @@ snapshots: '@types/d3-transition': 3.0.9 '@types/d3-zoom': 3.0.8 + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + '@types/dom-speech-recognition@0.0.1': {} '@types/draco3d@1.4.10': {} @@ -10456,6 +10784,12 @@ snapshots: '@types/hogan.js@3.0.5': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + '@types/mysql@2.15.27': dependencies: '@types/node': 25.5.0 @@ -10524,10 +10858,14 @@ snapshots: '@types/trusted-types@2.0.7': {} + '@types/unist@2.0.11': {} + '@types/unist@3.0.3': {} '@types/webxr@0.5.24': {} + '@ungap/structured-clone@1.4.0': {} + '@uploadthing/mime-types@0.3.6': {} '@uploadthing/react@7.3.3(react@19.2.3)(uploadthing@7.7.4(express@5.2.1)(tailwindcss@4.2.2))': @@ -10696,6 +11034,8 @@ snapshots: transitivePeerDependencies: - supports-color + bail@2.0.2: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -10815,6 +11155,8 @@ snapshots: caniuse-lite@1.0.30001784: {} + ccount@2.0.1: {} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -10828,6 +11170,14 @@ snapshots: chalk@5.6.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 @@ -10917,6 +11267,8 @@ snapshots: color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} + commander@7.2.0: {} commander@8.3.0: {} @@ -11192,6 +11544,10 @@ snapshots: dependencies: ms: 2.1.3 + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -11210,6 +11566,8 @@ snapshots: depd@2.0.0: {} + dequal@2.0.3: {} + detect-gpu@5.0.70: dependencies: webgl-constants: 1.1.1 @@ -11220,6 +11578,10 @@ snapshots: devalue@5.9.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + diff@8.0.4: {} discord-interactions@4.4.0: {} @@ -11521,12 +11883,16 @@ snapshots: escape-string-regexp@1.0.5: {} + escape-string-regexp@5.0.0: {} + esprima@4.0.1: {} esrap@2.3.2: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + estree-util-is-identifier-name@3.0.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -11604,6 +11970,8 @@ snapshots: dependencies: is-extendable: 0.1.1 + extend@3.0.2: {} + fast-check@3.23.2: dependencies: pure-rand: 6.1.0 @@ -11800,6 +12168,98 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.2.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.4.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-sanitize@5.0.2: + dependencies: + '@types/hast': 3.0.4 + '@ungap/structured-clone': 1.4.0 + unist-util-position: 5.0.0 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + + highlight.js@11.11.2: {} + hls.js@1.6.15: {} hogan.js@3.0.2: @@ -11813,6 +12273,10 @@ snapshots: htm@3.1.1: {} + html-url-attributes@3.0.1: {} + + html-void-elements@3.0.0: {} + htmlparser2@10.1.0: dependencies: domelementtype: 2.3.0 @@ -11869,6 +12333,8 @@ snapshots: inherits@2.0.4: {} + inline-style-parser@0.2.7: {} + instantsearch-ui-components@0.23.0(react@19.2.3): dependencies: '@swc/helpers': 0.5.18 @@ -11912,6 +12378,13 @@ snapshots: ipaddr.js@1.9.1: {} + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -11954,6 +12427,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-decimal@2.0.1: {} + is-docker@2.2.1: {} is-extendable@0.1.1: {} @@ -11972,6 +12447,8 @@ snapshots: has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 + is-hexadecimal@2.0.1: {} + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -12063,8 +12540,6 @@ snapshots: jose@6.2.2: {} - js-cookie@3.0.8: {} - js-tokens@10.0.0: {} js-tokens@4.0.0: {} @@ -12258,6 +12733,14 @@ snapshots: lodash@4.17.23: {} + longest-streak@3.1.0: {} + + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.11.2 + lru-cache@11.2.7: {} lru-cache@5.1.1: @@ -12273,6 +12756,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-table@3.0.4: {} + markdown-to-jsx@7.7.17(react@19.2.3): optionalDependencies: react: 19.2.3 @@ -12286,6 +12771,159 @@ snapshots: math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.4.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + media-typer@1.1.0: {} memorystream@0.3.1: {} @@ -12322,6 +12960,197 @@ snapshots: meshoptimizer@1.0.1: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.2: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.2 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + mime-db@1.54.0: {} mime-types@3.0.2: @@ -12594,6 +13423,16 @@ snapshots: pako@1.0.11: {} + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-gitignore@2.0.0: {} parse-json@4.0.0: @@ -12734,6 +13573,8 @@ snapshots: is-promise: 2.2.2 lie: 3.3.0 + property-information@7.2.0: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -12794,6 +13635,24 @@ snapshots: react-dom: 19.2.3(react@19.2.3) react-instantsearch-core: 7.29.0(algoliasearch@5.50.0)(react@19.2.3) + react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.3): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.14 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.3 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.3): dependencies: react: 19.2.3 @@ -12871,6 +13730,59 @@ snapshots: regexparam@2.0.2: {} + rehype-highlight@7.0.2: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-text: 4.0.2 + lowlight: 3.3.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-sanitize@6.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-sanitize: 5.0.2 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + remove-accents@0.5.0: {} remove-markdown@0.6.3: {} @@ -13191,6 +14103,8 @@ snapshots: source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -13279,6 +14193,11 @@ snapshots: dependencies: safe-buffer: 5.1.2 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -13291,6 +14210,14 @@ snapshots: style-mod@4.1.3: {} + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + stylis@4.3.6: {} supports-color@10.2.2: {} @@ -13393,16 +14320,12 @@ snapshots: tinypool@2.1.0: {} - tldts-core@7.4.2: {} - - tldts@7.4.2: - dependencies: - tldts-core: 7.4.2 - toidentifier@1.0.1: {} tr46@0.0.3: {} + trim-lines@3.0.1: {} + troika-three-text@0.52.4(three@0.183.2): dependencies: bidi-js: 1.0.3 @@ -13417,6 +14340,8 @@ snapshots: troika-worker-utils@0.52.0: {} + trough@2.2.0: {} + ts-algebra@2.0.0: {} ts-dedent@2.2.0: {} @@ -13509,6 +14434,44 @@ snapshots: unicorn-magic@0.4.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universal-user-agent@7.0.3: {} unpipe@1.0.0: {} @@ -13585,6 +14548,21 @@ snapshots: vary@1.1.2: {} + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + vite-bundle-analyzer@1.3.6: {} vite@8.0.16(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.9.0): @@ -13625,6 +14603,8 @@ snapshots: w3c-keyname@2.2.8: {} + web-namespaces@2.0.1: {} + webgl-constants@1.1.1: {} webgl-sdf-generator@1.1.1: {} @@ -13802,3 +14782,5 @@ snapshots: '@types/react': 19.2.14 react: 19.2.3 use-sync-external-store: 1.6.0(react@19.2.3) + + zwitch@2.0.4: {} diff --git a/src/components/AiDock.tsx b/src/components/AiDock.tsx index 6d3315428..8f17a233a 100644 --- a/src/components/AiDock.tsx +++ b/src/components/AiDock.tsx @@ -29,12 +29,11 @@ import { ArrowsInSimpleIcon, } from '@phosphor-icons/react' import { - DefaultKapaApiService, - KapaProvider, - processStream, + fetchServerSentEvents, useChat, - type StreamSource, -} from '@kapaai/react-sdk' + type ConnectConnectionAdapter, + type UIMessage, +} from '@tanstack/ai-react' import { streamingMarkdownExtension } from '@tanstack/markdown/extensions/streaming' import { Markdown as TanStackMarkdown } from '@tanstack/markdown/react' import { useSearchContext } from '~/contexts/SearchContext' @@ -43,7 +42,6 @@ import { frameworkOptions } from '~/libraries/frameworks' import { capitalize } from '~/utils/utils' import { CodeBlock } from '~/components/markdown/CodeBlock' import { InlineCode } from '~/ui/InlineCode' -import { env } from '~/utils/env' import { isSafeHref } from '~/utils/url-boundary' import { type AlgoliaHit, @@ -67,8 +65,20 @@ const AI_DOCK_MIN_WIDTH = 320 const AI_DOCK_DEFAULT_WIDTH = 360 const AI_DOCK_MAX_WIDTH_RATIO = 0.5 const AI_DOCK_MAXIMIZED_WIDTH = 1200 -const KAPA_RECAPTCHA_READY_TIMEOUT_MS = 8_000 -const KAPA_INTEGRATION_ID = '86e864f7-401e-48c8-ac6f-5fcbc86c5668' +// Algolia Agent Studio (staging while the AG-UI compatibility mode rolls out +// to production). The API key is search-ACL only and safe to ship to browsers; +// abuse is bounded by Agent Studio's approved domains and per-agent/per-IP +// rate limits. +const AGENT_STUDIO_APP_ID = 'betaHAXPMHIMMC' +const AGENT_STUDIO_SEARCH_API_KEY = '8b00405cba281a7d800ccec393e9af24' +const AGENT_STUDIO_AGENT_ID = '97a1b15d-9837-48ed-b4b4-f905b0199cee' +const AGENT_STUDIO_BASE_URL = 'https://agent-studio.staging.eu.algolia.com/1' +const AGENT_STUDIO_COMPLETIONS_URL = `${AGENT_STUDIO_BASE_URL}/agents/${AGENT_STUDIO_AGENT_ID}/completions?compatibilityMode=ag-ui&stream=true` +const AGENT_STUDIO_FEEDBACK_URL = `${AGENT_STUDIO_BASE_URL}/feedback` +const AGENT_STUDIO_AUTH_HEADERS = { + 'X-Algolia-Application-Id': AGENT_STUDIO_APP_ID, + 'X-Algolia-API-Key': AGENT_STUDIO_SEARCH_API_KEY, +} type SearchSurface = 'modal' | 'dock' type AiDockStyle = React.CSSProperties & { @@ -113,72 +123,7 @@ function writeAiDockWidth(width: number) { localStorage.setItem(AI_DOCK_WIDTH_STORAGE_KEY, String(Math.round(width))) } -function waitForKapaRecaptchaReady() { - if (typeof window === 'undefined') { - return Promise.resolve() - } - - return new Promise((resolve) => { - let timeoutId: number | null = null - let intervalId: number | null = null - let isSettled = false - let hasRegisteredReadyCallback = false - - const cleanup = () => { - if (timeoutId !== null) { - window.clearTimeout(timeoutId) - timeoutId = null - } - - if (intervalId !== null) { - window.clearInterval(intervalId) - intervalId = null - } - } - - const finish = () => { - if (isSettled) { - return - } - - isSettled = true - cleanup() - window.setTimeout(resolve, 0) - } - - const checkReady = () => { - if (hasRegisteredReadyCallback) { - return true - } - - const enterprise = window.grecaptcha?.enterprise - - if (!enterprise) { - return false - } - - hasRegisteredReadyCallback = true - - if (intervalId !== null) { - window.clearInterval(intervalId) - intervalId = null - } - - enterprise.ready(finish) - return true - } - - timeoutId = window.setTimeout(finish, KAPA_RECAPTCHA_READY_TIMEOUT_MS) - - if (checkReady()) { - return - } - - intervalId = window.setInterval(checkReady, 50) - }) -} - -function KapaMarkdownLink({ +function AiMarkdownLink({ href, children, ...props @@ -194,7 +139,7 @@ function KapaMarkdownLink({ ) } -function KapaMarkdownTable({ +function AiMarkdownTable({ children, className, ...props @@ -214,7 +159,7 @@ function KapaMarkdownTable({ ) } -function KapaMarkdownTableHead({ +function AiMarkdownTableHead({ children, className, ...props @@ -232,7 +177,7 @@ function KapaMarkdownTableHead({ ) } -function KapaMarkdownTableBody({ +function AiMarkdownTableBody({ children, className, ...props @@ -250,7 +195,7 @@ function KapaMarkdownTableBody({ ) } -function KapaMarkdownTableCell({ +function AiMarkdownTableCell({ children, className, ...props @@ -268,7 +213,7 @@ function KapaMarkdownTableCell({ ) } -function KapaMarkdownTableHeaderCell({ +function AiMarkdownTableHeaderCell({ children, className, ...props @@ -286,15 +231,40 @@ function KapaMarkdownTableHeaderCell({ ) } -const kapaMarkdownComponents = { +const aiMarkdownComponents = { pre: CodeBlock, code: InlineCode, - a: KapaMarkdownLink, - table: KapaMarkdownTable, - thead: KapaMarkdownTableHead, - tbody: KapaMarkdownTableBody, - td: KapaMarkdownTableCell, - th: KapaMarkdownTableHeaderCell, + a: AiMarkdownLink, + table: AiMarkdownTable, + thead: AiMarkdownTableHead, + tbody: AiMarkdownTableBody, + td: AiMarkdownTableCell, + th: AiMarkdownTableHeaderCell, + p: ({ children }: { children?: React.ReactNode }) => ( +

{children}

+ ), + // Sized in em so headings scale with the compact (13px) and regular (14px) + // chat bubbles alike. + h1: ({ children }: { children?: React.ReactNode }) => ( +

{children}

+ ), + h2: ({ children }: { children?: React.ReactNode }) => ( +

+ {children} +

+ ), + h3: ({ children }: { children?: React.ReactNode }) => ( +

{children}

+ ), + h4: ({ children }: { children?: React.ReactNode }) => ( +

{children}

+ ), + h5: ({ children }: { children?: React.ReactNode }) => ( +
{children}
+ ), + h6: ({ children }: { children?: React.ReactNode }) => ( +
{children}
+ ), ul: ({ children }: { children?: React.ReactNode }) => ( ), @@ -308,76 +278,323 @@ const kapaMarkdownComponents = { ), } -const kapaMarkdownExtensions = [streamingMarkdownExtension()] +const aiMarkdownExtensions = [streamingMarkdownExtension()] -function parseSourceGroupIDs(value: string | undefined) { - if (!value) { - return undefined - } +type FeedbackReaction = 'upvote' | 'downvote' - const ids = value - .split(',') - .map((id) => id.trim()) - .filter((id) => id.length > 0) +type AnswerSource = { + title: string + subtitle: string + source_url: string + source_type: string +} - return ids.length > 0 ? ids : undefined +type AnswerToolCall = { + id: string + query: string + isPending: boolean } -type KapaReaction = 'upvote' | 'downvote' +type AnswerSegment = + | { type: 'text'; content: string } + | { type: 'tool-call'; toolCall: AnswerToolCall } -type KapaDisplayQA = { +type DisplayQA = { id: string | null question: string answer: string - sources: Array + sources: Array + // Ordered answer flow (text interleaved with search-tool activity) for the + // live conversation; not persisted to chat history, which falls back to + // the flat `answer` text. + segments?: Array isGenerationAborted: boolean isFeedbackSubmissionEnabled?: boolean - reaction: KapaReaction | null + reaction: FeedbackReaction | null } -type KapaHistoryItem = { +type ChatHistoryItem = { threadId: string title: string createdAt: number updatedAt: number - conversation: Array + conversation: Array } -type KapaSubmitQueryArgs = Parameters[0] -type KapaChatStreamCallbacks = Parameters< - DefaultKapaApiService['submitQuery'] ->[1] -type KapaSubmitFeedbackArgs = Parameters< - DefaultKapaApiService['addFeedback'] ->[0] - -const KAPA_HISTORY_STORAGE_KEY = 'tanstack-kapa-chat-history' -const KAPA_HISTORY_LIMIT = 5 - -class KapaThreadOverrideApiService { - private service = new DefaultKapaApiService(processStream) - - constructor( - private threadIdOverrideRef: React.MutableRefObject, - ) {} - - submitQuery( - args: KapaSubmitQueryArgs, - callbacks: KapaChatStreamCallbacks, - ): Promise { - return this.service.submitQuery( - { ...args, threadId: this.threadIdOverrideRef.current }, - callbacks, - ) +const CHAT_HISTORY_STORAGE_KEY = 'tanstack-ai-chat-history' +const CHAT_HISTORY_LIMIT = 5 + +function createThreadId() { + if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) { + return crypto.randomUUID() + } + + return `thread_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}` +} + +function messageText(message: UIMessage) { + return message.parts + .map((part) => (part.type === 'text' ? part.content : '')) + .join('') +} + +/** + * Pull doc links out of the agent's Algolia search tool results. The tool + * output is provider-shaped JSON, so this walks it defensively and collects + * anything that looks like a hit: a record with a safe URL plus a usable + * title (explicit `title` or the deepest DocSearch `hierarchy` level). + */ +function collectSourcesFromToolOutput( + value: unknown, + into: Map, + depth = 0, +) { + if (depth > 6 || into.size >= 6) { + return + } + + if (Array.isArray(value)) { + for (const item of value) { + collectSourcesFromToolOutput(item, into, depth + 1) + } + return + } + + if (!isUnknownRecord(value)) { + return + } + + const url = readString(value.url) || readString(value.source_url) + + if (url && isSafeHref(url) && !into.has(url)) { + const hierarchy = isUnknownRecord(value.hierarchy) ? value.hierarchy : {} + const hierarchyLevels = ['lvl6', 'lvl5', 'lvl4', 'lvl3', 'lvl2', 'lvl1'] + .map((level) => readString(hierarchy[level])) + .filter((level) => level.length > 0) + const title = + readString(value.title) || + hierarchyLevels[0] || + readString(hierarchy.lvl0) + + if (title) { + into.set(url, { + title, + subtitle: readString(hierarchy.lvl0), + source_url: url, + source_type: 'documentation', + }) + return + } + } + + for (const nested of Object.values(value)) { + collectSourcesFromToolOutput(nested, into, depth + 1) + } +} + +/** + * Turn the search tool's input into a human-readable label. The agent's + * Algolia tools take `{ queries: [{ query }], originalQuery, ... }`. + */ +function describeToolCallQuery(input: unknown, rawArguments: string) { + let parsed = input + + if (parsed === undefined && rawArguments) { + try { + parsed = JSON.parse(rawArguments) + } catch { + return '' + } } - addFeedback(args: KapaSubmitFeedbackArgs): Promise { - return this.service.addFeedback(args) + if (!isUnknownRecord(parsed)) { + return '' } - abortCurrent(): void { - this.service.abortCurrent() + const queries = Array.isArray(parsed.queries) ? parsed.queries : [] + const queryTexts = queries + .map((entry) => (isUnknownRecord(entry) ? readString(entry.query) : '')) + .filter((query) => query.length > 0) + + return queryTexts.join(', ') || readString(parsed.originalQuery) +} + +/** + * Fold the AG-UI message list into the question/answer pairs the panel + * renders: each user message opens a QA, and the assistant messages that + * follow contribute answer text, search-tool activity, and tool-derived + * sources. + */ +function messagesToDisplayQAs( + messages: Array, + reactions: Record, +): Array { + const qas: Array = [] + let current: DisplayQA | null = null + + for (const message of messages) { + if (message.role === 'user') { + current = { + id: null, + question: messageText(message), + answer: '', + sources: [], + segments: [], + isGenerationAborted: false, + isFeedbackSubmissionEnabled: false, + reaction: null, + } + qas.push(current) + continue + } + + if (message.role !== 'assistant' || !current) { + continue + } + + const sources = new Map( + current.sources.map((source) => [source.source_url, source]), + ) + const segments = current.segments ?? [] + + for (const part of message.parts) { + if (part.type === 'text' && part.content.trim()) { + segments.push({ type: 'text', content: part.content.trim() }) + } + + if (part.type === 'tool-call') { + segments.push({ + type: 'tool-call', + toolCall: { + id: part.id, + query: describeToolCallQuery(part.input, part.arguments), + isPending: part.output === undefined && part.state !== 'error', + }, + }) + + if (part.output !== undefined) { + collectSourcesFromToolOutput(part.output, sources) + } + } + } + + current.id = message.id + current.segments = segments + // Flat text fallback, used for chat history persistence. The agent + // narrates between tool calls, so join the chunks as paragraphs. + current.answer = segments + .flatMap((segment) => (segment.type === 'text' ? [segment.content] : [])) + .join('\n\n') + current.sources = Array.from(sources.values()) + current.isFeedbackSubmissionEnabled = true + current.reaction = reactions[message.id] ?? null } + + return qas +} + +/** + * Chat state for the AI panel, backed by Algolia Agent Studio's AG-UI + * completions endpoint via TanStack AI. Conversations are keyed by + * `threadId` on the server, so resuming a thread only needs the same id. + */ +function useAgentStudioChat(threadId: string) { + const connection = React.useMemo(() => { + const transport = fetchServerSentEvents(AGENT_STUDIO_COMPLETIONS_URL, { + headers: AGENT_STUDIO_AUTH_HEADERS, + }) + + // Two Agent Studio quirks to work around when replaying history: + // - TanStack AI stamps `metadata.tanstack` on every wire message (from + // the UIMessage's `metadata` and `createdAt`), but the AG-UI request + // model rejects `metadata` on assistant messages ("Extra inputs are + // not permitted") and 422s. + // - A run that ends mid-search leaves a tool call without a result in + // history; replaying that orphan `tool_use` makes the LLM provider + // reject the whole conversation ("Bad request to provider"). + // Both are stripped from the outgoing copies only. + const sanitizingConnect: ConnectConnectionAdapter['connect'] = ( + messages, + data, + abortSignal, + runContext, + ) => + transport.connect( + isUIMessageArray(messages) + ? messages.map( + ({ metadata: _metadata, createdAt: _createdAt, ...message }) => ({ + ...message, + parts: message.parts.filter( + (part) => + part.type !== 'tool-call' || + part.output !== undefined || + message.parts.some( + (sibling) => + sibling.type === 'tool-result' && + sibling.toolCallId === part.id, + ), + ), + }), + ) + : messages, + data, + abortSignal, + runContext, + ) + + return { ...transport, connect: sanitizingConnect } + }, []) + const { messages, sendMessage, isLoading, error, stop } = useChat({ + connection, + threadId, + }) + const [reactions, setReactions] = React.useState< + Record + >({}) + + const conversation = React.useMemo( + () => messagesToDisplayQAs(messages, reactions), + [messages, reactions], + ) + + const addFeedback = React.useCallback( + (messageId: string, reaction: FeedbackReaction) => { + // Optimistic: Agent Studio accepts a single vote per message, and the + // UI already locks the buttons after the first reaction. + setReactions((current) => ({ ...current, [messageId]: reaction })) + + fetch(AGENT_STUDIO_FEEDBACK_URL, { + method: 'POST', + headers: { + ...AGENT_STUDIO_AUTH_HEADERS, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + messageId, + vote: reaction === 'upvote' ? 1 : 0, + }), + }).catch(() => { + // Keep the optimistic reaction; feedback is best-effort. + }) + }, + [], + ) + + return { + conversation, + submitQuery: sendMessage, + isBusy: isLoading, + stopGeneration: stop, + addFeedback, + errorMessage: error?.message ?? null, + } +} + +/** AG-UI adapters accept UI or model messages; only UI messages carry parts. */ +function isUIMessageArray( + messages: Parameters[0], +): messages is Array { + return messages.every((message) => 'parts' in message) } function isUnknownRecord(value: unknown): value is Record { @@ -396,7 +613,7 @@ function readNumber(value: unknown) { return typeof value === 'number' && Number.isFinite(value) ? value : 0 } -function parseKapaReaction(value: unknown): KapaReaction | null { +function parseFeedbackReaction(value: unknown): FeedbackReaction | null { if (value === 'upvote' || value === 'downvote') { return value } @@ -404,7 +621,7 @@ function parseKapaReaction(value: unknown): KapaReaction | null { return null } -function parseKapaSource(value: unknown): StreamSource | null { +function parseAnswerSource(value: unknown): AnswerSource | null { if (!isUnknownRecord(value)) { return null } @@ -430,7 +647,7 @@ function parseKapaSource(value: unknown): StreamSource | null { } } -function parseKapaHistoryQA(value: unknown): KapaDisplayQA | null { +function parseChatHistoryQA(value: unknown): DisplayQA | null { if (!isUnknownRecord(value)) { return null } @@ -447,14 +664,14 @@ function parseKapaHistoryQA(value: unknown): KapaDisplayQA | null { id: typeof value.id === 'string' ? value.id : null, question, answer: readString(value.answer), - sources: sourceValues.map(parseKapaSource).filter(isDefined), + sources: sourceValues.map(parseAnswerSource).filter(isDefined), isGenerationAborted: value.isGenerationAborted === true, isFeedbackSubmissionEnabled: value.isFeedbackSubmissionEnabled === true, - reaction: parseKapaReaction(value.reaction), + reaction: parseFeedbackReaction(value.reaction), } } -function parseKapaHistoryItem(value: unknown): KapaHistoryItem | null { +function parseChatHistoryItem(value: unknown): ChatHistoryItem | null { if (!isUnknownRecord(value)) { return null } @@ -464,7 +681,7 @@ function parseKapaHistoryItem(value: unknown): KapaHistoryItem | null { ? value.conversation : [] const conversation = conversationValues - .map(parseKapaHistoryQA) + .map(parseChatHistoryQA) .filter(isDefined) if (!threadId || conversation.length === 0) { @@ -475,20 +692,20 @@ function parseKapaHistoryItem(value: unknown): KapaHistoryItem | null { return { threadId, - title: readString(value.title) || buildKapaHistoryTitle(conversation), + title: readString(value.title) || buildChatHistoryTitle(conversation), createdAt: readNumber(value.createdAt) || updatedAt, updatedAt, conversation, } } -function readKapaHistory() { +function readChatHistory() { if (typeof window === 'undefined') { return [] } try { - const raw = localStorage.getItem(KAPA_HISTORY_STORAGE_KEY) + const raw = localStorage.getItem(CHAT_HISTORY_STORAGE_KEY) if (!raw) { return [] } @@ -499,31 +716,31 @@ function readKapaHistory() { } return parsed - .map(parseKapaHistoryItem) + .map(parseChatHistoryItem) .filter(isDefined) .sort((a, b) => b.updatedAt - a.updatedAt) - .slice(0, KAPA_HISTORY_LIMIT) + .slice(0, CHAT_HISTORY_LIMIT) } catch { return [] } } -function writeKapaHistory(items: Array) { +function writeChatHistory(items: Array) { if (typeof window === 'undefined') { return } try { localStorage.setItem( - KAPA_HISTORY_STORAGE_KEY, - JSON.stringify(items.slice(0, KAPA_HISTORY_LIMIT)), + CHAT_HISTORY_STORAGE_KEY, + JSON.stringify(items.slice(0, CHAT_HISTORY_LIMIT)), ) } catch { // Ignore storage failures so chat keeps working. } } -function buildKapaHistoryTitle(conversation: Array) { +function buildChatHistoryTitle(conversation: Array) { const firstQuestion = conversation .find((qa) => qa.question.trim().length > 0) @@ -535,7 +752,7 @@ function buildKapaHistoryTitle(conversation: Array) { : firstQuestion } -function toStoredKapaQA(qa: KapaDisplayQA): KapaDisplayQA { +function toStoredQA(qa: DisplayQA): DisplayQA { return { id: qa.id, question: qa.question, @@ -552,30 +769,30 @@ function toStoredKapaQA(qa: KapaDisplayQA): KapaDisplayQA { } } -function upsertKapaHistoryItem( +function upsertChatHistoryItem( threadId: string, - conversation: Array, + conversation: Array, ) { - const current = readKapaHistory() + const current = readChatHistory() const existing = current.find((item) => item.threadId === threadId) const now = Date.now() - const nextItem: KapaHistoryItem = { + const nextItem: ChatHistoryItem = { threadId, - title: buildKapaHistoryTitle(conversation), + title: buildChatHistoryTitle(conversation), createdAt: existing?.createdAt ?? now, updatedAt: now, - conversation: conversation.map(toStoredKapaQA), + conversation: conversation.map(toStoredQA), } const next = [ nextItem, ...current.filter((item) => item.threadId !== threadId), - ].slice(0, KAPA_HISTORY_LIMIT) + ].slice(0, CHAT_HISTORY_LIMIT) - writeKapaHistory(next) + writeChatHistory(next) return next } -function formatKapaHistoryTime(updatedAt: number) { +function formatChatHistoryTime(updatedAt: number) { const elapsed = Date.now() - updatedAt if (elapsed < 60_000) { @@ -596,17 +813,17 @@ function formatKapaHistoryTime(updatedAt: number) { }).format(new Date(updatedAt)) } -function useKapaChatHistory() { +function useChatHistory() { const [items, setItems] = - React.useState>(readKapaHistory) + React.useState>(readChatHistory) const save = React.useCallback( - (threadId: string, conversation: Array) => { + (threadId: string, conversation: Array) => { if (conversation.length === 0) { return } - setItems(upsertKapaHistoryItem(threadId, conversation)) + setItems(upsertChatHistoryItem(threadId, conversation)) }, [], ) @@ -726,7 +943,7 @@ function CopyChatButton({ conversation, compact = false, }: { - conversation: Array + conversation: Array compact?: boolean }) { const [copied, setCopied] = React.useState(false) @@ -794,17 +1011,17 @@ function CopyChatButton({ ) } -function KapaHistoryButton({ +function ChatHistoryButton({ items, activeThreadId, isBusy, onSelect, compact = false, }: { - items: Array + items: Array activeThreadId: string | null isBusy: boolean - onSelect: (item: KapaHistoryItem) => void + onSelect: (item: ChatHistoryItem) => void compact?: boolean }) { const { cancelAiDockHoverClose } = useSearchContext() @@ -875,7 +1092,7 @@ function KapaHistoryButton({ {item.title} - {formatKapaHistoryTime(item.updatedAt)} + {formatChatHistoryTime(item.updatedAt)} {isActive ? ( @@ -948,12 +1165,24 @@ function AIMessageHeader({ action }: { action?: React.ReactNode }) { TanStack - answers by kapa.ai + + powered by + + Algolia + Algolia @@ -962,7 +1191,7 @@ function AIMessageHeader({ action }: { action?: React.ReactNode }) { ) } -function getKapaSuggestions(selectedLibrary: string) { +function getAiSuggestions(selectedLibrary: string) { if (selectedLibrary === 'query') { return [ 'How do I invalidate a query?', @@ -1010,7 +1239,7 @@ function getKapaSuggestions(selectedLibrary: string) { ] } -function KapaWelcome({ +function AiWelcome({ selectedLibrary, selectedFramework, onSuggestion, @@ -1023,7 +1252,7 @@ function KapaWelcome({ disabled?: boolean compact?: boolean }) { - const suggestions = getKapaSuggestions(selectedLibrary) + const suggestions = getAiSuggestions(selectedLibrary) return (
@@ -1081,7 +1310,7 @@ function KapaWelcome({ ) } -function KapaAnswer({ +function AiAnswer({ qa, isStreaming, error, @@ -1089,7 +1318,7 @@ function KapaAnswer({ onFeedback, compact = false, }: { - qa: KapaDisplayQA + qa: DisplayQA isStreaming: boolean error?: string | null onCopyQuestion: () => void @@ -1099,6 +1328,8 @@ function KapaAnswer({ const canSubmitFeedback = qa.id !== null && qa.isFeedbackSubmissionEnabled && qa.reaction === null const hasAnswerError = !isStreaming && !qa.answer && !!error + const segments = qa.segments ?? [] + const hasAnswerContent = segments.length > 0 || !!qa.answer return (
@@ -1131,21 +1362,58 @@ function KapaAnswer({ > {error}

- ) : qa.answer ? ( + ) : hasAnswerContent ? (
- - {qa.answer} - + {segments.length > 0 ? ( + segments.map((segment, index) => + segment.type === 'tool-call' ? ( +
+ + + {segment.toolCall.isPending && isStreaming + ? segment.toolCall.query + ? `Searching the docs for “${segment.toolCall.query}”…` + : 'Searching the docs…' + : segment.toolCall.query + ? `Searched the docs for “${segment.toolCall.query}”` + : 'Searched the docs'} + +
+ ) : ( + + {segment.content} + + ), + ) + ) : ( + + {qa.answer} + + )}
) : (
@@ -1236,11 +1504,10 @@ function KapaAnswer({ ) } -function KapaChatPanel({ +function AiChatPanel({ onReset, isFullHeight, onToggleFullHeight, - threadIdOverrideRef, newChatRequestId, surface, isDockMaximized = false, @@ -1249,23 +1516,20 @@ function KapaChatPanel({ onReset: () => void isFullHeight: boolean onToggleFullHeight: () => void - threadIdOverrideRef: React.MutableRefObject newChatRequestId: number surface: SearchSurface isDockMaximized?: boolean onToggleDockMaximized?: () => void }) { + const [threadId, setThreadId] = React.useState(createThreadId) const { conversation, - threadId, submitQuery, - isGeneratingAnswer, - isPreparingAnswer, + isBusy, stopGeneration, - resetConversation, addFeedback, - error, - } = useChat() + errorMessage, + } = useAgentStudioChat(threadId) const { aiDockAskRequest, clearAiDockAskRequest, @@ -1275,27 +1539,29 @@ function KapaChatPanel({ const { selectedLibrary, selectedFramework, showSearchResults, searchQuery } = useSearchFilters() const scrollRef = React.useRef(null) - const { items: historyItems, save: saveHistory } = useKapaChatHistory() + const { items: historyItems, save: saveHistory } = useChatHistory() const [selectedHistoryItem, setSelectedHistoryItem] = - React.useState(null) - const isBusy = isGeneratingAnswer || isPreparingAnswer + React.useState(null) + // Question waiting for the chat client of a freshly created thread. Changing + // `threadId` recreates the client on the next render, so a "clear then ask" + // flow (e.g. Ask AI from the search modal) must defer the submit. + const [pendingQuestion, setPendingQuestion] = React.useState( + null, + ) const isDock = surface === 'dock' const isSubmittingRef = React.useRef(false) - const pendingSubmitIdRef = React.useRef(0) const lockedToBottom = React.useRef(true) const handledNewChatRequestId = React.useRef(newChatRequestId) const handledAiDockAskRequestId = React.useRef(0) - const displayConversation = React.useMemo>(() => { - const liveConversation = Array.from(conversation) - + const displayConversation = React.useMemo>(() => { if (!selectedHistoryItem) { - return liveConversation + return conversation } - return [...selectedHistoryItem.conversation, ...liveConversation] + return [...selectedHistoryItem.conversation, ...conversation] }, [conversation, selectedHistoryItem]) const hasConversation = displayConversation.length > 0 - const activeThreadId = threadId ?? selectedHistoryItem?.threadId ?? null + const activeThreadId = threadId React.useEffect(() => { if (!isDock) { @@ -1308,13 +1574,7 @@ function KapaChatPanel({ }, [displayConversation.length, isBusy, isDock, searchQuery, setAiDockDirty]) React.useEffect(() => { - if (threadId) { - threadIdOverrideRef.current = threadId - } - }, [threadId, threadIdOverrideRef]) - - React.useEffect(() => { - if (!threadId || isBusy || displayConversation.length === 0) { + if (isBusy || displayConversation.length === 0) { return } @@ -1361,44 +1621,35 @@ function KapaChatPanel({ return } - const submitId = pendingSubmitIdRef.current + 1 - pendingSubmitIdRef.current = submitId isSubmittingRef.current = true - - waitForKapaRecaptchaReady().then(() => { - if (pendingSubmitIdRef.current !== submitId) { - return - } - - submitQuery(trimmed) - }) + void submitQuery(trimmed) }, [isBusy, submitQuery], ) const selectHistoryItem = React.useCallback( - (item: KapaHistoryItem) => { + (item: ChatHistoryItem) => { if (isBusy) { return } - resetConversation() - threadIdOverrideRef.current = item.threadId + // Agent Studio persists the conversation server-side keyed by threadId, + // so adopting the stored id resumes the thread; the stored QAs are + // rendered as the prefix of the transcript. setSelectedHistoryItem(item) + setThreadId(item.threadId) lockedToBottom.current = true }, - [isBusy, resetConversation, threadIdOverrideRef], + [isBusy], ) const clearActiveChat = React.useCallback(() => { - pendingSubmitIdRef.current += 1 isSubmittingRef.current = false - resetConversation() - threadIdOverrideRef.current = null setSelectedHistoryItem(null) + setThreadId(createThreadId()) lockedToBottom.current = true onReset() - }, [onReset, resetConversation, threadIdOverrideRef]) + }, [onReset]) const startNewChat = React.useCallback(() => { if (isBusy) { @@ -1417,6 +1668,15 @@ function KapaChatPanel({ startNewChat() }, [newChatRequestId, startNewChat]) + React.useEffect(() => { + if (pendingQuestion === null || isBusy) { + return + } + + setPendingQuestion(null) + ask(pendingQuestion) + }, [ask, isBusy, pendingQuestion]) + React.useEffect(() => { if (!isDock || !aiDockAskRequest) { return @@ -1433,10 +1693,9 @@ function KapaChatPanel({ handledAiDockAskRequestId.current = aiDockAskRequest.id clearActiveChat() - ask(aiDockAskRequest.question) + setPendingQuestion(aiDockAskRequest.question) clearAiDockAskRequest(aiDockAskRequest.id) }, [ - ask, aiDockAskRequest, clearActiveChat, clearAiDockAskRequest, @@ -1512,7 +1771,7 @@ function KapaChatPanel({ onToggle={onToggleDockMaximized} /> ) : null} - {displayConversation.length === 0 ? ( - = 0 const isLatestLiveQA = liveIndex === conversation.length - 1 const isStreamingLatest = isLiveQA && isLatestLiveQA && isBusy - const answerError = isLiveQA && isLatestLiveQA ? error : null + const answerError = isLiveQA && isLatestLiveQA ? errorMessage : null return ( - - {error} + {errorMessage}
)}
@@ -1644,122 +1903,6 @@ function StickyTopBlur() { ) } -function KapaUnavailablePanel({ - isFullHeight, - onToggleFullHeight, - surface, - isDockMaximized = false, - onToggleDockMaximized, -}: { - isFullHeight: boolean - onToggleFullHeight: () => void - surface: SearchSurface - isDockMaximized?: boolean - onToggleDockMaximized?: () => void -}) { - const { closeSearch, setAiDockDirty } = useSearchContext() - const { selectedLibrary, selectedFramework, showSearchResults, searchQuery } = - useSearchFilters() - const isDock = surface === 'dock' - - React.useEffect(() => { - if (!isDock) { - return - } - - setAiDockDirty(searchQuery.trim().length > 0) - }, [isDock, searchQuery, setAiDockDirty]) - - return ( -
-
- {isDock ? null : } -
- {isDock ? ( -
-
- TanStack AI -
-
- ) : ( - <> - - - - )} -
- {isDock && onToggleDockMaximized ? ( -
- -
- ) : null} -
-
-
- Kapa is not configured. Add{' '} - VITE_KAPA_INTEGRATION_ID to enable - AI answers. -
- {}} - disabled - compact={isDock} - /> -
-
-
- -
- {}} surface={surface} /> -
-
- ) -} - function SearchPanel({ isFullHeight, onToggleFullHeight, @@ -1775,23 +1918,6 @@ function SearchPanel({ isDockMaximized?: boolean onToggleDockMaximized?: () => void }) { - const integrationId = KAPA_INTEGRATION_ID - const threadIdOverrideRef = React.useRef(null) - const apiService = React.useMemo( - () => new KapaThreadOverrideApiService(threadIdOverrideRef), - [], - ) - const sourceGroupIDsInclude = React.useMemo( - () => parseSourceGroupIDs(env.VITE_KAPA_SOURCE_GROUP_IDS), - [], - ) - - React.useEffect(() => { - return () => { - apiService.abortCurrent() - } - }, [apiService]) - return (
- {integrationId ? ( - - {}} - isFullHeight={isFullHeight} - onToggleFullHeight={onToggleFullHeight} - threadIdOverrideRef={threadIdOverrideRef} - newChatRequestId={newChatRequestId} - surface={surface} - isDockMaximized={isDockMaximized} - onToggleDockMaximized={onToggleDockMaximized} - /> - - ) : ( - - )} + {}} + isFullHeight={isFullHeight} + onToggleFullHeight={onToggleFullHeight} + newChatRequestId={newChatRequestId} + surface={surface} + isDockMaximized={isDockMaximized} + onToggleDockMaximized={onToggleDockMaximized} + />
) } diff --git a/src/styles/app.css b/src/styles/app.css index 10926a505..738cc32b3 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -2951,12 +2951,6 @@ html:not(.dark) .library-landing-graphic [style*='drop-shadow'] { @apply my-4; } -/* Kapa injects invisible reCAPTCHA; keep it from occupying the UI corner. */ -.grecaptcha-badge { - display: none !important; - pointer-events: none !important; -} - button[aria-label='Open TanStack Devtools'] { display: none !important; } diff --git a/src/utils/env.ts b/src/utils/env.ts index c5855418d..c6c4e7c14 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -23,8 +23,6 @@ const serverEnvSchema = v.object({ }) const clientEnvSchema = v.object({ - VITE_KAPA_INTEGRATION_ID: v.optional(v.string()), - VITE_KAPA_SOURCE_GROUP_IDS: v.optional(v.string()), VITE_WEBCONTAINER_API_KEY: v.optional(v.string()), }) diff --git a/vite.config.ts b/vite.config.ts index 8ae2ecb23..5e79a2027 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -161,7 +161,7 @@ const useSyncExternalStoreShimIndexAlias = { const serverBundledClientPackages = [ ...(shouldUseRedact ? ['@tanstack/redact'] : []), /^@radix-ui\//, - '@kapaai/react-sdk', + /^@tanstack\/ai(?:-|$)/, '@tanstack/highlight', '@tanstack/markdown', '@tanstack/react-hotkeys', From a0a703128ad1d8e34e9d06c2a4026b023acd2dbe Mon Sep 17 00:00:00 2001 From: Dylan Tientcheu Date: Thu, 27 Aug 2026 19:22:04 +0200 Subject: [PATCH 2/8] Drop AG-UI client workarounds fixed server-side Agent Studio now tolerates vendor metadata on AG-UI messages and prunes unresolved historical tool calls before provider replay (algolia/conversational-ai#1629), so the outgoing-message sanitizer is no longer needed. --- src/components/AiDock.tsx | 60 +++++---------------------------------- 1 file changed, 7 insertions(+), 53 deletions(-) diff --git a/src/components/AiDock.tsx b/src/components/AiDock.tsx index 8f17a233a..c9996bf3e 100644 --- a/src/components/AiDock.tsx +++ b/src/components/AiDock.tsx @@ -31,7 +31,6 @@ import { import { fetchServerSentEvents, useChat, - type ConnectConnectionAdapter, type UIMessage, } from '@tanstack/ai-react' import { streamingMarkdownExtension } from '@tanstack/markdown/extensions/streaming' @@ -499,51 +498,13 @@ function messagesToDisplayQAs( * `threadId` on the server, so resuming a thread only needs the same id. */ function useAgentStudioChat(threadId: string) { - const connection = React.useMemo(() => { - const transport = fetchServerSentEvents(AGENT_STUDIO_COMPLETIONS_URL, { - headers: AGENT_STUDIO_AUTH_HEADERS, - }) - - // Two Agent Studio quirks to work around when replaying history: - // - TanStack AI stamps `metadata.tanstack` on every wire message (from - // the UIMessage's `metadata` and `createdAt`), but the AG-UI request - // model rejects `metadata` on assistant messages ("Extra inputs are - // not permitted") and 422s. - // - A run that ends mid-search leaves a tool call without a result in - // history; replaying that orphan `tool_use` makes the LLM provider - // reject the whole conversation ("Bad request to provider"). - // Both are stripped from the outgoing copies only. - const sanitizingConnect: ConnectConnectionAdapter['connect'] = ( - messages, - data, - abortSignal, - runContext, - ) => - transport.connect( - isUIMessageArray(messages) - ? messages.map( - ({ metadata: _metadata, createdAt: _createdAt, ...message }) => ({ - ...message, - parts: message.parts.filter( - (part) => - part.type !== 'tool-call' || - part.output !== undefined || - message.parts.some( - (sibling) => - sibling.type === 'tool-result' && - sibling.toolCallId === part.id, - ), - ), - }), - ) - : messages, - data, - abortSignal, - runContext, - ) - - return { ...transport, connect: sanitizingConnect } - }, []) + const connection = React.useMemo( + () => + fetchServerSentEvents(AGENT_STUDIO_COMPLETIONS_URL, { + headers: AGENT_STUDIO_AUTH_HEADERS, + }), + [], + ) const { messages, sendMessage, isLoading, error, stop } = useChat({ connection, threadId, @@ -590,13 +551,6 @@ function useAgentStudioChat(threadId: string) { } } -/** AG-UI adapters accept UI or model messages; only UI messages carry parts. */ -function isUIMessageArray( - messages: Parameters[0], -): messages is Array { - return messages.every((message) => 'parts' in message) -} - function isUnknownRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) } From 8fb143a16b6752049dfa3b667da4893726ac734b Mon Sep 17 00:00:00 2001 From: Dylan Tientcheu Date: Thu, 27 Aug 2026 20:43:37 +0200 Subject: [PATCH 3/8] Point the AI dock at production Agent Studio Swaps the staging agent for the production one now that AG-UI compatibility mode and its message-tolerance fixes are deployed there. --- src/components/AiDock.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/AiDock.tsx b/src/components/AiDock.tsx index c9996bf3e..710d14ff2 100644 --- a/src/components/AiDock.tsx +++ b/src/components/AiDock.tsx @@ -64,14 +64,14 @@ const AI_DOCK_MIN_WIDTH = 320 const AI_DOCK_DEFAULT_WIDTH = 360 const AI_DOCK_MAX_WIDTH_RATIO = 0.5 const AI_DOCK_MAXIMIZED_WIDTH = 1200 -// Algolia Agent Studio (staging while the AG-UI compatibility mode rolls out -// to production). The API key is search-ACL only and safe to ship to browsers; -// abuse is bounded by Agent Studio's approved domains and per-agent/per-IP -// rate limits. -const AGENT_STUDIO_APP_ID = 'betaHAXPMHIMMC' -const AGENT_STUDIO_SEARCH_API_KEY = '8b00405cba281a7d800ccec393e9af24' -const AGENT_STUDIO_AGENT_ID = '97a1b15d-9837-48ed-b4b4-f905b0199cee' -const AGENT_STUDIO_BASE_URL = 'https://agent-studio.staging.eu.algolia.com/1' + +// Algolia Agent Studio. The API key is search-ACL only and safe to ship to +// browsers; abuse is bounded by Agent Studio's approved domains and +// per-agent/per-IP rate limits. +const AGENT_STUDIO_APP_ID = 'FQ0DQ6MA3C' +const AGENT_STUDIO_SEARCH_API_KEY = '2a257513b7a9e64906b9e0e53a6bc3ce' +const AGENT_STUDIO_AGENT_ID = 'eeaedc22-16c5-4314-974f-38d93631761b' +const AGENT_STUDIO_BASE_URL = `https://${AGENT_STUDIO_APP_ID.toLowerCase()}.algolia.net/agent-studio/1` const AGENT_STUDIO_COMPLETIONS_URL = `${AGENT_STUDIO_BASE_URL}/agents/${AGENT_STUDIO_AGENT_ID}/completions?compatibilityMode=ag-ui&stream=true` const AGENT_STUDIO_FEEDBACK_URL = `${AGENT_STUDIO_BASE_URL}/feedback` const AGENT_STUDIO_AUTH_HEADERS = { From 0874f284766e21ee24a5ebdaf35be5c89df2953e Mon Sep 17 00:00:00 2001 From: Dylan Tientcheu Date: Thu, 27 Aug 2026 21:04:06 +0200 Subject: [PATCH 4/8] Address PR review: tool-result sources and feedback payload Collect answer sources from sibling tool-result parts, which hydrated threads use instead of inline tool-call output. Send the required agentId with feedback votes and unlock the thumbs buttons when the endpoint rejects a vote. --- src/components/AiDock.tsx | 46 ++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/components/AiDock.tsx b/src/components/AiDock.tsx index 710d14ff2..e19895f70 100644 --- a/src/components/AiDock.tsx +++ b/src/components/AiDock.tsx @@ -418,6 +418,19 @@ function describeToolCallQuery(input: unknown, rawArguments: string) { return queryTexts.join(', ') || readString(parsed.originalQuery) } +/** Tool results arrive as JSON strings on the wire; parse them for source extraction. */ +function parseToolResult(content: string | Array): unknown { + if (typeof content !== 'string') { + return content + } + + try { + return JSON.parse(content) + } catch { + return undefined + } +} + /** * Fold the AG-UI message list into the question/answer pairs the panel * renders: each user message opens a QA, and the assistant messages that @@ -456,23 +469,35 @@ function messagesToDisplayQAs( ) const segments = current.segments ?? [] + // A completed tool call carries its output inline while streaming, but a + // hydrated or replayed thread can represent it as a sibling tool-result + // part instead. Index those so both shapes yield sources. + const toolResultOutputs = new Map() + for (const part of message.parts) { + if (part.type === 'tool-result' && part.error === undefined) { + toolResultOutputs.set(part.toolCallId, parseToolResult(part.content)) + } + } + for (const part of message.parts) { if (part.type === 'text' && part.content.trim()) { segments.push({ type: 'text', content: part.content.trim() }) } if (part.type === 'tool-call') { + const output = part.output ?? toolResultOutputs.get(part.id) + segments.push({ type: 'tool-call', toolCall: { id: part.id, query: describeToolCallQuery(part.input, part.arguments), - isPending: part.output === undefined && part.state !== 'error', + isPending: output === undefined && part.state !== 'error', }, }) - if (part.output !== undefined) { - collectSourcesFromToolOutput(part.output, sources) + if (output !== undefined) { + collectSourcesFromToolOutput(output, sources) } } } @@ -532,11 +557,22 @@ function useAgentStudioChat(threadId: string) { }, body: JSON.stringify({ messageId, + agentId: AGENT_STUDIO_AGENT_ID, vote: reaction === 'upvote' ? 1 : 0, }), - }).catch(() => { - // Keep the optimistic reaction; feedback is best-effort. }) + .then((response) => { + if (!response.ok) { + throw new Error(`feedback rejected: ${response.status}`) + } + }) + .catch(() => { + // Unlock the buttons so the vote can be retried. + setReactions((current) => { + const { [messageId]: _failed, ...rest } = current + return rest + }) + }) }, [], ) From 00fa08890dd2a4beab853ae200ea1f589652d7df Mon Sep 17 00:00:00 2001 From: Dylan Tientcheu Date: Thu, 27 Aug 2026 21:43:20 +0200 Subject: [PATCH 5/8] Autolink bare URLs in AI answers The agent emits bare URLs in prose, and CommonMark only autolinks URLs wrapped in angle brackets. A markdown extension turns bare http(s) URLs in text nodes into links, leaving code spans and existing links alone, and long link labels now wrap cleanly inside the chat bubble. --- src/components/AiDock.tsx | 64 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/src/components/AiDock.tsx b/src/components/AiDock.tsx index e19895f70..b601de73c 100644 --- a/src/components/AiDock.tsx +++ b/src/components/AiDock.tsx @@ -35,6 +35,7 @@ import { } from '@tanstack/ai-react' import { streamingMarkdownExtension } from '@tanstack/markdown/extensions/streaming' import { Markdown as TanStackMarkdown } from '@tanstack/markdown/react' +import type { InlineNode, MarkdownExtension } from '@tanstack/markdown' import { useSearchContext } from '~/contexts/SearchContext' import { publicLibraries } from '~/libraries' import { frameworkOptions } from '~/libraries/frameworks' @@ -130,7 +131,7 @@ function AiMarkdownLink({ return ( {children} @@ -277,7 +278,66 @@ const aiMarkdownComponents = { ), } -const aiMarkdownExtensions = [streamingMarkdownExtension()] +// The agent often emits bare URLs in prose, and CommonMark only autolinks +// URLs wrapped in angle brackets. Turn bare http(s) URLs inside text nodes +// into links. Code spans and existing links are separate inline node types, +// so this transform can never touch them. +const BARE_URL_PATTERN = /https?:\/\/[^\s<>]+/g +const TRAILING_PUNCTUATION_PATTERN = /[.,;:!?)'"\]]+$/ + +function autolinkInlineNodes(nodes: Array): Array { + return nodes.flatMap((node): Array => { + if ( + node.type === 'strong' || + node.type === 'emphasis' || + node.type === 'strike' + ) { + return [{ ...node, children: autolinkInlineNodes(node.children) }] + } + + if (node.type !== 'text') { + return [node] + } + + const parts: Array = [] + let cursor = 0 + + for (const match of node.value.matchAll(BARE_URL_PATTERN)) { + const index = match.index ?? 0 + const url = match[0].replace(TRAILING_PUNCTUATION_PATTERN, '') + + if (index > cursor) { + parts.push({ type: 'text', value: node.value.slice(cursor, index) }) + } + parts.push({ + type: 'link', + href: url, + children: [{ type: 'text', value: url }], + }) + cursor = index + url.length + } + + if (parts.length === 0) { + return [node] + } + + if (cursor < node.value.length) { + parts.push({ type: 'text', value: node.value.slice(cursor) }) + } + + return parts + }) +} + +const autolinkBareUrlsExtension: MarkdownExtension = { + name: 'autolink-bare-urls', + transformInline: autolinkInlineNodes, +} + +const aiMarkdownExtensions = [ + streamingMarkdownExtension(), + autolinkBareUrlsExtension, +] type FeedbackReaction = 'upvote' | 'downvote' From d0261ace8724b8b404be762dbf9d409d7f8a84b4 Mon Sep 17 00:00:00 2001 From: Dylan Tientcheu Date: Wed, 16 Sep 2026 11:12:06 +0200 Subject: [PATCH 6/8] Address PR review: history reselect, reset errors, URL parens - Selecting the history item that is already live is a no-op. The chat client keeps its messages when `threadId` does not change, so restoring the saved transcript as a prefix duplicated every answer. - New Chat calls the SDK `clear()`. Changing `threadId` swaps the client and drops messages, but the error state survived and rendered under the empty conversation. `clear()` also cancels the in-flight stream, so the separate stop-then-clear path is gone. - Bare URL autolinking keeps balanced parentheses (GFM rule), so `/routes/(auth)` links intact while `(see https://x/a)` still drops the wrapper. The transform moves to its own module with a unit test. - `createThreadId` uses `crypto.randomUUID()` directly. --- src/components/AiDock.tsx | 146 +++++++------------- src/components/markdown/autolinkBareUrls.ts | 72 ++++++++++ tests/ai-dock-autolink.test.ts | 50 +++++++ 3 files changed, 172 insertions(+), 96 deletions(-) create mode 100644 src/components/markdown/autolinkBareUrls.ts create mode 100644 tests/ai-dock-autolink.test.ts diff --git a/src/components/AiDock.tsx b/src/components/AiDock.tsx index b601de73c..c7604a3ab 100644 --- a/src/components/AiDock.tsx +++ b/src/components/AiDock.tsx @@ -35,7 +35,7 @@ import { } from '@tanstack/ai-react' import { streamingMarkdownExtension } from '@tanstack/markdown/extensions/streaming' import { Markdown as TanStackMarkdown } from '@tanstack/markdown/react' -import type { InlineNode, MarkdownExtension } from '@tanstack/markdown' +import { autolinkBareUrlsExtension } from '~/components/markdown/autolinkBareUrls' import { useSearchContext } from '~/contexts/SearchContext' import { publicLibraries } from '~/libraries' import { frameworkOptions } from '~/libraries/frameworks' @@ -278,62 +278,6 @@ const aiMarkdownComponents = { ), } -// The agent often emits bare URLs in prose, and CommonMark only autolinks -// URLs wrapped in angle brackets. Turn bare http(s) URLs inside text nodes -// into links. Code spans and existing links are separate inline node types, -// so this transform can never touch them. -const BARE_URL_PATTERN = /https?:\/\/[^\s<>]+/g -const TRAILING_PUNCTUATION_PATTERN = /[.,;:!?)'"\]]+$/ - -function autolinkInlineNodes(nodes: Array): Array { - return nodes.flatMap((node): Array => { - if ( - node.type === 'strong' || - node.type === 'emphasis' || - node.type === 'strike' - ) { - return [{ ...node, children: autolinkInlineNodes(node.children) }] - } - - if (node.type !== 'text') { - return [node] - } - - const parts: Array = [] - let cursor = 0 - - for (const match of node.value.matchAll(BARE_URL_PATTERN)) { - const index = match.index ?? 0 - const url = match[0].replace(TRAILING_PUNCTUATION_PATTERN, '') - - if (index > cursor) { - parts.push({ type: 'text', value: node.value.slice(cursor, index) }) - } - parts.push({ - type: 'link', - href: url, - children: [{ type: 'text', value: url }], - }) - cursor = index + url.length - } - - if (parts.length === 0) { - return [node] - } - - if (cursor < node.value.length) { - parts.push({ type: 'text', value: node.value.slice(cursor) }) - } - - return parts - }) -} - -const autolinkBareUrlsExtension: MarkdownExtension = { - name: 'autolink-bare-urls', - transformInline: autolinkInlineNodes, -} - const aiMarkdownExtensions = [ streamingMarkdownExtension(), autolinkBareUrlsExtension, @@ -383,12 +327,35 @@ type ChatHistoryItem = { const CHAT_HISTORY_STORAGE_KEY = 'tanstack-ai-chat-history' const CHAT_HISTORY_LIMIT = 5 -function createThreadId() { - if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) { - return crypto.randomUUID() +/** + * Which Agent Studio thread the panel is on, plus the QAs restored from local + * history for that thread. Agent Studio persists conversations server-side + * keyed by `threadId`, so resuming only needs the id; the saved QAs render + * as the prefix of the transcript, ahead of whatever the live client holds. + */ +type ThreadSelection = { + threadId: string + savedConversation: Array +} + +function freshThread(): ThreadSelection { + return { threadId: crypto.randomUUID(), savedConversation: [] } +} + +/** + * Re-selecting the thread that is already live must be a no-op: the client + * keeps its messages when `threadId` is unchanged, so restoring the saved + * transcript as a prefix would duplicate every answer. + */ +function selectThread( + current: ThreadSelection, + item: ChatHistoryItem, +): ThreadSelection { + if (item.threadId === current.threadId) { + return current } - return `thread_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}` + return { threadId: item.threadId, savedConversation: item.conversation } } function messageText(message: UIMessage) { @@ -590,7 +557,7 @@ function useAgentStudioChat(threadId: string) { }), [], ) - const { messages, sendMessage, isLoading, error, stop } = useChat({ + const { messages, sendMessage, isLoading, error, stop, clear } = useChat({ connection, threadId, }) @@ -642,6 +609,9 @@ function useAgentStudioChat(threadId: string) { submitQuery: sendMessage, isBusy: isLoading, stopGeneration: stop, + // Cancels any in-flight stream and drops messages *and* the error, which + // would otherwise outlive a threadId change (the SDK only resets messages). + resetChat: clear, addFeedback, errorMessage: error?.message ?? null, } @@ -1571,12 +1541,14 @@ function AiChatPanel({ isDockMaximized?: boolean onToggleDockMaximized?: () => void }) { - const [threadId, setThreadId] = React.useState(createThreadId) + const [thread, setThread] = React.useState(freshThread) + const { threadId, savedConversation } = thread const { conversation, submitQuery, isBusy, stopGeneration, + resetChat, addFeedback, errorMessage, } = useAgentStudioChat(threadId) @@ -1590,8 +1562,6 @@ function AiChatPanel({ useSearchFilters() const scrollRef = React.useRef(null) const { items: historyItems, save: saveHistory } = useChatHistory() - const [selectedHistoryItem, setSelectedHistoryItem] = - React.useState(null) // Question waiting for the chat client of a freshly created thread. Changing // `threadId` recreates the client on the next render, so a "clear then ask" // flow (e.g. Ask AI from the search modal) must defer the submit. @@ -1603,13 +1573,13 @@ function AiChatPanel({ const lockedToBottom = React.useRef(true) const handledNewChatRequestId = React.useRef(newChatRequestId) const handledAiDockAskRequestId = React.useRef(0) - const displayConversation = React.useMemo>(() => { - if (!selectedHistoryItem) { - return conversation - } - - return [...selectedHistoryItem.conversation, ...conversation] - }, [conversation, selectedHistoryItem]) + const displayConversation = React.useMemo>( + () => + savedConversation.length === 0 + ? conversation + : [...savedConversation, ...conversation], + [conversation, savedConversation], + ) const hasConversation = displayConversation.length > 0 const activeThreadId = threadId @@ -1683,11 +1653,7 @@ function AiChatPanel({ return } - // Agent Studio persists the conversation server-side keyed by threadId, - // so adopting the stored id resumes the thread; the stored QAs are - // rendered as the prefix of the transcript. - setSelectedHistoryItem(item) - setThreadId(item.threadId) + setThread((current) => selectThread(current, item)) lockedToBottom.current = true }, [isBusy], @@ -1695,19 +1661,11 @@ function AiChatPanel({ const clearActiveChat = React.useCallback(() => { isSubmittingRef.current = false - setSelectedHistoryItem(null) - setThreadId(createThreadId()) + resetChat() + setThread(freshThread()) lockedToBottom.current = true onReset() - }, [onReset]) - - const startNewChat = React.useCallback(() => { - if (isBusy) { - stopGeneration() - } - - clearActiveChat() - }, [clearActiveChat, isBusy, stopGeneration]) + }, [onReset, resetChat]) React.useEffect(() => { if (handledNewChatRequestId.current === newChatRequestId) { @@ -1715,8 +1673,8 @@ function AiChatPanel({ } handledNewChatRequestId.current = newChatRequestId - startNewChat() - }, [newChatRequestId, startNewChat]) + clearActiveChat() + }, [newChatRequestId, clearActiveChat]) React.useEffect(() => { if (pendingQuestion === null || isBusy) { @@ -1836,7 +1794,7 @@ function AiChatPanel({ />
-
- - TanStack - - - - powered by - - Algolia - Algolia - -
+ + TanStack AI + {action} @@ -1735,10 +1714,35 @@ function AiChatPanel({ {isDock ? null : }
{isDock ? ( -
-
+
+ + + TanStack AI -
+ + + + + Algolia +
) : ( <> diff --git a/src/components/icons/AlgoliaIcon.tsx b/src/components/icons/AlgoliaIcon.tsx new file mode 100644 index 000000000..3ae4c2d4c --- /dev/null +++ b/src/components/icons/AlgoliaIcon.tsx @@ -0,0 +1,24 @@ +import * as React from 'react' + +export function AlgoliaIcon({ + className, + width = '1em', + height = '1em', + ...props +}: React.SVGProps) { + return ( + + + + ) +} From 157063ba9a552e8eff20c3c8e7a63e0376158ed6 Mon Sep 17 00:00:00 2001 From: Dylan Tientcheu Date: Thu, 17 Sep 2026 15:36:29 +0200 Subject: [PATCH 8/8] Use brand wordmark and full Algolia logo in the AI dock header The header now shows the TanStack AI wordmark (LibraryWordmark, linking to /ai/latest) next to the full Algolia logo. Message bubbles no longer carry a per-message header, since the dock header already identifies the assistant. --- src/components/AiDock.tsx | 74 ++++++++++------------------ src/components/icons/AlgoliaIcon.tsx | 24 --------- 2 files changed, 25 insertions(+), 73 deletions(-) delete mode 100644 src/components/icons/AlgoliaIcon.tsx diff --git a/src/components/AiDock.tsx b/src/components/AiDock.tsx index d2353a980..1d51f641c 100644 --- a/src/components/AiDock.tsx +++ b/src/components/AiDock.tsx @@ -36,9 +36,9 @@ import { import { streamingMarkdownExtension } from '@tanstack/markdown/extensions/streaming' import { Markdown as TanStackMarkdown } from '@tanstack/markdown/react' import { autolinkBareUrlsExtension } from '~/components/markdown/autolinkBareUrls' -import { AlgoliaIcon } from '~/components/icons/AlgoliaIcon' +import { LibraryWordmark } from '~/components/LibraryWordmark' import { useSearchContext } from '~/contexts/SearchContext' -import { publicLibraries } from '~/libraries' +import { ai, publicLibraries } from '~/libraries' import { frameworkOptions } from '~/libraries/frameworks' import { capitalize } from '~/utils/utils' import { CodeBlock } from '~/components/markdown/CodeBlock' @@ -1164,32 +1164,6 @@ function MessageActionButton({ ) } -function AIMessageHeader({ action }: { action?: React.ReactNode }) { - return ( -
-
-
- TanStack - -
- - TanStack AI - -
- {action} -
- ) -} - function getAiSuggestions(selectedLibrary: string) { if (selectedLibrary === 'query') { return [ @@ -1255,7 +1229,6 @@ function AiWelcome({ return (
-

-
{hasAnswerError ? (

{isDock ? (

- - - - TanStack AI - + + + - - - Algolia - + Algolia + Algolia +
) : ( <> diff --git a/src/components/icons/AlgoliaIcon.tsx b/src/components/icons/AlgoliaIcon.tsx deleted file mode 100644 index 3ae4c2d4c..000000000 --- a/src/components/icons/AlgoliaIcon.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from 'react' - -export function AlgoliaIcon({ - className, - width = '1em', - height = '1em', - ...props -}: React.SVGProps) { - return ( - - - - ) -}