Claude
Skills
Sign in
Back

AI SDK UI

Included with Lifetime
$97 forever

This skill should be used when the user asks to "add AI chat", "implement streaming UI", "use useChat hook", "add AI completion", "implement useCompletion", "create conversational interface", "add streaming text", "implement tool calling UI", "create generative UI", "add AI-powered features", "implement StreamableUI", or mentions AI SDK, streaming responses, chat interfaces, or AI-generated content in React/Next.js applications.

Design

What this skill does


# AI SDK UI Development

## Purpose

Implement AI-powered user interfaces with the Vercel AI SDK. This skill covers streaming UI patterns, conversational interfaces, completion features, tool calling with visual feedback, and generative UI using React Server Components.

**When to use:**
- Adding chat interfaces or conversational features
- Implementing streaming text/content display
- Building AI completion features (autocomplete, suggestions)
- Creating tool calling UIs with visual feedback
- Implementing generative UI with Server Components

## Core Concepts

### Client-Side Hooks

The AI SDK provides React hooks for client-side AI interactions:

**useChat** - Conversational interfaces with message history:
```typescript
'use client';

import { useChat } from 'ai/react';

export function ChatInterface() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: '/api/chat',
  });

  return (
    <div className="flex flex-col h-full">
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((message) => (
          <div
            key={message.id}
            className={message.role === 'user' ? 'text-right' : 'text-left'}
          >
            <div className={`inline-block p-3 rounded-lg ${
              message.role === 'user'
                ? 'bg-blue-500 text-white'
                : 'bg-gray-100 text-gray-900'
            }`}>
              {message.content}
            </div>
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit} className="p-4 border-t">
        <div className="flex gap-2">
          <input
            value={input}
            onChange={handleInputChange}
            placeholder="Type a message..."
            className="flex-1 p-2 border rounded"
            disabled={isLoading}
          />
          <button
            type="submit"
            disabled={isLoading}
            className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
          >
            {isLoading ? 'Sending...' : 'Send'}
          </button>
        </div>
      </form>
    </div>
  );
}
```

**useCompletion** - Single completions without message history:
```typescript
'use client';

import { useCompletion } from 'ai/react';

export function CompletionInput() {
  const { completion, input, handleInputChange, handleSubmit, isLoading } = useCompletion({
    api: '/api/completion',
  });

  return (
    <div className="space-y-4">
      <form onSubmit={handleSubmit}>
        <textarea
          value={input}
          onChange={handleInputChange}
          placeholder="Enter prompt..."
          className="w-full p-3 border rounded"
          rows={4}
        />
        <button
          type="submit"
          disabled={isLoading}
          className="mt-2 px-4 py-2 bg-green-500 text-white rounded"
        >
          {isLoading ? 'Generating...' : 'Generate'}
        </button>
      </form>

      {completion && (
        <div className="p-4 bg-gray-50 rounded">
          <h3 className="font-semibold mb-2">Result:</h3>
          <p className="whitespace-pre-wrap">{completion}</p>
        </div>
      )}
    </div>
  );
}
```

### Server-Side API Routes

Create API routes that stream responses:

**Chat API Route** (`app/api/chat/route.ts`):
```typescript
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-4o'),
    messages,
    system: 'You are a helpful assistant.',
  });

  return result.toDataStreamResponse();
}
```

**Completion API Route** (`app/api/completion/route.ts`):
```typescript
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

export async function POST(req: Request) {
  const { prompt } = await req.json();

  const result = streamText({
    model: openai('gpt-4o'),
    prompt,
  });

  return result.toDataStreamResponse();
}
```

### Streaming UI Patterns

**Token-by-token streaming display:**
```typescript
'use client';

import { useChat } from 'ai/react';

export function StreamingChat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();

  return (
    <div>
      {messages.map((message) => (
        <div key={message.id}>
          <strong>{message.role}:</strong>
          {/* Content streams in token-by-token */}
          <span className="animate-pulse">{message.content}</span>
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
```

**Loading states and indicators:**
```typescript
'use client';

import { useChat } from 'ai/react';

export function ChatWithLoadingStates() {
  const { messages, input, handleInputChange, handleSubmit, isLoading, error } = useChat();

  return (
    <div>
      {messages.map((message) => (
        <div key={message.id}>{message.content}</div>
      ))}

      {isLoading && (
        <div className="flex items-center gap-2 text-gray-500">
          <div className="animate-spin h-4 w-4 border-2 border-gray-300 border-t-blue-500 rounded-full" />
          <span>AI is thinking...</span>
        </div>
      )}

      {error && (
        <div className="text-red-500 p-2 bg-red-50 rounded">
          Error: {error.message}
        </div>
      )}

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading}>
          Send
        </button>
      </form>
    </div>
  );
}
```

### Tool Calling UI

Implement tools that Claude can call with visual feedback:

**Server-side tool definition:**
```typescript
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-4o'),
    messages,
    tools: {
      getWeather: tool({
        description: 'Get current weather for a location',
        parameters: z.object({
          location: z.string().describe('City name'),
        }),
        execute: async ({ location }) => {
          // Fetch weather data
          return { temperature: 72, condition: 'sunny', location };
        },
      }),
      searchProducts: tool({
        description: 'Search for products',
        parameters: z.object({
          query: z.string(),
          maxResults: z.number().optional().default(5),
        }),
        execute: async ({ query, maxResults }) => {
          // Search products
          return { results: [], query, count: 0 };
        },
      }),
    },
  });

  return result.toDataStreamResponse();
}
```

**Client-side tool result rendering:**
```typescript
'use client';

import { useChat } from 'ai/react';

function WeatherCard({ data }: { data: { temperature: number; condition: string; location: string } }) {
  return (
    <div className="p-4 bg-blue-50 rounded-lg">
      <h3 className="font-semibold">{data.location}</h3>
      <p className="text-2xl">{data.temperature}°F</p>
      <p className="text-gray-600">{data.condition}</p>
    </div>
  );
}

export function ChatWithTools() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    maxSteps: 5, // Allow multi-step tool use
  });

  return (
    <div>
      {messages.map((message) => (
        <div key={message.id}>
          {message.role === 'assistant' && message.toolInvocations?.map((tool) => (
            <div key={tool.toolCallId}>
              {tool.toolName === 'getWeather' && tool.state === 'result' && (
                <WeatherCard data={tool.result} />
              )}
              {tool.state === 'call' && (
                <div className="animate-pulse p-2 bg-gray-100 rounded">
                  Ca

Related in Design