| name | build-chatwindow |
| description | Construit des chatwindows avec widgets interactifs ChatWidget. Guide la création de widgets selon le contrat ChatWidget, leur intégration dans ChatWindow, la configuration des workflows (OpenAI Agent / n8n), et la validation via le Playground. Utilise ce skill lorsque l'utilisateur demande de créer, modifier ou intégrer des widgets dans une conversation ChatWindow. |
| license | MIT |
| metadata | [object Object] |
Construction de ChatWindows avec Widgets
Ce skill guide la construction de chatwindows avec widgets interactifs ChatWidget selon l'architecture ChatWindow. Cette architecture est réutilisable dans tout projet utilisant Supabase, React et des workflows (OpenAI Agent Builder / n8n).
Quand utiliser ce skill
Utilisez ce skill lorsque :
- L'utilisateur demande de créer un nouveau widget ChatWidget
- Il faut intégrer un widget dans une conversation ChatWindow
- Il faut configurer un workflow pour renvoyer des widgets
- Il faut modifier un widget existant
- Il faut valider le rendu d'un widget dans le Playground
- Il faut comprendre le contrat ChatWidget et les types supportés
Architecture ChatWindow
ChatWindow est une interface de conversation réutilisable qui s'appuie sur :
- Frontend : Composants
ChatWindow+ChatWidget+ hook de gestion (ex:useChatKit) - Stockage : Threads + messages (Supabase Postgres ou autre base de données)
- Workflows :
openai_agentoun8n(configuration en base de données) - Routeur SSE : Edge Function (ex:
chatkit) qui stream les réponses + widgets
Note pour adaptation : Les noms de composants et fonctions peuvent varier selon votre projet. Adaptez les chemins et noms selon votre structure de code.
Flux de données
Utilisateur → ChatWindow → useChatKit → Edge Function chatkit → Workflow → SSE → Widget
Contrat ChatWidget
Format de transport (SSE)
Un widget est envoyé via un événement SSE :
{
"type": "widget",
"widget": {
"id": "w-unique-001",
"type": "button",
"label": "Actions disponibles",
"data": { /* données spécifiques au type */ },
"actions": [ /* ChatWidgetAction[] */ ]
}
}
Schéma TypeScript
interface ChatWidget {
id: string; // Identifiant unique stable
type: 'button' | 'form' | 'select' | 'checkbox' | 'radio' | 'summary_confirm';
label?: string; // Libellé affiché
data?: Record<string, unknown>; // Données spécifiques au type
actions?: ChatWidgetAction[]; // Actions disponibles
}
interface ChatWidgetAction {
id: string; // Identifiant unique
label: string; // Libellé du bouton
action: string; // Identifiant stable de l'action
payload?: Record<string, unknown>; // Valeurs fixes fusionnées avec formData
}
⚠️ Important : input existe dans les types mais n'est pas rendu actuellement. Éviter ce type.
Types de widgets supportés
1. button — Actions rapides
Usage : Afficher des boutons d'action rapide sans saisie utilisateur.
Structure data :
- Pas de
datarequis (ou vide) - Les actions sont définies dans
actions[]
Exemple :
{
"id": "w-actions-001",
"type": "button",
"label": "Actions disponibles",
"actions": [
{
"id": "a-open-linear",
"label": "Ouvrir dans Linear",
"action": "open_url",
"payload": { "url": "https://linear.app/org/issue/APP-123" }
},
{
"id": "a-navigate",
"label": "Voir les détails",
"action": "navigate",
"payload": { "path": "/requests/uuid-ticket" }
}
]
}
2. form — Collecte d'informations
Usage : Formulaire avec champs multiples pour collecter des données.
Structure data :
{
description?: string; // Description affichée au-dessus du formulaire
fields: Array<{
name: string; // Nom du champ (clé dans formData)
type: 'text' | 'textarea' | 'number' | 'email' | ...;
label: string; // Libellé affiché
required?: boolean; // Champ obligatoire
}>;
// Valeurs initiales (optionnel) : clés au même niveau que fields
titre?: string;
description?: string;
}
Exemple :
{
"id": "w-ticket-form-001",
"type": "form",
"label": "Compléter la demande",
"data": {
"description": "Merci de compléter ces informations avant création.",
"fields": [
{ "name": "titre", "type": "text", "label": "Titre", "required": true },
{ "name": "description", "type": "textarea", "label": "Description", "required": true },
{ "name": "impact", "type": "text", "label": "Impact", "required": false }
],
"titre": "Erreur sur fiche client",
"description": "Quand on ouvre une fiche client, un message d'erreur apparaît.",
"impact": "Blocage partiel"
},
"actions": [
{
"id": "a-create-ticket",
"label": "Créer la demande",
"action": "create_ticket",
"payload": { "module": "clients", "priority": "P1" }
}
]
}
3. select — Choix unique (liste déroulante)
Usage : Permettre à l'utilisateur de choisir une option parmi plusieurs.
Structure data :
{
options: Array<{ value: string; label: string }>;
value?: string; // Valeur présélectionnée (optionnel)
}
Comportement : Si actions.length === 1, l'action est déclenchée automatiquement lors de la sélection.
Exemple :
{
"id": "w-priority-001",
"type": "select",
"label": "Choisir la priorité",
"data": {
"options": [
{ "value": "P0", "label": "P0 (Critique)" },
{ "value": "P1", "label": "P1 (Haute)" },
{ "value": "P2", "label": "P2 (Normale)" },
{ "value": "P3", "label": "P3 (Faible)" }
],
"value": "P2"
},
"actions": [
{ "id": "a-set-priority", "label": "Confirmer", "action": "set_priority" }
]
}
4. checkbox — Choix multiples
Usage : Permettre à l'utilisateur de sélectionner plusieurs options.
Structure data :
{
options: Array<{ value: string; label: string }>;
}
Comportement : formData[option.value] devient un booléen (true si coché, false sinon).
Exemple :
{
"id": "w-flags-001",
"type": "checkbox",
"label": "Informations disponibles",
"data": {
"options": [
{ "value": "logs", "label": "Logs" },
{ "value": "steps", "label": "Étapes pour reproduire" },
{ "value": "screenshot", "label": "Capture" }
]
},
"actions": [
{ "id": "a-submit-flags", "label": "Continuer", "action": "confirm_flags" }
]
}
5. radio — Choix unique (boutons radio)
Usage : Permettre à l'utilisateur de choisir une option parmi plusieurs (affichage radio).
Structure data :
{
options: Array<{ value: string; label: string }>;
value?: string; // Valeur présélectionnée (optionnel)
}
Comportement : Comme select, auto-trigger si actions.length === 1.
Exemple :
{
"id": "w-type-001",
"type": "radio",
"label": "Type de demande",
"data": {
"options": [
{ "value": "incident", "label": "Incident" },
{ "value": "question", "label": "Question" },
{ "value": "amelioration", "label": "Amélioration" }
],
"value": "incident"
},
"actions": [
{ "id": "a-confirm-type", "label": "Confirmer", "action": "set_type" }
]
}
6. summary_confirm — Résumé et confirmation
Usage : Afficher un résumé lisible avec confirmation avant action finale.
Structure data :
{
title?: string; // Titre du résumé
understood?: string[]; // Liste des points compris
confirmation?: string; // Question de confirmation
next_step?: string; // Prochaine étape après confirmation
}
Spécificité : Le renderer envoie un contexte minimal à l'action ({ widget_id: widget.id }) pour éviter d'envoyer tout widget.data.
Exemple :
{
"id": "w-orchestrator-validation-001",
"type": "summary_confirm",
"data": {
"title": "✅ Demande — Bug",
"understood": [
"Quand vous cliquez sur \"Client\", vous arrivez sur une page blanche.",
"Vous vous attendiez à voir la fiche client s'afficher normalement."
],
"confirmation": "Pouvez-vous confirmer que c'est bien ça ? OK / à corriger",
"next_step": "Parfait, votre demande est prise en charge par l'équipe et sera traitée rapidement."
},
"actions": [
{
"id": "a-confirm-orchestrator",
"label": "Confirmé",
"action": "confirm_orchestrator",
"payload": { "confirmed": true }
}
]
}
Convention d'exécution des actions
Lorsqu'un utilisateur déclenche une action :
- Fusion des données :
payload(valeurs fixes) est fusionné avecformData(valeurs saisies) - Envoi : Le frontend appelle
triggerAction(action.action, mergedPayload) - Traitement : L'action repasse par le même routeur SSE que les messages
Bonnes pratiques pour les actions
action: Identifiant stable et descriptif (open_url,navigate,create_ticket,set_priority, etc.)payload: Valeurs immuables (ex:{ path: "/requests/..." },{ module: "clients" })formData: Valeurs dynamiques saisies par l'utilisateur (fusionnées automatiquement)
Processus de construction
Étape 1 : Définir le widget
- Choisir le type : Sélectionner le type de widget approprié selon le besoin
- Définir l'ID : Utiliser un ID stable et descriptif (ex:
w-ticket-form-001) - Structurer les données : Préparer
dataselon le type choisi - Définir les actions : Identifier les actions possibles et leurs
payload
Étape 2 : Créer le JSON du widget
Créer le widget selon le contrat ChatWidget :
{
"id": "w-unique-id",
"type": "button",
"label": "Libellé du widget",
"data": { /* selon le type */ },
"actions": [ /* ChatWidgetAction[] */ ]
}
Étape 3 : Tester dans le Playground
- Accéder au Playground : Route de votre projet (ex:
/admin/widget-playgroundou/widgets/playground) - Copier le JSON : Coller le widget dans le Playground
- Vérifier le rendu : S'assurer que le widget s'affiche correctement
- Tester les actions : Vérifier que les actions déclenchent les bons événements (toast + log console)
- Vérifier la console : Confirmer 0 erreur console
Note : Si votre projet n'a pas encore de Playground, créez-en un en vous basant sur les composants
ChatWidgetexistants. Voir la section "Sources de vérité" pour les fichiers de référence.
Étape 4 : Intégrer dans le workflow
Pour OpenAI Agent Builder
Dans le prompt de l'agent, inclure le widget dans la réponse :
{
"type": "widget",
"widget": { /* ChatWidget */ }
}
Pour n8n
Dans le webhook n8n, renvoyer :
{
"content": "Message texte",
"widget": { /* ChatWidget */ }
}
Le routeur SSE convertit automatiquement en événement SSE.
Étape 5 : Valider dans ChatWindow
- Utiliser la bulle de test : Composant de test ChatWindow (ex:
ChatWindowWidgetou équivalent) - Sélectionner un workflow : Choisir un workflow actif dans votre configuration
- Envoyer un message : Déclencher le workflow qui renvoie le widget
- Vérifier le rendu : S'assurer que le widget s'affiche sous le message
- Tester les actions : Vérifier que les actions fonctionnent correctement
- Vérifier la console : Confirmer 0 erreur console
Note : Adaptez cette étape selon votre mécanisme de test. L'important est de valider le widget dans un contexte de conversation réel.
Checklist de validation
Validation Playground (obligatoire)
- Le widget s'affiche correctement dans votre Playground (route selon votre projet)
- Les champs/formulaires sont interactifs
- Les actions déclenchent un toast + log console
- Le JSON du widget est valide (pas d'erreur de parsing)
- L'enveloppe SSE (
{ type: "widget", widget: ... }) est correcte - 0 erreur console dans le navigateur
Validation ChatWindow (obligatoire)
- Le widget s'affiche sous le message dans ChatWindow
- Les interactions utilisateur fonctionnent (saisie, sélection, clics)
- Les actions renvoient un événement observable (message/action)
- Le streaming SSE fonctionne correctement
- Les messages user/assistant sont persistés en base
- 0 erreur console dans le navigateur
Bonnes pratiques
IDs stables
- Utiliser des IDs déterministes et descriptifs
- Format recommandé :
w-{type}-{purpose}-{number}(ex:w-ticket-form-001) - Éviter les IDs générés aléatoirement
Actions stables
actiondoit être stable et descriptif- Exemples :
open_url,navigate,create_ticket,set_priority,confirm_orchestrator - Éviter les actions génériques comme
submit,click,confirm
Données minimales
- Éviter de renvoyer un
widget.dataénorme côté backend - Préférer
payload(valeurs fixes) + champs saisis (formData) - Pour
summary_confirm, utiliser le contexte minimal ({ widget_id })
Accessibilité
- Associer des labels aux champs
- Gérer le focus correctement (composants shadcn/radix aident)
- Tester au clavier (Tab, Enter, Espace)
Ajouter un nouveau type de widget
Si vous devez ajouter un nouveau type de widget :
- Étendre le type TS : Ajouter le type dans vos types TypeScript (ex:
src/types/chat.ts) - Implémenter le rendu : Ajouter le rendu dans votre composant ChatWidget (ex:
src/components/chat/ChatWidget.tsx) - Ajouter un exemple : Ajouter un exemple dans votre Playground (ex:
src/pages/WidgetPlayground.tsx) - Mettre à jour le contrat : Documenter dans votre contrat widgets (ex:
agentbuilder/WIDGETS_CONTRACT.mdou équivalent) - Valider : Tester dans le Playground + contexte de conversation réel (console = 0 erreur)
Note : Adaptez les chemins de fichiers selon la structure de votre projet.
Sources de vérité (structure type)
Dans un projet utilisant cette architecture, vous devriez avoir :
- Contrat widgets : Documentation du format ChatWidget (ex:
agentbuilder/WIDGETS_CONTRACT.mdoudocs/widgets-contract.md) - Types TS : Types TypeScript pour ChatWidget (ex:
src/types/chat.tsou équivalent) - Renderer widgets : Composant qui rend les widgets (ex:
src/components/chat/ChatWidget.tsx) - UI conversation : Composant principal de conversation (ex:
src/components/chat/ChatWindow.tsx) - Hook data/transport : Hook qui gère les messages et widgets (ex:
src/hooks/useChatKit.tsou équivalent) - Playground widgets : Page de test/prévisualisation (ex:
src/pages/WidgetPlayground.tsx) - Bulle de test : Composant de test ChatWindow (ex:
src/components/chat/ChatWindowWidget.tsx) - Routeur SSE : Edge Function ou API qui stream les réponses (ex:
supabase/functions/chatkit/index.tsou équivalent) - Documentation : Documentation du projet (ex:
docs/chatbot/ou équivalent)
Note pour adaptation : Ces chemins sont des exemples. Adaptez-les selon la structure de votre projet. L'important est d'avoir ces composants/fichiers quelque part dans votre codebase.
Références
Documentation générique
- Ce skill fournit toutes les informations nécessaires pour créer des widgets ChatWidget
- Les exemples dans
references/WIDGET_EXAMPLES.mdsont réutilisables tels quels - Le guide d'intégration dans
references/WORKFLOW_INTEGRATION.mdest applicable à tout projet
Documentation associée (pack)
Dans ce pack, une documentation générique est fournie ici :
docs/chatwindow/README.md
Note : Cette doc est conçue pour être réutilisable. Adaptez les chemins/fichiers à votre projet si nécessaire.
Adaptation pour un nouveau projet
Ce skill est conçu pour être réutilisable dans tout projet. Pour l'adapter :
1. Structure de fichiers
Adaptez les chemins mentionnés selon votre structure :
- Types TypeScript : où vous définissez vos types
ChatWidget - Composants : où vous avez vos composants
ChatWindowetChatWidget - Playground : route et composant de votre Playground
- Routeur SSE : votre Edge Function ou API qui stream les réponses
2. Noms de composants
Les noms de composants peuvent varier :
ChatWindow→ votre composant de conversationChatWidget→ votre composant de widgetuseChatKit→ votre hook de gestion (ou équivalent)
3. Configuration
- Base de données : Adaptez selon votre système (Supabase, autre)
- Workflows : Adaptez selon vos workflows (OpenAI Agent Builder, n8n, autre)
- Routes : Adaptez les routes du Playground selon votre routing
4. Contrat ChatWidget
Le contrat ChatWidget décrit dans ce skill est universel et peut être utilisé tel quel. Seuls les chemins de fichiers et noms de composants doivent être adaptés.
5. Validation
Les processus de validation décrits (Playground + ChatWindow) sont applicables à tout projet. Adaptez uniquement les routes et noms de composants.