Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tired-years-stick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-preact': minor
---

Create initial release for preact
317 changes: 317 additions & 0 deletions docs/api/ai-preact.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
---
title: "@tanstack/ai-preact"
slug: /api/ai-preact
order: 5
---

Preact hooks for TanStack AI, providing convenient Preact bindings for the headless client.

## Installation

```bash
npm install @tanstack/ai-preact
```

## `useChat(options?)`

Main hook for managing chat state in Preact with full type safety.

```typescript
import { useChat, fetchServerSentEvents } from "@tanstack/ai-preact";
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";

function ChatComponent() {
// Create client tool implementations
const updateUI = updateUIDef.client((input) => {
setNotification(input.message);
return { success: true };
});

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(updateUI);

const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});

// Fully typed messages!
type ChatMessages = InferChatMessages<typeof chatOptions>;

const { messages, sendMessage, isLoading, error, addToolApprovalResponse } =
useChat(chatOptions);

return <div>{/* Chat UI with typed messages */}</div>;
}
```

### Options

Extends `ChatClientOptions` from `@tanstack/ai-client`:

- `connection` - Connection adapter (required)
- `tools?` - Array of client tool implementations (with `.client()` method)
- `initialMessages?` - Initial messages array
- `id?` - Unique identifier for this chat instance
- `body?` - Additional body parameters to send
- `onResponse?` - Callback when response is received
- `onChunk?` - Callback when stream chunk is received
- `onFinish?` - Callback when response finishes
- `onError?` - Callback when error occurs
- `streamProcessor?` - Stream processing configuration

**Note:** Client tools are now automatically executed - no `onToolCall` callback needed!

### Returns

```typescript
interface UseChatReturn {
messages: UIMessage[];
sendMessage: (content: string) => Promise<void>;
append: (message: ModelMessage | UIMessage) => Promise<void>;
addToolResult: (result: {
toolCallId: string;
tool: string;
output: any;
state?: "output-available" | "output-error";
errorText?: string;
}) => Promise<void>;
addToolApprovalResponse: (response: {
id: string;
approved: boolean;
}) => Promise<void>;
reload: () => Promise<void>;
stop: () => void;
isLoading: boolean;
error: Error | undefined;
setMessages: (messages: UIMessage[]) => void;
clear: () => void;
}
```

## Connection Adapters

Re-exported from `@tanstack/ai-client` for convenience:

```typescript
import {
fetchServerSentEvents,
fetchHttpStream,
stream,
type ConnectionAdapter,
} from "@tanstack/ai-preact";
```

## Example: Basic Chat

```typescript
import { useState } from "preact/hooks";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-preact";

export function Chat() {
const [input, setInput] = useState("");

const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});

const handleSubmit = (e) => {
e.preventDefault();
if (input.trim() && !isLoading) {
sendMessage(input);
setInput("");
}
};

return (
<div>
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong>
{message.parts.map((part, idx) => {
if (part.type === "thinking") {
return (
<div key={idx} class="text-sm text-gray-500 italic">
💭 Thinking: {part.content}
</div>
);
}
if (part.type === "text") {
return <span key={idx}>{part.content}</span>;
}
return null;
})}
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
value={input}
onInput={(e) => setInput(e.currentTarget.value)}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
</div>
);
}
```

## Example: Tool Approval

```typescript
import { useChat, fetchServerSentEvents } from "@tanstack/ai-preact";

export function ChatWithApproval() {
const { messages, sendMessage, addToolApprovalResponse } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});

return (
<div>
{messages.map((message) =>
message.parts.map((part) => {
if (
part.type === "tool-call" &&
part.state === "approval-requested" &&
part.approval
) {
return (
<div key={part.id}>
<p>Approve: {part.name}</p>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval!.id,
approved: true,
})
}
>
Approve
</button>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval!.id,
approved: false,
})
}
>
Deny
</button>
</div>
);
}
return null;
})
)}
</div>
);
}
```

## Example: Client Tools with Type Safety

```typescript
import { useChat, fetchServerSentEvents } from "@tanstack/ai-preact";
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "./tool-definitions";
import { useState } from "preact/hooks";

export function ChatWithClientTools() {
const [notification, setNotification] = useState(null);

// Create client implementations
const updateUI = updateUIDef.client((input) => {
// ✅ input is fully typed!
setNotification({ message: input.message, type: input.type });
return { success: true };
});

const saveToStorage = saveToStorageDef.client((input) => {
localStorage.setItem(input.key, input.value);
return { saved: true };
});

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(updateUI, saveToStorage);

const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
tools, // ✅ Automatic execution, full type safety
});

return (
<div>
{messages.map((message) =>
message.parts.map((part) => {
if (part.type === "tool-call" && part.name === "updateUI") {
// ✅ part.input and part.output are fully typed!
return <div>Tool executed: {part.name}</div>;
}
})
)}
</div>
);
}
```

## `createChatClientOptions(options)`

Helper to create typed chat options (re-exported from `@tanstack/ai-client`).

```typescript
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(tool1, tool2);

const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});

type Messages = InferChatMessages<typeof chatOptions>;
```

## Types

Re-exported from `@tanstack/ai-client`:

- `UIMessage<TTools>` - Message type with tool type parameter
- `MessagePart<TTools>` - Message part with tool type parameter
- `TextPart` - Text content part
- `ThinkingPart` - Thinking content part
- `ToolCallPart<TTools>` - Tool call part (discriminated union)
- `ToolResultPart` - Tool result part
- `ChatClientOptions<TTools>` - Chat client options
- `ConnectionAdapter` - Connection adapter interface
- `InferChatMessages<T>` - Extract message type from options

Re-exported from `@tanstack/ai`:

- `toolDefinition()` - Create isomorphic tool definition
- `ToolDefinitionInstance` - Tool definition type
- `ClientTool` - Client tool type
- `ServerTool` - Server tool type

## Next Steps

- [Getting Started](../getting-started/quick-start) - Learn the basics
- [Tools Guide](../guides/tools) - Learn about the isomorphic tool system
- [Client Tools](../guides/client-tools) - Learn about client-side tools
4 changes: 4 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@
"label": "@tanstack/ai-react",
"to": "api/ai-react"
},
{
"label": "@tanstack/ai-preact",
"to": "api/ai-preact"
},
{
"label": "@tanstack/ai-solid",
"to": "api/ai-solid"
Expand Down
1 change: 1 addition & 0 deletions packages/typescript/ai-preact/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @tanstack/ai-preact
Loading
Loading