écrits/tutorial/2026/07
Tutorial3 juil. 2026·28 min

Créer un copilote IA in-page avec Alibaba Page Agent et TypeScript

Apprenez à intégrer Alibaba Page Agent dans une application Next.js 15 pour créer un copilote IA zéro-infrastructure qui contrôle votre interface en langage naturel — sans captures d'écran, sans navigateurs headless, juste du TypeScript qui lit le DOM.

La plupart des outils d'automatisation de navigateur fonctionnent comme un touriste avec un appareil photo — ils prennent une capture d'écran, l'envoient à un modèle de vision, devinent les coordonnées en pixels, puis cliquent. Un seul changement CSS brise la logique. Page Agent d'Alibaba prend le chemin inverse : il lit le DOM directement, à la manière d'un développeur qui inspecte le panneau Elements. Le résultat est une bibliothèque TypeScript qui transforme n'importe quelle interface web en quelque chose qu'un modèle de langage peut piloter avec des phrases simples — sans Chrome headless, sans processus backend, sans tokens d'image.

Ce tutoriel vous guide pas à pas pour intégrer Page Agent dans une application Next.js 15 et construire un panneau copilote IA flottant. À la fin, l'utilisateur tape « Ajouter une tâche appelée Déployer le backend pour vendredi, priorité haute » — et l'agent remplit le formulaire et le soumet automatiquement.

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 20 ou supérieur installé
  • Une connaissance de base de React et TypeScript
  • Une clé API d'un fournisseur compatible OpenAI (OpenAI, Anthropic, DeepSeek ou un modèle local via Ollama)
  • npm 10+ ou pnpm 9+

Ce que vous allez construire

Un tableau de bord de gestion de tâches Next.js 15 avec un panneau copilote flottant alimenté par Page Agent. Le copilote lit le DOM en direct de votre application et interprète des commandes en langage naturel — remplir des champs, cliquer sur des boutons, cocher des cases — sans captures d'écran ni devinettes de coordonnées.

Fonctionnement interne de Page Agent

Lors de l'appel à agent.execute(task), le runtime Page Agent effectue les étapes suivantes :

  1. Déshydratation du DOM en une structure texte compacte appelée FlatDomTree — une carte aplatie de chaque élément interactif et de son rôle sémantique.
  2. Envoi de l'arbre et de la tâche en langage naturel au point de terminaison LLM configuré.
  3. Réception d'une action structurée du modèle (clic, saisie, défilement, sélection).
  4. Application de l'action au nœud DOM réel, puis boucle jusqu'à la fin de la tâche.

Aucun modèle de vision n'est requis car l'inférence textuelle sur un DOM compressé est plus rapide et moins coûteuse que l'inférence sur une capture d'écran.

Étape 1 : Créer le projet Next.js

Créez une nouvelle application Next.js 15 avec TypeScript et Tailwind :

npx create-next-app@latest page-agent-demo --typescript --app --tailwind
cd page-agent-demo

Installez Page Agent :

npm install page-agent

Étape 2 : Configurer les variables d'environnement

Créez un fichier .env.local à la racine du projet :

NEXT_PUBLIC_LLM_API_KEY=votre_clé_ici
NEXT_PUBLIC_LLM_BASE_URL=https://api.openai.com/v1
NEXT_PUBLIC_LLM_MODEL=gpt-4o-mini

Le préfixe NEXT_PUBLIC_ expose ces valeurs au navigateur, ce qui est nécessaire car Page Agent fonctionne entièrement côté client. Nous verrons plus tard comment déplacer la clé API côté serveur.

Étape 3 : Construire le tableau de bord des tâches

Créez une page de gestion des tâches dans app/dashboard/page.tsx. Notez les attributs id explicites sur chaque élément du formulaire — la déshydratation DOM de Page Agent les utilise comme ancres sémantiques, rendant le ciblage plus fiable sur des formulaires complexes.

"use client";
 
import { useState } from "react";
 
type Priority = "low" | "medium" | "high";
 
interface Task {
  id: string;
  title: string;
  dueDate: string;
  priority: Priority;
  done: boolean;
}
 
