Rendu Markdown en streaming pour les interfaces d'IA

Dans le développement d'interfaces pour l'Intelligence Artificielle, la gestion des réponses provenant de modèles de langage (LLM) comme GPT ou Claude nécessite une approche spécifique. Ces modèles retournent généralement du contenu structuré au format Markdown de manière progressive (streaming). Pour offrir une expérience utilisateur fluide, il est crucial de transformer ce flux textuel en HTML sécurisé et stylisé en temps réel.

Pourquoi privilégier le Markdown pour l'IA ?

Les LLM utilisent le Markdown pour structurer leurs réponses, incluant :

  • Des blocs de code avec spécification du langage.
  • Des tableaux comparatifs complexes.
  • Des notations mathématiques (LaTeX).
  • Des éléments de mise en forme standards (listes, gras, citations).

L'utilisation directe de propriétés comme dangerouslySetInnerHTML est fortement déconseillée car elle expose l'application aux failles XSS et ne permet pas nativement l'interprétation des blocs de code ou des formules mathématiques.

Sélection de la pile technologique

Besoin Solution recommandée
Analyse sécurisée du Markdown react-markdown (basé sur AST)
Coloration syntaxique rehype-highlight associé à highlight.js
Éqautions mathématiques rehype-katex et le CSS de KaTeX
Tableaux et listes GFM remark-gfm

Implémentation technique

1. Préparation de l'environnement

Installez les dépendances nécessaires à la gestion du Markdown et des plug-ins de transformation :

npm install react-markdown remark-gfm rehype-highlight rehype-katex katex

2. Création du composant de rendu

Ce composant, baptisé MarkdownRenderer, gère la transformation du flux texte en éléments React tout en appliquant des styles personnalisés pour les tableaux et le code.

import React from 'react';
import ReactMarkdown from 'react-markdown';
import gfmPlugin from 'remark-gfm';
import highlightPlugin from 'rehype-highlight';
import katexPlugin from 'rehype-katex';
import 'katex/dist/katex.min.css';

const MarkdownRenderer = ({ markdownSource }) => {
  return (
    <ReactMarkdown
      remarkPlugins={[gfmPlugin]}
      rehypePlugins={[highlightPlugin, katexPlugin]}
      components={{
        // Personnalisation des blocs de code
        code({ node, inline, className, children, ...props }) {
          const isLanguageMatch = /language-(\w+)/.exec(className || '');
          return !inline && isLanguageMatch ? (
            
              {children}
            
          ) : (
            {children}
          );
        },
        // Conteneur responsive pour les tableaux
        table({ children }) {
          return (
            <div style={{ overflowX: 'auto', margin: '1rem 0' }}>
              <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                {children}
              </table>
            </div>
          );
        }
      }}
    >
      {markdownSource}
    </ReactMarkdown>
  );
};

export default MarkdownRenderer;

3. Simulation du flux de données (Streaming)

Pour reproduire l'effet "machine à écrire" typique des agents conversationnels, nous utilisons un effet React qui incrémente progressivement la chaîne de caractères affichée.

import { useState, useEffect } from 'react';
import MarkdownRenderer from './components/MarkdownRenderer';

const rawAiOutput = `
# Analyse IA en cours 🚀

Voici un exemple de rendu **dynamique**.

## Coloration de code
\`\`\`python
def salutation(nom):
    return f"Bonjour, {nom} ! Bienvenue dans l'interface IA."
\`\`\`

## Données structurées
| Module | Statut | Performance |
| :--- | :--- | :--- |
| Parsing | OK | 12ms |
| Rendu | OK | 45ms |
| Sécurité | Vérifié | - |

## Mathématiques avancées
L'identité d'Euler est définie par : $e^{i\\pi} + 1 = 0$

Et la distribution normale :
$$
f(x) = \\frac{1}{\\sigma\\sqrt{2\\pi}} e^{-\\frac{1}{2}(\\frac{x-\\mu}{\\sigma})^2}
$$
`;

export default function App() {
  const [visibleText, setVisibleText] = useState('');

  useEffect(() => {
    let charPointer = 0;
    const streamTimer = setInterval(() => {
      if (charPointer < rawAiOutput.length) {
        setVisibleText(rawAiOutput.substring(0, charPointer + 1));
        charPointer++;
      } else {
        clearInterval(streamTimer);
      }
    }, 15);

    return () => clearInterval(streamTimer);
  }, []);

  return (
    <div style={{ maxWidth: '800px', margin: '40px auto', padding: '0 20px' }}>
      <div style={{ 
        border: '1px solid #eaeaea', 
        borderRadius: '8px', 
        padding: '30px',
        backgroundColor: '#ffffff',
        boxShadow: '0 4px 6px rgba(0,0,0,0.1)'
      }}>
        <MarkdownRenderer markdownSource={visibleText} />
      </div>
    </div>
  );
}

Optimisation et considérations avancées

Le rendu fréquent d'un arbre syntaxique (AST) à chaque nouveau caractère peut impacter les performances sur des documents très longs. Voici quelques pistes d'amélioration :

  • Throttling : Au lieu de mettre à jour le state à chaque caractère, regroupez les mises à jour par petits paquets (chunks) toutes les 50ms.
  • Virtualisation : Si le contenu devient massif, intégrez une bibliothèque de listes virtuelles pour ne rendre que la partie visible du Markdown.
  • Gestion du scroll : Implémentez un comportement d'auto-scroll vers le bas uniquement si l'utilisateur n'a pas manuellement remonté la page pour lire une réponse précédente.

L'utilisation de react-markdown combinée à l'écosystème remark/rehype permet de transformer une simple chaîne de texte en une interface riche et sécurisée, essentielle pour toute application front-end moderne intégrant l'intelligence artificielle.

Étiquettes: react.js Markdown web-security KaTeX highlight.js

Publié le 29 juillet à 02h42