Aller au contenu principal
Leçon 18 sur 25 · Construire des Agents OpenAI

Theming et personnalisation

Leçon 18 sur 25 Mis à jour le 3 novembre 2026 Développeur
Sur cette page
  1. Un seul objet d’options
  2. Le thème
  3. L’écran d’accueil
  4. La zone de saisie
  5. L’en-tête, l’historique et les réponses
  6. La langue
  7. Tout assembler
  8. Points clés à retenir

Leçon réécrite le 3 novembre 2026. La version précédente s’appuyait sur des composants et des options qui n’existent pas dans ChatKit : ChatProvider, MessageInput, rendu personnalisé des messages, feuille de style injectée dans le chat. Elle suit désormais le guide Theming and customization d’OpenAI et les types de @openai/chatkit 1.9.0 ; l’exemple complet a été vérifié par compilation TypeScript. Deux noms diffèrent entre le guide et le paquet publié, et la leçon suit le paquet : label (et non name) pour les suggestions de l’écran d’accueil, leftAction et rightAction (et non customButtonLeft et customButtonRight) pour les boutons de l’en-tête. Sources : Theming and customization in ChatKit · référence ChatKitOptions · ChatKit Studio.

Un seul objet d’options

Toute la personnalisation de ChatKit passe par un objet d’options. En React, vous le passez à useChatKit({...}) ; avec l’élément web, à chatkit.setOptions({...}). Sa forme est la même dans les deux cas : ce que vous apprenez ici vaut pour toutes les intégrations.

Une conséquence découle de l’architecture vue à la leçon précédente : ChatKit s’affiche dans une iframe, et votre feuille de style ne l’atteint pas. Vos règles CSS s’appliquent au conteneur, la taille et la position que vous donnez à <ChatKit className="…" /> ; tout le reste se règle par les options. C’est une bonne nouvelle pour la maintenance : une mise à jour de ChatKit ne peut pas casser des sélecteurs CSS qui n’existent pas.

Pour essayer les options sans écrire de code, le bac à sable de ChatKit Studio affiche le rendu en direct.

Le thème

Le thème règle l’apparence générale : schéma de couleurs, couleur d’accent, arrondis, densité et typographie.

const { control } = useChatKit({
  api: { url: '/chatkit', domainKey: 'local-dev' },
  theme: {
    colorScheme: 'dark',
    color: {
      accent: { primary: '#2D8CFF', level: 2 },
    },
    radius: 'round',
    density: 'compact',
    typography: { fontFamily: "'Inter', sans-serif", baseSize: 16 },
  },
});

Chaque réglage a un jeu de valeurs fermé, que TypeScript vérifie pour vous :

OptionValeursEffet
colorScheme'light', 'dark'Thème clair (par défaut) ou sombre
color.accentprimary (hexadécimal, rgb(), hsl()…), level de 0 à 3Couleur d’accent de l’interface et intensité de sa palette
color.grayscalehue en degrés, tint de 0 à 9, shade de −4 à 4Teinte et nuance de la palette de gris
color.surfacebackground, foregroundCouleurs des surfaces : fond et premier plan (texte, icônes)
radius'pill', 'round', 'soft', 'sharp'Arrondi des bulles, boutons et champs
density'compact', 'normal', 'spacious'Espacement général
typographyfontFamily, fontFamilyMono, baseSize (14 à 18), fontSourcesPolices et taille de base ; fontSources charge une police web (family, src, weight…)

Pour suivre la préférence de l’utilisateur, choisissez le schéma au moment de construire les options, par exemple avec window.matchMedia('(prefers-color-scheme: dark)').matches. Un theme réduit à une chaîne, theme: 'dark', est aussi accepté quand vous ne réglez que le schéma de couleurs.

L’écran d’accueil

Un fil vide est intimidant. L’écran d’accueil le remplace par un message et des suggestions, qui montrent à l’utilisateur ce que l’assistant sait faire :

startScreen: {
  greeting: 'Bonjour ! Comment puis-je vous aider ?',
  prompts: [
    { label: 'Suivre une commande', prompt: 'Où en est ma dernière commande ?', icon: 'search' },
    { label: 'Ouvrir un ticket', prompt: 'Je voudrais signaler un problème.', icon: 'write' },
  ],
},

label est le texte du bouton, prompt le message envoyé quand l’utilisateur clique, et icon un nom pris dans la liste des icônes de ChatKit (search, write, calendar, lightbulb…), ou une icône Lucide sous la forme 'lucide:nom'.

La zone de saisie

L’objet composer règle le texte d’invite, les outils proposés à l’utilisateur et les pièces jointes :

