Qu'est-ce que le Streaming SSE et Pourquoi C'est Magique ?

Imaginez que vous envoyez un message à un assistant IA et que vous voyez les réponses apparaître mot par mot, comme si quelqu'un les tapait en temps réel devant vous. C'est exactement ce que permet la technologie Server-Sent Events (SSE) !

Dans ce tutoriel, nous allons apprendre ensemble à recevoir des flux de données en continu depuis une API IA. Vous n'avez besoin d'aucune expérience préalable en programmation d'API — nous partirons de zéro.

Prérequis Avant de Commencer

Comprendre le Principe du Streaming

Traditionnellement, quand vous demandez quelque chose à une API, vous attendez que le serveur calcule TOUTE la réponse avant de vous la renvoyer. Avec le streaming SSE, le serveur envoie les morceaux de réponse au fur et à mesure — comme un train qui arrive wagon par wagon plutôt que d'attendre que le convoi entier soit prêt.

Implémentation Pas à Pas

Étape 1 : Créer le Fichier HTML de Base

Créez un fichier nommé streaming-demo.html et ajoutez ce code :

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Démonstration Streaming SSE</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        #response {
            border: 2px solid #4CAF50;
            border-radius: 10px;
            padding: 20px;
            min-height: 200px;
            background-color: white;
            margin-top: 20px;
        }
        #status {
            color: #666;
            font-style: italic;
        }
    </style>
</head>
<body>
    <h1>Mon Premier Chat IA avec Streaming</h1>
    <textarea id="message" rows="4" cols="60" placeholder="Tapez votre message ici..."></textarea>
    <br><br>
    <button id="sendBtn">Envoyer</button>
    <p id="status">En attente...</p>
    <div id="response"></div>

    <script>
        // Notre code JavaScript viendra ici
    </script>
</body>
</html>

💡 Conseil : Vous devriez voir une zone de texte, un bouton vert, et un espace blanc où apparaîtront les réponses.

Étape 2 : Comprendre le Code JavaScript de Connexion

Ajoutez ce code JavaScript à l'intérieur des balises <script> de votre fichier :

const messageInput = document.getElementById('message');
const responseDiv = document.getElementById('response');
const statusText = document.getElementById('status');
const sendButton = document.getElementById('sendBtn');

// Remplacez par votre vraie clé API
const API_KEY = 'YOUR_HOLYSHEEP_API_KEY';
const BASE_URL = 'https://api.holysheep.ai/v1';

let eventSource = null;

function connectToStream(userMessage) {
    // On vide la réponse précédente
    responseDiv.innerHTML = '';
    statusText.textContent = 'Connexion en cours...';
    sendButton.disabled = true;

    // Construire l'URL avec les paramètres
    const url = ${BASE_URL}/chat/completions;
    
    // Créer l'événement Source (la connexion SSE)
    eventSource = new EventSourcePolyfill(url, {
        headers: {
            'Authorization': Bearer ${API_KEY},
            'Content-Type': 'application/json'
        },
        query: {
            'model': 'gpt-4.1',
            'stream': 'true'
        }
    });

    // Gérer les données reçues
    eventSource.onmessage = function(event) {
        statusText.textContent = 'Réception des données...';
        
        try {
            const data = JSON.parse(event.data);
            
            // Vérifier si c'est la fin du flux
            if (data.choices && data.choices[0].finish_reason === 'stop') {
                statusText.textContent = 'Terminé !';
                closeConnection();
                return;
            }
            
            // Extraire et afficher le contenu
            const content = data.choices[0].delta.content;
            if (content) {
                responseDiv.innerHTML += content;
            }
        } catch (e) {
            console.log('Données partielles:', event.data);
        }
    };

    // Gérer les erreurs de connexion
    eventSource.onerror = function(error) {
        console.error('Erreur de connexion:', error);
        statusText.textContent = 'Erreur de connexion';
        closeConnection();
        
        // Implémenter la reconnexion automatique
        setTimeout(() => {
            statusText.textContent = 'Tentative de reconnexion...';
            connectToStream(userMessage);
        }, 3000);
    };
}

function closeConnection() {
    if (eventSource) {
        eventSource.close();
        eventSource = null;
    }
    sendButton.disabled = false;
}

// Gestion du clic sur le bouton
sendButton.addEventListener('click', function() {
    const message = messageInput.value.trim();
    if (message) {
        connectToStream(message);
    }
});

⚠️ Note importante : Cette méthode EventSource standard a des limitations avec les requêtes POST. Pour une implémentation complète, utilisez plutôt l'approche Fetch avec ReadableStream présentée ci-dessous.

Étape 3 : L'Approche Moderne avec Fetch et ReadableStream

Cette méthode est plus fiable et fonctionne parfaitement avec les API modernes comme HolySheep AI :

