Anthropic a récemment lancé une nouvelle interaction d'interface utilisateur générative dans Claude. Elle vous aide à introduire des concepts et des informations visuellement dans le flux de chat, rendant la compréhension bien plus facile qu'avec du texte brut. !Image J'étudiais des solutions similaires, et quand Claude a publié la sienne, j'ai senti que je devais accélérer mon travail. En parallèle, je pouvais rétro-ingénierer leur approche pour m'en inspirer. Après deux jours à solliciter intensément Codex et Claude, j'ai réussi ! !https://pbs.twimg.com/amplify_video_thumb/2033112086713364480/img/EUmHCjsTK7tfnToZ.jpg Cette fonctionnalité permet à l'IA de dessiner des graphiques interactifs directement dans le chat avec un flux de sortie, rendant le résultat au fur et à mesure de sa génération. Avant, quand on demandait à l'IA d'écrire une page web, il fallait attendre que tout le code soit généré avant de voir le rendu, ce qui prenait une éternité. Maintenant, c'est différent. Vous pouvez regarder les graphiques se dessiner trait par trait sur le canevas, avec les nœuds SVG qui apparaissent les uns après les autres. Le processus de génération en lui-même est époustouflant, et le résultat est interactif immédiatement après la fin. Testez-le directement dans mon produit Agent, Code Pilot : https://github.com/op7418/CodePilot Dans cet article, je vais présenter son utilisation, le processus d'implémentation spécifique, et quelques précautions. ## Quels sont les cas d'utilisation amusants ? Analyse de données : enfin, les chiffres ont du sens Par exemple, demandez-lui de dessiner un graphique pour « Estimation du coût quotidien du conflit États-Unis-Iran ». Avant, l'IA vous donnait un mur de texte où les relations numériques étaient difficiles à voir. Maintenant, elle génère directement un graphique, rendant le montant de chaque partie clair d'un coup d'œil. Texte et graphiques sont produits ensemble, expliquant là où c'est nécessaire et dessinant là où c'est nécessaire. !Image Petits outils : créez une calculatrice interactive ou quelque chose de similaire Demandez-lui de faire une calculatrice d'intérêts composés. Faites glisser les curseurs pour changer le montant initial ou les années d'investissement, et les graphiques et chiffres ci-dessous se mettent à jour en temps réel. Ce n'est pas une image statique ; c'est un outil véritablement interactif. Les calculs de prêt, les conversions d'unités, etc., peuvent tous être réalisés. !Image Diagrammes d'architecture : le favori des programmeurs Vous pouvez lui demander de vous aider à dessiner l'architecture d'un projet ou de visualiser un plan d'implémentation. Par exemple, ici je lui ai demandé de dessiner le flux complet de l'API à l'authentification JWT. Les comparaisons de fonctionnalités, les organigrammes et les structures hiérarchiques sont tous graphiques, rendant l'architecture bien plus rapide à comprendre que la lecture de descriptions textuelles. !Image Analyser des données en ligne Une autre façon de jouer est de lui donner un lien vers un dépôt GitHub ; il va explorer les données et effectuer une analyse visuelle. Par exemple, j'ai envoyé l'adresse de mon propre projet, CodePilot, pour analyse. Les étoiles, les forks, la pile technologique, la conception de l'architecture et les modules principaux sont tous dessinés sous forme de graphiques. Vous pouvez voir l'ensemble du projet d'un coup d'œil, ce qui est bien mieux que de lire un long texte. !Image Explications interactives et approfondies Le point le plus fort est son intégration étroite avec le modèle ; ce n'est pas juste une sortie unique. Vous pouvez interagir avec les diagrammes générés et demander des explications plus détaillées. Par exemple, ici je lui ai demandé d'expliquer la relation entre les moussons et les courants océaniques. !Image Si nous voulons en savoir plus, nous pouvons cliquer sur le bouton pour le mécanisme des courants océaniques. Il enverra automatiquement une commande au modèle actuel pour continuer à générer un diagramme du mécanisme des courants océaniques. !Image Bien sûr, nous pouvons effectuer des interactions plus complexes, comme visualiser des formules mathématiques et physiques courantes. C'est très utile pour les étudiants ; chaque paramètre peut être contrôlé via des curseurs et des entrées, et l'animation change immédiatement. !Image Prise en charge des modèles chinois Une fois implémenté dans CodePilot, ce n'est pas réservé à Claude. Kimi K2.5, Minimax M2.5 et les modèles natifs d'Anthropic fonctionnent tous très bien. Je pense que les graphiques dessinés par K2.5 sont encore plus beaux que ceux de Sonnet 3.5, et l'analyse de l'architecture est très détaillée. Si vous utilisez cette fonctionnalité, je vous recommande d'essayer K2.5 en premier. Bon, c'est à peu près tout pour les démonstrations de modèles. Si la façon dont c'est implémenté ne vous intéresse pas, vous pouvez simplement installer CodePilot et vous amuser. ## Comment cela a-t-il été implémenté ? !Image Comment Claude fait Officiellement, Claude.ai utilise le mécanisme tool_use. Le modèle appelle un outil dédié pour produire un contenu structuré de widget, et le front-end analyse les paramètres d'entrée de l'appel d'outil pour effectuer le rendu. Cette solution fonctionne bien sous l'architecture propre de Claude.ai. Mais elle ne fonctionne pas pour CodePilot pour trois raisons : Premièrement, les limitations du SDK. CodePilot utilise le mode prédéfini claude_code du SDK Claude Agent, qui ne permet pas d'enregistrer des outils personnalisés. Le SDK expose un flux de texte delta, il n'est donc pas possible de l'étendre au niveau des outils. Deuxièmement, l'expérience de streaming. Les résultats de tool_use doivent attendre la fin de input_json_delta avant de faire le rendu, et cela ne prend pas en charge le rendu HTML incrémental. Avec les blocs de code, le HTML arrive avec le flux de texte, permettant un rendu pendant la génération. Troisièmement, l'isolation du rendu. Claude.ai utilise Shadow DOM pour l'isolation. Nous avons choisi un iframe sandboxé. L'isolation par iframe est plus complète – un environnement d'exécution JavaScript totalement indépendant, CSP contrôle précisément le chargement des ressources, et il n'y a pas de fuite de style ni d'échappement de script. Comment nous avons fait Déclencheur : blocs de code Le modèle produit un bloc de code Markdown spécial pour déclencher le rendu : ``show-widget {"title":"training_flow","widget_code":"<svg width=\"100%\" viewBox=\"0 0 680 400\">...</svg>"} ` Ce format réutilise les modes de blocs de code existants de CodePilot (image-gen-request, batch-plan, etc.), que la chaîne d'analyse du front-end prend naturellement en charge.  **Rendu : iframe sandboxé** Chaque widget est rendu dans un iframe avec sandbox="allow-scripts". Le srcdoc de l'iframe est une page réceptrice soigneusement construite. La politique CSP n'autorise les scripts externes qu'à partir de 4 domaines CDN, et connect-src 'none' interdit toutes les requêtes réseau. Les mises à jour de contenu sont reçues via postMessage. Pendant la phase d'aperçu en streaming, widget:update est envoyé sans exécuter les scripts. Le rendu final envoie widget:finalize, qui exécute les scripts. Un ResizeObserver surveille les changements de hauteur du contenu et les signale à la page parente via postMessage. Tous les clics sur <a> sont interceptés et redirigés vers la page parente pour s'ouvrir dans une nouvelle fenêtre. La synchronisation du thème repose sur la surveillance des changements de classe sur la page parente pour basculer en temps réel entre les modes sombre et clair.  **Pontage des variables CSS** C'est la clé pour que le widget se fonde visuellement dans l'application. CodePilot utilise des variables CSS dans l'espace colorimétrique OKLCH. Les directives de conception de widget d'Anthropic utilisent des noms de variables standard comme --color-background-primary. La couche de pontage injecte les valeurs des variables de CodePilot dans le :root de l'iframe lors de son initialisation. Le CSS écrit par le modèle selon les directives peut directement utiliser les couleurs du thème actuel. Lors du passage en mode sombre, la page parente détecte le changement de classe, recalcule les valeurs des variables et les pousse vers l'iframe.  **Rendu en streaming** C'est la partie la plus complexe de toute l'implémentation. Le modèle génère token par token. À tout moment, le code du widget reçu peut être un JSON incomplet, un HTML incomplet ou une balise <script> incomplète. Le flux de traitement est le suivant : Une expression régulière fait correspondre `show-widget pour distinguer les états « non fermé » et « fermé ». Localiser manuellement le contenu après "widget_code":" et le déséchapper caractère par caractère. JSON.parse ne peut pas être utilisé car le JSON n'est pas encore terminé. Lorsqu'une balise <script> non fermée est détectée, tronquer avant le <script pour éviter d'afficher le code JavaScript comme texte visible. Un debounce de 120 ms empêche l'iframe de se mettre à jour trop fréquemment. Le contenu en streaming supprime tous les scripts et gestionnaires d'événements ; l'interaction n'est pas nécessaire pendant la phase d'aperçu.  **Peaufiner l'expérience : les détails qui ne devraient pas être remarqués** D'un point de vue code ou implémentation, ce n'est pas si complexe ; la complexité réside dans le peaufinage de l'expérience. Il y a trop d'endroits qui peuvent affecter l'expérience, et les utilisateurs ne doivent pas remarquer ces détails ni le processus de génération. Cela nécessite de gérer chaque étape différemment : 1. Les détails qui ne ressemblent pas à du streaming 2. Le contenu qui ne devrait pas apparaître  **Disparition du texte** Le modèle produit d'abord un texte d'introduction (« Laissez-moi vous expliquer visuellement... »), puis commence le bloc de widget. Dès que le bloc apparaît, le texte précédent disparaît soudainement et ne revient qu'après la fin du rendu du widget. La raison est que parseAllShowWidgets() retourne un tableau vide pour le texte brut. Lorsque le bloc vient d'apparaître mais n'est pas fermé, le texte avant le bloc est passé à cette fonction et est supprimé. Correction : lorsque le texte avant le bloc ne contient pas de bloc de widget complet, le rendre directement comme <MessageResponse>, en contournant la fonction d'analyse. **Sauts de hauteur** Au moment où le widget termine son rendu, toute la zone de chat vibre. La hauteur initiale de l'iframe est de 0 px. Lorsque le contenu signale sa hauteur réelle pour la première fois, elle peut être de 400 px+, et une transition CSS fait que ce changement se produit sur 300 ms, entraînant un saut notable. Correction : désactiver temporairement les transitions CSS lors du premier rapport de hauteur pour que la hauteur se mette en place instantanément. Les ajustements de hauteur ultérieurs utilisent des transitions fluides. **Scintillement de la finalisation** Lorsque le widget passe de l'aperçu en streaming au rendu final, le contenu clignote. L'iframe réceptrice remplace tout le DOM avec root.innerHTML = html lors de la finalisation. Même si le nouveau et l'ancien contenu sont identiques (widget SVG pur), le navigateur déclenche une trame de repeinture. Correction : lors de la finalisation, analyser d'abord le nouveau HTML dans un conteneur temporaire et séparer les éléments de script. Comparer le HTML visuel (sans scripts) avec le DOM actuel – s'ils sont identiques, ignorer le remplacement de innerHTML et simplement ajouter les scripts pour exécution. Les widgets SVG purs atteignent une finalisation sans repeinture. **Sauts de défilement** Le chat défile automatiquement vers le bas, puis saute soudainement de plusieurs centaines de pixels en arrière et saute à nouveau. Lorsque le streaming se termine, le composant StreamingMessage se démonte et le composant MessageItem se monte. Ce sont deux composants React complètement différents, et le WidgetRenderer interne est détruit et reconstruit. La hauteur de l'iframe de la nouvelle instance commence à 0, ce qui fait chuter la hauteur de la zone de contenu, déclenchant un ajustement du défilement dans use-stick-to-bottom. Correction : mise en cache de la hauteur au niveau du module. Chaque fois qu'un widget signale sa hauteur, elle est écrite dans un cache en utilisant les 200 premiers caractères du widgetCode comme clé. La nouvelle instance de WidgetRenderer lit la hauteur depuis le cache lors de l'initialisation de useState, de sorte que l'iframe commence le rendu à la bonne hauteur, éliminant la transition 0→réel. **Fuite de code de script** Lorsqu'un widget avec Chart.js se charge, un gros bloc de code JavaScript est affiché en bas. Les balises <script> produites par le modèle arrivent caractère par caractère. Lorsque la balise d'ouverture arrive mais que la balise de fermeture n'est pas encore arrivée, sanitizeForStreaming supprime la balise d'ouverture, mais le code JavaScript à l'intérieur devient un nœud de texte brut rendu comme contenu visible. Correction : après avoir extrait le code partiel dans StreamingMessage, vérifier si le dernier <script a un </script> correspondant. Si ce n'est pas le cas, tronquer à la position <script. Les directives du widget spécifient que les scripts vont à la fin, donc la troncature n'affecte pas le contenu visuel. Pendant la troncature, un masque scintillant est affiché, et la barre d'état indique « Ajout d'animations interactives à la visualisation. » **Condition de concurrence de l'iframe prête** Dans de rares cas, le widget ne s'affiche pas du tout, restant à une hauteur de 0 px. WidgetRenderer enregistre un écouteur d'événement de message via useEffect. Cependant, le script récepteur de l'iframe envoie widget:ready dès qu'il se charge. Si l'iframe se charge plus rapidement que l'effet React ne s'exécute, widget:ready est envoyé avant que l'écouteur ne soit enregistré, et iframeReady ne devient jamais vrai. Correction : ajouter un callback onLoad à l'élément iframe comme solution de secours. Lorsque onLoad se déclenche, le script récepteur doit avoir fini de s'exécuter, fournissant un signal de disponibilité fiable. **Stabilité de l'arbre des composants React** Le widget clignote au moment où le bloc se ferme. Deux problèmes se chevauchent : le widget partiel en streaming n'a pas de clé React, et après la fermeture, il obtient key="w-0". Le changement de clé provoque un remontage. De plus, la superposition scintillante a été implémentée avec un <div> enveloppant, ce qui a modifié la structure de l'arbre des composants et a provoqué un autre remontage. Correction : attribuer une clé stable au widget partiel (w-N, où N est la position attendue dans le tableau final des segments), cohérente avec la clé après la fermeture. Déplacer la superposition scintillante à l'intérieur du WidgetRenderer, contrôlée via une prop showOverlay. L'arbre des composants reste <WidgetRenderer key="w-N"> tout au long. ## **Réflexions finales** Pour l'ensemble du système d'interface utilisateur générative, la partie difficile n'est pas de « faire fonctionner un morceau de HTML dans un iframe ». C'est simple. La vraie complexité réside dans le maintien de la stabilité visuelle de cet iframe pendant les transitions d'état comme le streaming, les changements de cycle de vie des composants et les changements de thème. Chaque « scintillement », « saut » ou « disparition » nécessite de comprendre la réconciliation de React, le pipeline de rendu du navigateur et le timing de postMessage`. Le résultat final est que les utilisateurs voient des graphiques et des diagrammes naturellement intercalés dans les réponses du modèle, comme s'ils avaient toujours été censés être là. C'est tout pour aujourd'hui. Si vous avez trouvé cela utile, n'hésitez pas à me donner un like ou à partager avec des amis qui pourraient en avoir besoin.
J'ai reproduit la nouvelle interface utilisateur générative de Claude !
- Vues
- 265K
- J'aime
- 446
- Reposts
- 63
- Commentaires
- 34
- Signets
- 815
TL;DR
Cet article détaille la reproduction de l'interface utilisateur générative de Claude dans Code Pilot. Il aborde les défis techniques tels que le rendu sandbox iframe, l'analyse JSON en streaming et les améliorations de l'expérience utilisateur pour garantir une fluidité optimale, sans scintillement.
Vous lisez la traduction en FRANÇAIS





