Buckets:

download
raw
67.7 kB
<meta charset="utf-8" /><meta name="hf:doc:metadata" content="{&quot;title&quot;:&quot;Building a Next.js AI Chatbot with Vercel AI SDK&quot;,&quot;local&quot;:&quot;building-a-nextjs-ai-chatbot-with-vercel-ai-sdk&quot;,&quot;sections&quot;:[{&quot;title&quot;:&quot;Prerequisites&quot;,&quot;local&quot;:&quot;prerequisites&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 1: Create the project&quot;,&quot;local&quot;:&quot;step-1-create-the-project&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 2: Configure Next.js for browser inference&quot;,&quot;local&quot;:&quot;step-2-configure-nextjs-for-browser-inference&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 3: Create the Web Worker&quot;,&quot;local&quot;:&quot;step-3-create-the-web-worker&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 4: Define the model configuration&quot;,&quot;local&quot;:&quot;step-4-define-the-model-configuration&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 5: Define tools&quot;,&quot;local&quot;:&quot;step-5-define-tools&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 6: Create the chat transport&quot;,&quot;local&quot;:&quot;step-6-create-the-chat-transport&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 7: Build the chat UI&quot;,&quot;local&quot;:&quot;step-7-build-the-chat-ui&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 8: Run the application&quot;,&quot;local&quot;:&quot;step-8-run-the-application&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Next steps&quot;,&quot;local&quot;:&quot;next-steps&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2}],&quot;depth&quot;:1}">
<link href="/docs/transformers.js/pr_1649/en/_app/immutable/assets/0.e3b0c442.css" rel="modulepreload">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/entry/start.5f753a22.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/chunks/scheduler.6efaaf90.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/chunks/singletons.c0807d75.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/chunks/paths.c5938264.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/entry/app.db3dfcb7.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/chunks/preload-helper.1d8e1e5d.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/chunks/index.eb3e1f0f.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/nodes/0.1b727385.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/chunks/each.e59479a4.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/nodes/37.e3def2cf.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/chunks/Tip.292c2c3d.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/chunks/MermaidChart.svelte_svelte_type_style_lang.16f13047.js">
<link rel="modulepreload" href="/docs/transformers.js/pr_1649/en/_app/immutable/chunks/CodeBlock.72c8dd07.js"><!-- HEAD_svelte-u9bgzb_START --><meta name="hf:doc:metadata" content="{&quot;title&quot;:&quot;Building a Next.js AI Chatbot with Vercel AI SDK&quot;,&quot;local&quot;:&quot;building-a-nextjs-ai-chatbot-with-vercel-ai-sdk&quot;,&quot;sections&quot;:[{&quot;title&quot;:&quot;Prerequisites&quot;,&quot;local&quot;:&quot;prerequisites&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 1: Create the project&quot;,&quot;local&quot;:&quot;step-1-create-the-project&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 2: Configure Next.js for browser inference&quot;,&quot;local&quot;:&quot;step-2-configure-nextjs-for-browser-inference&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 3: Create the Web Worker&quot;,&quot;local&quot;:&quot;step-3-create-the-web-worker&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 4: Define the model configuration&quot;,&quot;local&quot;:&quot;step-4-define-the-model-configuration&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 5: Define tools&quot;,&quot;local&quot;:&quot;step-5-define-tools&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 6: Create the chat transport&quot;,&quot;local&quot;:&quot;step-6-create-the-chat-transport&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 7: Build the chat UI&quot;,&quot;local&quot;:&quot;step-7-build-the-chat-ui&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Step 8: Run the application&quot;,&quot;local&quot;:&quot;step-8-run-the-application&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;Next steps&quot;,&quot;local&quot;:&quot;next-steps&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2}],&quot;depth&quot;:1}"><!-- HEAD_svelte-u9bgzb_END --> <p></p> <div class="items-center shrink-0 min-w-[100px] max-sm:min-w-[50px] justify-end ml-auto flex" style="float: right; margin-left: 10px; display: inline-flex; position: relative; z-index: 10;"><div class="inline-flex rounded-md max-sm:rounded-sm"><button class="inline-flex items-center gap-1 h-7 max-sm:h-7 px-2 max-sm:px-1.5 text-sm font-medium text-gray-800 border border-r-0 rounded-l-md max-sm:rounded-l-sm border-gray-200 bg-white hover:shadow-inner dark:border-gray-850 dark:bg-gray-950 dark:text-gray-200 dark:hover:bg-gray-800" aria-live="polite"><span class="inline-flex items-center justify-center rounded-md p-0.5 max-sm:p-0 hover:text-gray-800 dark:hover:text-gray-200"><svg class="sm:size-3.5 size-3" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg></span> <span>Copy page</span></button> <button class="inline-flex items-center justify-center w-6 max-sm:w-5 h-7 max-sm:h-7 disabled:pointer-events-none text-sm text-gray-500 hover:text-gray-700 dark:hover:text-white rounded-r-md max-sm:rounded-r-sm border border-l transition border-gray-200 bg-white hover:shadow-inner dark:border-gray-850 dark:bg-gray-950 dark:text-gray-200 dark:hover:bg-gray-800" aria-haspopup="menu" aria-expanded="false" aria-label="Open copy menu"><svg class="transition-transform text-gray-400 overflow-visible sm:size-3.5 size-3 rotate-0" width="1em" height="1em" viewBox="0 0 12 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1L6 6L11 1" stroke="currentColor"></path></svg></button></div> </div> <h1 class="relative group"><a id="building-a-nextjs-ai-chatbot-with-vercel-ai-sdk" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#building-a-nextjs-ai-chatbot-with-vercel-ai-sdk"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Building a Next.js AI Chatbot with Vercel AI SDK</span></h1> <p data-svelte-h="svelte-cdwkfs">In this tutorial, we’ll build an in-browser AI chatbot using Next.js, Transformers.js, and the Vercel AI SDK v6. The chatbot runs entirely client-side with WebGPU acceleration — and supports tool calling with human approval.</p> <p data-svelte-h="svelte-1tlxmzn">Useful links:</p> <ul data-svelte-h="svelte-yqurg4"><li><a href="https://github.com/huggingface/transformers.js-examples/tree/main/next-vercel-ai-sdk-v6-tool-calling" rel="nofollow">Source code</a></li> <li><a href="https://www.browser-ai.dev/docs/ai-sdk-v6/transformers-js" rel="nofollow"><code>@browser-ai/transformers-js</code> docs</a></li> <li><a href="https://ai-sdk.dev/" rel="nofollow">Vercel AI SDK docs</a></li></ul> <h2 class="relative group"><a id="prerequisites" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#prerequisites"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Prerequisites</span></h2> <ul data-svelte-h="svelte-oeluux"><li><a href="https://nodejs.org/en/" rel="nofollow">Node.js</a> version 18+</li> <li><a href="https://www.npmjs.com/" rel="nofollow">npm</a> version 9+</li> <li>A browser with WebGPU support (Chrome 113+, Edge 113+, or Firefox/Safari with flags enabled)</li></ul> <h2 class="relative group"><a id="step-1-create-the-project" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-1-create-the-project"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Step 1: Create the project</span></h2> <p data-svelte-h="svelte-1w40azh">Create a new Next.js application:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START -->npx create-next-app@latest next-ai-chatbot
<span class="hljs-built_in">cd</span> next-ai-chatbot<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-15hefzp">Install the AI and Transformers.js dependencies:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START -->npm install ai @ai-sdk/react @browser-ai/transformers-js @huggingface/transformers zod<!-- HTML_TAG_END --></pre></div> <h2 class="relative group"><a id="step-2-configure-nextjs-for-browser-inference" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-2-configure-nextjs-for-browser-inference"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Step 2: Configure Next.js for browser inference</span></h2> <p data-svelte-h="svelte-1kpoltd">Transformers.js uses ONNX Runtime under the hood for both browser and server-side (Node.js) inference. In our case we only need the browser runtime so we can tell Next.js to exclude the Node.js-specific packages when bundling for the browser. Update <code>next.config.ts</code></p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">NextConfig</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;next&quot;</span>;
<span class="hljs-keyword">const</span> <span class="hljs-attr">nextConfig</span>: <span class="hljs-title class_">NextConfig</span> = {
<span class="hljs-attr">output</span>: <span class="hljs-string">&quot;export&quot;</span>, <span class="hljs-comment">// optional: export as a static site</span>
<span class="hljs-attr">turbopack</span>: {},
<span class="hljs-attr">webpack</span>: <span class="hljs-function">(<span class="hljs-params">config</span>) =&gt;</span> {
config.<span class="hljs-property">resolve</span>.<span class="hljs-property">alias</span> = {
...config.<span class="hljs-property">resolve</span>.<span class="hljs-property">alias</span>,
<span class="hljs-attr">sharp$</span>: <span class="hljs-literal">false</span>,
<span class="hljs-string">&quot;onnxruntime-node$&quot;</span>: <span class="hljs-literal">false</span>,
};
<span class="hljs-keyword">return</span> config;
},
};
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> nextConfig;<!-- HTML_TAG_END --></pre></div> <h2 class="relative group"><a id="step-3-create-the-web-worker" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-3-create-the-web-worker"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Step 3: Create the Web Worker</span></h2> <p data-svelte-h="svelte-1ueuvz9">Running model inference on the main thread would block the UI. The <code>@browser-ai/transformers-js</code> package provides a ready-made worker handler that handles all the complexity for you.</p> <p data-svelte-h="svelte-1rngpb1">Create <code>src/app/worker.ts</code>:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-keyword">import</span> { <span class="hljs-title class_">TransformersJSWorkerHandler</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;@browser-ai/transformers-js&quot;</span>;
<span class="hljs-keyword">const</span> handler = <span class="hljs-keyword">new</span> <span class="hljs-title class_">TransformersJSWorkerHandler</span>();
self.<span class="hljs-property">onmessage</span> = <span class="hljs-function">(<span class="hljs-params">msg: MessageEvent</span>) =&gt;</span> {
handler.<span class="hljs-title function_">onmessage</span>(msg);
};<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-1tmmpn2">That’s it — the handler takes care of model loading, inference, streaming, and communication with the main thread.</p> <h2 class="relative group"><a id="step-4-define-the-model-configuration" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-4-define-the-model-configuration"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Step 4: Define the model configuration</span></h2> <p data-svelte-h="svelte-5qh26d">Create <code>src/app/models.ts</code> to define which models are available. These are ONNX-format models from Hugging Face:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-keyword">import</span> { <span class="hljs-title class_">WorkerLoadOptions</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;@browser-ai/transformers-js&quot;</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">ModelConfig</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Omit</span>&lt;<span class="hljs-title class_">WorkerLoadOptions</span>, &quot;modelId&quot;&gt; {
<span class="hljs-attr">id</span>: <span class="hljs-built_in">string</span>;
<span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>;
supportsWorker?: <span class="hljs-built_in">boolean</span>;
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-attr">MODELS</span>: <span class="hljs-title class_">ModelConfig</span>[] = [
{
<span class="hljs-attr">id</span>: <span class="hljs-string">&quot;onnx-community/Qwen3-0.6B-ONNX&quot;</span>,
<span class="hljs-attr">name</span>: <span class="hljs-string">&quot;Qwen3 0.6B&quot;</span>,
<span class="hljs-attr">device</span>: <span class="hljs-string">&quot;webgpu&quot;</span>,
<span class="hljs-attr">dtype</span>: <span class="hljs-string">&quot;q4f16&quot;</span>,
<span class="hljs-attr">supportsWorker</span>: <span class="hljs-literal">true</span>,
},
{
<span class="hljs-attr">id</span>: <span class="hljs-string">&quot;onnx-community/granite-4.0-350m-ONNX-web&quot;</span>,
<span class="hljs-attr">name</span>: <span class="hljs-string">&quot;Granite 4.0 350M&quot;</span>,
<span class="hljs-attr">device</span>: <span class="hljs-string">&quot;webgpu&quot;</span>,
<span class="hljs-attr">dtype</span>: <span class="hljs-string">&quot;fp16&quot;</span>,
<span class="hljs-attr">supportsWorker</span>: <span class="hljs-literal">true</span>,
},
];<!-- HTML_TAG_END --></pre></div> <blockquote class="tip"><p data-svelte-h="svelte-1pqpmuc">For tool calling, use reasoning models like Qwen3 which handle multi-step reasoning well, or fine-tuned model specifically for tool-calling capabilities. The <code>supportsWorker</code> flag controls whether the model is loaded in a Web Worker for better performance.</p></blockquote> <h2 class="relative group"><a id="step-5-define-tools" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-5-define-tools"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Step 5: Define tools</span></h2> <p data-svelte-h="svelte-oobfug">Create <code>src/app/tools.ts</code> with tools the model can call. Each tool uses <a href="https://zod.dev/" rel="nofollow">Zod</a> for input validation:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-keyword">import</span> { tool } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;ai&quot;</span>;
<span class="hljs-keyword">import</span> z <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;zod&quot;</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-title function_">createTools</span> = (<span class="hljs-params"></span>) =&gt; ({
<span class="hljs-attr">getCurrentTime</span>: <span class="hljs-title function_">tool</span>({
<span class="hljs-attr">description</span>: <span class="hljs-string">&quot;Get the current date and time.&quot;</span>,
<span class="hljs-attr">inputSchema</span>: z.<span class="hljs-title function_">object</span>({}),
<span class="hljs-attr">execute</span>: <span class="hljs-keyword">async</span> () =&gt; {
<span class="hljs-keyword">const</span> now = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>();
<span class="hljs-keyword">return</span> {
<span class="hljs-attr">timestamp</span>: now.<span class="hljs-title function_">toISOString</span>(),
<span class="hljs-attr">date</span>: now.<span class="hljs-title function_">toLocaleDateString</span>(<span class="hljs-string">&quot;en-US&quot;</span>, {
<span class="hljs-attr">weekday</span>: <span class="hljs-string">&quot;long&quot;</span>, <span class="hljs-attr">year</span>: <span class="hljs-string">&quot;numeric&quot;</span>, <span class="hljs-attr">month</span>: <span class="hljs-string">&quot;long&quot;</span>, <span class="hljs-attr">day</span>: <span class="hljs-string">&quot;numeric&quot;</span>,
}),
<span class="hljs-attr">time</span>: now.<span class="hljs-title function_">toLocaleTimeString</span>(<span class="hljs-string">&quot;en-US&quot;</span>, {
<span class="hljs-attr">hour</span>: <span class="hljs-string">&quot;2-digit&quot;</span>, <span class="hljs-attr">minute</span>: <span class="hljs-string">&quot;2-digit&quot;</span>, <span class="hljs-attr">second</span>: <span class="hljs-string">&quot;2-digit&quot;</span>, <span class="hljs-attr">hour12</span>: <span class="hljs-literal">true</span>,
}),
<span class="hljs-attr">timezone</span>: <span class="hljs-title class_">Intl</span>.<span class="hljs-title class_">DateTimeFormat</span>().<span class="hljs-title function_">resolvedOptions</span>().<span class="hljs-property">timeZone</span>,
};
},
}),
<span class="hljs-attr">randomNumber</span>: <span class="hljs-title function_">tool</span>({
<span class="hljs-attr">description</span>: <span class="hljs-string">&quot;Generate a random integer between min and max (inclusive).&quot;</span>,
<span class="hljs-attr">inputSchema</span>: z.<span class="hljs-title function_">object</span>({
<span class="hljs-attr">min</span>: z.<span class="hljs-title function_">number</span>().<span class="hljs-title function_">describe</span>(<span class="hljs-string">&quot;The minimum value (inclusive)&quot;</span>),
<span class="hljs-attr">max</span>: z.<span class="hljs-title function_">number</span>().<span class="hljs-title function_">describe</span>(<span class="hljs-string">&quot;The maximum value (inclusive)&quot;</span>),
}),
<span class="hljs-attr">execute</span>: <span class="hljs-keyword">async</span> ({ min, max }) =&gt; {
<span class="hljs-keyword">return</span> <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">floor</span>(<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>() * (<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">floor</span>(max) - <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">ceil</span>(min) + <span class="hljs-number">1</span>)) + <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">ceil</span>(min);
},
}),
<span class="hljs-attr">getLocation</span>: <span class="hljs-title function_">tool</span>({
<span class="hljs-attr">description</span>: <span class="hljs-string">&quot;Get the user&#x27;s current geographic location.&quot;</span>,
<span class="hljs-attr">inputSchema</span>: z.<span class="hljs-title function_">object</span>({}),
<span class="hljs-attr">needsApproval</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// requires user confirmation before executing</span>
<span class="hljs-attr">execute</span>: <span class="hljs-keyword">async</span> () =&gt; {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
navigator.<span class="hljs-property">geolocation</span>.<span class="hljs-title function_">getCurrentPosition</span>(
<span class="hljs-function">(<span class="hljs-params">pos</span>) =&gt;</span> <span class="hljs-title function_">resolve</span>({
<span class="hljs-attr">latitude</span>: pos.<span class="hljs-property">coords</span>.<span class="hljs-property">latitude</span>,
<span class="hljs-attr">longitude</span>: pos.<span class="hljs-property">coords</span>.<span class="hljs-property">longitude</span>,
}),
<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-title function_">reject</span>(err.<span class="hljs-property">message</span>),
);
});
},
}),
});<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-fcq603">The <code>getLocation</code> tool uses <code>needsApproval: true</code>, which means the AI SDK will pause execution and wait for the user to approve or reject the tool call before running it.</p> <h2 class="relative group"><a id="step-6-create-the-chat-transport" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-6-create-the-chat-transport"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Step 6: Create the chat transport</span></h2> <p data-svelte-h="svelte-yuv7mn">The Vercel AI SDK’s <code>useChat</code> hook needs a <a href="https://ai-sdk.dev/docs/ai-sdk-ui/transport" rel="nofollow">transport</a> that handles communication with the model. For client-side inference, we implement a custom <code>ChatTransport</code>.</p> <p data-svelte-h="svelte-56csg1">Create <code>src/app/chat-transport.ts</code>:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-keyword">import</span> {
<span class="hljs-title class_">ChatTransport</span>, <span class="hljs-title class_">UIMessageChunk</span>, streamText,
convertToModelMessages, <span class="hljs-title class_">ChatRequestOptions</span>,
createUIMessageStream, stepCountIs,
} <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;ai&quot;</span>;
<span class="hljs-keyword">import</span> {
<span class="hljs-title class_">TransformersJSLanguageModel</span>,
<span class="hljs-title class_">TransformersUIMessage</span>,
transformersJS,
} <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;@browser-ai/transformers-js&quot;</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-variable constant_">MODELS</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;./models&quot;</span>;
<span class="hljs-keyword">import</span> { createTools } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;./tools&quot;</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">TransformersChatTransport</span>
<span class="hljs-keyword">implements</span> <span class="hljs-title class_">ChatTransport</span>&lt;<span class="hljs-title class_">TransformersUIMessage</span>&gt;
{
<span class="hljs-keyword">private</span> <span class="hljs-attr">model</span>: <span class="hljs-title class_">TransformersJSLanguageModel</span>;
<span class="hljs-keyword">private</span> <span class="hljs-attr">tools</span>: <span class="hljs-title class_">ReturnType</span>&lt;<span class="hljs-keyword">typeof</span> createTools&gt;;
<span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> config = <span class="hljs-variable constant_">MODELS</span>[<span class="hljs-number">0</span>];
<span class="hljs-variable language_">this</span>.<span class="hljs-property">model</span> = <span class="hljs-title function_">transformersJS</span>(config.<span class="hljs-property">id</span>, {
<span class="hljs-attr">device</span>: config.<span class="hljs-property">device</span>,
<span class="hljs-attr">dtype</span>: config.<span class="hljs-property">dtype</span>,
...(config.<span class="hljs-property">supportsWorker</span>
? {
<span class="hljs-attr">worker</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Worker</span>(<span class="hljs-keyword">new</span> <span class="hljs-title function_">URL</span>(<span class="hljs-string">&quot;./worker.ts&quot;</span>, <span class="hljs-keyword">import</span>.<span class="hljs-property">meta</span>.<span class="hljs-property">url</span>), {
<span class="hljs-attr">type</span>: <span class="hljs-string">&quot;module&quot;</span>,
}),
}
: {}),
});
<span class="hljs-variable language_">this</span>.<span class="hljs-property">tools</span> = <span class="hljs-title function_">createTools</span>();
}
<span class="hljs-keyword">async</span> <span class="hljs-title function_">sendMessages</span>(
<span class="hljs-attr">options</span>: {
<span class="hljs-attr">chatId</span>: <span class="hljs-built_in">string</span>;
<span class="hljs-attr">messages</span>: <span class="hljs-title class_">TransformersUIMessage</span>[];
<span class="hljs-attr">abortSignal</span>: <span class="hljs-title class_">AbortSignal</span> | <span class="hljs-literal">undefined</span>;
} &amp; {
<span class="hljs-attr">trigger</span>: <span class="hljs-string">&quot;submit-message&quot;</span> | <span class="hljs-string">&quot;submit-tool-result&quot;</span> | <span class="hljs-string">&quot;regenerate-message&quot;</span>;
<span class="hljs-attr">messageId</span>: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span>;
} &amp; <span class="hljs-title class_">ChatRequestOptions</span>,
): <span class="hljs-title class_">Promise</span>&lt;<span class="hljs-title class_">ReadableStream</span>&lt;<span class="hljs-title class_">UIMessageChunk</span>&gt;&gt; {
<span class="hljs-keyword">const</span> { messages, abortSignal } = options;
<span class="hljs-keyword">const</span> prompt = <span class="hljs-keyword">await</span> <span class="hljs-title function_">convertToModelMessages</span>(messages);
<span class="hljs-keyword">return</span> createUIMessageStream&lt;<span class="hljs-title class_">TransformersUIMessage</span>&gt;({
<span class="hljs-attr">execute</span>: <span class="hljs-keyword">async</span> ({ writer }) =&gt; {
<span class="hljs-comment">// Track download progress if the model hasn&#x27;t been downloaded yet</span>
<span class="hljs-keyword">let</span> <span class="hljs-attr">downloadProgressId</span>: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span>;
<span class="hljs-keyword">const</span> availability = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">model</span>.<span class="hljs-title function_">availability</span>();
<span class="hljs-keyword">if</span> (availability !== <span class="hljs-string">&quot;available&quot;</span>) {
<span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">model</span>.<span class="hljs-title function_">createSessionWithProgress</span>(
<span class="hljs-function">(<span class="hljs-params">progress: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
<span class="hljs-keyword">const</span> percent = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">round</span>(progress * <span class="hljs-number">100</span>);
<span class="hljs-keyword">if</span> (progress &gt;= <span class="hljs-number">1</span>) {
<span class="hljs-keyword">if</span> (downloadProgressId) {
writer.<span class="hljs-title function_">write</span>({
<span class="hljs-attr">type</span>: <span class="hljs-string">&quot;data-modelDownloadProgress&quot;</span>,
<span class="hljs-attr">id</span>: downloadProgressId,
<span class="hljs-attr">data</span>: {
<span class="hljs-attr">status</span>: <span class="hljs-string">&quot;complete&quot;</span>, <span class="hljs-attr">progress</span>: <span class="hljs-number">100</span>,
<span class="hljs-attr">message</span>: <span class="hljs-string">&quot;Model ready!&quot;</span>,
},
});
}
<span class="hljs-keyword">return</span>;
}
<span class="hljs-keyword">if</span> (!downloadProgressId) {
downloadProgressId = <span class="hljs-string">`download-<span class="hljs-subst">${<span class="hljs-built_in">Date</span>.now()}</span>`</span>;
}
writer.<span class="hljs-title function_">write</span>({
<span class="hljs-attr">type</span>: <span class="hljs-string">&quot;data-modelDownloadProgress&quot;</span>,
<span class="hljs-attr">id</span>: downloadProgressId,
<span class="hljs-attr">data</span>: {
<span class="hljs-attr">status</span>: <span class="hljs-string">&quot;downloading&quot;</span>, <span class="hljs-attr">progress</span>: percent,
<span class="hljs-attr">message</span>: <span class="hljs-string">`Downloading model... <span class="hljs-subst">${percent}</span>%`</span>,
},
});
},
);
}
<span class="hljs-keyword">const</span> result = <span class="hljs-title function_">streamText</span>({
<span class="hljs-attr">model</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">model</span>,
<span class="hljs-attr">tools</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">tools</span>,
<span class="hljs-attr">stopWhen</span>: <span class="hljs-title function_">stepCountIs</span>(<span class="hljs-number">5</span>),
<span class="hljs-attr">messages</span>: prompt,
abortSignal,
});
writer.<span class="hljs-title function_">merge</span>(result.<span class="hljs-title function_">toUIMessageStream</span>({ <span class="hljs-attr">sendStart</span>: <span class="hljs-literal">false</span> }));
},
});
}
<span class="hljs-keyword">async</span> <span class="hljs-title function_">reconnectToStream</span>(): <span class="hljs-title class_">Promise</span>&lt;<span class="hljs-title class_">ReadableStream</span>&lt;<span class="hljs-title class_">UIMessageChunk</span>&gt; | <span class="hljs-literal">null</span>&gt; {
<span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}
}<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-1ew9lxu">Key parts of the transport:</p> <ul data-svelte-h="svelte-19ftkyp"><li><strong>Availability check</strong>: Determines if the model needs downloading before inference.</li> <li><strong>Progress streaming</strong>: Sends download progress as custom data parts (<code>data-modelDownloadProgress</code>) that the UI can render as a progress bar.</li> <li><strong>Tool support</strong>: Passes the tools to <code>streamText()</code> so the model can call them.</li> <li><strong>Step limiting</strong>: <code>stopWhen: stepCountIs(5)</code> prevents infinite tool-calling loops.</li></ul> <h2 class="relative group"><a id="step-7-build-the-chat-ui" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-7-build-the-chat-ui"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Step 7: Build the chat UI</span></h2> <p data-svelte-h="svelte-5i8jhk">Now wire everything together in your page component. Create <code>src/app/page.tsx</code>:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-string">&quot;use client&quot;</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;react&quot;</span>;
<span class="hljs-keyword">import</span> { useChat } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;@ai-sdk/react&quot;</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">TransformersUIMessage</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;@browser-ai/transformers-js&quot;</span>;
<span class="hljs-keyword">import</span> { lastAssistantMessageIsCompleteWithApprovalResponses } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;ai&quot;</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">TransformersChatTransport</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;./chat-transport&quot;</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">ChatPage</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> [input, setInput] = <span class="hljs-title function_">useState</span>(<span class="hljs-string">&quot;&quot;</span>);
<span class="hljs-keyword">const</span> {
messages,
sendMessage,
status,
stop,
addToolApprovalResponse,
} = useChat&lt;<span class="hljs-title class_">TransformersUIMessage</span>&gt;({
<span class="hljs-attr">transport</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">TransformersChatTransport</span>(),
<span class="hljs-attr">experimental_throttle</span>: <span class="hljs-number">75</span>,
<span class="hljs-comment">// Automatically resumes after tool approval responses are submitted</span>
<span class="hljs-attr">sendAutomaticallyWhen</span>: lastAssistantMessageIsCompleteWithApprovalResponses,
});
<span class="hljs-keyword">const</span> <span class="hljs-title function_">handleSubmit</span> = (<span class="hljs-params">e: React.FormEvent</span>) =&gt; {
e.<span class="hljs-title function_">preventDefault</span>();
<span class="hljs-keyword">if</span> (input.<span class="hljs-title function_">trim</span>() &amp;&amp; status === <span class="hljs-string">&quot;ready&quot;</span>) {
<span class="hljs-title function_">sendMessage</span>({ <span class="hljs-attr">text</span>: input });
<span class="hljs-title function_">setInput</span>(<span class="hljs-string">&quot;&quot;</span>);
}
};
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">maxWidth:</span> <span class="hljs-attr">600</span>, <span class="hljs-attr">margin:</span> &quot;<span class="hljs-attr">0</span> <span class="hljs-attr">auto</span>&quot;, <span class="hljs-attr">padding:</span> <span class="hljs-attr">24</span> }}&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>AI Chatbot<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
{messages.map((message) =&gt; (
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{message.id}</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginBottom:</span> <span class="hljs-attr">16</span> }}&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{message.role === &quot;user&quot; ? &quot;You&quot; : &quot;Assistant&quot;}:<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
{message.parts.map((part, i) =&gt; {
switch (part.type) {
case &quot;text&quot;:
return <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span>&gt;</span>{part.text}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>;
case &quot;data-modelDownloadProgress&quot;:
if (!part.data.message) return null;
return (
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{part.data.message}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
{part.data.status === &quot;downloading&quot; &amp;&amp; (
<span class="hljs-tag">&lt;<span class="hljs-name">progress</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{part.data.progress}</span> <span class="hljs-attr">max</span>=<span class="hljs-string">{100}</span> /&gt;</span>
)}
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
);
default:
// Handle tool parts
if (part.type.startsWith(&quot;tool-&quot;) &amp;&amp; &quot;state&quot; in part) {
if (
part.state === &quot;approval-requested&quot; &amp;&amp;
&quot;approval&quot; in part
) {
return (
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">border:</span> &quot;<span class="hljs-attr">1px</span> <span class="hljs-attr">solid</span> #<span class="hljs-attr">ccc</span>&quot;, <span class="hljs-attr">padding:</span> <span class="hljs-attr">8</span> }}&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Tool <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{part.type.replace(&quot;tool-&quot;, &quot;&quot;)}<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> wants to run.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span>
addToolApprovalResponse({ id: part.approval!.id, approved: true })
}&gt;
Approve
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span>
addToolApprovalResponse({
id: part.approval!.id, approved: false,
reason: &quot;User denied&quot;,
})
}&gt;
Deny
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
);
}
if (&quot;output&quot; in part &amp;&amp; part.output) {
return (
<span class="hljs-tag">&lt;<span class="hljs-name">pre</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">background:</span> &quot;#<span class="hljs-attr">f5f5f5</span>&quot;, <span class="hljs-attr">padding:</span> <span class="hljs-attr">8</span> }}&gt;</span>
{JSON.stringify(part.output, null, 2)}
<span class="hljs-tag">&lt;/<span class="hljs-name">pre</span>&gt;</span>
);
}
}
return null;
}
})}
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
))}
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{status === &quot;submitted&quot; &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">em</span>&gt;</span>Thinking...<span class="hljs-tag">&lt;/<span class="hljs-name">em</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>}
<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmit}</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span>
<span class="hljs-attr">value</span>=<span class="hljs-string">{input}</span>
<span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setInput(e.target.value)}
placeholder=&quot;Ask something...&quot;
style={{ width: &quot;100%&quot;, padding: 8 }}
/&gt;
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginTop:</span> <span class="hljs-attr">8</span> }}&gt;</span>
{status === &quot;streaming&quot; ? (
<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;button&quot;</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{stop}</span>&gt;</span>Stop<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
) : (
<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;submit&quot;</span> <span class="hljs-attr">disabled</span>=<span class="hljs-string">{!input.trim()}</span>&gt;</span>Send<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
)}
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);
}<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-1fmepox">The component renders message parts based on their <code>type</code>:</p> <ul data-svelte-h="svelte-1xtdjbi"><li><code>text</code> — standard text output from the model.</li> <li><code>data-modelDownloadProgress</code> — custom data parts sent by the transport during model download.</li> <li><code>tool-*</code> — tool call parts with states like <code>approval-requested</code>, <code>output-available</code>, etc.</li></ul> <p data-svelte-h="svelte-f6xors">The <code>sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses</code> option tells <code>useChat</code> to automatically resume generation after the user responds to a tool approval request.</p> <h2 class="relative group"><a id="step-8-run-the-application" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-8-run-the-application"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Step 8: Run the application</span></h2> <p data-svelte-h="svelte-1htrph">Start the development server:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START -->npm run dev<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-1ouaoyk">Open your browser and navigate to the URL shown in the terminal. The first time you send a message, the model will be downloaded and cached in the browser. Subsequent visits will load the cached model.</p> <p data-svelte-h="svelte-tp2g41">Try prompts like:</p> <ul data-svelte-h="svelte-knpt5y"><li>“What time is it?”</li> <li>“Generate a random number between 1 and 100”</li> <li>“Where am I located?” (this will trigger a tool approval prompt)</li></ul> <h2 class="relative group"><a id="next-steps" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#next-steps"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>Next steps</span></h2> <ul data-svelte-h="svelte-1uwz74p"><li>Add more models and a model selector — see the <a href="https://github.com/huggingface/transformers.js-examples/tree/main/next-vercel-ai-sdk-v6-tool-calling" rel="nofollow">full example source</a> for a multi-model implementation with Zustand state management.</li> <li>Add a browser compatibility check with <code>doesBrowserSupportTransformersJS()</code> and fall back to a server-side route if WebGPU is unavailable.</li> <li>Explore the <a href="https://ai-sdk.dev/docs/agents/overview" rel="nofollow">Vercel AI SDK agents documentation</a> for more complex agent patterns.</li> <li>See the <a href="../integrations/vercel-ai-sdk">Vercel AI SDK guide</a> for a reference of all supported features (embeddings, vision, transcription, etc.).</li></ul> <a class="!text-gray-400 !no-underline text-sm flex items-center not-prose mt-4" href="https://github.com/huggingface/transformers.js/blob/main/packages/transformers/docs/source/tutorials/next-ai-sdk.md" target="_blank"><svg class="mr-1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M31,16l-7,7l-1.41-1.41L28.17,16l-5.58-5.59L24,9l7,7z"></path><path d="M1,16l7-7l1.41,1.41L3.83,16l5.58,5.59L8,23l-7-7z"></path><path d="M12.419,25.484L17.639,6.552l1.932,0.518L14.351,26.002z"></path></svg> <span data-svelte-h="svelte-zjs2n5"><span class="underline">Update</span> on GitHub</span></a> <p></p>
<script>
{
__sveltekit_rvrl7f = {
assets: "/docs/transformers.js/pr_1649/en",
base: "/docs/transformers.js/pr_1649/en",
env: {}
};
const element = document.currentScript.parentElement;
const data = [null,null];
Promise.all([
import("/docs/transformers.js/pr_1649/en/_app/immutable/entry/start.5f753a22.js"),
import("/docs/transformers.js/pr_1649/en/_app/immutable/entry/app.db3dfcb7.js")
]).then(([kit, app]) => {
kit.start(app, element, {
node_ids: [0, 37],
data,
form: null,
error: null
});
});
}
</script>

Xet Storage Details

Size:
67.7 kB
·
Xet hash:
906f62881e47bd43e2b007eb6865a3fc9460fe255af3ccf6e21da07ac940d4a5

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.