ChatIQ
ContactSolutionsDocsBlogDashboard
Sign inSign up
ContactSolutionsDocsBlogDashboard
Sign inSign up
ChatIQ

Build reliable AI chatbots powered by your team’s knowledge. Secure multi-tenant architecture, instant document ingestion, and guided analytics out of the box.

Product
How it worksFeaturesDemo
Resources
SolutionsBlogDocsContactDashboard
Legal & Preferences
Terms of ServicePrivacy PolicySecurityDPA
© 2026 ChatIQ. All rights reserved.Made with care in distributed workspaces worldwide.

Streaming Chat Example

Example of implementing real-time streaming chat responses with ChatIQ.

Streaming Chat Example

Learn how to implement real-time streaming chat responses that update as the AI generates text, providing a better user experience.


Overview

Streaming allows responses to appear word-by-word as they're generated, rather than waiting for the complete response. This example shows:

  • Server-Sent Events (SSE) handling
  • Real-time UI updates
  • Conversation state management
  • Error handling for streaming

Best for: Production applications, better UX, responsive feel


Step 1: Backend Proxy with Streaming

Create a backend endpoint that streams responses:

Node.js/Express Example

// server.js
const express = require('express');
const app = express();

app.use(express.json());

app.post('/api/chatbot', async (req, res) => {
  const { message, conversation_id } = req.body;

  try {
    const response = await fetch('https://chatiq.io/api/chat', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.CHATIQ_API_KEY}`,
      },
      body: JSON.stringify({
        message: message,
        bot_slug: process.env.CHATIQ_BOT_SLUG,
        stream: true, // Enable streaming
        conversation_id: conversation_id || null,
      }),
    });

    // Set headers for Server-Sent Events
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    // Pipe the stream to the client
    const reader = response.body.getReader();
    const decoder = new TextDecoder();

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

      const chunk = decoder.decode(value, { stream: true });
      res.write(chunk);
    }

    res.end();
  } catch (error) {
    res.status(500).json({ error: 'Streaming failed' });
  }
});

app.listen(3000);

Python/Flask Example

# app.py
from flask import Flask, request, Response, stream_with_context
import os
import requests

app = Flask(__name__)

@app.route('/api/chatbot', methods=['POST'])
def chatbot():
    data = request.json
    message = data.get('message')
    conversation_id = data.get('conversation_id')

    def generate():
        response = requests.post(
            'https://chatiq.io/api/chat',
            headers={
                'Content-Type': 'application/json',
                'Authorization': f'Bearer {os.getenv("CHATIQ_API_KEY")}',
            },
            json={
                'message': message,
                'bot_slug': os.getenv('CHATIQ_BOT_SLUG'),
                'stream': True,
                'conversation_id': conversation_id,
            },
            stream=True
        )

        for chunk in response.iter_content(chunk_size=None):
            if chunk:
                yield chunk

    return Response(
        stream_with_context(generate()),
        mimetype='text/event-stream',
        headers={
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
        }
    )

if __name__ == '__main__':
    app.run(port=3000)

Step 2: Client-Side Streaming Handler

Implement the streaming handler in JavaScript:

// chat.js
let conversationId = null;

async function sendMessageStreaming(message) {
  const response = await fetch('/api/chatbot', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      message: message,
      conversation_id: conversationId,
    }),
  });

  if (!response.body) {
    throw new Error('No response body');
  }

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

  // Create a placeholder message in the UI
  const messageId = Date.now();
  addMessagePlaceholder('assistant', messageId);

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

    const chunk = decoder.decode(value, { stream: true });
    const lines = chunk.split('\n');

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = line.slice(6).trim();
        
        if (data === '[DONE]') {
          updateMessageContent(messageId, fullResponse);
          if (receivedConversationId) {
            conversationId = receivedConversationId;
          }
          return;
        }

        try {
          const parsed = JSON.parse(data);
          
          // Handle conversation ID
          if (parsed.conversationId) {
            receivedConversationId = parsed.conversationId;
          }

          // Handle content chunks
          const content = parsed.choices?.[0]?.delta?.content;
          if (content) {
            fullResponse += content;
            updateMessageContent(messageId, fullResponse);
          }
        } catch (e) {
          // Skip invalid JSON
          console.warn('Invalid JSON:', data);
        }
      }
    }
  }
}

// UI helper functions
function addMessagePlaceholder(role, messageId) {
  const messagesDiv = document.getElementById('messages');
  const messageDiv = document.createElement('div');
  messageDiv.id = `message-${messageId}`;
  messageDiv.className = `message ${role}`;
  messageDiv.textContent = '';
  messagesDiv.appendChild(messageDiv);
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
}

function updateMessageContent(messageId, content) {
  const messageDiv = document.getElementById(`message-${messageId}`);
  if (messageDiv) {
    messageDiv.textContent = content;
    // Auto-scroll to bottom
    const messagesDiv = document.getElementById('messages');
    messagesDiv.scrollTop = messagesDiv.scrollHeight;
  }
}

Step 3: Complete HTML Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ChatIQ Streaming Example</title>
  <style>
    .chat-container {
      max-width: 600px;
      margin: 50px auto;
      border: 1px solid #ddd;
      border-radius: 8px;
      overflow: hidden;
      box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    }
    .chat-messages {
      height: 400px;
      overflow-y: auto;
      padding: 20px;
      background: #f9f9f9;
    }
    .message {
      margin-bottom: 15px;
      padding: 10px;
      border-radius: 8px;
      white-space: pre-wrap;
      word-wrap: break-word;
    }
    .message.user {
      background: #007bff;
      color: white;
      text-align: right;
    }
    .message.assistant {
      background: white;
      border: 1px solid #ddd;
    }
    .message.assistant.streaming::after {
      content: '▋';
      animation: blink 1s infinite;
    }
    @keyframes blink {
      0%, 50% { opacity: 1; }
      51%, 100% { opacity: 0; }
    }
    .chat-input {
      display: flex;
      padding: 15px;
      background: white;
      border-top: 1px solid #ddd;
    }
    .chat-input input {
      flex: 1;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
      margin-right: 10px;
    }
    .chat-input button {
      padding: 10px 20px;
      background: #007bff;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    .chat-input button:disabled {
      background: #ccc;
      cursor: not-allowed;
    }
  </style>
</head>
<body>
  <div class="chat-container">
    <div class="chat-messages" id="messages"></div>
    <div class="chat-input">
      <input type="text" id="messageInput" placeholder="Type your message...">
      <button id="sendButton" onclick="handleSend()">Send</button>
    </div>
  </div>

  <script>
    let conversationId = null;
    let isStreaming = false;

    async function handleSend() {
      const input = document.getElementById('messageInput');
      const button = document.getElementById('sendButton');
      const message = input.value.trim();
      
      if (!message || isStreaming) return;

      addMessage('user', message);
      input.value = '';
      button.disabled = true;
      isStreaming = true;

      try {
        await sendMessageStreaming(message);
      } catch (error) {
        addMessage('assistant', 'Sorry, something went wrong. Please try again.');
        console.error('Error:', error);
      } finally {
        button.disabled = false;
        isStreaming = false;
        input.focus();
      }
    }

    async function sendMessageStreaming(message) {
      const response = await fetch('/api/chatbot', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          message: message,
          conversation_id: conversationId,
        }),
      });

      if (!response.body) {
        throw new Error('No response body');
      }

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

      const messageId = Date.now();
      addMessagePlaceholder('assistant', messageId);

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

        const chunk = decoder.decode(value, { stream: true });
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6).trim();
            
            if (data === '[DONE]') {
              removeStreamingIndicator(messageId);
              if (receivedConversationId) {
                conversationId = receivedConversationId;
              }
              return;
            }

            try {
              const parsed = JSON.parse(data);
              
              if (parsed.conversationId) {
                receivedConversationId = parsed.conversationId;
              }

              const content = parsed.choices?.[0]?.delta?.content;
              if (content) {
                fullResponse += content;
                updateMessageContent(messageId, fullResponse);
                addStreamingIndicator(messageId);
              }
            } catch (e) {
              // Skip invalid JSON
            }
          }
        }
      }
    }

    function addMessage(role, content) {
      const messagesDiv = document.getElementById('messages');
      const messageDiv = document.createElement('div');
      messageDiv.className = `message ${role}`;
      messageDiv.textContent = content;
      messagesDiv.appendChild(messageDiv);
      messagesDiv.scrollTop = messagesDiv.scrollHeight;
    }

    function addMessagePlaceholder(role, messageId) {
      const messagesDiv = document.getElementById('messages');
      const messageDiv = document.createElement('div');
      messageDiv.id = `message-${messageId}`;
      messageDiv.className = `message ${role}`;
      messageDiv.textContent = '';
      messagesDiv.appendChild(messageDiv);
      messagesDiv.scrollTop = messagesDiv.scrollHeight;
    }

    function updateMessageContent(messageId, content) {
      const messageDiv = document.getElementById(`message-${messageId}`);
      if (messageDiv) {
        messageDiv.textContent = content;
        const messagesDiv = document.getElementById('messages');
        messagesDiv.scrollTop = messagesDiv.scrollHeight;
      }
    }

    function addStreamingIndicator(messageId) {
      const messageDiv = document.getElementById(`message-${messageId}`);
      if (messageDiv && !messageDiv.classList.contains('streaming')) {
        messageDiv.classList.add('streaming');
      }
    }

    function removeStreamingIndicator(messageId) {
      const messageDiv = document.getElementById(`message-${messageId}`);
      if (messageDiv) {
        messageDiv.classList.remove('streaming');
      }
    }

    // Enter key support
    document.getElementById('messageInput').addEventListener('keypress', (e) => {
      if (e.key === 'Enter' && !isStreaming) {
        handleSend();
      }
    });
  </script>
</body>
</html>

Step 4: Error Handling

Add robust error handling for network issues:

async function sendMessageStreaming(message) {
  try {
    const response = await fetch('/api/chatbot', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        message: message,
        conversation_id: conversationId,
      }),
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    if (!response.body) {
      throw new Error('No response body');
    }

    // ... rest of streaming code ...
  } catch (error) {
    if (error.name === 'TypeError' && error.message.includes('fetch')) {
      throw new Error('Network error. Please check your connection.');
    }
    throw error;
  }
}

Benefits of Streaming

  • Better UX: Users see responses immediately
  • Perceived Performance: Feels faster even if total time is similar
  • Progressive Loading: Long responses don't feel like they're "stuck"
  • Real-time Feel: More conversational and engaging

Next Steps

  • Add React integration for component-based architecture
  • Implement conversation history persistence
  • Add typing indicators
  • Customize streaming animation styles

See Also

  • API Streaming Guide - Complete API reference
  • Basic Chat Example - Non-streaming version
  • React Integration - React-specific implementation