diff --git a/README.md b/README.md index f1175eb..f811353 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ export default function Chat() { >
- +
diff --git a/next.config.ts b/next.config.ts index e9ffa30..e23a120 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,20 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + // Run ESLint separately via `npm run lint` (avoids deprecated next lint) + eslint: { + ignoreDuringBuilds: true, + }, + // Stub optional peer deps from @standard-community/standard-json + webpack: (config) => { + config.resolve.alias = { + ...config.resolve.alias, + effect: false, + sury: false, + "@valibot/to-json-schema": false, + }; + return config; + }, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index 070d813..b08fec2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,7 @@ "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-tooltip": "1.2.8", "@tailwindcss/oxide": "^4.1.18", - "@tambo-ai/react": "^0.69.1", - "@tambo-ai/typescript-sdk": "^0.84.0", + "@tambo-ai/react": "^1.0.0-rc.4", "@tiptap/extension-document": "^3.12.1", "@tiptap/extension-hard-break": "^3.16.0", "@tiptap/extension-mention": "^3.12.1", @@ -62,6 +61,15 @@ "typescript": "5.9.3" } }, + "node_modules/@ag-ui/core": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/@ag-ui/core/-/core-0.0.43.tgz", + "integrity": "sha512-/T7kKwPAhtriFFiv3YxZ0yAzMHoY8a8I5UKzhPzwW934h92c6RJ54N93yb9PAqdX3XjCPId0C5gIBHb6QbeR3Q==", + "dependencies": { + "rxjs": "7.8.1", + "zod": "^3.22.4" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -311,9 +319,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -502,13 +510,12 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -517,24 +524,19 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz", + "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -543,11 +545,10 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -555,7 +556,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -567,36 +568,30 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", + "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^0.12.0", "levn": "^0.4.1" }, "engines": { @@ -642,9 +637,9 @@ "license": "MIT" }, "node_modules/@hono/node-server": { - "version": "1.19.7", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.7.tgz", - "integrity": "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1202,12 +1197,12 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", - "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.7", + "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -1215,14 +1210,15 @@ "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "jose": "^6.1.1", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.0" + "zod-to-json-schema": "^3.25.1" }, "engines": { "node": ">=18" @@ -1274,9 +1270,9 @@ } }, "node_modules/@next/env": { - "version": "15.5.10", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.10.tgz", - "integrity": "sha512-plg+9A/KoZcTS26fe15LHg+QxReTazrIOoKKUC3Uz4leGGeNPgLHdevVraAAOX0snnUs3WkRx3eUQpj9mreG6A==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.12.tgz", + "integrity": "sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1290,9 +1286,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz", - "integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.12.tgz", + "integrity": "sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==", "cpu": [ "arm64" ], @@ -1306,9 +1302,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz", - "integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.12.tgz", + "integrity": "sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==", "cpu": [ "x64" ], @@ -1322,9 +1318,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz", - "integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.12.tgz", + "integrity": "sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==", "cpu": [ "arm64" ], @@ -1338,9 +1334,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz", - "integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.12.tgz", + "integrity": "sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==", "cpu": [ "arm64" ], @@ -1354,9 +1350,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz", - "integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.12.tgz", + "integrity": "sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==", "cpu": [ "x64" ], @@ -1370,9 +1366,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz", - "integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.12.tgz", + "integrity": "sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==", "cpu": [ "x64" ], @@ -1386,9 +1382,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz", - "integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.12.tgz", + "integrity": "sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==", "cpu": [ "arm64" ], @@ -1402,9 +1398,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz", - "integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.12.tgz", + "integrity": "sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==", "cpu": [ "x64" ], @@ -3335,30 +3331,33 @@ } }, "node_modules/@tambo-ai/react": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@tambo-ai/react/-/react-0.69.1.tgz", - "integrity": "sha512-YlBgRLSywFmpO+QgQ1KmjZkgFvfYf+bTZW2AK2LoqjoMCSyI0BO/w+uK/gDOxgutubB9tJPf0kmqfF+FynbkBw==", + "version": "1.0.0-rc.4", + "resolved": "https://registry.npmjs.org/@tambo-ai/react/-/react-1.0.0-rc.4.tgz", + "integrity": "sha512-tvVhw9i2fujj/NWOOtfXLiT3XlXoA02SaLLBFDlKJl2KBMHKLQQpzU0LOPguLj+7hZKeoZsmYFIH9JosPg5IGg==", "license": "MIT", "dependencies": { + "@ag-ui/core": "^0.0.43", + "@modelcontextprotocol/sdk": "^1.26.0", "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.1.0", - "@tambo-ai/typescript-sdk": "^0.80.0", + "@tambo-ai/typescript-sdk": "^0.92.0", "@tanstack/react-query": "^5.90.16", "fast-equals": "^5.3.3", + "fast-json-patch": "^3.1.1", "partial-json": "^0.1.7", "react-fast-compare": "^3.2.2", "react-media-recorder": "^1.7.2", - "ts-essentials": "^10.1.1", "ts-node": "^10.9.2", + "type-fest": "^5.4.3", "use-debounce": "^10.0.6" }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.1", + "@modelcontextprotocol/sdk": "^1.26.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", - "zod": "^3.25.76 || ~4.0.0", + "zod": "^3.25.76 || ^4", "zod-to-json-schema": "^3.25.1" }, "peerDependenciesMeta": { @@ -3370,15 +3369,6 @@ } } }, - "node_modules/@tambo-ai/react/node_modules/@tambo-ai/typescript-sdk": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@tambo-ai/typescript-sdk/-/typescript-sdk-0.80.0.tgz", - "integrity": "sha512-bH/+/VYD7HD6xj0+UDihn13psyKmxonpOrNbqMutokS7B7LvvixqPkIjwB+rAjg7jJ5VtLBH8RnY1H7qzs4qvg==", - "license": "Apache-2.0", - "bin": { - "tambo-ai-typescript-sdk": "bin/cli" - } - }, "node_modules/@tambo-ai/react/node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -3423,18 +3413,18 @@ } }, "node_modules/@tambo-ai/typescript-sdk": { - "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@tambo-ai/typescript-sdk/-/typescript-sdk-0.84.0.tgz", - "integrity": "sha512-1Quu++jrnJcCdnfZPvEYpys3/ZThfZiQO7gxA/CTc+y2jVkk+nL6jdohFsaNDswRJ/f4KLbiZ85PfwP0jYMkFg==", + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@tambo-ai/typescript-sdk/-/typescript-sdk-0.92.0.tgz", + "integrity": "sha512-nLJeV3S5Szca0X0y+A57JgVKTPuRjrGgSb5TI4l95E2enRB8wI4m/bUaZl89e7jajiB9cykPelPucz/AzXpnyQ==", "license": "Apache-2.0", "bin": { "tambo-ai-typescript-sdk": "bin/cli" } }, "node_modules/@tanstack/query-core": { - "version": "5.90.19", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.19.tgz", - "integrity": "sha512-GLW5sjPVIvH491VV1ufddnfldyVB+teCnpPIvweEfkpRx7CfUmUGhoh9cdcUKBh/KwVxk22aNEDxeTsvmyB/WA==", + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", "license": "MIT", "funding": { "type": "github", @@ -3442,12 +3432,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.19", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.19.tgz", - "integrity": "sha512-qTZRZ4QyTzQc+M0IzrbKHxSeISUmRB3RPGmao5bT+sI6ayxSRhn0FXEnT5Hg3as8SBFcRosrXXRFB+yAcxVxJQ==", + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", + "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.90.19" + "@tanstack/query-core": "5.90.20" }, "funding": { "type": "github", @@ -3458,9 +3448,9 @@ } }, "node_modules/@tiptap/core": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.16.0.tgz", - "integrity": "sha512-XegRaNuoQ/guzBQU2xHxOwFXXrtoXW9tiyXDhssSqylvZmBVSlRIPNHA6ArkHBKm6ehLf6+6Y9fF3uky1yCXYQ==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.19.0.tgz", + "integrity": "sha512-bpqELwPW+DG8gWiD8iiFtSl4vIBooG5uVJod92Qxn3rA9nFatyXRr4kNbMJmOZ66ezUvmCjXVe/5/G4i5cyzKA==", "license": "MIT", "peer": true, "funding": { @@ -3468,7 +3458,7 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^3.16.0" + "@tiptap/pm": "^3.19.0" } }, "node_modules/@tiptap/extension-bubble-menu": { @@ -3519,16 +3509,16 @@ } }, "node_modules/@tiptap/extension-hard-break": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.16.0.tgz", - "integrity": "sha512-nwUTixlHYo9V1lfOYsRi2JiAYCRC7pObB3Kt7rEeMxB3XmcRcSpHtxYs6r+TvifsLFys8RG5wOFXIV/YXZHcDg==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.19.0.tgz", + "integrity": "sha512-lAmQraYhPS5hafvCl74xDB5+bLuNwBKIEsVoim35I0sDJj5nTrfhaZgMJ91VamMvT+6FF5f1dvBlxBxAWa8jew==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.16.0" + "@tiptap/core": "^3.19.0" } }, "node_modules/@tiptap/extension-mention": { @@ -3601,9 +3591,9 @@ } }, "node_modules/@tiptap/pm": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.16.0.tgz", - "integrity": "sha512-FMxZ6Tc5ONKa/EByDV8lswct6YW2lF/wn11zqXmrfBZhdG7UQPTijpSwb6TCqaO5GOHmixaIaDPj+zimUREHQA==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.19.0.tgz", + "integrity": "sha512-789zcnM4a8OWzvbD2DL31d0wbSm9BVeO/R7PLQwLIGysDI3qzrcclyZ8yhqOEVuvPitRRwYLq+mY14jz7kY4cw==", "license": "MIT", "peer": true, "dependencies": { @@ -3673,9 +3663,9 @@ } }, "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", "license": "MIT" }, "node_modules/@tsconfig/node12": { @@ -4406,9 +4396,9 @@ "license": "MIT" }, "node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, "license": "MIT", "dependencies": { @@ -4650,12 +4640,12 @@ } }, "node_modules/automation-events": { - "version": "7.1.13", - "resolved": "https://registry.npmjs.org/automation-events/-/automation-events-7.1.13.tgz", - "integrity": "sha512-1Hay5TQPzxsskSqPTH3YXyzE9Iirz82zZDse2vr3+kOR7Sc7om17qIEPsESchlNX0EgKxANwR40i2g/O3GM1Tw==", + "version": "7.1.15", + "resolved": "https://registry.npmjs.org/automation-events/-/automation-events-7.1.15.tgz", + "integrity": "sha512-NsHJlve3twcgs8IyP4iEYph7Fzpnh6klN7G5LahwvypakBjFbsiGHJxrqTmeHKREdu/Tx6oZboqNI0tD4MnFlA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", + "@babel/runtime": "^7.28.6", "tslib": "^2.8.1" }, "engines": { @@ -4663,9 +4653,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "version": "10.4.24", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", + "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", "dev": true, "funding": [ { @@ -4684,7 +4674,7 @@ "license": "MIT", "dependencies": { "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", + "caniuse-lite": "^1.0.30001766", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -4759,9 +4749,9 @@ } }, "node_modules/body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -4770,7 +4760,7 @@ "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", + "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" }, @@ -4783,11 +4773,10 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4807,15 +4796,15 @@ } }, "node_modules/broker-factory": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.10.tgz", - "integrity": "sha512-BzqK5GYFhvVFvO13uzPN0SCiOsOQuhMUbsGvTXDJMA2/N4GvIlFdxEuueE+60Zk841bBU5G3+fl2cqYEo0wgGg==", + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.13.tgz", + "integrity": "sha512-H2VALe31mEtO/SRcNp4cUU5BAm1biwhc/JaF77AigUuni/1YT0FLCJfbUxwIEs9y6Kssjk2fmXgf+Y9ALvmKlw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "fast-unique-numbers": "^9.0.24", + "@babel/runtime": "^7.28.6", + "fast-unique-numbers": "^9.0.26", "tslib": "^2.8.1", - "worker-factory": "^7.0.46" + "worker-factory": "^7.0.48" } }, "node_modules/browserslist": { @@ -4916,9 +4905,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001765", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz", - "integrity": "sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "funding": [ { "type": "opencollective", @@ -5121,9 +5110,9 @@ } }, "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", "engines": { @@ -5199,9 +5188,9 @@ } }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -5209,6 +5198,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/create-require": { @@ -5624,14 +5617,14 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -5868,32 +5861,32 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", + "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", "dev": true, - "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", + "@eslint/config-array": "^0.19.2", + "@eslint/config-helpers": "^0.1.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.22.0", + "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -6209,11 +6202,10 @@ } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -6239,15 +6231,14 @@ } }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6273,7 +6264,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -6389,10 +6379,13 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, "engines": { "node": ">= 16" }, @@ -6425,38 +6418,38 @@ } }, "node_modules/extendable-media-recorder-wav-encoder": { - "version": "7.0.132", - "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder/-/extendable-media-recorder-wav-encoder-7.0.132.tgz", - "integrity": "sha512-i+DWP7eDBP+V/jVzmpVMET6XsQPTWcW3vmYZP8lGShnWx5vkwB+mdgK2kAwdu9i9r0IVMFVQETsO1nGYOW1cwg==", + "version": "7.0.136", + "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder/-/extendable-media-recorder-wav-encoder-7.0.136.tgz", + "integrity": "sha512-K4ZcMSbsTlI7gv92K+UY+czvk37PIPSWLqp5NF3pNDF9S6iffCbNJnl+k1zce02P6tmOAf+ZlzVRRjxSXNEoog==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "extendable-media-recorder-wav-encoder-broker": "^7.0.122", - "extendable-media-recorder-wav-encoder-worker": "^8.0.119", + "@babel/runtime": "^7.28.6", + "extendable-media-recorder-wav-encoder-broker": "^7.0.125", + "extendable-media-recorder-wav-encoder-worker": "^8.0.121", "tslib": "^2.8.1" } }, "node_modules/extendable-media-recorder-wav-encoder-broker": { - "version": "7.0.122", - "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-broker/-/extendable-media-recorder-wav-encoder-broker-7.0.122.tgz", - "integrity": "sha512-vupumv22Zb7WsUJ/K16SYrRTs/aTPc+48Smgd0Cq16IzH8eR7BiScp7ciFva04uZlWZtCtnYz8uLGJWrN+rlyA==", + "version": "7.0.125", + "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-broker/-/extendable-media-recorder-wav-encoder-broker-7.0.125.tgz", + "integrity": "sha512-HVmznJvyG+eFZJRYLd9h3OF0oNNIGEmEHAP4IQ0Y5gwxJcmrFhVmGB4hLi1GT/jNM8aSoCxIePVkCX+5tuGvpA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "broker-factory": "^3.1.10", - "extendable-media-recorder-wav-encoder-worker": "^8.0.119", + "@babel/runtime": "^7.28.6", + "broker-factory": "^3.1.13", + "extendable-media-recorder-wav-encoder-worker": "^8.0.121", "tslib": "^2.8.1" } }, "node_modules/extendable-media-recorder-wav-encoder-worker": { - "version": "8.0.119", - "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-worker/-/extendable-media-recorder-wav-encoder-worker-8.0.119.tgz", - "integrity": "sha512-7RRdga3SvQ5k/KLuFPxZJl3wBe9vhanBVZD/2+STwHxfHkbbh3VLKv3rjtH7h4F2OYOEGCLLKKcfI5LFWiXA7g==", + "version": "8.0.121", + "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-worker/-/extendable-media-recorder-wav-encoder-worker-8.0.121.tgz", + "integrity": "sha512-UBBgWkyE9fpCLDdrWdTZM56FkImAljpUuxr6+y9W6LHvY7XWhkZP+yO5uZUUquS5IpsBlY2uKOWpKwiLdo3FOg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", + "@babel/runtime": "^7.28.6", "tslib": "^2.8.1", - "worker-factory": "^7.0.46" + "worker-factory": "^7.0.48" } }, "node_modules/fast-deep-equal": { @@ -6503,6 +6496,12 @@ "node": ">= 6" } }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6516,12 +6515,12 @@ "dev": true }, "node_modules/fast-unique-numbers": { - "version": "9.0.24", - "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.24.tgz", - "integrity": "sha512-Dv0BYn4waOWse94j16rsZ5w/0zoaCa74O3q6IZjMqaXbtT92Q+Sb6pPk+phGzD8Xh+nueQmSRI3tSCaHKidzKw==", + "version": "9.0.26", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.26.tgz", + "integrity": "sha512-3Mtq8p1zQinjGyWfKeuBunbuFoixG72AUkk4VvzbX4ykCW9Q4FzRaNyIlfQhUjnKw2ARVP+/CKnoyr6wfHftig==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", + "@babel/runtime": "^7.28.6", "tslib": "^2.8.1" }, "engines": { @@ -6674,13 +6673,13 @@ } }, "node_modules/framer-motion": { - "version": "12.29.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.29.0.tgz", - "integrity": "sha512-1gEFGXHYV2BD42ZPTFmSU9buehppU+bCuOnHU0AD18DKh9j4DuTx47MvqY5ax+NNWRtK32qIcJf1UxKo1WwjWg==", + "version": "12.34.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.0.tgz", + "integrity": "sha512-+/H49owhzkzQyxtn7nZeF4kdH++I2FWrESQ184Zbcw5cEqNHYkE5yxWxcTLSj5lNx3NWdbIRy5FHqUvetD8FWg==", "license": "MIT", "dependencies": { - "motion-dom": "^12.29.0", - "motion-utils": "^12.27.2", + "motion-dom": "^12.34.0", + "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { @@ -7168,11 +7167,10 @@ } }, "node_modules/hono": { - "version": "4.11.7", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", - "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -7234,9 +7232,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -7331,6 +7329,15 @@ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -9432,18 +9439,18 @@ } }, "node_modules/motion-dom": { - "version": "12.29.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.29.0.tgz", - "integrity": "sha512-3eiz9bb32yvY8Q6XNM4AwkSOBPgU//EIKTZwsSWgA9uzbPBhZJeScCVcBuwwYVqhfamewpv7ZNmVKTGp5qnzkA==", + "version": "12.34.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.0.tgz", + "integrity": "sha512-Lql3NuEcScRDxTAO6GgUsRHBZOWI/3fnMlkMcH5NftzcN37zJta+bpbMAV9px4Nj057TuvRooMK7QrzMCgtz6Q==", "license": "MIT", "dependencies": { - "motion-utils": "^12.27.2" + "motion-utils": "^12.29.2" } }, "node_modules/motion-utils": { - "version": "12.27.2", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.27.2.tgz", - "integrity": "sha512-B55gcoL85Mcdt2IEStY5EEAsrMSVE2sI14xQ/uAdPL+mfQxhKKFaEag9JmfxedJOR4vZpBGoPeC/Gm13I/4g5Q==", + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", "license": "MIT" }, "node_modules/ms": { @@ -9511,12 +9518,12 @@ } }, "node_modules/next": { - "version": "15.5.10", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.10.tgz", - "integrity": "sha512-r0X65PNwyDDyOrWNKpQoZvOatw7BcsTPRKdwEqtc9cj3wv7mbBIk9tKed4klRaFXJdX0rugpuMTHslDrAU1bBg==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", + "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", "license": "MIT", "dependencies": { - "@next/env": "15.5.10", + "@next/env": "15.5.12", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -9529,14 +9536,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.7", - "@next/swc-darwin-x64": "15.5.7", - "@next/swc-linux-arm64-gnu": "15.5.7", - "@next/swc-linux-arm64-musl": "15.5.7", - "@next/swc-linux-x64-gnu": "15.5.7", - "@next/swc-linux-x64-musl": "15.5.7", - "@next/swc-win32-arm64-msvc": "15.5.7", - "@next/swc-win32-x64-msvc": "15.5.7", + "@next/swc-darwin-arm64": "15.5.12", + "@next/swc-darwin-x64": "15.5.12", + "@next/swc-linux-arm64-gnu": "15.5.12", + "@next/swc-linux-arm64-musl": "15.5.12", + "@next/swc-linux-x64-gnu": "15.5.12", + "@next/swc-linux-x64-musl": "15.5.12", + "@next/swc-win32-arm64-msvc": "15.5.12", + "@next/swc-win32-x64-msvc": "15.5.12", "sharp": "^0.34.3" }, "peerDependencies": { @@ -10844,9 +10851,9 @@ } }, "node_modules/remend": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remend/-/remend-1.1.0.tgz", - "integrity": "sha512-JENGyuIhTwzUfCarW43X4r9cehoqTo9QyYxfNDZSud2AmqeuWjZ5pfybasTa4q0dxTJAj5m8NB+wR+YueAFpxQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remend/-/remend-1.2.0.tgz", + "integrity": "sha512-NbKrdWweTRuByPYErzQCNpNtsR9M1QQ0hK2UzmnmlSaEqHnkQ5Korlyi8KpdbOJ0rImJfRy4EAY0uDxYnL9Plw==", "license": "Apache-2.0" }, "node_modules/require-from-string": { @@ -11006,6 +11013,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/rxjs-interop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/rxjs-interop/-/rxjs-interop-2.0.0.tgz", @@ -11090,31 +11106,35 @@ } }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -11124,6 +11144,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/set-function-length": { @@ -11420,9 +11444,9 @@ } }, "node_modules/streamdown": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/streamdown/-/streamdown-2.1.0.tgz", - "integrity": "sha512-u9gWd0AmjKg1d+74P44XaPlGrMeC21oDOSIhjGNEYMAttDMzCzlJO6lpTyJ9JkSinQQF65YcK4eOd3q9iTvULw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/streamdown/-/streamdown-2.2.0.tgz", + "integrity": "sha512-Y51o1I/sjpAy4Yn7j7R4TbUl9gcUZ7BTrHS+68IhrUBoYpNQZ28z06vww1MBFu4mSwvgF8xQIxIH2b9S9IHDyQ==", "license": "Apache-2.0", "dependencies": { "clsx": "^2.1.1", @@ -11435,10 +11459,11 @@ "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", - "remend": "1.1.0", + "remend": "1.2.0", "tailwind-merge": "^3.4.0", "unified": "^11.0.5", - "unist-util-visit": "^5.0.0" + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" @@ -11455,9 +11480,9 @@ } }, "node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", "dev": true, "license": "MIT", "dependencies": { @@ -11670,12 +11695,12 @@ } }, "node_modules/subscribable-things": { - "version": "2.1.55", - "resolved": "https://registry.npmjs.org/subscribable-things/-/subscribable-things-2.1.55.tgz", - "integrity": "sha512-WBx7R/NJYPOGX+cRpruSTFOYsMWOKBx+4cRKf0IowVXFGNDb2dF+215rwKtwk4eAa+QAv4HyTjL8jj/9Ks05pw==", + "version": "2.1.57", + "resolved": "https://registry.npmjs.org/subscribable-things/-/subscribable-things-2.1.57.tgz", + "integrity": "sha512-Ebcu2SJUntGnfJlTKc5jIGcDbuev4Ys2bRstzl5DUyzjWTZV9ymONZ0x8kEiN8NtnDlVuFe40EB8t3XvH8SWkw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", + "@babel/runtime": "^7.28.6", "rxjs-interop": "^2.0.0", "tslib": "^2.8.1" } @@ -11704,6 +11729,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tailwind-merge": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", @@ -11853,20 +11890,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-essentials": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.1.1.tgz", - "integrity": "sha512-4aTB7KLHKmUvkjNj8V+EdnmuVTiECzn3K+zIbRthumvHu+j44x3w63xpfs0JL3NGIzGXqoQ7AV591xHO+XrOTw==", - "license": "MIT", - "peerDependencies": { - "typescript": ">=4.5.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -11896,6 +11919,21 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", + "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -12457,13 +12495,13 @@ } }, "node_modules/worker-factory": { - "version": "7.0.46", - "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.46.tgz", - "integrity": "sha512-Sr1hq2FMgNa04UVhYQacsw+i58BtMimzDb4+CqYphZ97OfefRpURu0UZ+JxMr/H36VVJBfuVkxTK7MytsanC3w==", + "version": "7.0.48", + "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.48.tgz", + "integrity": "sha512-CGmBy3tJvpBPjUvb0t4PrpKubUsfkI1Ohg0/GGFU2RvA9j/tiVYwKU8O7yu7gH06YtzbeJLzdUR29lmZKn5pag==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "fast-unique-numbers": "^9.0.24", + "@babel/runtime": "^7.28.6", + "fast-unique-numbers": "^9.0.26", "tslib": "^2.8.1" } }, diff --git a/package.json b/package.json index 7daae7e..c9ba86a 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,7 @@ "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-tooltip": "1.2.8", "@tailwindcss/oxide": "^4.1.18", - "@tambo-ai/react": "^0.69.1", - "@tambo-ai/typescript-sdk": "^0.84.0", + "@tambo-ai/react": "^1.0.0-rc.4", "@tiptap/extension-document": "^3.12.1", "@tiptap/extension-hard-break": "^3.16.0", "@tiptap/extension-mention": "^3.12.1", diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index 3954833..949f5c6 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -4,70 +4,27 @@ import { MessageThreadFull } from "@/components/tambo/message-thread-full"; import ComponentsCanvas from "@/components/ui/components-canvas"; import { InteractableCanvasDetails } from "@/components/ui/interactable-canvas-details"; import { InteractableTabs } from "@/components/ui/interactable-tabs"; +import { useAnonymousUserKey } from "@/lib/use-anonymous-user-key"; import { components, tools } from "@/lib/tambo"; import { TamboProvider } from "@tambo-ai/react"; import { TamboMcpProvider } from "@tambo-ai/react/mcp"; -import { useSyncExternalStore } from "react"; -const STORAGE_KEY = "tambo-demo-context-key"; - -function getContextKey(): string { - let key = localStorage.getItem(STORAGE_KEY); - if (!key) { - key = crypto.randomUUID(); - localStorage.setItem(STORAGE_KEY, key); - } - return key; -} - -function subscribe(callback: () => void): () => void { - window.addEventListener("storage", callback); - return () => window.removeEventListener("storage", callback); -} - -/** - * Gets or creates a unique context key for thread isolation. - * - * NOTE: For production, use `userToken` prop instead of `contextKey`. - * The userToken integrates with your auth provider (e.g., Better Auth, Clerk) - * for proper user isolation with token refresh handling. - * - * Example: - * const userToken = useUserToken(); // from your auth provider - * - */ -function useContextKey(): string | null { - return useSyncExternalStore(subscribe, getContextKey, () => null); -} - -/** - * Home page component that renders the Tambo chat interface. - * - * @remarks - * The `NEXT_PUBLIC_TAMBO_URL` environment variable specifies the URL of the Tambo server. - * You do not need to set it if you are using the default Tambo server. - * It is only required if you are running the API server locally. - * - * @see {@link https://github.com/tambo-ai/tambo/blob/main/CONTRIBUTING.md} for instructions on running the API server locally. - */ export default function Home() { const mcpServers = useMcpServers(); - const contextKey = useContextKey(); + const userKey = useAnonymousUserKey(); - // Wait for contextKey to be loaded from localStorage - if (!contextKey) { - return null; - } + // You can customize default suggestions via MessageThreadFull internals return (
diff --git a/src/app/globals.css b/src/app/globals.css index 6136a16..f1afe9b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -49,6 +49,16 @@ body { "Segoe UI Emoji"; } +/* TipTap editor placeholder - required for @tiptap/extension-placeholder to display */ +.tiptap p.is-editor-empty:first-child::before { + content: attr(data-placeholder); + float: left; + color: hsl(var(--muted-foreground)); + opacity: 0.5; + pointer-events: none; + height: 0; +} + @layer base { :root { --card: 0 0% 100%; diff --git a/src/components/tambo/dictation-button.tsx b/src/components/tambo/dictation-button.tsx index 4ba0b46..1c2e37e 100644 --- a/src/components/tambo/dictation-button.tsx +++ b/src/components/tambo/dictation-button.tsx @@ -1,4 +1,4 @@ -import { Tooltip } from "@/components/tambo/suggestions-tooltip"; +import { Tooltip } from "@/components/tambo/message-suggestions"; import { useTamboThreadInput, useTamboVoice } from "@tambo-ai/react"; import { Loader2Icon, Mic, Square } from "lucide-react"; import React, { useEffect, useRef } from "react"; @@ -15,7 +15,7 @@ export default function DictationButton() { transcript, transcriptionError, } = useTamboVoice(); - const { value, setValue } = useTamboThreadInput(); + const { setValue } = useTamboThreadInput(); const lastProcessedTranscriptRef = useRef(""); const handleStartRecording = () => { @@ -30,9 +30,9 @@ export default function DictationButton() { useEffect(() => { if (transcript && transcript !== lastProcessedTranscriptRef.current) { lastProcessedTranscriptRef.current = transcript; - setValue(value + " " + transcript); + setValue((prev) => prev + " " + transcript); } - }, [transcript, value, setValue]); + }, [transcript, setValue]); if (isTranscribing) { return ( diff --git a/src/components/tambo/graph.tsx b/src/components/tambo/graph.tsx index 6910d41..0cc148b 100644 --- a/src/components/tambo/graph.tsx +++ b/src/components/tambo/graph.tsx @@ -196,15 +196,12 @@ export const Graph = React.forwardRef( { className, variant, size, data, title, showLegend = true, ...props }, ref, ) => { - // Use larger size for pie charts by default to give them more room - const effectiveSize = size ?? (data?.type === "pie" ? "lg" : "default"); - // If no data received yet, show loading if (!data) { return (
@@ -235,7 +232,7 @@ export const Graph = React.forwardRef( return (
@@ -260,7 +257,7 @@ export const Graph = React.forwardRef( return (
@@ -468,10 +465,10 @@ export const Graph = React.forwardRef( }; return ( - +
diff --git a/src/components/tambo/mcp-components.tsx b/src/components/tambo/mcp-components.tsx index e84e9e7..dff03dc 100644 --- a/src/components/tambo/mcp-components.tsx +++ b/src/components/tambo/mcp-components.tsx @@ -3,7 +3,7 @@ import { Tooltip, TooltipProvider, -} from "@/components/tambo/suggestions-tooltip"; +} from "@/components/tambo/message-suggestions"; import { cn } from "@/lib/utils"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import { @@ -11,9 +11,66 @@ import { useTamboMcpPromptList, useTamboMcpResourceList, } from "@tambo-ai/react/mcp"; -import { AtSign, FileText, Search } from "lucide-react"; +import { AlertCircle, AtSign, FileText, Search } from "lucide-react"; import * as React from "react"; +/** + * Represents a single message content item from an MCP prompt. + */ +interface PromptMessageContent { + type?: string; + text?: string; +} + +/** + * Represents a single message from an MCP prompt. + */ +interface PromptMessage { + content?: PromptMessageContent; +} + +/** + * Validates that prompt data has a valid messages array structure. + * @param promptData - The prompt data to validate + * @returns true if the prompt data has valid messages, false otherwise + */ +function isValidPromptData( + promptData: unknown, +): promptData is { messages: PromptMessage[] } { + if (!promptData || typeof promptData !== "object") { + return false; + } + + const data = promptData as { messages?: unknown }; + if (!Array.isArray(data.messages)) { + return false; + } + + return true; +} + +/** + * Safely extracts text content from prompt messages. + * Handles malformed or missing content gracefully. + * @param messages - Array of prompt messages + * @returns Extracted text content joined by newlines + */ +function extractPromptText(messages: PromptMessage[]): string { + return messages + .map((msg) => { + // Safely access nested properties + if ( + msg?.content?.type === "text" && + typeof msg.content.text === "string" + ) { + return msg.content.text; + } + return ""; + }) + .filter(Boolean) + .join("\n"); +} + /** * Props for the McpPromptButton component. */ @@ -45,21 +102,32 @@ export const McpPromptButton = React.forwardRef< const [selectedPromptName, setSelectedPromptName] = React.useState< string | null >(null); - const { data: promptData } = useTamboMcpPrompt(selectedPromptName ?? ""); + const [promptError, setPromptError] = React.useState(null); + const { data: promptData, error: fetchError } = useTamboMcpPrompt( + selectedPromptName ?? "", + ); - // When prompt data is fetched, insert it into the input + // When prompt data is fetched, validate and insert it into the input React.useEffect(() => { - if (promptData && selectedPromptName) { - // Extract the text from the prompt messages - const promptText = promptData.messages - .map((msg) => { - if (msg.content.type === "text") { - return msg.content.text; - } - return ""; - }) - .filter(Boolean) - .join("\n"); + if (selectedPromptName && promptData) { + // Validate prompt data structure + if (!isValidPromptData(promptData)) { + setPromptError("Invalid prompt format received"); + setSelectedPromptName(null); + return; + } + + // Extract text with safe access + const promptText = extractPromptText(promptData.messages); + + if (!promptText) { + setPromptError("Prompt contains no text content"); + setSelectedPromptName(null); + return; + } + + // Clear any previous errors + setPromptError(null); // Insert the prompt text, appending to existing value if any const newValue = value ? `${value}\n\n${promptText}` : promptText; @@ -70,6 +138,22 @@ export const McpPromptButton = React.forwardRef< } }, [promptData, selectedPromptName, onInsertText, value]); + // Handle fetch errors + React.useEffect(() => { + if (fetchError) { + setPromptError("Failed to load prompt"); + setSelectedPromptName(null); + } + }, [fetchError]); + + // Clear error after a delay + React.useEffect(() => { + if (promptError) { + const timer = setTimeout(() => setPromptError(null), 3000); + return () => clearTimeout(timer); + } + }, [promptError]); + // Only show button if prompts are available (hide during loading and when no prompts) if (!promptList || promptList.length === 0) { return null; @@ -83,21 +167,31 @@ export const McpPromptButton = React.forwardRef< return ( diff --git a/src/components/tambo/message-generation-stage.tsx b/src/components/tambo/message-generation-stage.tsx index 8887cc8..9dd83e3 100644 --- a/src/components/tambo/message-generation-stage.tsx +++ b/src/components/tambo/message-generation-stage.tsx @@ -1,7 +1,7 @@ "use client"; import { cn } from "@/lib/utils"; -import { type GenerationStage, useTambo } from "@tambo-ai/react"; +import { useTambo } from "@tambo-ai/react"; import { Loader2Icon } from "lucide-react"; import * as React from "react"; @@ -20,30 +20,20 @@ export function MessageGenerationStage({ showLabel = true, ...props }: GenerationStageProps) { - const { thread, isIdle } = useTambo(); - const stage = thread?.generationStage; + const { isStreaming, isWaiting, isIdle } = useTambo(); - // Only render if we have a generation stage - if (!stage) { + if (isIdle) { return null; } - // Map stage names to more user-friendly labels - const stageLabels: Record = { - IDLE: "Idle", - CHOOSING_COMPONENT: "Choosing component", - FETCHING_CONTEXT: "Fetching context", - HYDRATING_COMPONENT: "Preparing component", - STREAMING_RESPONSE: "Generating response", - COMPLETE: "Complete", - ERROR: "Error", - CANCELLED: "Cancelled", - }; - - const label = - stageLabels[stage] || stage.charAt(0).toUpperCase() + stage.slice(1); + let label = ""; + if (isWaiting) { + label = "Preparing response"; + } else if (isStreaming) { + label = "Generating response"; + } - if (isIdle) { + if (!label) { return null; } diff --git a/src/components/tambo/message-input.tsx b/src/components/tambo/message-input.tsx index c067e5f..6dbb392 100644 --- a/src/components/tambo/message-input.tsx +++ b/src/components/tambo/message-input.tsx @@ -13,7 +13,7 @@ import { import { cn } from "@/lib/utils"; import { useIsTamboTokenUpdating, - useTamboThread, + useTambo, useTamboThreadInput, type StagedImage, } from "@tambo-ai/react"; @@ -46,7 +46,7 @@ import { } from "./text-editor"; // Lazy load DictationButton for code splitting (framework-agnostic alternative to next/dynamic) -// eslint-disable-next-line @typescript-eslint/promise-function-async + const LazyDictationButton = React.lazy(() => import("./dictation-button")); /** @@ -334,10 +334,10 @@ const messageInputVariants = cva("w-full", { interface MessageInputContextValue { value: string; setValue: (value: string) => void; - submit: (options: { - streamResponse?: boolean; - resourceNames: Record; - }) => Promise; + submit: (options?: { + debug?: boolean; + toolChoice?: unknown; + }) => Promise<{ threadId: string | undefined }>; handleSubmit: (e: React.FormEvent) => Promise; isPending: boolean; error: Error | null; @@ -457,7 +457,7 @@ const MessageInputInternal = React.forwardRef< addImages, removeImage, } = useTamboThreadInput(); - const { cancel, thread } = useTamboThread(); + const { cancelRun, currentThreadId } = useTambo(); const [displayValue, setDisplayValue] = React.useState(""); const [submitError, setSubmitError] = React.useState(null); const [imageError, setImageError] = React.useState(null); @@ -465,24 +465,26 @@ const MessageInputInternal = React.forwardRef< const [isDragging, setIsDragging] = React.useState(false); const editorRef = React.useRef(null!); const dragCounter = React.useRef(0); + const submittingRef = React.useRef(false); // Use elicitation context (optional) const { elicitation, resolveElicitation } = useTamboElicitationContext(); React.useEffect(() => { // On mount, load any stored draft value, but only if current value is empty - const storedValue = getValueFromSessionStorage(thread.id); + const storedValue = getValueFromSessionStorage(currentThreadId); if (!storedValue) return; setValue((value) => value ?? storedValue); - }, [setValue, thread.id]); + }, [setValue, currentThreadId]); React.useEffect(() => { + if (submittingRef.current) return; setDisplayValue(value); - storeValueInSessionStorage(thread.id, value); + storeValueInSessionStorage(currentThreadId, value); if (value && editorRef.current) { editorRef.current.focus(); } - }, [value, thread.id]); + }, [value, currentThreadId]); const handleSubmit = React.useCallback( async (e: React.FormEvent) => { @@ -493,24 +495,14 @@ const MessageInputInternal = React.forwardRef< setSubmitError(null); setImageError(null); setDisplayValue(""); - storeValueInSessionStorage(thread.id); + storeValueInSessionStorage(currentThreadId); + submittingRef.current = true; setIsSubmitting(true); - // Extract resource names directly from editor at submit time to ensure we have the latest - let latestResourceNames: Record = {}; - const editor = editorRef.current; - if (editor) { - const extracted = editor.getTextWithResourceURIs(); - latestResourceNames = extracted.resourceNames; - } - const imageIdsAtSubmitTime = images.map((image) => image.id); try { - await submit({ - streamResponse: true, - resourceNames: latestResourceNames, - }); + await submit(); setValue(""); // Clear only the images that were staged when submission started so // any images added while the request was in-flight are preserved. @@ -532,9 +524,10 @@ const MessageInputInternal = React.forwardRef< : "Failed to send message. Please try again.", ); - // Cancel the thread to reset loading state - await cancel(); + // Cancel the run to reset loading state + await cancelRun(); } finally { + submittingRef.current = false; setIsSubmitting(false); } }, @@ -544,12 +537,12 @@ const MessageInputInternal = React.forwardRef< setValue, setDisplayValue, setSubmitError, - cancel, + cancelRun, isSubmitting, images, removeImage, editorRef, - thread.id, + currentThreadId, ], ); @@ -778,7 +771,7 @@ const MessageInputTextarea = ({ }: MessageInputTextareaProps) => { const { value, setValue, handleSubmit, editorRef, setImageError } = useMessageInputContext(); - const { isIdle } = useTamboThread(); + const { isIdle } = useTambo(); const { addImage, images } = useTamboThreadInput(); const isUpdatingToken = useIsTamboTokenUpdating(); // Resource names are extracted from editor at submit time, no need to track in state @@ -922,7 +915,7 @@ const MessageInputPlainTextarea = ({ }: MessageInputPlainTextareaProps) => { const { value, setValue, handleSubmit, setImageError } = useMessageInputContext(); - const { isIdle } = useTamboThread(); + const { isIdle } = useTambo(); const { addImage, images } = useTamboThreadInput(); const isUpdatingToken = useIsTamboTokenUpdating(); const isPending = !isIdle; @@ -1019,7 +1012,7 @@ const MessageInputSubmitButton = React.forwardRef< MessageInputSubmitButtonProps >(({ className, children, ...props }, ref) => { const { isPending } = useMessageInputContext(); - const { cancel, isIdle } = useTamboThread(); + const { cancelRun, isIdle } = useTambo(); const isUpdatingToken = useIsTamboTokenUpdating(); // Show cancel button if either: @@ -1030,7 +1023,7 @@ const MessageInputSubmitButton = React.forwardRef< const handleCancel = async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - await cancel(); + await cancelRun(); }; const buttonClasses = cn( diff --git a/src/components/tambo/message-suggestions.tsx b/src/components/tambo/message-suggestions.tsx index c2c9584..a09beb7 100644 --- a/src/components/tambo/message-suggestions.tsx +++ b/src/components/tambo/message-suggestions.tsx @@ -3,13 +3,12 @@ import { MessageGenerationStage } from "./message-generation-stage"; import { Tooltip, TooltipProvider } from "./suggestions-tooltip"; import { cn } from "@/lib/utils"; -import type { Suggestion, TamboThread } from "@tambo-ai/react"; +import type { Suggestion, TamboThreadMessage } from "@tambo-ai/react"; import { - GenerationStage, useTambo, useTamboSuggestions, + useTamboThreadInput, } from "@tambo-ai/react"; -import { Loader2Icon } from "lucide-react"; import * as React from "react"; import { useEffect, useRef } from "react"; @@ -28,7 +27,8 @@ interface MessageSuggestionsContextValue { accept: (options: { suggestion: Suggestion }) => Promise; isGenerating: boolean; error: Error | null; - thread: TamboThread; + messages: TamboThreadMessage[]; + isStreaming: boolean; isMac: boolean; } @@ -94,24 +94,26 @@ const MessageSuggestions = React.forwardRef< }, ref, ) => { - const { thread } = useTambo(); + const { messages, isStreaming } = useTambo(); + const { setValue } = useTamboThreadInput(); const { suggestions: generatedSuggestions, selectedSuggestionId, accept, - generateResult: { isPending: isGenerating, error }, + isGenerating, + error, } = useTamboSuggestions({ maxSuggestions }); // Combine initial and generated suggestions, but only use initial ones when thread is empty const suggestions = React.useMemo(() => { // Only use pre-seeded suggestions if thread is empty - if (!thread?.messages?.length && initialSuggestions.length > 0) { + if (!messages.length && initialSuggestions.length > 0) { return initialSuggestions.slice(0, maxSuggestions); } // Otherwise use generated suggestions return generatedSuggestions; }, [ - thread?.messages?.length, + messages.length, generatedSuggestions, initialSuggestions, maxSuggestions, @@ -131,7 +133,8 @@ const MessageSuggestions = React.forwardRef< accept, isGenerating, error, - thread, + messages, + isStreaming, isMac, }), [ @@ -140,15 +143,18 @@ const MessageSuggestions = React.forwardRef< accept, isGenerating, error, - thread, + messages, + isStreaming, isMac, ], ); // Find the last AI message - const lastAiMessage = thread?.messages - ? [...thread.messages].reverse().find((msg) => msg.role === "assistant") - : null; + const lastAiMessage = + messages.length > 0 + ? (messages.toReversed().find((msg) => msg.role === "assistant") ?? + null) + : null; // When a new AI message appears, update the reference useEffect(() => { @@ -182,8 +188,12 @@ const MessageSuggestions = React.forwardRef< const keyNum = parseInt(event.key); if (!isNaN(keyNum) && keyNum > 0 && keyNum <= suggestions.length) { event.preventDefault(); - const suggestionIndex = keyNum - 1; - void accept({ suggestion: suggestions[suggestionIndex] }); + const suggestion = suggestions[keyNum - 1]; + const content = + suggestion.detailedSuggestion ?? suggestion.title; + if (content) { + setValue(content); + } } } }; @@ -193,10 +203,10 @@ const MessageSuggestions = React.forwardRef< return () => { document.removeEventListener("keydown", handleKeyDown); }; - }, [suggestions, accept, isMac]); + }, [suggestions, setValue, isMac]); // If we have no messages yet and no initial suggestions, render nothing - if (!thread?.messages?.length && initialSuggestions.length === 0) { + if (!messages.length && initialSuggestions.length === 0) { return null; } @@ -241,19 +251,14 @@ const MessageSuggestionsStatus = React.forwardRef< HTMLDivElement, MessageSuggestionsStatusProps >(({ className, ...props }, ref) => { - const { error, isGenerating, thread } = useMessageSuggestionsContext(); + const { error, isGenerating, isStreaming } = useMessageSuggestionsContext(); return (
- + {isStreaming && }
); }); MessageSuggestionsStatus.displayName = "MessageSuggestions.Status"; -/** - * Internal component to render generation stage content - */ -function GenerationStageContent({ - generationStage, - isGenerating, -}: { - generationStage?: string; - isGenerating: boolean; -}) { - if (generationStage && generationStage !== GenerationStage.COMPLETE) { - return ; - } - if (isGenerating) { - return ( -
- -

Generating suggestions...

-
- ); - } - return null; -} - /** * Props for the MessageSuggestionsList component. * Extends standard HTMLDivElement attributes. @@ -324,8 +302,9 @@ const MessageSuggestionsList = React.forwardRef< HTMLDivElement, MessageSuggestionsListProps >(({ className, ...props }, ref) => { - const { suggestions, selectedSuggestionId, accept, isGenerating, isMac } = + const { suggestions, selectedSuggestionId, isGenerating, isMac } = useMessageSuggestionsContext(); + const { setValue } = useTamboThreadInput(); const modKey = isMac ? "⌘" : "Ctrl"; const altKey = isMac ? "⌥" : "Alt"; @@ -333,6 +312,18 @@ const MessageSuggestionsList = React.forwardRef< // Create placeholder suggestions when there are no real suggestions const placeholders = Array(3).fill(null); + const handleSuggestionClick = React.useCallback( + async (suggestion: Suggestion) => { + if (isGenerating) return; + + const content = suggestion.detailedSuggestion ?? suggestion.title; + if (content) { + setValue(content); + } + }, + [isGenerating, setValue], + ); + return (
- !isGenerating && (await accept({ suggestion })) - } + onClick={() => handleSuggestionClick(suggestion)} disabled={isGenerating} data-suggestion-id={suggestion.id} data-suggestion-index={index} @@ -409,4 +398,10 @@ function getSuggestionButtonClassName({ return "bg-background hover:bg-accent hover:text-accent-foreground"; } -export { MessageSuggestions, MessageSuggestionsList, MessageSuggestionsStatus }; +export { + MessageSuggestions, + MessageSuggestionsList, + MessageSuggestionsStatus, + Tooltip, + TooltipProvider, +}; diff --git a/src/components/tambo/message-thread-full.tsx b/src/components/tambo/message-thread-full.tsx index 43ba932..3bac288 100644 --- a/src/components/tambo/message-thread-full.tsx +++ b/src/components/tambo/message-thread-full.tsx @@ -17,7 +17,7 @@ import { MessageSuggestionsStatus, } from "@/components/tambo/message-suggestions"; import { ScrollableMessageContainer } from "@/components/tambo/scrollable-message-container"; -import { ThreadContainer, useThreadContainerContext } from "./thread-container"; +import { ThreadContainer, useThreadContainerContext } from "@/components/tambo/thread-container"; import { ThreadContent, ThreadContentMessages, diff --git a/src/components/tambo/message.tsx b/src/components/tambo/message.tsx index a31f767..7fc83c4 100644 --- a/src/components/tambo/message.tsx +++ b/src/components/tambo/message.tsx @@ -9,12 +9,16 @@ import { getSafeContent, } from "@/lib/thread-hooks"; import { cn } from "@/lib/utils"; -import type { TamboThreadMessage } from "@tambo-ai/react"; +import type { + TamboThreadMessage, + TamboToolUseContent, + TamboComponentContent, + Content, +} from "@tambo-ai/react"; import { useTambo } from "@tambo-ai/react"; -import type TamboAI from "@tambo-ai/typescript-sdk"; import { cva, type VariantProps } from "class-variance-authority"; import stringify from "json-stringify-pretty-compact"; -import { Check, ChevronDown, Loader2, X } from "lucide-react"; +import { Check, ChevronDown, ExternalLink, Loader2, X } from "lucide-react"; import Image from "next/image"; import * as React from "react"; import { useState } from "react"; @@ -80,15 +84,46 @@ const useMessageContext = () => { }; /** - * Get the tool call request from the message, or the component tool call request + * Get tool_use content blocks from the message content array. + * In V1, tool calls are content blocks of type "tool_use" within message.content. * - * @param message - The message to get the tool call request from - * @returns The tool call request + * @param message - The message to get tool use blocks from + * @returns Array of TamboToolUseContent blocks */ -export function getToolCallRequest( +export function getToolUseBlocks( message: TamboThreadMessage, -): TamboAI.ToolCallRequest | undefined { - return message.toolCallRequest ?? message.component?.toolCallRequest; +): TamboToolUseContent[] { + return message.content.filter( + (c): c is TamboToolUseContent => c.type === "tool_use", + ); +} + +/** + * Get the first tool_use content block from a message (convenience helper). + * @param message - The message to get the tool use block from + * @returns The first TamboToolUseContent block or undefined + */ +export function getFirstToolUseBlock( + message: TamboThreadMessage, +): TamboToolUseContent | undefined { + return message.content.find( + (c): c is TamboToolUseContent => c.type === "tool_use", + ); +} + +/** + * Get rendered component content blocks from the message content array. + * In V1, rendered components are content blocks of type "component" within message.content. + * + * @param message - The message to get component blocks from + * @returns Array of TamboComponentContent blocks + */ +export function getComponentBlocks( + message: TamboThreadMessage, +): TamboComponentContent[] { + return message.content.filter( + (c): c is TamboComponentContent => c.type === "component", + ); } // --- Sub-Components --- @@ -135,10 +170,7 @@ const Message = React.forwardRef( [role, variant, isLoading, message], ); - // Don't render tool response messages as they're shown in tool call dropdowns - if (message.role === "tool") { - return null; - } + // In V1, there is no "tool" role. Tool results are content blocks within messages. return ( @@ -281,6 +313,7 @@ const MessageContent = React.forwardRef( ref, ) => { const { message, isLoading } = useMessageContext(); + const { thread } = useTambo(); const contentToRender = children ?? contentProp ?? message.content; const safeContent = React.useMemo( @@ -294,6 +327,16 @@ const MessageContent = React.forwardRef( const showLoading = isLoading && !hasContent; + // Show cancellation indicator on the last assistant message when the thread's last run was cancelled. + const isLastAssistantMessage = React.useMemo(() => { + if (message.role !== "assistant") return false; + const messages = thread?.thread.messages ?? []; + const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant"); + return lastAssistant?.id === message.id; + }, [message.role, message.id, thread?.thread.messages]); + + const wasCancelled = isLastAssistantMessage && (thread?.thread.lastRunCancelled ?? false); + return (
( safeContent={safeContent} markdown={markdown} /> - {message.isCancelled && ( + {wasCancelled && ( cancelled )}
@@ -345,20 +388,15 @@ export interface ToolcallInfoProps extends Omit< } function getToolStatusMessage( - message: TamboThreadMessage, + toolUseBlock: TamboToolUseContent, isLoading: boolean | undefined, ) { - if (message.role !== "assistant" || !getToolCallRequest(message)) { - return null; + if (toolUseBlock.statusMessage) { + return toolUseBlock.statusMessage; } - - const toolCallMessage = isLoading - ? `Calling ${getToolCallRequest(message)?.toolName ?? "tool"}` - : `Called ${getToolCallRequest(message)?.toolName ?? "tool"}`; - const toolStatusMessage = isLoading - ? message.component?.statusMessage - : message.component?.completionStatusMessage; - return toolStatusMessage ?? toolCallMessage; + return isLoading + ? `Calling ${toolUseBlock.name ?? "tool"}` + : `Called ${toolUseBlock.name ?? "tool"}`; } /** @@ -391,103 +429,100 @@ const ToolcallInfo = React.forwardRef( ({ className, markdown = true, ...props }, ref) => { const [isExpanded, setIsExpanded] = useState(false); const { message, isLoading } = useMessageContext(); - const { thread } = useTambo(); + const { messages } = useTambo(); const toolDetailsId = React.useId(); - const associatedToolResponse = React.useMemo(() => { - if (!thread?.messages) return null; - const currentMessageIndex = thread.messages.findIndex( - (m: TamboThreadMessage) => m.id === message.id, - ); - if (currentMessageIndex === -1) return null; - for (let i = currentMessageIndex + 1; i < thread.messages.length; i++) { - const nextMessage = thread.messages[i]; - if (nextMessage.role === "tool") { - return nextMessage; - } - if ( - nextMessage.role === "assistant" && - getToolCallRequest(nextMessage) - ) { - break; + const toolUseBlocks = getToolUseBlocks(message); + + const associatedToolResults = React.useMemo(() => { + if (!messages || toolUseBlocks.length === 0) return []; + // Look through all messages for tool_result blocks matching our tool_use IDs + const toolUseIds = new Set(toolUseBlocks.map((t) => t.id)); + const results: { toolUseId: string; content: Content[] }[] = []; + for (const msg of messages) { + for (const block of msg.content) { + if (block.type === "tool_result" && toolUseIds.has(block.toolUseId)) { + results.push({ + toolUseId: block.toolUseId, + content: block.content ? (Array.isArray(block.content) ? block.content : [block.content]) : [], + }); + } } } - return null; - }, [message, thread?.messages]); + return results; + }, [messages, toolUseBlocks]); - if (message.role !== "assistant" || !getToolCallRequest(message)) { + if (message.role !== "assistant" || toolUseBlocks.length === 0) { return null; } - const toolCallRequest: TamboAI.ToolCallRequest | undefined = - getToolCallRequest(message); - const hasToolError = !!message.error; - - const toolStatusMessage = getToolStatusMessage(message, isLoading); - + // Render each tool_use block return (
-
- -
- - tool: {toolCallRequest?.toolName} - - - parameters:{"\n"} - {stringify(keyifyParameters(toolCallRequest?.parameters))} - - - {associatedToolResponse && ( -
- result: -
- {!associatedToolResponse.content ? ( - - Empty response - - ) : ( - formatToolResult(associatedToolResponse.content, markdown) + {toolUseBlocks.map((toolUseBlock) => { + const toolStatusMessage = getToolStatusMessage(toolUseBlock, isLoading && !toolUseBlock.hasCompleted); + const hasToolError = false; + const toolResult = associatedToolResults.find( + (r) => r.toolUseId === toolUseBlock.id, + ); + + return ( +
+ +
+ + tool: {toolUseBlock.name} + + + parameters:{"\n"} + {stringify(toolUseBlock.input)} + + {toolResult && toolResult.content.length > 0 && ( +
+ result: +
+ {formatToolResult(toolResult.content as TamboThreadMessage["content"], markdown)} +
+
+ )}
- )} -
-
+
+ ); + })}
); }, @@ -495,82 +530,6 @@ const ToolcallInfo = React.forwardRef( ToolcallInfo.displayName = "ToolcallInfo"; -/** - * Displays a message's child messages in a collapsible dropdown. - * Used for MCP sampling sub-threads. - * @component SamplingSubThread - */ -const SamplingSubThread = ({ - parentMessageId, - titleText = "finished additional work", -}: { - parentMessageId: string; - titleText?: string; -}) => { - const { thread } = useTambo(); - const [isExpanded, setIsExpanded] = useState(false); - const samplingDetailsId = React.useId(); - - const childMessages = React.useMemo(() => { - return thread?.messages?.filter( - (m: TamboThreadMessage) => m.parentMessageId === parentMessageId, - ); - }, [thread?.messages, parentMessageId]); - - if (!childMessages?.length) return null; - - return ( -
- -
-
-
- {childMessages?.map((m: TamboThreadMessage) => ( -
- - {getSafeContent(m.content)} - -
- ))} -
-
-
-
- ); -}; -SamplingSubThread.displayName = "SamplingSubThread"; - /** * Props for the ReasoningInfo component. * Extends standard HTMLDivElement attributes. @@ -696,13 +655,6 @@ const ReasoningInfo = React.forwardRef( ReasoningInfo.displayName = "ReasoningInfo"; -function keyifyParameters(parameters: TamboAI.ToolCallParameter[] | undefined) { - if (!parameters) return; - return Object.fromEntries( - parameters.map((p) => [p.parameterName, p.parameterValue]), - ); -} - /** * Internal component to render reasoning status text */ @@ -812,64 +764,33 @@ export type MessageRenderedComponentAreaProps = React.HTMLAttributes; /** - * Helper function to extract component type and props from rendered component + * Helper function to extract component type and props from a component content block. + * In V1, the component name and props are available directly on the TamboComponentContent + * block, so we no longer need to introspect the React element tree. */ -function extractComponentInfo(renderedComponent: React.ReactNode): { +function extractComponentInfo(componentBlock: TamboComponentContent | undefined): { componentType: string; componentProps: Record; } { - let componentType = "unknown"; - let componentProps: Record = {}; - - const wrapperElement = renderedComponent as React.ReactElement; - - if ( - React.isValidElement(wrapperElement) && - (wrapperElement as { props?: { children?: React.ReactElement } }).props - ?.children - ) { - const actualComponent = ( - wrapperElement as { props: { children: React.ReactElement } } - ).props.children as React.ReactElement; - - if (React.isValidElement(actualComponent)) { - const matchedComponent = components.find( - (comp) => comp.component === actualComponent.type, - ); - if (matchedComponent) { - componentType = matchedComponent.name; - } else if (typeof actualComponent.type === "function") { - const typeFunc = actualComponent.type as React.ComponentType & { - displayName?: string; - name?: string; - }; - const funcName = typeFunc.displayName || typeFunc.name || "unknown"; - componentType = funcName === "Graph" ? "Graph" : funcName; - } + if (!componentBlock) { + return { componentType: "unknown", componentProps: {} }; + } - if (actualComponent.props) { - // Normalize props for Graph so subsequent edits (title/type) - // via CanvasDetails work whether the component was added by - // button or drag-and-drop. - if (componentType === "Graph") { - const { data, title, showLegend, variant, size, className } = - actualComponent.props as Record; - componentProps = { - data, - title, - showLegend, - variant, - size, - className, - }; - } else { - componentProps = { ...actualComponent.props }; - } - } - } + const componentType = componentBlock.name ?? "unknown"; + const rawProps = (componentBlock.props ?? {}) as Record; + + // Normalize props for Graph so subsequent edits (title/type) + // via CanvasDetails work whether the component was added by + // button or drag-and-drop. + if (componentType === "Graph") { + const { data, title, showLegend, variant, size, className } = rawProps; + return { + componentType, + componentProps: { data, title, showLegend, variant, size, className }, + }; } - return { componentType, componentProps }; + return { componentType, componentProps: { ...rawProps } }; } /** @@ -891,12 +812,18 @@ const MessageRenderedComponentArea = React.forwardRef< MessageRenderedComponentAreaProps >(({ className, children, ...props }, ref) => { const { message, role } = useMessageContext(); + const { thread } = useTambo(); + const [canvasExists, setCanvasExists] = React.useState(false); const { addComponent, activeCanvasId, createCanvas } = useCanvasStore(); + const componentBlocks = getComponentBlocks(message); + const firstComponentBlock = componentBlocks[0]; + const renderedComponent = firstComponentBlock?.renderedComponent; + // Extract component info once to check if it's draggable const { componentType, componentProps } = React.useMemo( - () => extractComponentInfo(message.renderedComponent), - [message.renderedComponent], + () => extractComponentInfo(firstComponentBlock), + [firstComponentBlock], ); const canDrag = isDraggableComponent(componentType); @@ -935,10 +862,27 @@ const MessageRenderedComponentArea = React.forwardRef< [componentType, componentProps], ); + // Check if canvas exists on mount and window resize + React.useEffect(() => { + const checkCanvasExists = () => { + const canvas = document.querySelector('[data-canvas-space="true"]'); + setCanvasExists(!!canvas); + }; + + checkCanvasExists(); + window.addEventListener("resize", checkCanvasExists); + + return () => { + window.removeEventListener("resize", checkCanvasExists); + }; + }, []); + + const isCancelled = thread?.thread?.lastRunCancelled ?? false; + if ( - !message.renderedComponent || + !renderedComponent || role !== "assistant" || - message.isCancelled + isCancelled ) { return null; } @@ -951,9 +895,38 @@ const MessageRenderedComponentArea = React.forwardRef< {...props} > {children ?? - (canDrag ? ( + (canvasExists && canDrag ? ( +
+ + +
+ ) : canDrag ? (
-
+
) : (
- {message.renderedComponent} + {renderedComponent}
))}
diff --git a/src/components/tambo/scrollable-message-container.tsx b/src/components/tambo/scrollable-message-container.tsx index 1af38b4..958bb04 100644 --- a/src/components/tambo/scrollable-message-container.tsx +++ b/src/components/tambo/scrollable-message-container.tsx @@ -1,9 +1,9 @@ "use client"; +import { useTambo } from "@tambo-ai/react"; import { cn } from "@/lib/utils"; -import { GenerationStage, useTambo } from "@tambo-ai/react"; import * as React from "react"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState, useMemo } from "react"; /** * Props for the ScrollableMessageContainer component @@ -29,7 +29,7 @@ export const ScrollableMessageContainer = React.forwardRef< ScrollableMessageContainerProps >(({ className, children, ...props }, ref) => { const scrollContainerRef = useRef(null); - const { thread } = useTambo(); + const { messages, isStreaming } = useTambo(); const [shouldAutoscroll, setShouldAutoscroll] = useState(true); const lastScrollTopRef = useRef(0); @@ -37,23 +37,18 @@ export const ScrollableMessageContainer = React.forwardRef< React.useImperativeHandle(ref, () => scrollContainerRef.current!, []); // Create a dependency that represents all content that should trigger autoscroll - const messagesContent = React.useMemo(() => { - if (!thread.messages) return null; + const messagesContent = useMemo(() => { + if (!messages.length) return null; - return thread.messages.map((message) => ({ + return messages.map((message) => ({ id: message.id, content: message.content, - tool_calls: message.tool_calls, - component: message.component, reasoning: message.reasoning, - componentState: message.componentState, })); - }, [thread.messages]); - - const generationStage = thread?.generationStage ?? GenerationStage.IDLE; + }, [messages]); // Handle scroll events to detect user scrolling - const handleScroll = () => { + const handleScroll = useCallback(() => { if (!scrollContainerRef.current) return; const { scrollTop, scrollHeight, clientHeight } = @@ -70,7 +65,7 @@ export const ScrollableMessageContainer = React.forwardRef< } lastScrollTopRef.current = scrollTop; - }; + }, []); // Auto-scroll to bottom when message content changes useEffect(() => { @@ -84,7 +79,7 @@ export const ScrollableMessageContainer = React.forwardRef< } }; - if (generationStage === GenerationStage.STREAMING_RESPONSE) { + if (isStreaming) { // During streaming, scroll immediately requestAnimationFrame(scroll); } else { @@ -93,7 +88,7 @@ export const ScrollableMessageContainer = React.forwardRef< return () => clearTimeout(timeoutId); } } - }, [messagesContent, generationStage, shouldAutoscroll]); + }, [messagesContent, isStreaming, shouldAutoscroll]); return (
( class: "mention resource inline-flex items-center rounded-md bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground", }, + /* eslint-disable react-hooks/refs */ suggestion: createResourceMentionConfig( stableSearchResources, handleResourceSelect, resourceRef, ), + /* eslint-enable react-hooks/refs */ renderLabel: ({ node }) => `@${(node.attrs.label as string) ?? ""}`, }), + /* eslint-disable react-hooks/refs */ createPromptCommandExtension( stableSearchPrompts, handlePromptSelect, promptRef, ), + /* eslint-enable react-hooks/refs */ ], content: value, editable: !disabled, @@ -831,14 +834,14 @@ export const TextEditor = React.forwardRef( resourceRef.current.setState({ isOpen: false })} - defaultIcon={} + defaultIcon={} emptyMessage="No results found" monoSecondary /> promptRef.current.setState({ isOpen: false })} - defaultIcon={} + defaultIcon={} emptyMessage="No prompts found" /> diff --git a/src/components/tambo/thread-content.tsx b/src/components/tambo/thread-content.tsx index c888209..0b2eabe 100644 --- a/src/components/tambo/thread-content.tsx +++ b/src/components/tambo/thread-content.tsx @@ -24,7 +24,6 @@ import * as React from "react"; interface ThreadContentContextValue { messages: TamboThreadMessage[]; isGenerating: boolean; - generationStage?: string; variant?: VariantProps["variant"]; } @@ -75,17 +74,16 @@ export interface ThreadContentProps extends React.HTMLAttributes */ const ThreadContent = React.forwardRef( ({ children, className, variant, ...props }, ref) => { - const { thread, generationStage, isIdle } = useTambo(); + const { messages, isIdle } = useTambo(); const isGenerating = !isIdle; const contextValue = React.useMemo( () => ({ - messages: thread?.messages ?? [], + messages, isGenerating, - generationStage, variant, }), - [thread?.messages, isGenerating, generationStage, variant], + [messages, isGenerating, variant], ); return ( @@ -127,9 +125,19 @@ const ThreadContentMessages = React.forwardRef< >(({ className, ...props }, ref) => { const { messages, isGenerating, variant } = useThreadContentContext(); - const filteredMessages = messages.filter( - (message) => message.role !== "system" && !message.parentMessageId, - ); + const filteredMessages = messages.filter((message) => { + if (message.role === "system") return false; + // Hide messages that only contain tool_result content blocks. + // These are consumed by ToolcallInfo on the preceding tool_use message + // and shouldn't render as standalone message bubbles. + if ( + message.content.length > 0 && + message.content.every((block) => block.type === "tool_result") + ) { + return false; + } + return true; + }); return (
Promise; - currentThread: TamboThread; - switchCurrentThread: (threadId: string) => void; - startNewThread: () => void; + currentThreadId: string; + switchThread: (threadId: string) => void; + startNewThread: () => string; searchQuery: string; setSearchQuery: React.Dispatch>; isCollapsed: boolean; setIsCollapsed: React.Dispatch>; onThreadChange?: () => void; position?: "left" | "right"; - updateThreadName: (newName: string, threadId?: string) => Promise; - generateThreadName: (threadId: string) => Promise; } const ThreadHistoryContext = @@ -80,13 +80,7 @@ const ThreadHistory = React.forwardRef( const { data: threads, isLoading, error, refetch } = useTamboThreadList(); - const { - switchCurrentThread, - startNewThread, - thread: currentThread, - updateThreadName, - generateThreadName, - } = useTamboThread(); + const { switchThread, startNewThread, currentThreadId } = useTambo(); // Update CSS variable when sidebar collapses/expands React.useEffect(() => { @@ -110,8 +104,8 @@ const ThreadHistory = React.forwardRef( isLoading, error, refetch, - currentThread, - switchCurrentThread, + currentThreadId, + switchThread, startNewThread, searchQuery, setSearchQuery, @@ -119,30 +113,24 @@ const ThreadHistory = React.forwardRef( setIsCollapsed, onThreadChange, position, - updateThreadName, - generateThreadName, }), [ threads, isLoading, error, refetch, - currentThread, - switchCurrentThread, + currentThreadId, + switchThread, startNewThread, searchQuery, isCollapsed, onThreadChange, position, - updateThreadName, - generateThreadName, ], ); return ( - +
( - null, - ); + const [hasMounted, setHasMounted] = React.useState(false); + const [editingThread, setEditingThread] = + React.useState(null); const [newName, setNewName] = React.useState(""); const inputRef = React.useRef(null); + // Prevent hydration mismatch: treat as loading until mounted on the client + React.useEffect(() => { + setHasMounted(true); + }, []); + // Handle click outside name editing input React.useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -417,14 +407,12 @@ const ThreadHistoryList = React.forwardRef< // While collapsed we do not need the list, avoid extra work. if (isCollapsed) return []; - if (!threads?.items) return []; + if (!threads?.threads) return []; const query = searchQuery.toLowerCase(); - return threads.items.filter((thread: TamboThread) => { - const nameMatches = thread.name?.toLowerCase().includes(query) ?? false; + return threads.threads.filter((thread: ThreadListItem) => { const idMatches = thread.id.toLowerCase().includes(query); - - return idMatches ? true : nameMatches; + return idMatches; }); }, [isCollapsed, threads, searchQuery]); @@ -432,43 +420,29 @@ const ThreadHistoryList = React.forwardRef< if (e) e.stopPropagation(); try { - switchCurrentThread(threadId); + switchThread(threadId); onThreadChange?.(); } catch (error) { console.error("Failed to switch thread:", error); } }; - const handleRename = (thread: TamboThread) => { + const handleRename = (thread: ThreadListItem) => { setEditingThread(thread); - setNewName(thread.name ?? ""); - }; - - const handleGenerateName = async (thread: TamboThread) => { - try { - await generateThreadName(thread.id); - await refetch(); - } catch (error) { - console.error("Failed to generate name:", error); - } + setNewName(`Thread ${thread.id.substring(0, 8)}`); }; const handleNameSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!editingThread) return; - try { - await updateThreadName(newName, editingThread.id); - await refetch(); - setEditingThread(null); - } catch (error) { - console.error("Failed to rename thread:", error); - } + // Thread renaming is not supported in V1 API + setEditingThread(null); }; // Content to show let content; - if (isLoading) { + if (!hasMounted || isLoading) { content = (
- {filteredThreads.map((thread: TamboThread) => ( + {filteredThreads.map((thread: ThreadListItem) => (
await handleSwitchThread(thread.id)} className={cn( "p-2 rounded-md hover:bg-backdrop cursor-pointer group flex items-center justify-between", - currentThread?.id === thread.id ? "bg-muted" : "", + currentThreadId === thread.id ? "bg-muted" : "", editingThread?.id === thread.id ? "bg-muted" : "", )} > @@ -545,7 +519,7 @@ const ThreadHistoryList = React.forwardRef< ) : ( <> - {thread.name ?? `Thread ${thread.id.substring(0, 8)}`} + {`Thread ${thread.id.substring(0, 8)}`}

{new Date(thread.createdAt).toLocaleString(undefined, { @@ -558,11 +532,7 @@ const ThreadHistoryList = React.forwardRef< )}

- +
))}
@@ -593,11 +563,9 @@ ThreadHistoryList.displayName = "ThreadHistory.List"; const ThreadOptionsDropdown = ({ thread, onRename, - onGenerateName, }: { - thread: TamboThread; - onRename: (thread: TamboThread) => void; - onGenerateName: (thread: TamboThread) => void; + thread: ThreadListItem; + onRename: (thread: ThreadListItem) => void; }) => { return ( @@ -625,16 +593,6 @@ const ThreadOptionsDropdown = ({ Rename - { - e.stopPropagation(); - onGenerateName(thread); - }} - > - - Generate Name - diff --git a/src/components/ui/interactable-canvas-details.tsx b/src/components/ui/interactable-canvas-details.tsx index 365d871..bb1d1f0 100644 --- a/src/components/ui/interactable-canvas-details.tsx +++ b/src/components/ui/interactable-canvas-details.tsx @@ -1,7 +1,7 @@ "use client"; import { useCanvasStore } from "@/lib/canvas-storage"; -import { useTamboInteractable, withInteractable } from "@tambo-ai/react"; +import { useTamboInteractable, withTamboInteractable } from "@tambo-ai/react"; import { useCallback, useEffect, useRef } from "react"; import { z } from "zod"; @@ -192,7 +192,7 @@ function CanvasDetailsWrapper(props: CanvasDetailsProps) { ); } -export const InteractableCanvasDetails = withInteractable( +export const InteractableCanvasDetails = withTamboInteractable( CanvasDetailsWrapper, { componentName: "CanvasDetails", diff --git a/src/components/ui/interactable-tabs.tsx b/src/components/ui/interactable-tabs.tsx index 8970906..8905f78 100644 --- a/src/components/ui/interactable-tabs.tsx +++ b/src/components/ui/interactable-tabs.tsx @@ -2,7 +2,7 @@ import type { Canvas, CanvasComponent } from "@/lib/canvas-storage"; import { useCanvasStore } from "@/lib/canvas-storage"; -import { useTamboInteractable, withInteractable } from "@tambo-ai/react"; +import { useTamboInteractable, withTamboInteractable } from "@tambo-ai/react"; import { useCallback, useEffect, useRef } from "react"; import { z } from "zod"; @@ -164,7 +164,7 @@ function TabsWrapper(props: TabsProps) { return
; } -export const InteractableTabs = withInteractable(TabsWrapper, { +export const InteractableTabs = withTamboInteractable(TabsWrapper, { componentName: "Tabs", description: "Tabs-only interactable. Manages canvases (id, name) and activeCanvasId. Use CanvasDetails to edit charts for the selected tab.", diff --git a/src/lib/tambo.ts b/src/lib/tambo.ts index ca8f173..8fd4d98 100644 --- a/src/lib/tambo.ts +++ b/src/lib/tambo.ts @@ -10,8 +10,7 @@ import { Graph, graphSchema } from "@/components/tambo/graph"; import { SelectForm, selectFormSchema } from "@/components/tambo/select-form"; -import type { TamboComponent } from "@tambo-ai/react"; -import { TamboTool } from "@tambo-ai/react"; +import type { TamboComponent, TamboTool } from "@tambo-ai/react"; import { z } from "zod"; import { getSalesData, @@ -34,53 +33,41 @@ export const tools: TamboTool[] = [ description: "Get monthly sales revenue and units data. Can filter by region (North, South, East, West) or category (Electronics, Clothing, Home)", tool: getSalesData, - toolSchema: z.function().args( - z - .object({ - region: z.string().optional(), - category: z.string().optional(), - }) - .default({}), - ), + inputSchema: z.object({ + region: z.string().optional(), + category: z.string().optional(), + }), + outputSchema: z.any(), }, { name: "getProducts", description: "Get top products with sales and revenue information. Can filter by category (Electronics, Furniture, Appliances)", tool: getProducts, - toolSchema: z.function().args( - z - .object({ - category: z.string().optional(), - }) - .default({}), - ), + inputSchema: z.object({ + category: z.string().optional(), + }), + outputSchema: z.any(), }, { name: "getUserData", description: "Get monthly user growth and activity data. Can filter by segment (Free, Premium, Enterprise)", tool: getUserData, - toolSchema: z.function().args( - z - .object({ - segment: z.string().optional(), - }) - .default({}), - ), + inputSchema: z.object({ + segment: z.string().optional(), + }), + outputSchema: z.any(), }, { name: "getKPIs", description: "Get key business performance indicators. Can filter by category (Financial, Growth, Quality, Retention, Marketing)", tool: getKPIs, - toolSchema: z.function().args( - z - .object({ - category: z.string().optional(), - }) - .default({}), - ), + inputSchema: z.object({ + category: z.string().optional(), + }), + outputSchema: z.any(), }, ]; @@ -96,14 +83,16 @@ export const components: TamboComponent[] = [ name: "Graph", description: "Use this when you want to display a chart. It supports bar, line, and pie charts. When you see data generally use this component. IMPORTANT: When asked to create a graph, always generate it first in the chat - do NOT add it directly to the canvas/dashboard. Let the user decide if they want to add it.", - component: Graph, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + component: Graph as any, propsSchema: graphSchema, }, { name: "SelectForm", description: "ALWAYS use this component instead of listing options as bullet points in text. Whenever you need to ask the user a question and would normally follow up with bullet points or numbered options, use this component instead. For yes/no or single-choice questions, use mode='single'. For questions where the user can select multiple options, use mode='multi' (default). Each group has a label (the question) and options (the choices). Examples: 'Would you like to continue?' with Yes/No options, or 'Which regions interest you?' with multiple region options.", - component: SelectForm, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + component: SelectForm as any, propsSchema: selectFormSchema, }, // Add more components here diff --git a/src/lib/use-anonymous-user-key.ts b/src/lib/use-anonymous-user-key.ts new file mode 100644 index 0000000..a1da28f --- /dev/null +++ b/src/lib/use-anonymous-user-key.ts @@ -0,0 +1,21 @@ +import { useState } from "react"; + +const STORAGE_KEY = "tambo-user-key"; + +function getOrCreateUserKey(): string { + if (typeof window === "undefined") { + return ""; + } + const existing = localStorage.getItem(STORAGE_KEY); + if (existing) { + return existing; + } + const newKey = crypto.randomUUID(); + localStorage.setItem(STORAGE_KEY, newKey); + return newKey; +} + +export function useAnonymousUserKey(): string { + const [userKey] = useState(getOrCreateUserKey); + return userKey; +}