ONIR CMS
Documentation

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 / connaissances

Conséquence : le template fonctionne à l'identique en mock et en production. Seul l'hôte change.

Les actions ont leur propre documentation.

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
  }
}
ChampContenuQui le produit
statephase (identity/chat), statut, messages, typingl'hôte (serveur + conversation)
resources.mediaphotos et liens actifsmédiathèque (dashboard)
resources.modelFormchamps de la fiche modèleversion du modèle du template
themecouleurs du lienconfiguration du lien
paramsvariables déclarées (défauts ⊕ lien), geléesmanifeste + lien
consenttexte et lien de la politique de confidentialitépolitique rattachée au lien
capabilitiesce que l'hôte est autorisé à exposermanifeste 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

ActionEffet 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éeSi absente
messagesstate.messagesliste vide
mediaresources.media{ photos: [], liens: [] }
modelFormresources.modelFormnull
themethemeobjet vide
userStatemessage.userState (messages utilisateur)champ absent + double garde lien
knowledgemessage.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émentComportement mock
resources.media2 photos et 1 lien fictifs
resources.modelFormchamps fictifs
paramsdéfauts du manifeste, surchargés par la query string (?titre=…)
submitIdentitypasse en phase chat + message d'accueil
sendMessagemessage utilisateur, typing, réponse mock avec une photo
finishstatut finalisee
  • ?phase=chat saute le sas d'identité.
  • ?consent=0 retire la politique de confidentialité.
  • ?titre=Mon%20titre surcharge 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 du templateKey du 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

  1. Le template appelle actions.sendMessage(texte).
  2. L'hôte ajoute le message utilisateur et passe typing = true.
  3. 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.
  4. La réponse contient le message assistant, les références de connaissances réellement utilisées et mediaPending.
  5. Si mediaPending, l'hôte appelle /api/chat/media pour attacher photos/liens à la réponse — sans retarder le texte.
  6. L'hôte met à jour state.messages et 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

ObjetCréé parConsommé via
Photo / lienl'exploitant (dashboard → médiathèque)resources.media
Connaissancel'exploitant (source du lien)message.knowledge
Paramètrel'exploitant (formulaire du lien)params
Conversation / prospect / messagesle serveurstate.messages
Réponse LLMle serveurstate.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

EnvironnementContexteActions
Harness (npm run dev)mocksimulées localement
Aperçu du lienréel (médias, params, consentement)simulées (aucun appel API)
Lien publicréelré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.media est vide.
La capacité media n'est pas déclarée, ou aucun média actif ne porte le templateKey du lien.
Les connaissances n'apparaissent pas dans le contexte.
C'est normal : elles ne sont pas dans resources, mais dans message.knowledge des réponses assistant, et seulement si la capacité knowledge est 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.