Les actions d'un template
Le seul canal du template vers l'hôte : identité, envoi de message, fin, ressources. Ce qui se passe côté serveur, en mock et en production.
Un template ne fait jamais de requête réseau. Il appelle une action ; l'hôte l'exécute (API, base, LLM, médiathèque) puis met à jour le contexte.
Template ──action──▶ Hôte ──HTTP──▶ API / PostgreSQL / LLM / médiathèque
▲ │
└── nouveau contexte ◀┘1. Transport et validation
- Web Component (transport officiel) :
ctx.actionsest une fonction locale fournie par l'hôte. L'appel est direct, sanspostMessage. - Iframe (community, hors SDK v1) : le template envoie
{ action, payload }enpostMessage. L'hôte valide chaque action avecparseTemplateActionet rejette tout ce qui n'est pas conforme.
Les actions sont donc validées côté hôte : nom requis, email valide, contenu non vide et ≤ 4000 caractères, URL http(s), hauteur numérique. Une action invalide est ignorée (iframe) ou n'a aucun effet.
2. Catalogue
actions.submitIdentity({ nom, email, contacts?, consentement? })
actions.sendMessage("Bonjour")
actions.attachFile("cin", file) // champ fichier (ADR-0034)
actions.removeAttachment(attachmentId) // retire une pièce en attente
actions.finish()
actions.request("media" | "modelForm" | "theme" | "messages")
actions.openMedia(url)
actions.resize(hauteur)| Action | Rôle | Change le contexte |
|---|---|---|
submitIdentity | démarre la conversation | phase, messages |
sendMessage | envoie un message et récupère la réponse | messages, typing |
finish | clôt la conversation | status |
request | ressource à la demande | événement resources |
openMedia | ouvre une URL | aucun |
resize | hauteur du cadre | aucun |
ready | signal de montage (iframe) | aucun |
3. submitIdentity — démarrer
// Identité : démarre la conversation.
async function submit(event) {
event.preventDefault();
const data = new FormData(event.currentTarget);
actions.submitIdentity({
nom: String(data.get("nom") ?? "").trim(),
email: String(data.get("email") ?? "").trim(),
consentement: data.get("consentement") === "on",
});
}Payload : { nom, email, contacts?, consentement? }. L'hôte normalise (nom et email trimmés, email en minuscules) et valide (nom non vide, email conforme).
Côté hôte : POST /api/chat/session avec le token du lien, l'identité, le consentement et loadedAt (anti-bot). Le serveur crée ou retrouve le prospect, crée la conversation, applique la politique de confidentialité et renvoie { conversationId, messages }.
- Réussite :
phase = "chat", messages d'accueil remplacés,typing = false. - Erreur : la phase ne change pas ; l'hôte affiche un bandeau (« Démarrage impossible » ou « Erreur réseau »).
- Mock : passage en chat et message d'accueil local, sans réseau.
Désactivez le bouton pendant l'appel : l'hôte ne déduplique pas deux soumissions simultanées.
4. sendMessage — envoyer et traiter
// Message : l'hôte ajoute, affiche "typing", appelle l'API, puis répond.
function send() {
const texte = draft.trim();
if (texte === "" || typing) return; // garde anti double-envoi
actions.sendMessage(texte);
setDraft("");
}Payload : { content }, trimmé, non vide, ≤ 4000 caractères.
Côté hôte, dans l'ordre :
- Ajout optimiste du message utilisateur,
typing = true. - Si la capacité
userStateet le flag du lien sont actifs :POST /api/chat/stateen parallèle pour attachermessage.userState(échec sans impact). POST /api/chatavec{ conversationId, content }.- Le serveur persiste le message, borne l'historique à 20 tours, construit le prompt, sélectionne les connaissances (mini-RAG, si la source est active), appelle le LLM, persiste la réponse.
- Réponse :
{ message, connaissances, mediaPending }. Lesconnaissancesne sont que des références (titre + identifiant), jamais le contenu interne. - Si
mediaPending:POST /api/chat/mediaavec{ conversationId, messageId }pour attacher photos/liens — le texte n'attend pas cette recherche. - L'hôte ajoute la réponse,
typing = false, puis réassigne le contexte.
| Situation | Résultat |
|---|---|
| Message vide ou > 4000 caractères | ignoré / 400 côté serveur |
| Réseau ou LLM en échec | message utilisateur conservé, pas de réponse, bandeau d'erreur ; vous pouvez réessayer |
mediaPending | médias attachés après coup à la réponse |
| Mock | réponse locale après 500 ms, avec une photo fictive |
Garde-fou : if (typing) return ou bouton désactivé pendant l'attente.
5. finish — clôturer
// Fin : clôture (et génération éventuelle côté serveur).
actions.finish();Côté hôte : POST /api/chat/finish avec { conversationId }. Le serveur clôt la conversation (et génère la sortie prévue). Le contexte passe à status = "finalisee".
- Erreur : bandeau « Génération impossible pour le moment » ; le statut ne change pas.
- Mock :
status = "finalisee"immédiatement. - N'appelez
finishqu'une fois : masquez le bouton après succès.
6. attachFile — pièces jointes
Les champs de type file arrivent dans ctx.resources.modelForm avec accept, maxFiles et maxBytes. Le fichier est lu par le template puis envoyé à l'hôte via attachFile(fieldName, file) ; l'hôte valide et stocke. Les pièces en attente sont exposées dans ctx.state.pendingAttachments. Un message peut être envoyé sans texte si des pièces sont en attente. Aucun OCR ni analyse IA n'est effectué.
7. request, openMedia, resize, ready
// Ressource à la demande (facultatif : déjà dans le contexte au chargement).
const media = await actions.request("media");
// Ouvrir un média (nouvel onglet, URL http(s) uniquement).
actions.openMedia(photo.url);
// Ajuster la hauteur (utile en transport iframe).
actions.resize(720);request(capability): renvoiemedia,modelForm,themeoumessages, et émet l'événementresources. Utile pour un chargement paresseux ; les données sont déjà dans le contexte initial.openMedia(url): ouvre un nouvel onglet (noopener) ; seules les URL http(s) sont acceptées.resize(height): ajuste la hauteur du cadre (0..20000, borné à 200 min par l'hôte). Surtout utile en iframe.ready(): signal de montage, sans effet en Web Component.
8. Événements de l'hôte
const offState = on("state", (state) => { … }); // à chaque contexte
const offRes = on("resources", (value) => { … }); // après request()
offState(); offRes(); // désabonnementstate est émis à chaque mise à jour (message, typing, phase, statut). resources après un request. Les erreurs sont affichées par l'hôte dans son bandeau : prévoyez un état de reprise côté UI (bouton Réessayer).
9. Machine à états
| État | Action | État suivant |
|---|---|---|
identity | submitIdentity (succès) | chat |
chat | sendMessage | chat + messages + typing |
chat | finish (succès) | finalisee |
10. Mock, aperçu, production
| Environnement | Actions | Ce qui est réellement exécuté |
|---|---|---|
| Harness | simulées localement | rien hors navigateur |
| Aperçu du lien | simulées | le contexte (médias, params, consentement) |
| Lien public | réelles | API, LLM, RAG, persistance |
Pour observer une action réelle : ouvrez le lien public, DevTools → Réseau, filtrez /api/chat. Vous verrez session, chat, media, finish et leurs réponses.
11. Questions fréquentes
- Le message n'est pas envoyé.
- Vérifiez
conversationId(identité d'abord), le contenu non vide et le statut du lien. Une erreur réseau est affichée par l'hôte. - Deux messages partent pour un seul clic.
- Désactivez l'envoi pendant
typing: l'hôte ne déduplique pas. - La réponse arrive sans photo.
- Vérifiez les sources du lien (
photothèque/lien) et les médias actifs dutemplateKey.mediaPendingn'est vrai que si des médias éligibles existent. - Les connaissances ne sont pas visibles.
- Elles arrivent dans
message.knowledgeuniquement si la capacitéknowledgeest déclarée et la source active sur le lien. Le mock n'en fournit pas. - Puis-je appeler l'API directement ?
- Non : le token reste dans l'hôte. Toute action passe par
actions.
12. Références
- Template ↔ hôte — contexte, ressources, création des objets.
- Contrat des templates visuels — actions et validation.
- Code :
src/components/templates/TemplateHost.tsx,src/domain/template-contract.ts,src/application/use-cases/chat/send-message.ts.