composer: {
  placeholder: 'Posez votre question sur votre commande…',
  tools: [
    { id: 'recherche-commande', label: 'Rechercher une commande', icon: 'search', pinned: true },
  ],
  attachments: {
    enabled: true,
    maxCount: 3,
    maxSize: 20 * 1024 * 1024, // 20 Mo par fichier
    accept: { 'application/pdf': ['.pdf'], 'image/*': ['.png', '.jpg'] },
  },
},

L’outil choisi dans le menu est transmis au serveur comme une préférence d’outil pour le tour suivant ; à vous d’en tenir compte dans votre agent.

Les pièces jointes sont désactivées par défaut, et les activer demande du travail côté serveur. Avec votre propre serveur, il faut aussi indiquer dans api comment les fichiers sont envoyés : uploadStrategy: { type: 'direct', uploadUrl: '…' } (l’interface poste le fichier à votre point d’accès) ou { type: 'two_phase' } (elle demande une URL signée, puis dépose le fichier dans votre stockage). Côté Python, vous fournissez alors un AttachmentStore. Ne cochez pas enabled: true tant que ce circuit n’existe pas.

L’en-tête, l’historique et les réponses

header: {
  title: { text: 'Assistant commandes' },
  leftAction: { icon: 'settings-cog', onClick: () => ouvrirReglages() },
},
history: { enabled: true, showRename: true, showDelete: false },
threadItemActions: { feedback: true, retry: true },
disclaimer: { text: "Les réponses peuvent contenir des erreurs. [Nos conditions](/cgu)" },
  • header affiche un titre fixe (sinon le titre du fil en cours) et jusqu’à deux boutons à vous, leftAction et rightAction, avec une icône d’en-tête (settings-cog, home…) et une fonction. header: { enabled: false } masque tout l’en-tête quand votre application fournit déjà sa propre barre.
  • history active le panneau des conversations passées, avec ou sans renommage et suppression. Désactivez-le quand la notion de fil n’a pas de sens pour vous, par exemple pour un assistant de support sans compte utilisateur.
  • threadItemActions.feedback affiche les pouces haut et bas sous les réponses. Chaque clic arrive sur votre serveur, dans la méthode add_feedback de votre ChatKitServer (thread_id, item_ids, feedback à 'positive' ou 'negative'). C’est la matière première de vos évaluations. retry ajoute le bouton qui relance une réponse.
  • disclaimer affiche sous la zone de saisie un court texte en Markdown.

La langue

Par défaut, ChatKit prend la langue du navigateur. Si votre application a son propre réglage de langue, imposez-la avec locale: 'fr', ou une variante régionale comme 'fr-FR' ou 'fr-CA'. Chaque requête envoyée à votre serveur porte cette langue dans l’en-tête Accept-Language : utilisez-la pour faire répondre l’agent et traduire les messages de vos outils dans la même langue que l’interface.

Tout assembler

Voici une configuration complète, compilée telle quelle avec @openai/chatkit-react 1.6.1 :

import { ChatKit, useChatKit } from '@openai/chatkit-react';

export function AssistantCommandes({ ouvrirReglages }: { ouvrirReglages: () => void }) {
  const sombre = window.matchMedia('(prefers-color-scheme: dark)').matches;

  const { control } = useChatKit({
    api: { url: '/chatkit', domainKey: 'local-dev' },
    locale: 'fr',
    theme: {
      colorScheme: sombre ? 'dark' : 'light',
      color: { accent: { primary: '#2D8CFF', level: 2 } },
      radius: 'round',
      density: 'normal',
    },
    startScreen: {
      greeting: 'Bonjour ! Comment puis-je vous aider ?',
      prompts: [
        { label: 'Suivre une commande', prompt: 'Où en est ma dernière commande ?', icon: 'search' },
        { label: 'Ouvrir un ticket', prompt: 'Je voudrais signaler un problème.', icon: 'write' },
      ],
    },
    composer: { placeholder: 'Posez votre question sur votre commande…' },
    header: {
      title: { text: 'Assistant commandes' },
      leftAction: { icon: 'settings-cog', onClick: ouvrirReglages },
    },
    history: { enabled: true, showRename: true, showDelete: false },
    threadItemActions: { feedback: true, retry: true },
  });

  return <ChatKit control={control} className="h-[640px] w-full max-w-md" />;
}

Points clés à retenir

  • Toute la personnalisation passe par un seul objet d’options, identique en React (useChatKit) et avec l’élément web (setOptions)
  • ChatKit s’affiche dans une iframe : votre CSS ne règle que le conteneur, le reste passe par theme
  • theme : colorScheme, color.accent, radius, density, typography, avec des valeurs fermées vérifiées par TypeScript
  • startScreen (accueil et suggestions), composer (invite, outils, pièces jointes), header, history, threadItemActions, disclaimer et locale complètent l’interface
  • Les pièces jointes et les retours utilisateur demandent un relais côté serveur : uploadStrategy et AttachmentStore, méthode add_feedback