async function envoyerMessageStreaming(message) {
    responseDiv.innerHTML = '';
    statusText.textContent = 'Connexion en cours...';
    sendButton.disabled = true;

    try {
        const response = await fetch(${BASE_URL}/chat/completions, {
            method: 'POST',
            headers: {
                'Authorization': Bearer ${API_KEY},
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                model: 'gpt-4.1',
                messages: [
                    { role: 'user', content: message }
                ],
                stream: true
            })
        });

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

        // Obtenir le reader du flux
        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        statusText.textContent = 'Réception des données...';

        // Lire le flux morceau par morceau
        while (true) {
            const { done, value } = await reader.read();
            
            if (done) {
                statusText.textContent = 'Terminé !';
                sendButton.disabled = false;
                break;
            }

            // Décoder les données binaires
            const chunk = decoder.decode(value, { stream: true });
            
            // Traiter chaque ligne SSE
            const lines = chunk.split('\n');
            for (const line of lines) {
                if (line.startsWith('data: ')) {
                    const data = line.slice(6);
                    
                    // Ignorer le message de fin
                    if (data === '[DONE]') continue;
                    
                    try {
                        const parsed = JSON.parse(data);
                        const content = parsed.choices?.[0]?.delta?.content;
                        if (content) {
                            responseDiv.innerHTML += content;
                        }
                    } catch (e) {
                        // Ignorer les erreurs de parsing partiel
                    }
                }
            }
        }
    } catch (error) {
        console.error('Erreur:', error);
        statusText.textContent = Erreur: ${error.message};
        sendButton.disabled = false;
        
        // Tentative de reconnexion après 5 secondes
        setTimeout(() => {
            if (confirm('Voulez-vous réessayer ?')) {
                envoyerMessageStreaming(message);
            }
        }, 5000);
    }
}

Comprendre la Reconnexion Automatique

Imaginez que vous regardez une vidéo et que votre WiFi coupe pendant 2 secondes. La vidéo ne s'arrête pas définitivement — elle reprend là où elle en était. C'est exactement le même principe pour notre connexion SSE !

Stratégie de Reconnexion Complète

class StreamingManager {
    constructor() {
        this.maxRetries = 5;
        this.currentRetry = 0;
        this.retryDelay = 1000;
        this.isConnected = false;
        this.abortController = null;
    }

    async envoyerAvecReconnexion(message) {
        this.currentRetry = 0;
        this.retryDelay = 1000;
        
        while (this.currentRetry < this.maxRetries) {
            try {
                await this.faireRequeteStreaming(message);
                this.currentRetry = 0; // Réussite, réinitialiser
                return;
            } catch (error) {
                this.currentRetry++;
                console.log(Tentative ${this.currentRetry}/${this.maxRetries});
                
                if (this.currentRetry >= this.maxRetries) {
                    statusText.textContent = 'Nombre maximum de tentatives atteint';
                    alert('Impossible de se connecter. Veuillez vérifier votre connexion internet.');
                    return;
                }

                // Afficher le compte à rebours
                await this.afficherCompteARebours();
                
                // Augmenter le délai exponentiellement (1s, 2s, 4s, 8s...)
                this.retryDelay = Math.min(this.retryDelay * 2, 30000);
            }
        }
    }

    async faireRequeteStreaming(message) {
        // Annuler toute requête précédente
        if (this.abortController) {
            this.abortController.abort();
        }
        
        this.abortController = new AbortController();
        
        const response = await fetch(${BASE_URL}/chat/completions, {
            method: 'POST',
            headers: {
                'Authorization': Bearer ${API_KEY},
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                model: 'gpt-4.1',
                messages: [{ role: 'user', content: message }],
                stream: true
            }),
            signal: this.abortController.signal
        });

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

        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 });
            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);
                        const content = parsed.choices?.[0]?.delta?.content;
                        if (content) {
                            responseDiv.innerHTML += content;
                        }
                    } catch (e) {}
                }
            }
        }
    }

    async afficherCompteARebours() {
        const seconds = Math.ceil(this.retryDelay / 1000);
        statusText.textContent = Reconnexion dans ${seconds} secondes...;
        
        for (let i = seconds; i > 0; i--) {
            statusText.textContent = Reconnexion dans ${i} secondes...;
            await new Promise(r => setTimeout(r, 1000));
        }
    }
}

// Utilisation
const manager = new StreamingManager();
sendButton.addEventListener('click', () => {
    const message = messageInput.value.trim();
    if (message) {
        manager.envoyerAvecReconnexion(message);
    }
});

Les Avantages HolySheep AI Expliqués Simplement

Comparaison des Prix 2026 (par million de tokens)

Modèle Prix Input Prix Output
DeepSeek V3.2 $0.42 $0.42
Gemini 2.5 Flash $2.50 $10
GPT-4.1 $8 $24
Claude Sonnet 4.5 $15 $75

Erreurs Courantes et Solutions

1. Erreur : "Failed to load - No 'Access-Control-Allow-Origin' header"

Cause : Votre page web et l'API ne sont pas sur le même domaine, et le navigateur bloque la requête pour des raisons de sécurité.

Solution : Vérifiez que votre clé API est valide et que vous avez correctement configuré les en-têtes CORS. Avec HolySheep AI, les en-têtes CORS sont déjà configurés — si cette erreur persiste, regeneratez votre clé API dans votre tableau de bord.

2. Erreur : "JSON.parse error on streaming data"

Cause : Vous recevez des données partielles qui ne sont pas du JSON valide.

Solution : Entourez votre parsing JSON d'un bloc try/catch et ignorez les erreurs partielles. Les données SSE arrivent souvent en plusieurs morceaux incomplets. Voici le code corrigé :

// Fragment de code pour gérer les données partielles
let buffer = ''; // Tampon pour accumulate

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

// Traiter les lignes complètes uniquement
const lines = buffer.split('\n');
buffer = lines.pop() || ''; // Garder le dernier fragment incomplet

for (const line of lines) {
    if (line.startsWith('data: ') && line.length > 6) {
        try {
            const parsed = JSON.parse(line.slice(6));
            // Traitement...
        } catch (e) {
            // Ignorer, c'est un fragment incomplet
        }
    }
}

3. Erreur : "Connection closed unexpectedly"

Cause : La connexion a été fermée brutalement par le serveur ou à cause d'un problème réseau.

Solution : Implémentez la reconnexion automatique avec backoff