πŸ“„ Spec Technique : Module Circuy AI "Page-Skill" & Dynamic Agent

1. Vision & Architecture Globale

Le module Page-Skill permet Γ  l'agent Circuy d'apprendre la structure fonctionnelle d'une page Γ  partir du DOM rendu (Accessibility Tree) pour gΓ©nΓ©rer instantanΓ©ment des visites guidΓ©es (Shepherd.js) en rΓ©ponse aux questions en langage naturel des utilisateurs.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PAGE WEB CLIENT (DOM Rendu)                                                β”‚
β”‚                                                                             β”‚
β”‚  [ Input Client ]    [ Select Pays ]    [ Btn Valider ]                     β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚ 1. Scan DOM / A11y Tree Extraction (SDK JS)
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CIRCUY OVERLAY / CONTENT SCRIPT                                             β”‚
β”‚                                                                             β”‚
β”‚  - Masquage PII / Anonymisation                                             β”‚
β”‚  - Simplification : Balises interactives + aria-* + data-*                  β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚ 2. Payload AnonymisΓ© (JSON A11y)
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CIRCUY BACKEND (Hono / Node.js + LLM Engine)                                β”‚
β”‚                                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ A. Mode Recording            β”‚     β”‚ B. Mode Runtime Question         β”‚  β”‚
β”‚  β”‚  Generates Page-Skill JSON   β”‚  OR β”‚  Maps Intent -> Shepherd.js JSON β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚                                      β”‚
                  β–Ό                                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ DB / Vector Store (Scaleway/OVH) β”‚   β”‚ SDK Client (ExΓ©cute le Tour)         β”‚
β”‚ Table: `page_skills`             β”‚   β”‚ `shepherd.addSteps().start()`        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜



2. Γ‰tape 1 : Le Moteur d'Extraction & Anonymisation (SDK JS)

Le SDK extrait un sous-ensemble minimal du DOM pour ne pas surcharger la fenΓͺtre de context du LLM (token-efficiency) et protΓ©ger la vie privΓ©e (RGPD).

2.1 Algorithme de Filtrage (A11y Tree Builder)

Le script parcourt le DOM et filtre uniquement les Γ©lΓ©ments possΓ©dant une sΓ©mantique d'interaction :

2.2 Anonymisation des DonnΓ©es Sensibles (PII Masking)

Avant tout envoi au serveur, le SDK applique un masque d'expressions régulières (Regex) sur le texte et les valeurs des inputs :

2.3 Structure de donnΓ©es envoyΓ©e au Backend (A11yNode[])
// types/ai.ts
export interface A11yNode {
  id: string;
  tag: string;
  type?: string;
  label?: string;
  placeholder?: string;
  role?: string;
  selector: string; // SΓ©lecteur rΓ©silient gΓ©nΓ©rΓ© par le SDK
  isInteractive: boolean;
}

export interface PageScanPayload {
  urlPattern: string; // Ex: /finance/invoices/*
  pageTitle: string;
  nodes: A11yNode[];
}



3. Étape 2 : Le Modèle de Données "Page-Skill"

Lorsqu'un crΓ©ateur/admin enregistre une page, le backend passe le PageScanPayload au LLM pour gΓ©nΓ©rer un Page-Skill JSON structurΓ©.

