Template ↔ hôte : données, actions et mode mock
Qui fournit la photothèque, les liens, les connaissances et le traitement des messages — et ce que votre template doit (ou ne doit pas) faire.
1. Le principe
Un template est une interface pure. Il ne lit jamais la base de données, la médiathèque, la base de connaissances ni le LLM. L'hôte (le harness en développement, le dashboard en production) construit un contexte, l'injecte dans l'élément, puis exécute les actions que le template demande.
Template (UI) ──actions.sendMessage()──▶ Hôte (harness | dashboard)
▲ │
└──── contexte (state, resources, params) ◀┘
│ HTTP
▼
API / PostgreSQL / LLM / médiathèque / connaissancesConséquence : le template fonctionne à l'identique en mock et en production. Seul l'hôte change.
Les actions d'un template détaillent chaque appel (identité, envoi, fin, ressources) et son traitement côté hôte.
2. Ce que le template reçoit
// Ce que le template reçoit (sans les actions, ajoutées par l'hôte)
{
host: { protocol: "onirtech.template-host.v1", version: 1 },
link: { type: "standard", label: "…", userStateEnabled: false },
locale: "fr",
brand: { name: "ONIRTECH", logoUrl: null },
theme: { … }, // couleurs du lien
capabilities: ["messages", "media", "theme"],
params: { titre: "…" }, // défauts ⊕ valeurs du lien (gelé)
consent: { texte: "…", lien: "…" } | null,
state: { phase: "identity", status: "en_cours", messages: [], typing: false },
resources: {
media: { photos: [ { id, libelle, url } ], liens: [ … ] },
modelForm: [ { name, label, type, required } ] | null
}
}| Champ | Contenu | Qui le produit |
|---|---|---|
state | phase (identity/chat), statut, messages, typing | l'hôte (serveur + conversation) |
resources.media | photos et liens actifs | médiathèque (dashboard) |
resources.modelForm | champs de la fiche modèle | version du modèle du template |
theme | couleurs du lien | configuration du lien |
params | variables déclarées (défauts ⊕ lien), gelées | manifeste + lien |
consent | texte et lien de la politique de confidentialité | politique rattachée au lien |
capabilities | ce que l'hôte est autorisé à exposer | manifeste du template |
Les actions ne font pas partie du contexte sérialisé : l'hôte les ajoute à l'exécution (un template ne peut donc jamais être “rejoué” avec des actions forgées côté serveur).
3. Ce que le template peut faire
| Action | Effet côté hôte |
|---|---|
submitIdentity(data) | crée la conversation/prospect via POST /api/chat/session, applique le consentement, renvoie les premiers messages |
sendMessage(texte) | ajoute le message, passe en typing, appelle POST /api/chat (LLM + RAG + persistance), ajoute la réponse |
finish() | clôt la conversation via POST /api/chat/finish |
request(capability) | renvoie une ressource à la demande (média, fiche, thème…) |
openMedia(url) | ouvre un média dans un nouvel onglet |
resize(height) | ajuste la hauteur du cadre hôte |
Vous pouvez aussi vous abonner à on("state" | "resources" | "error", cb) pour réagir aux mises à jour poussées par l'hôte.
// Côté template : on lit le contexte, on appelle les actions.
const { state, resources, params, actions } = useTemplate();
if (state.typing) afficherIndicateur();
for (const photo of resources.media.photos) afficherPhoto(photo);
actions.sendMessage("Bonjour");
actions.finish();4. Les capacités filtrent tout
Une ressource n'est fournie que si le manifeste déclare la capacité correspondante. C'est le premier niveau de défense : sans déclaration, la donnée est absente ou vide.
| Capacité | Donnée exposée | Si absente |
|---|---|---|
messages | state.messages | liste vide |
media | resources.media | { photos: [], liens: [] } |
modelForm | resources.modelForm | null |
theme | theme | objet vide |
userState | message.userState (messages utilisateur) | champ absent + double garde lien |
knowledge | message.knowledge (messages assistant) | champ absent + double garde source |
userState et knowledge sont soumis à une double garde (ADR-0020) : capacité déclarée et option active sur le lien (link.userStateEnabled / sources.connaissances).
5. En mode mock (harness)
npm run dev lance harness/host.mjs, qui fabrique un contexte factice et implémente les actions localement, sans réseau :
| Élément | Comportement mock |
|---|---|
resources.media | 2 photos et 1 lien fictifs |
resources.modelForm | champs fictifs |
params | défauts du manifeste, surchargés par la query string (?titre=…) |
submitIdentity | passe en phase chat + message d'accueil |
sendMessage | message utilisateur, typing, réponse mock avec une photo |
finish | statut finalisee |
?phase=chatsaute le sas d'identité.?consent=0retire la politique de confidentialité.?titre=Mon%20titresurcharge un paramètre.- Recharger la page réinitialise tout : rien n'est persisté.
Le mock ne teste pas : le LLM, le RAG, la persistance, les droits d'accès aux médias ni le consentement réel. Pour cela, utilisez l'aperçu ou le lien public.
Pour des fixtures plus riches, modifiez le contexte mock dans harness/host.mjs (il est copié dans votre projet, il n'est pas publié).
6. En production (dashboard)
La page /l/[token] charge le lien, le template et le consentement, puis construit le contexte avec buildLinkContext :
- Médias :
listActivesByType("photo"|"lien", link.templateKey). Seuls les médias actifs dutemplateKeydu lien sont exposés. Les photos passent par/api/medias/:id/file(proxy autorisé), jamais par une URL directe. - Fiche modèle : champs de la version du modèle du template.
- Thème / paramètres : configuration du lien + défauts du manifeste (valeurs invalides → défaut).
- Messages : vides en lien standard (phase identité), issus de la conversation en lien spécifique.
TemplateHost injecte ensuite les actions réelles et réassigne element.onirtechContext à chaque changement d'état (nouveau message,typing, phase, statut).
7. Cycle d'un message
- Le template appelle
actions.sendMessage(texte). - L'hôte ajoute le message utilisateur et passe
typing = true. POST /api/chat: validation (≤ 4000 caractères), persistance, historique borné (20 tours), prompt système, puis mini-RAG sur les connaissances si la source est active, puis appel LLM.- La réponse contient le message assistant, les références de connaissances réellement utilisées et
mediaPending. - Si
mediaPending, l'hôte appelle/api/chat/mediapour attacher photos/liens à la réponse — sans retarder le texte. - L'hôte met à jour
state.messageset réassigne le contexte : votre composant se re-rend.
L'identité suit le même principe : submitIdentity → POST /api/chat/session → création de la conversation/prospect, des messages d'accueil, puis passage en phase chat.
8. Qui crée quoi
| Objet | Créé par | Consommé via |
|---|---|---|
| Photo / lien | l'exploitant (dashboard → médiathèque) | resources.media |
| Connaissance | l'exploitant (source du lien) | message.knowledge |
| Paramètre | l'exploitant (formulaire du lien) | params |
| Conversation / prospect / messages | le serveur | state.messages |
| Réponse LLM | le serveur | state.messages |
9. Sécurité
- Le token du lien reste dans les fermetures de l'hôte : il n'apparaît jamais dans le contexte. Un template ne peut pas appeler l'API directement.
- Les actions sont validées côté hôte (contenu non vide, ≤ 4000 caractères, identité conforme) — l'hôte ne fait jamais confiance au template.
- Les médias sont servis par un proxy autorisé ; les URL directes ne sont pas exposées.
- Les valeurs du contexte sont insérées en texte, jamais en HTML.
- Les paramètres sont publics : n'y mettez jamais de secret.
10. Tester avec de vraies données
| Environnement | Contexte | Actions |
|---|---|---|
Harness (npm run dev) | mock | simulées localement |
| Aperçu du lien | réel (médias, params, consentement) | simulées (aucun appel API) |
| Lien public | réel | réelles (LLM, RAG, persistance) |
L'aperçu est le meilleur compromis pour valider l'affichage avec vos vraies photos et connaissances sans créer de conversation.
11. Questions fréquentes
resources.mediaest vide.- La capacité
median'est pas déclarée, ou aucun média actif ne porte letemplateKeydu lien. - Les connaissances n'apparaissent pas dans le contexte.
- C'est normal : elles ne sont pas dans
resources, mais dansmessage.knowledgedes réponses assistant, et seulement si la capacitéknowledgeest déclarée et la source active sur le lien. - Le mock ne renvoie pas mes vraies photos.
- Le harness est hors-ligne. Utilisez l'aperçu du lien pour le contexte réel, ou modifiez les fixtures du harness.
- Puis-je appeler l'API depuis mon template ?
- Non : le contrat l'interdit (pas de token, pas d'URL d'API). Passez toujours par
actions. - Le message est envoyé mais rien ne revient.
- Vérifiez la console réseau de l'hôte (lien public), le statut de la conversation et la configuration du lien (sources, modèle).
12. Références
- Contrat des templates visuels — manifeste, capacités, paramètres.
- SDK JavaScript et Your first package.
- ADR-0019 (packages UI-only), ADR-0020 (capacités cœur), ADR-0023 (paramètres).
- Code :
src/domain/template-contract.ts,src/application/use-cases/visual-templates/link-template.ts,src/components/templates/TemplateHost.tsx.