Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# AI Provider (at least one required)
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# ANTHROPIC_MODEL=claude-sonnet-4-20250514
# ANTHROPIC_MODEL=claude-opus-5
# Required when the key above is identity-linked (scoped to all workspaces).
# ANTHROPIC_WORKSPACE_ID=wrkspc_xxxxxxxxxxxxxxxxxxxxxxx
# OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# OPENAI_MODEL=gpt-4-turbo

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ bun run kill # Kill processes on ports 3000, 3002, 8081

**Server:**
- `ANTHROPIC_API_KEY` (required)
- `ANTHROPIC_WORKSPACE_ID` (required for an identity-linked key): sent as the `anthropic-workspace-id` header
- `LOG_FORMAT` (optional): `pretty` (default) or `json`
- `RATE_LIMIT_MAX_REQUESTS` (optional): Max requests per window (0 = unlimited)
- `RATE_LIMIT_WINDOW_MS` (optional): Window in ms (default: 60000)
Expand Down
80 changes: 77 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ services:
# - OPENAI_API_KEY=${OPENAI_API_KEY}

# Optional: Model selection
# - ANTHROPIC_MODEL=claude-sonnet-4-20250514
# - ANTHROPIC_MODEL=claude-opus-5
# - OPENAI_MODEL=gpt-4-turbo

# Optional: Server configuration
Expand Down Expand Up @@ -723,6 +723,79 @@ root.render(
);
```

For partial customization, override only the built-in chat regions you own.
Every slot receives the built-in UI as `children`: render it to decorate the
default behavior, or omit it to replace that region completely.

```tsx
import type {
ChatMessageSlotProps,
ChatComposerSlotProps,
} from '@meetsmore-oss/use-ai-client';

function Message({ message, children }: ChatMessageSlotProps) {
return <div className={`my-message my-message-${message.role}`}>{children}</div>;
}

function Composer({ input, onInputChange, onSend, canSend }: ChatComposerSlotProps) {
return (
<form onSubmit={(event) => { event.preventDefault(); onSend(); }}>
<textarea value={input} onChange={(event) => onInputChange(event.target.value)} />
<button disabled={!canSend}>Send</button>
</form>
);
}

<UseAIProvider
serverUrl="wss://your-server.com"
chatComponents={{ Message, Composer }}
>
<App />
</UseAIProvider>
```

Available slots are `Header`, `EmptyState`, `Message`, `PendingIndicator`,
`Composer`, `ToolApproval`, and `Disclaimer`. Provider-level `chatComponents` apply to all
chat instances. A specific `<UseAIChat components={...} />` takes precedence.

**Migrating a `CustomChat` from 1.17:** `ChatPanelProps` no longer carries
`streamingText` and `streamingReasoning`, and `ReasoningProps` no longer carries
`streamingText`. The in-flight answer now arrives as `streamingParts`, the
ordered parts of the run. Flatten them with the exported helpers:

```tsx
import {
getTextFromStreamingParts,
getReasoningPartsFromStreamingParts,
} from '@meetsmore-oss/use-ai-client';

const streamingText = getTextFromStreamingParts(streamingParts);
const streamingReasoning = getReasoningPartsFromStreamingParts(streamingParts);
```

The answer being streamed goes through `Message` too, as a provisional entry
carrying the id it will be persisted under, so `streaming` tells the two apart.

Each region's built-in implementation is exported as `DefaultHeader`,
`DefaultMessage` and so on, taking exactly the props its slot receives. Reuse
one when only part of a region needs to change:

```tsx
import { DefaultMessage, type ChatMessageSlotProps } from '@meetsmore-oss/use-ai-client';

function Message(props: ChatMessageSlotProps) {
return (
<>
<DefaultMessage {...props} />
{props.isLast && <Citations />}
</>
);
}
```

A working example of every slot, including a turn rendered as a timeline, lives
in `apps/example` at `/custom-slots-demo`.

You can also disable them by passing `null`:

```tsx
Expand Down Expand Up @@ -927,7 +1000,8 @@ For most use cases, you can just use `@meetsmore-oss/use-ai-server` as-is, and c
```bash
# AI Provider (at least one required)
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# ANTHROPIC_MODEL=claude-sonnet-4-20250514
# ANTHROPIC_MODEL=claude-opus-5
# ANTHROPIC_WORKSPACE_ID=wrkspc_xxxxxxxxxxxxxxxxxxxxxxx # Required for an identity-linked key
# OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# OPENAI_MODEL=gpt-4-turbo

Expand Down Expand Up @@ -970,7 +1044,7 @@ const server = new UseAIServer({
'claude': new AISDKAgent({
name: 'Claude',
annotation: 'Powered by Anthropic', // shown in agent selector UI
hooks: { loadConfig: () => ({ model: anthropic('claude-sonnet-4-20250514') }) },
hooks: { loadConfig: () => ({ model: anthropic('claude-opus-5') }) },
})
},
defaultAgent: 'claude',
Expand Down
3 changes: 3 additions & 0 deletions apps/example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import ClientToolsPage from './pages/ClientToolsPage';
import ChatHistoryPage from './pages/ChatHistoryPage';
import SlashCommandsPage from './pages/SlashCommandsPage';
import CustomUIPage from './pages/CustomUIPage';
import CustomSlotsDemoPage from './pages/CustomSlotsDemoPage';
import ThemeI18nPage from './pages/ThemeI18nPage';
import SuggestionsPage from './pages/SuggestionsPage';
import DestructiveApprovalPage from './pages/DestructiveApprovalPage';
Expand Down Expand Up @@ -66,6 +67,7 @@ const NAV_CATEGORIES: NavCategory[] = [
{
label: 'UI Customization',
items: [
{ path: '/custom-slots-demo', label: 'Component Slots Demo' },
{ path: '/custom-ui', label: 'Custom UI' },
{ path: '/theme-i18n', label: 'Theme & i18n' },
{ path: '/submit-mode', label: 'Submit Mode (Enter key)' },
Expand Down Expand Up @@ -174,6 +176,7 @@ function AppContent() {
<Route path="/programmatic-chat"><ProgrammaticChatPage /></Route>
<Route path="/chat-history"><ChatHistoryPage /></Route>
<Route path="/slash-commands"><SlashCommandsPage /></Route>
<Route path="/custom-slots-demo"><CustomSlotsDemoPage /></Route>
<Route path="/custom-ui"><CustomUIPage /></Route>
<Route path="/theme-i18n"><ThemeI18nPage /></Route>
<Route path="/submit-mode"><SubmitModePage /></Route>
Expand Down
Loading
Loading