Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Sveltekit cache directory
.svelte-kit/
Expand Down Expand Up @@ -150,3 +149,5 @@ vite.config.ts.timestamp-*
local.properties
android.iml
*.hprof

.idea/
153 changes: 153 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is a monorepo for Stream.io's AI component SDKs across multiple platforms (React, React Native, and Node.js). The project provides UI components and utilities for building AI chat interfaces with streaming messages, markdown rendering with syntax highlighting, charts/graphs, and message composition with file attachments.

## Monorepo Structure

This is a pnpm workspace with three main packages and example applications:

- `packages/react-sdk` - React web components for AI chat interfaces
- `packages/react-native-sdk` - React Native components for mobile AI chat
- `packages/node-sdk` - Node.js storage connector using Stream Chat infrastructure (integrates with Vercel AI SDK)
- `examples/react-example` - Web example application
- `examples/nextjs-ai-chatbot` - Next.js chatbot example

## Common Commands

### Development

```bash
# Install dependencies
pnpm install

# Build all packages
pnpm packages:build:all

# Build all examples
pnpm examples:build:all

# Test all packages
pnpm packages:test:all

# Lint everything
pnpm lint:all

# Format code
pnpm prettier:fix-all
```

### Package-Specific Development

Navigate to the package directory for targeted work:

```bash
# React SDK
cd packages/react-sdk
pnpm build # Build JS + types + styles
pnpm dev # Watch mode for development
pnpm build:styles # Compile SCSS to CSS

# React Native SDK
cd packages/react-native-sdk
pnpm build # Build with react-native-builder-bob
pnpm start # TypeScript watch mode
pnpm test # Run tests with vitest

# Node SDK
cd packages/node-sdk
pnpm build # Compile TypeScript
pnpm dev # TypeScript watch mode
```

### Publishing

This repo uses Changesets for version management:

```bash
# Create a changeset (do this when making user-facing changes)
pnpm changeset

# Publish packages (automated via CI)
pnpm ci:publish
```

## Architecture Notes

### React SDK (`packages/react-sdk`)

**Main Components:**
- `AIMarkdown` - Markdown renderer with GFM support, syntax highlighting (Prism), and extensible tool components
- `StreamingMessage` - Typewriter-style text streaming with configurable speed
- `AIMessageComposer` - Message input with file attachments, speech-to-text, and state management via `@stream-io/state-store`

**Tool Component System:**
The `AIMarkdown` component supports custom "tool" renderers via the `toolComponents` prop. Tools are registered by language identifier (e.g., `chartjs`) and can render custom visualizations within markdown code blocks. Built-in tools include Chart.js integration via `SuspendedChart`.

**Build System:**
- Uses Vite for bundling (dual format: ESM + CJS)
- TypeScript for type definitions
- SCSS compiled to CSS (distributed as separate files in `dist/styles/`)
- Exports: main entry + `/stream` subpath + `/styles/*` for CSS

### React Native SDK (`packages/react-native-sdk`)

**Key Differences from React SDK:**
- Uses `@khanacademy/simple-markdown` instead of `react-markdown` for markdown parsing
- Charts powered by `victory-native` (React Native/Skia-based)
- Built with `react-native-builder-bob` (CommonJS + ESM + TypeScript)
- Custom markdown components in `src/markdown/`

**Native Components:**
- `MarkdownReactiveScrollView` - Performance-optimized markdown scrolling
- `PerfText` - Text rendering optimization
- Syntax highlighting via `react-syntax-highlighter` (shared with web)

### Node SDK (`packages/node-sdk`)

**Purpose:**
Storage adapter that bridges Vercel AI SDK with Stream Chat backend. Enables persisting AI conversations using Stream's infrastructure.

**Core Classes:**
- `StreamStorage` - Main class for channel/message management
- `ai-sdk-helpers.ts` - Utilities for converting between AI SDK message format and Stream Chat format
- Supports streaming responses from AI SDK back to Stream Chat

### Shared Patterns

**State Management:**
Both React and React Native SDKs use `@stream-io/state-store` for component state (e.g., message composer state).

**Markdown Rendering:**
- Web: `react-markdown` + `remark-gfm`
- Mobile: `@khanacademy/simple-markdown` (custom port)
- Both: `react-syntax-highlighter` for code blocks

**Styling:**
- React SDK: SCSS files compiled to CSS, consumers import from `@stream-io/ai-chat-react/styles/*`
- React Native: StyleSheet-based, no external styles

## TypeScript Configuration

The monorepo uses a shared `tsconfig.root.json` with strict settings:
- `strict: true`
- `noUncheckedIndexedAccess: true`
- `noImplicitOverride: true`
- `verbatimModuleSyntax: true`