3.1 SchΓ©ma de la table PostgreSQL (page_skills)
CREATE TABLE page_skills (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  organization_id UUID NOT NULL REFERENCES organizations(id),
  name VARCHAR(255) NOT NULL, -- ex: "CrΓ©ation de Facture"
  route_pattern VARCHAR(512) NOT NULL, -- ex: "/app/invoices/new"
  summary TEXT NOT NULL, -- Description globale par le LLM
  actions_json JSONB NOT NULL, -- Liste des intents et sΓ©lecteurs
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_page_skills_org_route ON page_skills(organization_id, route_pattern);


3.2 Structure du JSON de Skill (actions_json)
{
  "summary": "Formulaire de crΓ©ation de facture client avec sΓ©lection d'articles et tva.",
  "intents": [
    {
      "intent_id": "select_client",
      "user_goal": "Choisir ou changer de client",
      "selector": "#client-select-dropdown",
      "fallback_selector": "select[name='clientId']",
      "action_type": "click"
    },
    {
      "intent_id": "add_discount",
      "user_goal": "Appliquer une remise ou un pourcentage de rΓ©duction",
      "selector": "[data-testid='invoice-discount-input']",
      "fallback_selector": "input[name='discount']",
      "action_type": "input"
    }
  ]
}



4. Γ‰tape 3 : Execution Runtime (Question $\rightarrow$ Tour Shepherd.js)

Quand l'utilisateur pose une question dans le widget flottant Circuy ( "Comment ajouter une remise ?" ) :

4.1 Prompt Système LLM (Hono Backend)

Le backend rΓ©cupΓ¨re le Page-Skill associΓ© Γ  l'URL courante et envoie la requΓͺte suivante au LLM (ex: Claude 3.5 Sonnet ou GPT-4o) avec Structured Outputs (JSON Schema) :

Tu es l'agent d'exΓ©cution UI de Circuy.
A partir de la question de l'utilisateur et du "Page-Skill" fourni, génère les étapes d'un tour Shepherd.js.

RÈGLES STRICTES :
1. N'utilise QUE les sΓ©lecteurs CSS fournis dans le Page-Skill.
2. Chaque Γ©tape doit comporter un titre court et un texte explicatif concis.
3. Retourne un tableau d'Γ©tapes au format JSON strict.

[PAGE SKILL]
{actions_json}

[QUESTION UTILISATEUR]
"{user_query}"


4.2 Reponse JSON gΓ©nΓ©rΓ©e par le LLM
{
  "tour_id": "dynamic_ai_tour",
  "steps": [
    {
      "attachTo": {
        "element": "[data-testid='invoice-discount-input']",
        "on": "bottom"
      },
      "title": "Saisir la remise",
      "text": "Entrez ici le pourcentage de rΓ©duction Γ  appliquer Γ  la facture.",
      "buttons": [
        {
          "text": "TerminΓ©",
          "action": "next"
        }
      ]
    }
  ]
}



5. Γ‰tape 4 : IntΓ©gration SDK Client & Shepherd.js

Le SDK rΓ©agit Γ  la rΓ©ponse de l'API et dΓ©marre le guide interactif dans la page hΓ΄te.

// sdk/src/ai-agent.ts
import Shepherd from 'shepherd.js';

export async function handleUserQuestion(question: string) {
  const currentPath = window.location.pathname;

  // 1. Appel API au backend Circuy
  const response = await fetch('https://api.circuy.com/v1/ai/ask', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ question, route: currentPath })
  });

  const { steps } = await response.json();

  if (!steps || steps.length === 0) {
    showFallbackMessage("DΓ©solΓ©, je n'ai pas trouvΓ© l'Γ©lΓ©ment correspondant sur cette page.");
    return;
  }

  // 2. Instanciation dynamique du tour Shepherd.js
  const tour = new Shepherd.Tour({
    defaultStepOptions: {
      cancelIcon: { enabled: true },
      classes: 'circuy-shepherd-theme',
      scrollTo: { behavior: 'smooth', block: 'center' }
    }
  });

  // 3. Injection des Γ©tapes gΓ©nΓ©rΓ©es par l'IA
  steps.forEach((step: any) => {
    tour.addStep({
      id: step.attachTo.element,
      text: step.text,
      title: step.title,
      attachTo: step.attachTo,
      buttons: [
        {
          text: 'Compris',
          action: tour.next
        }
      ]
    });
  });

  // 4. Lancement du parcours
  tour.start();
}



6. SΓ©curitΓ© & Robustesse

  1. SΓ©lecteurs de secours (Fallback Selectors) : Si le sΓ©lecteur principal n'est pas trouvΓ© dans le DOM au moment du tour.start(), le SDK tente automatiquement d'utiliser le fallback_selector gΓ©nΓ©rΓ© lors de l'apprentissage du Page-Skill.
  2. Isolation CSS de l'Infobulle : Les styles Shepherd.js de Circuy sont appliquΓ©s avec une classe prΓ©fixΓ©e (.circuy-shepherd-theme) et encapsulΓ©s pour ne pas impacter le CSS du site client.
  3. Budget Tokens / Latence : En stockant les Page-Skills prΓ©-calculΓ©s en BDD, la requΓͺte runtime de l'utilisateur n'envoie que la question + le JSON du skill (environ 500 tokens), garantissant une rΓ©ponse sous < 800ms .

7. Gouvernance & Workflow de Validation (Human-in-the-Loop)

Afin de garantir la qualité et la pertinence des guides sur les environnements de production, tout parcours généré dynamiquement par le LLM est soumis à un statut de modération strict. Lorsqu'une question d'un utilisateur déclenche la création d'un nouveau guide, celui-ci est exécuté uniquement pour l'utilisateur demandeur en mode éphémère et simultanément enregistré dans la base de données avec le statut pending_review. Une notification est envoyée dans l'espace d'administration (ou sur Slack/Teams) de l'équipe Produit/Admin. Tant que le parcours n'a pas été relu, éventuellement ajusté et explicitement approuvé par un administrateur (status: 'published'), il reste invisible pour l'ensemble des autres utilisateurs de la plateforme. Cela permet de bénéficier de la réactivité de l'IA tout en maintenant un contrôle qualité total sur la documentation et l'UX globale.