export default function Dashboard() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [title, setTitle] = useState("");
  const [dueDate, setDueDate] = useState("");
  const [priority, setPriority] = useState<Priority>("medium");
 
  function addTask() {
    if (!title.trim()) return;
    setTasks((prev) => [
      ...prev,
      { id: crypto.randomUUID(), title, dueDate, priority, done: false },
    ]);
    setTitle("");
    setDueDate("");
    setPriority("medium");
  }
 
  function toggleDone(id: string) {
    setTasks((prev) =>
      prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t))
    );
  }
 
  return (
    <main className="max-w-2xl mx-auto p-8">
      <h1 className="text-2xl font-bold mb-6">Gestionnaire de tâches</h1>
 
      <section id="task-form" className="bg-gray-50 p-4 rounded-lg mb-8">
        <input
          id="task-title"
          placeholder="Titre de la tâche"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          className="w-full border rounded p-2 mb-2"
        />
        <input
          id="task-due-date"
          type="date"
          value={dueDate}
          onChange={(e) => setDueDate(e.target.value)}
          className="w-full border rounded p-2 mb-2"
        />
        <select
          id="task-priority"
          value={priority}
          onChange={(e) => setPriority(e.target.value as Priority)}
          className="w-full border rounded p-2 mb-2"
        >
          <option value="low">Basse</option>
          <option value="medium">Moyenne</option>
          <option value="high">Haute</option>
        </select>
        <button
          id="add-task-btn"
          onClick={addTask}
          className="w-full bg-blue-600 text-white rounded p-2"
        >
          Ajouter la tâche
        </button>
      </section>
 
      <ul className="space-y-2">
        {tasks.map((task) => (
          <li key={task.id} className="border rounded p-3 flex items-center gap-3">
            <input
              type="checkbox"
              checked={task.done}
              onChange={() => toggleDone(task.id)}
            />
            <div className="flex-1">
              <p className={task.done ? "line-through text-gray-400" : ""}>
                {task.title}
              </p>
              <p className="text-xs text-gray-500">
                {task.dueDate} · {task.priority}
              </p>
            </div>
          </li>
        ))}
      </ul>
    </main>
  );
}

Étape 4 : Créer le composant Copilote

Créez components/Copilot.tsx. Le pattern singleton pour agentInstance garantit qu'un seul objet Page Agent est réutilisé entre les rendus, préservant l'état interne de la page entre les commandes consécutives.

"use client";
 
import { useState, useCallback } from "react";
import { PageAgent } from "page-agent";
 
let agentInstance: PageAgent | null = null;
 
function getAgent(): PageAgent {
  if (!agentInstance) {
    agentInstance = new PageAgent({
      model: process.env.NEXT_PUBLIC_LLM_MODEL ?? "gpt-4o-mini",
      baseURL: process.env.NEXT_PUBLIC_LLM_BASE_URL ?? "https://api.openai.com/v1",
      apiKey: process.env.NEXT_PUBLIC_LLM_API_KEY ?? "",
      language: "fr-FR",
    });
  }
  return agentInstance;
}
 
type Status = "idle" | "running" | "done" | "error";
 
export function Copilot() {
  const [input, setInput] = useState("");
  const [status, setStatus] = useState<Status>("idle");
  const [lastTask, setLastTask] = useState("");
 
  const runTask = useCallback(async () => {
    const task = input.trim();
    if (!task || status === "running") return;
 
    setStatus("running");
    setLastTask(task);
    setInput("");
 
    try {
      const agent = getAgent();
      await agent.execute(task);
      setStatus("done");
    } catch {
      setStatus("error");
    }
  }, [input, status]);
 
  const statusText =
    status === "idle"
      ? "Prêt"
      : status === "running"
      ? "En cours…"
      : status === "done"
      ? `Terminé : ${lastTask}`
      : "Une erreur s'est produite — reformulez la tâche";
 
  return (
    <div className="fixed bottom-4 right-4 w-80 bg-white shadow-xl rounded-xl p-4 border">
      <p className="text-xs font-semibold text-gray-500 mb-2">Copilote IA</p>
      <textarea
        rows={2}
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="Dites à l'agent ce qu'il doit faire…"
        className="w-full border rounded p-2 text-sm resize-none mb-2"
        onKeyDown={(e) => {
          if (e.key === "Enter" && !e.shiftKey) {
            e.preventDefault();
            runTask();
          }
        }}
      />
      <button
        onClick={runTask}
        disabled={status === "running"}
        className="w-full bg-indigo-600 text-white rounded p-2 text-sm disabled:opacity-50"
      >
        {status === "running" ? "En cours…" : "Exécuter"}
      </button>
      <p className="text-xs text-gray-400 mt-2">{statusText}</p>
    </div>
  );
}

Étape 5 : Intégrer le copilote au tableau de bord

Mettez à jour app/dashboard/page.tsx pour importer et afficher le copilote :

import { Copilot } from "@/components/Copilot";

Ajoutez le composant après la balise de fermeture </main> dans le return :

  return (
    <>
      <main className="max-w-2xl mx-auto p-8">
        {/* contenu existant du tableau de bord */}
      </main>
      <Copilot />
    </>
  );

Étape 6 : Utiliser un modèle local avec Ollama

Si vous préférez une configuration entièrement locale sans frais d'API cloud, Page Agent est compatible avec tout serveur compatible OpenAI, y compris Ollama. Installez Ollama et téléchargez Qwen 2.5 7B — le modèle recommandé par la documentation officielle d'Alibaba pour Page Agent :

ollama pull qwen2.5:7b

Mettez à jour .env.local :

