From be4108d350039ff1ab5c3edec7e17d516c919c52 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Tue, 26 May 2026 22:15:20 +0200 Subject: [PATCH 1/4] feat: add React Native AI support --- .changeset/react-native-ai-support.md | 26 + docs/advanced/tree-shaking.md | 6 +- docs/api/ai-client.md | 69 +- docs/api/ai-react.md | 25 + docs/chat/connection-adapters.md | 68 +- docs/config.json | 4 + docs/getting-started/overview.md | 2 + .../quick-start-react-native.md | 300 + docs/getting-started/quick-start.md | 4 + docs/structured-outputs/multi-turn.md | 5 + examples/ts-react-native-chat/.gitignore | 3 + examples/ts-react-native-chat/README.md | 216 + examples/ts-react-native-chat/app.json | 11 + examples/ts-react-native-chat/index.ts | 4 + .../ts-react-native-chat/metro.config.cjs | 80 + examples/ts-react-native-chat/package.json | 44 + examples/ts-react-native-chat/scripts/dev.mjs | 372 ++ .../ts-react-native-chat/scripts/dev.test.mjs | 284 + .../scripts/smoke-server.ts | 208 + .../scripts/verify-react-resolution.mjs | 75 + examples/ts-react-native-chat/src/App.tsx | 1104 ++++ .../ts-react-native-chat/src/server/app.ts | 449 ++ .../ts-react-native-chat/src/server/index.ts | 21 + examples/ts-react-native-chat/tsconfig.json | 31 + package.json | 7 +- packages/ai-client/src/chat-client.ts | 4 +- packages/ai-client/src/connection-adapters.ts | 428 +- packages/ai-client/src/events.ts | 2 +- packages/ai-client/src/generation-client.ts | 2 +- packages/ai-client/src/generation-types.ts | 2 +- packages/ai-client/src/index.ts | 10 +- packages/ai-client/src/realtime-client.ts | 4 +- packages/ai-client/src/realtime-types.ts | 2 +- packages/ai-client/src/response-stream.ts | 42 + packages/ai-client/src/sse-parser.ts | 16 +- packages/ai-client/src/sse-utils.ts | 8 + packages/ai-client/src/tool-types.ts | 2 +- packages/ai-client/src/types.ts | 4 +- .../ai-client/src/video-generation-client.ts | 2 +- .../ai-client/tests/chat-client-abort.test.ts | 4 +- packages/ai-client/tests/chat-client.test.ts | 4 +- packages/ai-client/tests/chat-fetcher.test.ts | 28 +- .../tests/connection-adapters-abort.test.ts | 2 +- .../tests/connection-adapters-xhr.test.ts | 439 ++ .../tests/connection-adapters.test.ts | 145 +- .../ai-client/tests/generation-client.test.ts | 72 +- .../tests/infer-chat-messages.test.ts | 2 +- packages/ai-client/tests/test-utils.ts | 2 +- packages/ai-client/tests/tool-types.test.ts | 2 +- .../tests/video-generation-client.test.ts | 4 +- packages/ai-devtools/src/env.d.ts | 5 + packages/ai-devtools/tsconfig.json | 1 + packages/ai-preact/src/index.ts | 3 + packages/ai-react/src/index.ts | 3 + packages/ai-react/src/types.ts | 2 +- packages/ai-react/src/use-chat.ts | 2 +- packages/ai-solid/src/index.ts | 3 + packages/ai-svelte/src/index.ts | 3 + packages/ai-vue/src/index.ts | 3 + packages/ai/package.json | 4 + packages/ai/src/client.ts | 132 + packages/ai/vite.config.ts | 1 + packages/preact-ai-devtools/src/env.d.ts | 5 + packages/react-ai-devtools/src/env.d.ts | 5 + packages/solid-ai-devtools/src/env.d.ts | 5 + pnpm-lock.yaml | 5794 ++++++++++++++++- pnpm-workspace.yaml | 3 + testing/react-native-smoke/.gitignore | 2 + testing/react-native-smoke/README.md | 20 + testing/react-native-smoke/app.json | 11 + testing/react-native-smoke/index.ts | 4 + testing/react-native-smoke/metro.config.cjs | 54 + testing/react-native-smoke/package.json | 38 + .../scripts/assert-bundle-output.ts | 60 + .../scripts/assert-import-surface.ts | 320 + .../scripts/esbuild-smoke.ts | 31 + .../scripts/react-native-runtime-stub.tsx | 41 + testing/react-native-smoke/src/App.tsx | 171 + testing/react-native-smoke/tsconfig.json | 24 + 79 files changed, 11040 insertions(+), 360 deletions(-) create mode 100644 .changeset/react-native-ai-support.md create mode 100644 docs/getting-started/quick-start-react-native.md create mode 100644 examples/ts-react-native-chat/.gitignore create mode 100644 examples/ts-react-native-chat/README.md create mode 100644 examples/ts-react-native-chat/app.json create mode 100644 examples/ts-react-native-chat/index.ts create mode 100644 examples/ts-react-native-chat/metro.config.cjs create mode 100644 examples/ts-react-native-chat/package.json create mode 100644 examples/ts-react-native-chat/scripts/dev.mjs create mode 100644 examples/ts-react-native-chat/scripts/dev.test.mjs create mode 100644 examples/ts-react-native-chat/scripts/smoke-server.ts create mode 100644 examples/ts-react-native-chat/scripts/verify-react-resolution.mjs create mode 100644 examples/ts-react-native-chat/src/App.tsx create mode 100644 examples/ts-react-native-chat/src/server/app.ts create mode 100644 examples/ts-react-native-chat/src/server/index.ts create mode 100644 examples/ts-react-native-chat/tsconfig.json create mode 100644 packages/ai-client/src/response-stream.ts create mode 100644 packages/ai-client/src/sse-utils.ts create mode 100644 packages/ai-client/tests/connection-adapters-xhr.test.ts create mode 100644 packages/ai-devtools/src/env.d.ts create mode 100644 packages/ai/src/client.ts create mode 100644 packages/preact-ai-devtools/src/env.d.ts create mode 100644 packages/react-ai-devtools/src/env.d.ts create mode 100644 packages/solid-ai-devtools/src/env.d.ts create mode 100644 testing/react-native-smoke/.gitignore create mode 100644 testing/react-native-smoke/README.md create mode 100644 testing/react-native-smoke/app.json create mode 100644 testing/react-native-smoke/index.ts create mode 100644 testing/react-native-smoke/metro.config.cjs create mode 100644 testing/react-native-smoke/package.json create mode 100644 testing/react-native-smoke/scripts/assert-bundle-output.ts create mode 100644 testing/react-native-smoke/scripts/assert-import-surface.ts create mode 100644 testing/react-native-smoke/scripts/esbuild-smoke.ts create mode 100644 testing/react-native-smoke/scripts/react-native-runtime-stub.tsx create mode 100644 testing/react-native-smoke/src/App.tsx create mode 100644 testing/react-native-smoke/tsconfig.json diff --git a/.changeset/react-native-ai-support.md b/.changeset/react-native-ai-support.md new file mode 100644 index 000000000..5864d508c --- /dev/null +++ b/.changeset/react-native-ai-support.md @@ -0,0 +1,26 @@ +--- +'@tanstack/ai': minor +'@tanstack/ai-client': minor +'@tanstack/ai-react': minor +'@tanstack/ai-preact': minor +'@tanstack/ai-solid': minor +'@tanstack/ai-svelte': minor +'@tanstack/ai-vue': minor +--- + +Add React Native support for chat clients and framework hooks, including +client-safe streaming utilities and connection adapters that work in mobile +environments. + +The `fetcher` option is now available on `ChatClient` and the framework chat +hooks (`useChat` / `createChat`), mirroring the generation hooks. Pass either +`connection` or `fetcher` -- the XOR is enforced at the type level via +`ChatTransport`. Fetchers may return either a `Response` (parsed as SSE) or an +`AsyncIterable` (yielded directly). + +The client-safe `@tanstack/ai/client` subpath is now public for framework +packages and mobile bundles. `stream()`, `fetchServerSentEvents`, +`fetchHttpStream`, `rpcStream`, `xhrServerSentEvents`, and `xhrHttpStream` are +available from the client package and framework re-exports. React Native docs, +an Expo chat example, and smoke tests are included for the supported mobile +setup. diff --git a/docs/advanced/tree-shaking.md b/docs/advanced/tree-shaking.md index 51b6162e6..9f34060cc 100644 --- a/docs/advanced/tree-shaking.md +++ b/docs/advanced/tree-shaking.md @@ -252,6 +252,11 @@ Modern bundlers (Vite, Webpack, Rollup, esbuild) can easily eliminate unused cod 2. **Use specific adapter functions** - Import `openaiText` not `openai` 3. **Separate activities by route** - Different API routes can use different activities 4. **Lazy load when possible** - Use dynamic imports for code-split routes +5. **Keep mobile chat bundles client-only** - React Native and Expo chat screens + should import `useChat` and chat connection adapters, not provider SDKs, + server response helpers, React DOM UI, devtools UI, or other framework + packages. See [Quick Start: React Native](../getting-started/quick-start-react-native) + for the server-only provider boundary and mobile transport setup. ```ts // ✅ Good - Only imports chat @@ -295,4 +300,3 @@ TanStack AI's tree-shakeable design means: - ✅ **Flexibility** - Mix and match activities and adapters as needed The functional, modular architecture ensures that modern bundlers can eliminate unused code effectively, resulting in optimal bundle sizes for your application. - diff --git a/docs/api/ai-client.md b/docs/api/ai-client.md index bf9f6eda6..886b44e86 100644 --- a/docs/api/ai-client.md +++ b/docs/api/ai-client.md @@ -144,6 +144,10 @@ await client.addToolApprovalResponse({ ## Connection Adapters +For a complete transport walkthrough, see +[Connection Adapters](../chat/connection-adapters). For React Native and Expo, +see [Quick Start: React Native](../getting-started/quick-start-react-native). + ### `fetchServerSentEvents(url, options?)` Creates an SSE connection adapter. @@ -160,7 +164,8 @@ const adapter = fetchServerSentEvents("/api/chat", { ### `fetchHttpStream(url, options?)` -Creates an HTTP stream connection adapter. +Creates a newline-delimited JSON HTTP stream connection adapter. Pair it with +`toHttpResponse()` on the server. ```typescript import { fetchHttpStream } from "@tanstack/ai-client"; @@ -168,6 +173,68 @@ import { fetchHttpStream } from "@tanstack/ai-client"; const adapter = fetchHttpStream("/api/chat"); ``` +`fetchHttpStream()` requires a runtime with streaming `fetch`, +`Response.body.getReader()`, and `TextDecoder`. If the runtime cannot expose an +incremental response body, it throws `UnsupportedResponseStreamError`; use the +XHR adapters in React Native or Expo. + +### `xhrHttpStream(url, options?)` + +Creates an `XMLHttpRequest`-backed newline-delimited JSON stream adapter. This +is the recommended default for React Native and Expo chat screens. Pair it with +`toHttpResponse()` on the server. + +```typescript +import { xhrHttpStream } from "@tanstack/ai-client"; + +const adapter = xhrHttpStream("http://192.168.1.10:8787/chat/http", { + headers: { Authorization: "Bearer token" }, + withCredentials: true, +}); +``` + +### `xhrServerSentEvents(url, options?)` + +Creates an `XMLHttpRequest`-backed SSE adapter for runtimes where XHR progress +events are more reliable than streaming `fetch`. Pair it with +`toServerSentEventsResponse()` on the server. + +```typescript +import { xhrServerSentEvents } from "@tanstack/ai-client"; + +const adapter = xhrServerSentEvents("http://192.168.1.10:8787/chat/sse"); +``` + +### Adapter options + +Fetch adapters accept: + +- `headers?: Record | Headers` +- `credentials?: RequestCredentials` +- `signal?: AbortSignal` +- `body?: Record` +- `fetchClient?: typeof globalThis.fetch` + +XHR adapters accept: + +- `headers?: Record | Headers` +- `withCredentials?: boolean` +- `signal?: AbortSignal` +- `body?: Record` +- `xhrFactory?: () => XMLHttpRequest` + +`body` is merged into the AG-UI `forwardedProps` payload. Values from +`forwardedProps` on the client and per-message `sendMessage(..., data)` calls +override static adapter `body` values. + +### Stream errors + +- `UnsupportedResponseStreamError` - thrown by fetch-based adapters when + `Response.body`, `Response.body.getReader()`, or `TextDecoder` is missing. +- `StreamTruncatedError` - thrown when an SSE or NDJSON stream ends with + unterminated trailing data, usually because the server, proxy, or network cut + the connection mid-line. + ### `stream(connectFn)` Creates a custom connection adapter. diff --git a/docs/api/ai-react.md b/docs/api/ai-react.md index ac10e1667..bf496be99 100644 --- a/docs/api/ai-react.md +++ b/docs/api/ai-react.md @@ -13,6 +13,12 @@ keywords: --- React hooks for TanStack AI, providing convenient React bindings for the headless client. +For React Native, the documented support surface is narrow: `useChat` with chat +connection adapters. React DOM-specific UI packages and TanStack AI devtools UI +are not part of the React Native support surface. + +For a complete native journey, see +[Quick Start: React Native](../getting-started/quick-start-react-native). ## Installation @@ -111,11 +117,30 @@ Re-exported from `@tanstack/ai-client` for convenience: import { fetchServerSentEvents, fetchHttpStream, + xhrServerSentEvents, + xhrHttpStream, stream, type ConnectionAdapter, + type FetchConnectionOptions, + type XhrConnectionOptions, } from "@tanstack/ai-react"; ``` +For React Native or Expo chat screens, use an absolute server URL and prefer +`xhrHttpStream()` with a server route that returns `toHttpResponse()`. Use +`xhrServerSentEvents()` with `toServerSentEventsResponse()` when you want SSE. +Use `fetchHttpStream()` only when the runtime supports streaming `fetch`, +`Response.body.getReader()`, and `TextDecoder`; otherwise it throws +`UnsupportedResponseStreamError`. + +XHR adapter options include `headers`, `withCredentials`, `signal`, `body`, and +`xhrFactory`. Fetch adapter options include `headers`, `credentials`, `signal`, +`body`, and `fetchClient`. Both option objects may be provided directly or as a +function that resolves per request. + +For error narrowing, import `UnsupportedResponseStreamError` and +`StreamTruncatedError` from `@tanstack/ai-client`. + ## Example: Basic Chat ```typescript diff --git a/docs/chat/connection-adapters.md b/docs/chat/connection-adapters.md index d11049fd0..46b79a2fb 100644 --- a/docs/chat/connection-adapters.md +++ b/docs/chat/connection-adapters.md @@ -26,7 +26,8 @@ This page covers every supported transport, when to pick which, and how to build | You have… | Use | | --- | --- | | A normal HTTP server and want the default | [`fetchServerSentEvents`](#server-sent-events-sse) | -| An environment that blocks SSE (some edge runtimes, RN, strict proxies) | [`fetchHttpStream`](#http-streaming-ndjson) | +| An environment that blocks SSE (some edge runtimes, strict proxies) | [`fetchHttpStream`](#http-streaming-ndjson) | +| React Native or Expo | [`xhrHttpStream`](#react-native-and-expo) by default, [`xhrServerSentEvents`](#react-native-and-expo) for SSE, or [`fetchHttpStream`](#http-streaming-ndjson) only when streaming `fetch` is available | | A TanStack Start (or other) server function that already returns an async iterable | [`stream`](#server-functions-and-direct-async-iterables) | | An RPC framework like Cap'n Web, gRPC-Web, or tRPC | [`rpcStream`](#rpc-streams) | | A single long-lived WebSocket (or BroadcastChannel, postMessage, shared worker) serving many runs | [Custom `subscribe` / `send` adapter](#persistent-transports-websockets-and-friends) | @@ -90,6 +91,71 @@ const { messages } = useChat({ Server-side, write each chunk as `JSON.stringify(chunk) + "\n"` to the response body. Options (`url`, `headers`, `body`, `fetchClient`, dynamic functions) match `fetchServerSentEvents` exactly. +## React Native and Expo + +You have a native app that needs to call your own backend rather than a +same-origin browser route. Use `useChat` from `@tanstack/ai-react` with an +explicit chat transport and an absolute URL. By the end of this section, the +client adapter and server response helper will be paired correctly for React +Native or Expo. + +```typescript +const baseUrl = + process.env.EXPO_PUBLIC_TANSTACK_AI_BASE_URL ?? + 'http://127.0.0.1:8787' +const httpUrl = `${baseUrl}/chat/http` +const sseUrl = `${baseUrl}/chat/sse` +``` + +Use the URL your runtime can reach. iOS simulators can often use `localhost` or +`127.0.0.1`, Android emulators commonly use `10.0.2.2` to reach the host +machine, and physical devices need a LAN or tunneled URL. + +Prefer `xhrHttpStream()` for Expo and React Native. It pairs with +`toHttpResponse()` and reads newline-delimited JSON through incremental XHR +progress events: + +```typescript +import { useChat, xhrHttpStream } from "@tanstack/ai-react"; + +const chat = useChat({ + connection: xhrHttpStream(httpUrl), +}); +``` + +Use `xhrServerSentEvents()` when your server returns `text/event-stream` via +`toServerSentEventsResponse()`: + +```typescript +import { useChat, xhrServerSentEvents } from "@tanstack/ai-react"; + +const chat = useChat({ + connection: xhrServerSentEvents(sseUrl), +}); +``` + +Only use `fetchHttpStream()` if your exact React Native runtime exposes +streaming `fetch` responses, `Response.body.getReader()`, and `TextDecoder`. +The server still returns newline-delimited JSON with `toHttpResponse()`: + +```typescript +import { useChat, fetchHttpStream } from "@tanstack/ai-react"; + +const chat = useChat({ + connection: fetchHttpStream(httpUrl), +}); +``` + +If one of those fetch-streaming APIs is missing, `fetchHttpStream()` throws +`UnsupportedResponseStreamError`. A polyfill that buffers the response does not +make fetch streaming compatible; the adapter needs incremental bytes. Switch to +`xhrHttpStream()` or `xhrServerSentEvents()` instead. + +Keep provider SDKs and server helpers on your backend. The React Native bundle +should import hooks and connection adapters, not OpenAI/Anthropic/Gemini SDKs, +React DOM UI, devtools UI, or other framework packages. For a complete mobile +walkthrough, see [Quick Start: React Native](../getting-started/quick-start-react-native). + ## Server Functions and Direct Async Iterables When your client can call into your server without going over HTTP — TanStack Start server functions, RSC streams, in-process tests — skip the transport entirely. `stream()` takes a factory that returns an `AsyncIterable` and wires it straight into the client: diff --git a/docs/config.json b/docs/config.json index 0f6b99f60..eb9f7a97c 100644 --- a/docs/config.json +++ b/docs/config.json @@ -17,6 +17,10 @@ "label": "Quick Start: React", "to": "getting-started/quick-start" }, + { + "label": "Quick Start: React Native", + "to": "getting-started/quick-start-react-native" + }, { "label": "Devtools", "to": "getting-started/devtools" diff --git a/docs/getting-started/overview.md b/docs/getting-started/overview.md index 187d7168d..bba915f5f 100644 --- a/docs/getting-started/overview.md +++ b/docs/getting-started/overview.md @@ -32,6 +32,7 @@ The framework-agnostic core of TanStack AI provides the building blocks for crea - **Next.js** - API routes and App Router - **TanStack Start** - React Start or Solid Start (recommended!) +- **React Native / Expo** - Native chat screens with `useChat`, absolute server URLs, and XHR streaming transports - **Express** - Node.js server - **React Router v7** - Loaders and actions @@ -114,5 +115,6 @@ With the help of adapters, TanStack AI can connect to various LLM providers. Ava ## Next Steps - [Quick Start Guide](./quick-start) - Get up and running in minutes +- [Quick Start: React Native](./quick-start-react-native) - Add mobile chat with Expo and a server-owned provider boundary - [Tools Guide](../tools/tools) - Learn about the isomorphic tool system - [API Reference](../api/ai) - Explore the full API diff --git a/docs/getting-started/quick-start-react-native.md b/docs/getting-started/quick-start-react-native.md new file mode 100644 index 000000000..1ad5ccc9e --- /dev/null +++ b/docs/getting-started/quick-start-react-native.md @@ -0,0 +1,300 @@ +--- +title: "Quick Start: React Native" +id: quick-start-react-native +order: 3 +description: "Build a React Native or Expo chat screen with TanStack AI's useChat hook, a server-only OpenAI backend, and mobile-compatible streaming transports." +keywords: + - tanstack ai + - react native + - expo + - mobile + - useChat + - streaming + - xhrHttpStream + - openai +--- + +You have a React Native or Expo app and you want to add streaming AI chat +without putting provider SDKs or API keys in the native bundle. By the end of +this guide, your app will call a server-owned Hono route with `useChat` from +`@tanstack/ai-react`, stream responses over a mobile-compatible transport, and +keep `OPENAI_API_KEY` / `OPENAI_MODEL` on the server. + +> **Coming from the web quick start?** The hook is the same, but the URL and +> transport are different. React Native needs an absolute backend URL, not +> `/api/chat`, and most Expo runtimes should start with `xhrHttpStream()`. + +## 1. Install packages + +Install TanStack AI, the React hook package, the OpenAI adapter for your +server, and Hono for the example backend: + +```bash +pnpm add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai hono @hono/node-server zod +``` + +If your Expo app lives in a workspace, run the command from the app package or +use your workspace filter. + +## 2. Keep OpenAI on the server + +Create a Hono route that owns the model, API key, and response format. The +native app sends chat messages to this route; it never imports +`@tanstack/ai-openai` and never receives `OPENAI_API_KEY`. + +```ts +// server.ts +import { serve } from '@hono/node-server' +import { chat, toHttpResponse, toServerSentEventsResponse } from '@tanstack/ai' +import { openaiText } from '@tanstack/ai-openai' +import { Hono } from 'hono' + +const app = new Hono() + +const model = process.env.OPENAI_MODEL ?? 'gpt-5.2' + +function requireOpenAIKey() { + if (!process.env.OPENAI_API_KEY) { + throw new Error('OPENAI_API_KEY is not configured on the server') + } +} + +app.get('/health', (c) => c.json({ ok: true })) + +app.post('/chat/http', async (c) => { + requireOpenAIKey() + const body = await c.req.json() + const stream = chat({ + adapter: openaiText(model), + messages: body.messages, + }) + + return toHttpResponse(stream, { + headers: { + 'Content-Type': 'application/x-ndjson', + 'Cache-Control': 'no-cache', + }, + }) +}) + +app.post('/chat/sse', async (c) => { + requireOpenAIKey() + const body = await c.req.json() + const stream = chat({ + adapter: openaiText(model), + messages: body.messages, + }) + + return toServerSentEventsResponse(stream) +}) + +serve({ + fetch: app.fetch, + hostname: '0.0.0.0', + port: Number(process.env.PORT ?? 8787), +}) +``` + +Set server-only environment variables where the Hono process runs: + +```env +OPENAI_API_KEY=sk-... +OPENAI_MODEL=gpt-5.2 +``` + +Run the Hono server before starting the native app. For a TypeScript-only +example, install `tsx` and add a script: + +```bash +pnpm add -D tsx +pnpm pkg set scripts.dev:server="tsx server.ts" +pnpm dev:server +``` + +> **Route pairing matters:** `xhrHttpStream()` and `fetchHttpStream()` expect +> the newline-delimited JSON response from `toHttpResponse()`. +> `xhrServerSentEvents()` expects the `text/event-stream` response from +> `toServerSentEventsResponse()`. + +## 3. Configure a native-reachable URL + +React Native is not served from your backend origin, so `/api/chat` cannot work +as a default. Expose the backend URL to Expo with a public variable: + +```env +EXPO_PUBLIC_TANSTACK_AI_BASE_URL=http://192.168.1.10:8787 +``` + +Use the address your device can reach: + +- iOS simulator: `http://127.0.0.1:8787` often works. +- Android emulator: use `http://10.0.2.2:8787`. +- Physical device: use your computer's LAN IP, for example + `http://192.168.1.10:8787`, or a tunneled HTTPS URL. + +Only `EXPO_PUBLIC_*` values are bundled into the app. Keep provider keys as +plain server variables such as `OPENAI_API_KEY`. + +## 4. Use `useChat` in your native screen + +Start with `xhrHttpStream()` for Expo and React Native. It reads the same +newline-delimited JSON produced by `toHttpResponse()` and relies on XHR progress +events, which are usually more reliable on phone runtimes than streaming +`fetch`. + +```tsx +// ChatScreen.tsx +import { useState } from 'react' +import { Button, ScrollView, Text, TextInput, View } from 'react-native' +import { useChat, xhrHttpStream } from '@tanstack/ai-react' + +const baseUrl = + process.env.EXPO_PUBLIC_TANSTACK_AI_BASE_URL ?? 'http://127.0.0.1:8787' + +export function ChatScreen() { + const [input, setInput] = useState('') + const { messages, sendMessage, isLoading, error } = useChat({ + connection: xhrHttpStream(`${baseUrl}/chat/http`), + }) + + async function send() { + const text = input.trim() + if (!text || isLoading) return + setInput('') + await sendMessage(text) + } + + return ( + + + {messages.map((message) => ( + + {message.role} + {message.parts.map((part, index) => + part.type === 'text' ? ( + {part.content} + ) : null, + )} + + ))} + + + {error ? {error.message} : null} + + +