Individual packages extend this config.

## Testing

Tests use Vitest (configured via `pnpm-workspace.yaml` catalog). Run tests from package directories or use `pnpm packages:test:all`.

## CI/CD

GitHub Actions workflow (`.github/workflows/ci.yml`) runs on PRs:
1. Install dependencies with pnpm
2. Run `pnpm packages:test:all`

Changesets workflow handles automated publishing.
1 change: 0 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export default tseslint.config(
'array-callback-return': 'error',
'arrow-body-style': 'off',
'comma-dangle': 'off',
'jsx-quotes': ['error', 'prefer-single'],
'linebreak-style': ['error', 'unix'],
'no-console': 'off',
'no-mixed-spaces-and-tabs': 'warn',
Expand Down
47 changes: 24 additions & 23 deletions examples/react-example/src/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ import type {
} from 'stream-chat';
import {
AIStateIndicator,
Attachment,
Channel,
ChannelList,
ChannelPreview,
type ChannelPreviewProps,
Chat,
useCreateChatClient,
MessageErrorIcon,
messageHasAttachments,
MessageInput,
MessageList,
Window,
type ChannelPreviewProps,
ChannelPreview,
useChannelActionContext,
useChannelStateContext,
useChatContext,
MessageInput,
useChannelActionContext,
useCreateChatClient,
useMessageComposer,
useMessageContext,
Attachment,
messageHasAttachments,
MessageErrorIcon,
Window,
} from 'stream-chat-react';

import { customAlphabet } from 'nanoid';
Expand Down Expand Up @@ -56,7 +56,6 @@ const filters: ChannelFilters = {
const options: ChannelOptions = { limit: 5, presence: true, state: true };
const sort: ChannelSort = { pinned_at: 1, last_message_at: -1, updated_at: -1 };

// @ts-ignore
const isMessageAIGenerated = (message: LocalMessage) => !!message?.ai_generated;

const InputComponent = () => {
Expand Down Expand Up @@ -94,18 +93,21 @@ const InputComponent = () => {
await channel.watch();

// TODO: wrap in retry (in case channel creation takes longer)
await fetch('http://localhost:3000/start-ai-agent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await fetch(
'https://stream-nodejs-ai-e5d85ed5ce6f.herokuapp.com/start-ai-agent',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel_id: channel.id,
channel_type: channel.type,
platform: 'openai',
model,
}),
},
body: JSON.stringify({
channel_id: channel.id,
channel_type: channel.type,
platform: 'openai',
model: model,
}),
});
);

await sendMessage(d);
}
Expand All @@ -118,8 +120,7 @@ const CustomPreview = (p: ChannelPreviewProps) => {
const { setActiveChannel } = useChatContext();
return (
<div onClick={() => setActiveChannel(p.channel)}>
{/* @ts-expect-error */}
{p.channel.data.summary ?? p.channel.id}
{p.channel.data?.summary ?? p.channel.id}
</div>
);
};
Expand Down
43 changes: 43 additions & 0 deletions examples/react-example/src/stream-chat.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type {
DefaultAttachmentData,
DefaultChannelData,
DefaultCommandData,
DefaultEventData,
DefaultMemberData,
DefaultMessageData,
DefaultPollData,
DefaultPollOptionData,
DefaultReactionData,
DefaultThreadData,
DefaultUserData,
} from 'stream-chat-react';

declare module 'stream-chat' {
interface CustomAttachmentData extends DefaultAttachmentData {
id?: string;
}

interface CustomChannelData extends DefaultChannelData {
summary?: string;
}

interface CustomCommandData extends DefaultCommandData {}

interface CustomEventData extends DefaultEventData {}

interface CustomMemberData extends DefaultMemberData {}

interface CustomUserData extends DefaultUserData {}

interface CustomMessageData extends DefaultMessageData {
ai_generated?: boolean;
}

interface CustomPollOptionData extends DefaultPollOptionData {}

interface CustomPollData extends DefaultPollData {}

interface CustomReactionData extends DefaultReactionData {}

interface CustomThreadData extends DefaultThreadData {}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@
"eslint-plugin-react-hooks": "^5.2.0",
"vitest": "^4.0.6"
},
"packageManager": "pnpm@10.13.1"
"packageManager": "pnpm@10.24.0"
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import {
type ComponentProps,
type ComponentPropsWithoutRef,
createContext,
type ReactNode,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
type ComponentProps,
type ComponentPropsWithoutRef,
type ReactNode,
} from 'react';
import { customAlphabet } from 'nanoid';
import { StateStore } from '@stream-io/state-store';
Expand Down