NEXT_PUBLIC_LLM_BASE_URL=http://localhost:11434/v1
NEXT_PUBLIC_LLM_API_KEY=ollama
NEXT_PUBLIC_LLM_MODEL=qwen2.5:7b

Qwen 2.5 7B tourne facilement avec 8 Go de RAM, gère avec précision les tâches de remplissage de formulaires, et ne coûte rien par token.

Étape 7 : Tester le copilote

Démarrez le serveur de développement :

npm run dev

Ouvrez http://localhost:3000/dashboard. Dans le panneau copilote, essayez ces commandes :

  • "Ajouter une tâche appelée Corriger le bug de connexion pour le 2026-08-01 avec priorité haute" — l'agent remplit les trois champs et clique sur Ajouter la tâche.
  • "Marquer toutes les tâches comme terminées" — l'agent coche chaque case en séquence.
  • "Ajouter trois tâches : Déployer l'API, Écrire les tests, et Mettre à jour la doc, toutes en priorité moyenne" — exécution multi-étapes sur trois soumissions de formulaire.

Étape 8 : Restreindre la portée de l'agent

En production, vous pouvez vouloir confiner l'agent à une section spécifique de la page pour éviter les interactions accidentelles. Passez un élément DOM comme context :

const formEl = document.getElementById("task-form");
 
await agent.execute("Remplir le formulaire avec les détails de la tâche", {
  context: formEl ?? undefined,
});

Quand un élément context est fourni, le FlatDomTree ne couvre que le sous-arbre de cet élément, réduisant l'utilisation de tokens et limitant les actions de l'agent.

Étape 9 : Ajouter un proxy CORS côté serveur

Exposer votre clé API dans des variables NEXT_PUBLIC_ est acceptable en développement mais constitue un risque de sécurité en production. Les API cloud bloquent aussi les requêtes directes du navigateur avec des erreurs CORS. La solution est un route handler Next.js qui proxifie la requête côté serveur.

Créez app/api/llm/route.ts :

import { NextRequest, NextResponse } from "next/server";
 
export async function POST(req: NextRequest) {
  const body = await req.json();
  const res = await fetch(
    `${process.env.LLM_BASE_URL}/chat/completions`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.LLM_API_KEY}`,
      },
      body: JSON.stringify(body),
    }
  );
  const data = await res.json();
  return NextResponse.json(data);
}

Déplacez les vraies credentials vers des variables côté serveur uniquement (sans préfixe NEXT_PUBLIC_) :

LLM_BASE_URL=https://api.openai.com/v1
LLM_API_KEY=votre_vraie_clé

Puis pointez le client Page Agent vers le proxy :

NEXT_PUBLIC_LLM_BASE_URL=/api/llm
NEXT_PUBLIC_LLM_API_KEY=proxy
NEXT_PUBLIC_LLM_MODEL=gpt-4o-mini

Résolution des problèmes courants

L'agent ne trouve pas mon élément. Ajoutez un label visible, un placeholder ou un id à l'élément. Page Agent lit le texte sémantique ; un champ de saisie sans étiquette ni placeholder est invisible pour lui.

Les actions se déclenchent mais l'état React ne se met pas à jour. Page Agent déclenche des événements DOM natifs (click, input, change). Assurez-vous que vos composants répondent aux événements DOM natifs.

Les coûts en tokens sont élevés. Passez à Qwen 2.5 7B via Ollama pour une inférence locale gratuite, ou utilisez l'option context pour réduire la taille du FlatDomTree.

Erreur « Could not complete the task ». Reformulez la commande de manière plus spécifique. « Cliquer sur le bouton bleu Ajouter la tâche » est plus fiable que « soumettre le formulaire » si la page contient plusieurs éléments de soumission.

Prochaines étapes

  • Automatisation multi-onglets — installez l'extension Chrome optionnelle de Page Agent pour coordonner des actions sur plusieurs onglets.
  • Saisie vocale — dirigez les transcriptions de l'API Web Speech directement vers agent.execute pour un copilote mains libres.
  • Intégration avec Vercel AI SDK — utilisez une interface de chat pour les réponses conversationnelles et agent.execute pour la manipulation de l'interface.
  • Explorer @page-agent/core — pour un contrôle de bas niveau sur la génération du FlatDomTree et l'envoi des actions.

Conclusion

Page Agent d'Alibaba supprime le surcoût d'infrastructure qui a historiquement rendu les copilotes IA dans les applications coûteux à déployer. En lisant le DOM plutôt qu'en prenant des captures d'écran, il atteint un contrôle précis et agnostique au modèle dans un paquet de moins de 50 ko, sans processus backend requis. En moins de 30 minutes, vous disposez d'un copilote en langage naturel fonctionnant dans une vraie application Next.js — compatible avec n'importe quel modèle OpenAI-compatible, de GPT-4o-mini à Qwen 2.5 hébergé localement.