-
-
Notifications
You must be signed in to change notification settings - Fork 95
Add preact integration #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
9e87162
8c8fbbd
f6fe950
07890c7
3848799
c13a2dd
42d16a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@tanstack/ai-preact': minor | ||
| --- | ||
|
|
||
| Create initial release for preact |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # @tanstack/ai-preact |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,157 @@ | ||||||
| <div align="center"> | ||||||
| <img src="./media/header_ai.png" > | ||||||
| </div> | ||||||
|
|
||||||
| <br /> | ||||||
|
|
||||||
| <div align="center"> | ||||||
| <a href="https://npmjs.com/package/@tanstack/ai" target="\_parent"> | ||||||
| <img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" /> | ||||||
| </a> | ||||||
| <a href="https://github.com/TanStack/ai" target="\_parent"> | ||||||
| <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" /> | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace hard tabs with spaces for consistent indentation. Lines 12, 23, 24, and 25 use hard tabs instead of spaces, which violates MD010. Standardize to spaces. 🔎 Proposed fix <a href="https://github.com/TanStack/ai" target="\_parent">
- <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
+ <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>
<div align="center">
<a href="#badge">
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
- <a href="#badge">
- <img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
- </a>
+ <a href="#badge">
+ <img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
+ </a>
<a href="https://twitter.com/tan_stack">
<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>Also applies to: 23-25 🧰 Tools🪛 markdownlint-cli2 (0.18.1)12-12: Hard tabs (MD010, no-hard-tabs) 🤖 Prompt for AI Agents |
||||||
| </a> | ||||||
| <a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent"> | ||||||
| <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" /> | ||||||
| </a> | ||||||
| </div> | ||||||
|
|
||||||
| <div align="center"> | ||||||
| <a href="#badge"> | ||||||
| <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg"> | ||||||
| </a> | ||||||
| <a href="#badge"> | ||||||
| <img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/> | ||||||
| </a> | ||||||
| <a href="https://twitter.com/tan_stack"> | ||||||
| <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/> | ||||||
| </a> | ||||||
| </div> | ||||||
|
|
||||||
| <div align="center"> | ||||||
|
|
||||||
| ### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/) | ||||||
| </div> | ||||||
|
|
||||||
| # TanStack AI | ||||||
|
|
||||||
| A powerful, type-safe AI SDK for building AI-powered applications. | ||||||
|
|
||||||
| - Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.) | ||||||
| - **Tree-shakeable adapters** - Import only what you need for smaller bundles | ||||||
| - **Multimodal content support** - Send images, audio, video, and documents | ||||||
| - **Image generation** - Generate images with OpenAI DALL-E/GPT-Image and Gemini Imagen | ||||||
| - Chat completion, streaming, and agent loop strategies | ||||||
| - Headless chat state management with adapters (SSE, HTTP stream, custom) | ||||||
| - Isomorphic type-safe tools with server/client execution | ||||||
| - **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions | ||||||
|
|
||||||
| ### <a href="https://tanstack.com/ai">Read the docs →</b></a> | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix heading level hierarchy. Line 49 uses 🔎 Proposed fix-### <a href="https://tanstack.com/ai">Read the docs →</b></a>
+## <a href="https://tanstack.com/ai">Read the docs →</b></a>📝 Committable suggestion
Suggested change
🧰 Tools🪛 markdownlint-cli2 (0.18.1)49-49: Heading levels should only increment by one level at a time (MD001, heading-increment) 🤖 Prompt for AI Agents |
||||||
|
|
||||||
| ## Tree-Shakeable Adapters | ||||||
|
|
||||||
| Import only the functionality you need for smaller bundle sizes: | ||||||
|
|
||||||
| ```typescript | ||||||
| // Only chat functionality - no summarization code bundled | ||||||
| import { openaiText } from '@tanstack/ai-openai/adapters' | ||||||
| import { generate } from '@tanstack/ai' | ||||||
|
|
||||||
| const textAdapter = openaiText() | ||||||
|
|
||||||
| const result = generate({ | ||||||
| adapter: textAdapter, | ||||||
| model: 'gpt-4o', | ||||||
| messages: [{ role: 'user', content: [{ type: 'text', content: 'Hello!' }] }], | ||||||
| }) | ||||||
|
|
||||||
| for await (const chunk of result) { | ||||||
| console.log(chunk) | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| Available adapters: `openaiText`, `openaiEmbed`, `openaiSummarize`, `anthropicText`, `geminiText`, `ollamaText`, and more. | ||||||
|
Comment on lines
+36
to
+73
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add Preact-specific documentation and usage examples. The README contains generic TanStack AI content but lacks Preact-specific documentation. Users need to see:
The current tree-shakeable adapters example (lines 55-71) uses generic 💡 Suggested structureConsider adding a "Quick Start" section after the feature list with a Preact-specific example: ## Quick Start
### Installation
\`\`\`bash
npm install @tanstack/ai-preact @tanstack/ai-client
\`\`\`
**Requirements:** Preact >= 10.11.0
### Basic Usage
\`\`\`tsx
import { useChat } from '@tanstack/ai-preact'
import { ChatClient } from '@tanstack/ai-client'
import { openaiText } from '@tanstack/ai-openai/adapters'
const chatClient = new ChatClient({
adapter: openaiText(),
model: 'gpt-4o',
})
function ChatComponent() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
client: chatClient,
})
return (
<div>
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong> {message.content}
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={handleInputChange}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
</div>
)
}
\`\`\`🧰 Tools🪛 markdownlint-cli2 (0.18.1)49-49: Heading levels should only increment by one level at a time (MD001, heading-increment) 🤖 Prompt for AI Agents |
||||||
|
|
||||||
| ## Bonus: TanStack Start Integration | ||||||
|
|
||||||
| TanStack AI works with **any** framework (Next.js, Express, Remix, etc.). | ||||||
|
|
||||||
| **With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`: | ||||||
|
|
||||||
| ```typescript | ||||||
| import { createServerFnTool } from '@tanstack/ai-preact' | ||||||
|
|
||||||
JoviDeCroock marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| // Define once, get AI tool AND server function (TanStack Start only) | ||||||
| const getProducts = createServerFnTool({ | ||||||
| name: 'getProducts', | ||||||
| inputSchema: z.object({ query: z.string() }), | ||||||
| execute: async ({ query }) => db.products.search(query), | ||||||
| }) | ||||||
|
|
||||||
| // Use in AI chat | ||||||
| chat({ tools: [getProducts.server] }) | ||||||
|
|
||||||
| // Call directly from components (no API endpoint needed!) | ||||||
| const products = await getProducts.serverFn({ query: 'laptop' }) | ||||||
| ``` | ||||||
|
|
||||||
| No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details. | ||||||
|
|
||||||
| ## Get Involved | ||||||
|
|
||||||
| - We welcome issues and pull requests! | ||||||
| - Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions) | ||||||
| - Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ) | ||||||
| - See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions | ||||||
JoviDeCroock marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| ## Partners | ||||||
|
|
||||||
| <table align="center"> | ||||||
| <tr> | ||||||
| <td> | ||||||
| <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" > | ||||||
| <picture> | ||||||
| <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" /> | ||||||
| <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" /> | ||||||
| <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" /> | ||||||
| </picture> | ||||||
| </a> | ||||||
| </td> | ||||||
| <td> | ||||||
| <a href="https://www.cloudflare.com?utm_source=tanstack"> | ||||||
| <picture> | ||||||
| <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" /> | ||||||
| <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" /> | ||||||
| <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" /> | ||||||
| </picture> | ||||||
| </a> | ||||||
| </td> | ||||||
| </tr> | ||||||
| </table> | ||||||
|
|
||||||
| <div align="center"> | ||||||
| <img src="./media/partner_logo.svg" alt="AI & you?" height="65"> | ||||||
| <p> | ||||||
| We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together. | ||||||
| </p> | ||||||
| <a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a> | ||||||
| </div> | ||||||
|
|
||||||
| ## Explore the TanStack Ecosystem | ||||||
|
|
||||||
| - <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages | ||||||
| - <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store | ||||||
| - <a href="https://github.com/tanstack/devtools"><b>TanStack Devtools</b></a> – Unified devtools panel | ||||||
| - <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state | ||||||
| - <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching | ||||||
| - <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching | ||||||
| - <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives | ||||||
| - <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state | ||||||
| - <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming | ||||||
| - <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store | ||||||
| - <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids | ||||||
| - <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering | ||||||
|
|
||||||
| … and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a> | ||||||
|
|
||||||
| <!-- USE THE FORCE LUKE --> | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| { | ||
| "name": "@tanstack/ai-preact", | ||
| "version": "0.0.0", | ||
| "description": "Preact hooks for TanStack AI", | ||
| "author": "", | ||
| "license": "MIT", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/TanStack/ai.git", | ||
| "directory": "packages/typescript/ai-preact" | ||
| }, | ||
| "type": "module", | ||
| "module": "./dist/esm/index.js", | ||
| "types": "./dist/esm/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/esm/index.d.ts", | ||
| "import": "./dist/esm/index.js" | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "src" | ||
| ], | ||
| "scripts": { | ||
| "clean": "premove ./build ./dist", | ||
| "lint:fix": "eslint ./src --fix", | ||
| "test:eslint": "eslint ./src", | ||
| "test:lib": "vitest run", | ||
| "test:lib:dev": "pnpm test:lib --watch", | ||
| "test:types": "tsc", | ||
| "test:build": "publint --strict", | ||
| "build": "vite build" | ||
| }, | ||
| "keywords": [ | ||
| "ai", | ||
| "preact", | ||
| "hooks", | ||
| "tanstack", | ||
| "chat", | ||
| "streaming" | ||
| ], | ||
| "dependencies": { | ||
| "@tanstack/ai-client": "workspace:*" | ||
| }, | ||
| "devDependencies": { | ||
| "@testing-library/preact": "^3.2.4", | ||
| "@vitest/coverage-v8": "4.0.14", | ||
| "jsdom": "^27.2.0", | ||
| "preact": "^10.26.9", | ||
| "vite": "^7.2.7" | ||
| }, | ||
| "peerDependencies": { | ||
| "@tanstack/ai": "workspace:^", | ||
| "preact": ">=10.0.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| export { useChat } from './use-chat' | ||
| export type { | ||
| UseChatOptions, | ||
| UseChatReturn, | ||
| UIMessage, | ||
| ChatRequestBody, | ||
| } from './types' | ||
|
|
||
| export { | ||
| fetchServerSentEvents, | ||
| fetchHttpStream, | ||
| stream, | ||
| createChatClientOptions, | ||
| type ConnectionAdapter, | ||
| type FetchConnectionOptions, | ||
| type InferChatMessages, | ||
| } from '@tanstack/ai-client' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| import type { AnyClientTool, ModelMessage } from '@tanstack/ai' | ||
| import type { | ||
| ChatClientOptions, | ||
| ChatRequestBody, | ||
| UIMessage, | ||
| } from '@tanstack/ai-client' | ||
|
|
||
| // Re-export types from ai-client | ||
| export type { UIMessage, ChatRequestBody } | ||
|
|
||
| /** | ||
| * Options for the useChat hook. | ||
| * | ||
| * This extends ChatClientOptions but omits the state change callbacks that are | ||
| * managed internally by Preact state: | ||
| * - `onMessagesChange` - Managed by Preact state (exposed as `messages`) | ||
| * - `onLoadingChange` - Managed by Preact state (exposed as `isLoading`) | ||
| * - `onErrorChange` - Managed by Preact state (exposed as `error`) | ||
| * | ||
| * All other callbacks (onResponse, onChunk, onFinish, onError) are | ||
| * passed through to the underlying ChatClient and can be used for side effects. | ||
| * | ||
| * Note: Connection and body changes will recreate the ChatClient instance. | ||
| * To update these options, remount the component or use a key prop. | ||
| */ | ||
| export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any> = | ||
| Omit< | ||
| ChatClientOptions<TTools>, | ||
| 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | ||
| > | ||
|
|
||
| export interface UseChatReturn< | ||
| TTools extends ReadonlyArray<AnyClientTool> = any, | ||
| > { | ||
| /** | ||
| * Current messages in the conversation | ||
| */ | ||
| messages: Array<UIMessage<TTools>> | ||
|
|
||
| /** | ||
| * Send a message and get a response | ||
| */ | ||
| sendMessage: (content: string) => Promise<void> | ||
|
|
||
| /** | ||
| * Append a message to the conversation | ||
| */ | ||
| append: (message: ModelMessage | UIMessage<TTools>) => Promise<void> | ||
|
|
||
| /** | ||
| * Add the result of a client-side tool execution | ||
| */ | ||
| addToolResult: (result: { | ||
| toolCallId: string | ||
| tool: string | ||
| output: any | ||
| state?: 'output-available' | 'output-error' | ||
| errorText?: string | ||
| }) => Promise<void> | ||
|
|
||
| /** | ||
| * Respond to a tool approval request | ||
| */ | ||
| addToolApprovalResponse: (response: { | ||
| id: string // approval.id, not toolCallId | ||
| approved: boolean | ||
| }) => Promise<void> | ||
|
|
||
| /** | ||
| * Reload the last assistant message | ||
| */ | ||
| reload: () => Promise<void> | ||
|
|
||
| /** | ||
| * Stop the current response generation | ||
| */ | ||
| stop: () => void | ||
|
|
||
| /** | ||
| * Whether a response is currently being generated | ||
| */ | ||
| isLoading: boolean | ||
|
|
||
| /** | ||
| * Current error, if any | ||
| */ | ||
| error: Error | undefined | ||
|
|
||
| /** | ||
| * Set messages manually | ||
| */ | ||
| setMessages: (messages: Array<UIMessage<TTools>>) => void | ||
|
|
||
| /** | ||
| * Clear all messages | ||
| */ | ||
| clear: () => void | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add alt text to the header image for accessibility.
The header image is missing the
altattribute, which is flagged by static analysis (MD045).🔎 Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
2-2: Images should have alternate text (alt text)
(MD045, no-alt-text)
🤖 Prompt for AI Agents