diff --git a/packages/typescript/ai-preact-ui/CHANGELOG.md b/packages/typescript/ai-preact-ui/CHANGELOG.md new file mode 100644 index 00000000..2b402699 --- /dev/null +++ b/packages/typescript/ai-preact-ui/CHANGELOG.md @@ -0,0 +1 @@ +# @tanstack/ai-preact-ui diff --git a/packages/typescript/ai-preact-ui/README.md b/packages/typescript/ai-preact-ui/README.md new file mode 100644 index 00000000..fb17ea36 --- /dev/null +++ b/packages/typescript/ai-preact-ui/README.md @@ -0,0 +1,306 @@ +
+ +
+ +
+ +
+ + + + + + + + + +
+ +
+ + semantic-release + + + Release + + + Follow @TanStack + +
+ +
+ +### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/) +
+ +# TanStack AI Preact UI + +Headless Preact components for building AI chat interfaces with TanStack AI. + +## Features + +- 🎨 **Headless components** - Full control over styling and markup +- 🔄 **Parts-based rendering** - Native support for text, tool calls, thinking, and tool results +- ⚡ **Streaming support** - Real-time message updates +- 🔧 **Tool approval workflows** - Built-in support for tool approval UI +- 📦 **Compound components** - Composable API with ``, ``, ``, etc. +- 🎯 **Type-safe** - Full TypeScript support +- 🎭 **Render props** - Customize any part of the UI +- 🪝 **Preact hooks** - Built on `@tanstack/ai-preact` + +## Installation + +```bash +npm install @tanstack/ai-preact-ui @tanstack/ai-preact +# or +pnpm add @tanstack/ai-preact-ui @tanstack/ai-preact +# or +yarn add @tanstack/ai-preact-ui @tanstack/ai-preact +``` + +## Quick Start + +```tsx +import { + Chat, + ChatMessages, + ChatInput, + ChatMessage, +} from '@tanstack/ai-preact-ui' +import { fetchServerSentEvents } from '@tanstack/ai-client' + +function App() { + return ( + + + {(message) => } + + + + ) +} +``` + +## Components + +### `` + +Root component that provides chat context to all child components. + +```tsx + console.log('Message complete:', message)} +> + {/* child components */} + +``` + +### `` + +Container for rendering all messages in the conversation. + +```tsx +No messages yet. Start a conversation!

} + loadingState={

Loading...

} +> + {(message) => } +
+``` + +### `` + +Renders a single message with all its parts (text, thinking, tool calls, tool results). + +```tsx + +``` + +#### Custom Part Renderers + +```tsx + ( + + )} + thinkingPartRenderer={({ content, isComplete }) => ( + + )} + toolsRenderer={{ + weatherLookup: ({ arguments: args, output }) => ( + + ), + }} +/> +``` + +### `` + +Input component for sending messages. + +```tsx + +``` + +#### Custom Input UI + +```tsx + + {({ value, onChange, onSubmit, isLoading }) => ( +
+ onChange(e.target.value)} + className="flex-1 px-4 py-2 border rounded" + /> + +
+ )} +
+``` + +### `` + +Renders text content with markdown support and syntax highlighting. + +````tsx + +```` + +### `` + +Renders model thinking/reasoning content with collapsible UI. + +```tsx + +``` + +### `` + +Renders approve/deny buttons for tools that require approval. + +```tsx + +``` + +## Hooks + +### `useChatContext()` + +Access the chat context from any child component. + +```tsx +import { useChatContext } from '@tanstack/ai-preact-ui' + +function CustomComponent() { + const { messages, isLoading, sendMessage } = useChatContext() + + return ( +
+

Messages: {messages.length}

+

Loading: {isLoading ? 'Yes' : 'No'}

+
+ ) +} +``` + +## Styling + +All components are headless and use data attributes for styling: + +```css +/* Message styling */ +[data-message-role='user'] { + background: #e3f2fd; + text-align: right; +} + +[data-message-role='assistant'] { + background: #f5f5f5; +} + +/* Part type styling */ +[data-part-type='text'] { + padding: 1rem; +} + +[data-part-type='thinking'] { + opacity: 0.8; + font-style: italic; +} + +[data-part-type='tool-call'] { + border-left: 3px solid #2196f3; + padding-left: 1rem; +} +``` + +## Advanced Usage + +### Custom Tool Renderers + +```tsx +const toolRenderers = { + searchWeb: ({ arguments: args, output }) => ( + + ), + generateImage: ({ arguments: args, output }) => ( + + ), +} + + +``` + +### Error Handling + +```tsx + ( +
+

Error: {error.message}

+ +
+ )} +/> +``` + +## Documentation + +For full documentation, visit [https://tanstack.com/ai](https://tanstack.com/ai) + +## License + +MIT diff --git a/packages/typescript/ai-preact-ui/package.json b/packages/typescript/ai-preact-ui/package.json new file mode 100644 index 00000000..e9d5233c --- /dev/null +++ b/packages/typescript/ai-preact-ui/package.json @@ -0,0 +1,56 @@ +{ + "name": "@tanstack/ai-preact-ui", + "version": "0.0.0", + "description": "Headless Preact components for building AI chat interfaces", + "module": "./dist/esm/index.js", + "types": "./dist/esm/index.d.ts", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/TanStack/ai.git", + "directory": "packages/typescript/ai-preact-ui" + }, + "exports": { + ".": { + "types": "./dist/esm/index.d.ts", + "import": "./dist/esm/index.js" + } + }, + "scripts": { + "build": "vite build", + "clean": "premove ./build ./dist", + "test:build": "publint --strict", + "test:eslint": "eslint ./src", + "test:lib": "vitest --passWithNoTests", + "test:lib:dev": "pnpm test:lib --watch", + "test:types": "tsc" + }, + "keywords": [ + "tanstack", + "ai", + "preact", + "chat", + "ui", + "headless", + "components" + ], + "dependencies": { + "@tanstack/ai-preact": "workspace:*", + "preact-md": "^0.2.1", + "rehype-highlight": "^7.0.2", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1" + }, + "peerDependencies": { + "@tanstack/ai-preact": "workspace:*", + "preact": ">=10.11.0" + }, + "devDependencies": { + "@tanstack/ai-client": "workspace:*", + "@vitest/coverage-v8": "4.0.14", + "vite": "^7.2.7" + }, + "files": [ + "dist" + ] +} diff --git a/packages/typescript/ai-preact-ui/src/chat-input.tsx b/packages/typescript/ai-preact-ui/src/chat-input.tsx new file mode 100644 index 00000000..be3b9dcc --- /dev/null +++ b/packages/typescript/ai-preact-ui/src/chat-input.tsx @@ -0,0 +1,172 @@ +import { useRef, useState } from 'preact/hooks' +import { useChatContext } from './chat' +import type { ComponentChildren, RefObject } from 'preact' + +export interface ChatInputRenderProps { + /** Current input value */ + value: string + /** Set input value */ + onChange: (value: string) => void + /** Submit the message */ + onSubmit: () => void + /** Is the chat currently loading */ + isLoading: boolean + /** Is input disabled */ + disabled: boolean + /** Ref to the input element */ + inputRef: RefObject +} + +export interface ChatInputProps { + /** Render prop for full control */ + children?: (props: ChatInputRenderProps) => ComponentChildren + /** CSS class name */ + className?: string + /** Placeholder text */ + placeholder?: string + /** Disable input */ + disabled?: boolean + /** Submit on Enter (Shift+Enter for new line) */ + submitOnEnter?: boolean +} + +/** + * Chat input component - handles message input and submission + * + * Features: + * - Auto-growing textarea + * - Submit on Enter (Shift+Enter for new line) + * - Loading state management + * - Full render prop support for custom UIs + * + * @example + * ```tsx + * + * ``` + * + * @example Custom UI with render prop + * ```tsx + * + * {({ value, onChange, onSubmit, isLoading }) => ( + *
+ *