> For the complete documentation index, see [llms.txt](https://docs.snackprompt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.snackprompt.com/bring-your-data-into-ai/how-to/how-to-implement-real-time-chat.md).

# How to Implement Real-time Chat

Learn how to implement chat with streaming to show the response as it's being generated.

### Problem

You want to create a smooth chat experience where the user sees the response being typed in real-time.

### Solution

Use the `/v1/kb/chat/stream` endpoint that returns the response via Server-Sent Events (SSE).

### Endpoint

```
POST /v1/kb/chat/stream
Content-Type: application/json
x-api-key: YOUR_API_KEY
```

### Response Format

The response comes as SSE events:

```
data: {"event":"message","data":{"content":"First"}}

data: {"event":"message","data":{"content":" part"}}

data: {"event":"message","data":{"content":" of the response"}}

data: [DONE]
```

### JavaScript Implementation

#### Using fetch + ReadableStream

```javascript
async function chatStream(query, tenantId, apiKey) {
  const response = await fetch('https://api-integrations.snackprompt.com/v1/kb/chat/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': apiKey,
    },
    body: JSON.stringify({
      query: query,
      filters: {
        tenant_id: tenantId
      }
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let fullResponse = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split('\n');

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = line.slice(6);

        if (data === '[DONE]') {
          console.log('Stream complete');
          return fullResponse;
        }

        try {
          const parsed = JSON.parse(data);
          if (parsed.event === 'message') {
            const content = parsed.data.content;
            fullResponse += content;
            // Update UI in real-time
            updateUI(content);
          } else if (parsed.event === 'error') {
            console.error('Stream error:', parsed.data.message);
          }
        } catch (e) {
          // Ignore lines that aren't valid JSON
        }
      }
    }
  }

  return fullResponse;
}

function updateUI(content) {
  const chatBox = document.getElementById('chat-response');
  chatBox.textContent += content;
}
```

#### Using EventSource (simpler)

> **Note:** EventSource only works with GET, so you'll need a proxy or use fetch.

```javascript
// With a proxy that converts POST to GET
const eventSource = new EventSource(
  `/api/chat-stream?query=${encodeURIComponent(query)}&tenant_id=${tenantId}`
);

eventSource.onmessage = (event) => {
  if (event.data === '[DONE]') {
    eventSource.close();
    return;
  }

  try {
    const data = JSON.parse(event.data);
    if (data.event === 'message') {
      document.getElementById('response').textContent += data.data.content;
    }
  } catch (e) {
    // Ignore
  }
};

eventSource.onerror = (error) => {
  console.error('SSE error:', error);
  eventSource.close();
};
```

### Python Implementation

```python
import requests
import json

def chat_stream(query: str, tenant_id: str, api_key: str):
    response = requests.post(
        'https://api-integrations.snackprompt.com/v1/kb/chat/stream',
        headers={'x-api-key': api_key},
        json={
            'query': query,
            'filters': {'tenant_id': tenant_id}
        },
        stream=True
    )

    full_response = ''

    for line in response.iter_lines():
        if line:
            line = line.decode('utf-8')
            if line.startswith('data: '):
                data = line[6:]

                if data == '[DONE]':
                    print('\n[Stream complete]')
                    break

                try:
                    parsed = json.loads(data)
                    if parsed.get('event') == 'message':
                        content = parsed['data']['content']
                        full_response += content
                        print(content, end='', flush=True)
                    elif parsed.get('event') == 'error':
                        print(f"\n[Error: {parsed['data']['message']}]")
                except json.JSONDecodeError:
                    pass

    return full_response


# Usage
response = chat_stream("what are the products?", "tenant-123", "YOUR_API_KEY")
```

### React Implementation

```jsx
import { useState, useCallback } from 'react';

function ChatComponent() {
  const [response, setResponse] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  const sendMessage = useCallback(async (query) => {
    setIsLoading(true);
    setResponse('');

    try {
      const res = await fetch('/v1/kb/chat/stream', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': 'YOUR_API_KEY'
        },
        body: JSON.stringify({
          query,
          filters: { tenant_id: 'your-tenant-id' }
        })
      });

      const reader = res.body.getReader();
      const decoder = new TextDecoder();

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value);
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6);
            if (data === '[DONE]') continue;

            try {
              const parsed = JSON.parse(data);
              if (parsed.event === 'message') {
                setResponse(prev => prev + parsed.data.content);
              }
            } catch (e) {}
          }
        }
      }
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setIsLoading(false);
    }
  }, []);

  return (
    <div>
      <button onClick={() => sendMessage('your question')} disabled={isLoading}>
        {isLoading ? 'Loading...' : 'Send'}
      </button>
      <div className="response">
        {response}
        {isLoading && <span className="cursor">|</span>}
      </div>
    </div>
  );
}
```

### Handling Errors

```javascript
for (const line of lines) {
  if (line.startsWith('data: ')) {
    const data = line.slice(6);

    try {
      const parsed = JSON.parse(data);

      switch (parsed.event) {
        case 'message':
          // Normal content
          handleContent(parsed.data.content);
          break;

        case 'error':
          // Error during streaming
          handleError(parsed.data.message);
          break;
      }
    } catch (e) {
      // Not JSON, probably [DONE]
      if (data === '[DONE]') {
        handleComplete();
      }
    }
  }
}
```

### Tips

#### 1. Show Loading Indicator

```css
.cursor {
  animation: blink 1s infinite;
}

@keyframes blink {
  50% { opacity: 0; }
}
```

#### 2. Auto-scroll

```javascript
function updateUI(content) {
  const chatBox = document.getElementById('chat-response');
  chatBox.textContent += content;
  chatBox.scrollTop = chatBox.scrollHeight; // Auto-scroll
}
```

#### 3. Allow Cancellation

```javascript
const controller = new AbortController();

fetch(url, { signal: controller.signal });

// To cancel
controller.abort();
```

### Related

* [Chat with your Data](/bring-your-data-into-ai/get-started/chat-with-your-data-rag.md)
* [Error Handling](/bring-your-data-into-ai/how-to/how-to-handle-errors.md)
* [Endpoints](/bring-your-data-into-ai/reference/endpoints.md)
