AI chat UI framework — streaming, context management, theming. Drop-in React components.
$ npx snappy-skills install crayonchat
Battle-tested patterns for building AI chat interfaces with @crayonai/react-core and @crayonai/react-ui.
Activates when:
@crayonai/react-core, or @crayonai/react-uiuseThreadManagerprocessMessage callbackstsximport { useThreadManager, processStreamedMessage } from "@crayonai/react-core";
import { CrayonChat } from "@crayonai/react-ui";
import "@crayonai/react-ui/styles/index.css";
const templates = [
{ name: "my_card", Component: MyCard },
];
function Chat() {
const threadManager = useThreadManager({
threadId: null,
loadThread: async (id) => [],
onProcessMessage: async ({ message, threadManager: tm, abortController }) => {
const response = await myProcessMessage({
threadId: "new",
messages: tm.messages.concat({ ...message, id: crypto.randomUUID() }),
abortController,
});
await processStreamedMessage({
response,
createMessage: tm.appendMessages,
updateMessage: tm.updateMessage,
deleteMessage: tm.deleteMessage,
});
return [];
},
responseTemplates: templates,
});
return (
<CrayonChat
type="standalone"
threadManager={threadManager}
responseTemplates={templates}
agentName="Assistant"
logoUrl="/logo.svg"
scrollVariant="always"
messageLoadingComponent={() => <MyLoader />}
welcomeMessage={{ title: "Hello", description: "How can I help?" }}
conversationStarters={{
variant: "long",
options: [{ displayText: "Get started", prompt: "Help me get started" }],
}}
/>
);
}
| Need to... | Read this |
|---|---|
| Understand CrayonChat props | component-api.md |
| Build SSE streaming | sse-streaming.md |
| Create custom templates | templates.md |
| Manage threads & state | thread-management.md |
| Style & theme the chat | styling.md |
| Avoid common pitfalls | gotchas.md |
CrayonChat (UI shell)
├── threadManager (state + actions)
│ ├── messages[] ← UserMessage | AssistantMessage
│ ├── isRunning ← true while streaming
│ └── processMessage() → SSE Response → processStreamedMessage()
├── responseTemplates[] ← { name, Component }
│ └── Templates use useThreadActions() / useThreadState()
└── SSE stream format:
event: text\ndata: word\n\n ← streamed text
event: tpl\ndata: {name, templateProps}\n\n ← template card
typescript// Messages
type UserMessage = { id: string; role: "user"; type: "prompt"; message?: string; context?: JSONValue[] };
type AssistantMessage = {
id: string; role: "assistant"; context?: JSONValue[];
message?: ({ type: "text"; text: string } | { type: "template"; name: string; templateProps: any })[];
};
type Message = UserMessage | AssistantMessage;
type CreateMessage = Omit<UserMessage, "id">;
// Templates
interface ResponseTemplate { name: string; Component: React.ComponentType<any> }
// Thread state
type ThreadState = {
isRunning?: boolean;
isLoadingMessages?: boolean;
messages: Message[];
error: Error | null | undefined;
responseTemplates: Record<string, ResponseTemplate>;
isInitialized: boolean;
};
// Thread actions
type ThreadActions = {
processMessage: (message: CreateMessage) => Promise<void>;
appendMessages: (...messages: Message[]) => void;
updateMessage: (message: Message) => void;
deleteMessage: (messageId: string) => void;
onCancel: () => void;
setMessages: (messages: Message[]) => void;
};
Imports:
typescript// Core
import { useThreadManager, useThreadState, useThreadActions, processStreamedMessage } from "@crayonai/react-core";
import type { Message, UserMessage, AssistantMessage, CreateMessage, ResponseTemplate, ThreadManager } from "@crayonai/react-core";
// UI
import { CrayonChat, CheckBoxGroup, CheckBoxItem } from "@crayonai/react-ui";
import "@crayonai/react-ui/styles/index.css";
CrayonChat types: "standalone" | "copilot" | "bottom-tray"
Scroll variants: "always" (auto-scroll) is the safe default
Template component hooks:
useThreadActions() → { processMessage } — send follow-up messages from cardsuseThreadState() → { isRunning, messages } — read current state in templatesComplete CrayonChat component props, welcome message config, conversation starters
SSE stream format, processMessage callback, building Response objects, text chunking
Template registry, creating custom templates, template props, interactive cards
useThreadManager hook, loading threads, processing messages, conversation persistence
CSS scoping, theme integration, card animations, dark mode patterns
Hard-won lessons: remount flashing, history building, response format parsing, state machine quirks
Skill Status: COMPLETE
Line Count: ~140
Progressive Disclosure: 6 resource files
---
name: crayonchat
description: CrayonChat (@crayonai/react-core, @crayonai/react-ui) integration guide. AI chat UI framework for building conversational interfaces with SSE streaming, custom response templates, thread management, and interactive cards. Covers CrayonChat component props, useThreadManager, useThreadState, useThreadActions, processStreamedMessage, template registration, conversation starters, welcome messages, and custom loading indicators.
---
# CrayonChat Integration Guide
## Purpose
Battle-tested patterns for building AI chat interfaces with `@crayonai/react-core` and `@crayonai/react-ui`.
## When to Use This Skill
Activates when:
- Working with CrayonChat, `@crayonai/react-core`, or `@crayonai/react-ui`
- Building AI chat interfaces with SSE streaming
- Creating custom response templates for chat
- Managing conversation threads with `useThreadManager`
- Implementing `processMessage` callbacks
---
## Quick Start
```tsx
import { useThreadManager, processStreamedMessage } from "@crayonai/react-core";
import { CrayonChat } from "@crayonai/react-ui";
import "@crayonai/react-ui/styles/index.css";
const templates = [
{ name: "my_card", Component: MyCard },
];
function Chat() {
const threadManager = useThreadManager({
threadId: null,
loadThread: async (id) => [],
onProcessMessage: async ({ message, threadManager: tm, abortController }) => {
const response = await myProcessMessage({
threadId: "new",
messages: tm.messages.concat({ ...message, id: crypto.randomUUID() }),
abortController,
});
await processStreamedMessage({
response,
createMessage: tm.appendMessages,
updateMessage: tm.updateMessage,
deleteMessage: tm.deleteMessage,
});
return [];
},
responseTemplates: templates,
});
return (
<CrayonChat
type="standalone"
threadManager={threadManager}
responseTemplates={templates}
agentName="Assistant"
logoUrl="/logo.svg"
scrollVariant="always"
messageLoadingComponent={() => <MyLoader />}
welcomeMessage={{ title: "Hello", description: "How can I help?" }}
conversationStarters={{
variant: "long",
options: [{ displayText: "Get started", prompt: "Help me get started" }],
}}
/>
);
}
```
---
## Navigation Guide
| Need to... | Read this |
|-----------------------------------|------------------------------------------|
| Understand CrayonChat props | [component-api.md](component-api.md) |
| Build SSE streaming | [sse-streaming.md](sse-streaming.md) |
| Create custom templates | [templates.md](templates.md) |
| Manage threads & state | [thread-management.md](thread-management.md) |
| Style & theme the chat | [styling.md](styling.md) |
| Avoid common pitfalls | [gotchas.md](gotchas.md) |
---
## Core Architecture
```
CrayonChat (UI shell)
├── threadManager (state + actions)
│ ├── messages[] ← UserMessage | AssistantMessage
│ ├── isRunning ← true while streaming
│ └── processMessage() → SSE Response → processStreamedMessage()
├── responseTemplates[] ← { name, Component }
│ └── Templates use useThreadActions() / useThreadState()
└── SSE stream format:
event: text\ndata: word\n\n ← streamed text
event: tpl\ndata: {name, templateProps}\n\n ← template card
```
---
## Key Types (from @crayonai/react-core)
```typescript
// Messages
type UserMessage = { id: string; role: "user"; type: "prompt"; message?: string; context?: JSONValue[] };
type AssistantMessage = {
id: string; role: "assistant"; context?: JSONValue[];
message?: ({ type: "text"; text: string } | { type: "template"; name: string; templateProps: any })[];
};
type Message = UserMessage | AssistantMessage;
type CreateMessage = Omit<UserMessage, "id">;
// Templates
interface ResponseTemplate { name: string; Component: React.ComponentType<any> }
// Thread state
type ThreadState = {
isRunning?: boolean;
isLoadingMessages?: boolean;
messages: Message[];
error: Error | null | undefined;
responseTemplates: Record<string, ResponseTemplate>;
isInitialized: boolean;
};
// Thread actions
type ThreadActions = {
processMessage: (message: CreateMessage) => Promise<void>;
appendMessages: (...messages: Message[]) => void;
updateMessage: (message: Message) => void;
deleteMessage: (messageId: string) => void;
onCancel: () => void;
setMessages: (messages: Message[]) => void;
};
```
---
## Quick Reference
**Imports:**
```typescript
// Core
import { useThreadManager, useThreadState, useThreadActions, processStreamedMessage } from "@crayonai/react-core";
import type { Message, UserMessage, AssistantMessage, CreateMessage, ResponseTemplate, ThreadManager } from "@crayonai/react-core";
// UI
import { CrayonChat, CheckBoxGroup, CheckBoxItem } from "@crayonai/react-ui";
import "@crayonai/react-ui/styles/index.css";
```
**CrayonChat types:** `"standalone"` | `"copilot"` | `"bottom-tray"`
**Scroll variants:** `"always"` (auto-scroll) is the safe default
**Template component hooks:**
- `useThreadActions()` → `{ processMessage }` — send follow-up messages from cards
- `useThreadState()` → `{ isRunning, messages }` — read current state in templates
---
## Resource Files
### [component-api.md](component-api.md)
Complete CrayonChat component props, welcome message config, conversation starters
### [sse-streaming.md](sse-streaming.md)
SSE stream format, processMessage callback, building Response objects, text chunking
### [templates.md](templates.md)
Template registry, creating custom templates, template props, interactive cards
### [thread-management.md](thread-management.md)
useThreadManager hook, loading threads, processing messages, conversation persistence
### [styling.md](styling.md)
CSS scoping, theme integration, card animations, dark mode patterns
### [gotchas.md](gotchas.md)
Hard-won lessons: remount flashing, history building, response format parsing, state machine quirks
---
**Skill Status**: COMPLETE
**Line Count**: ~140
**Progressive Disclosure**: 6 resource files