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 :
- 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.
- Envoi de l'arbre et de la tâche en langage naturel au point de terminaison LLM configuré.
- Réception d'une action structurée du modèle (clic, saisie, défilement, sélection).
- 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-demoInstallez 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-miniLe 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:7bMettez à jour .env.local :
NEXT_PUBLIC_LLM_BASE_URL=http://localhost:11434/v1
NEXT_PUBLIC_LLM_API_KEY=ollama
NEXT_PUBLIC_LLM_MODEL=qwen2.5:7bQwen 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 devOuvrez 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-miniRé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.executepour un copilote mains libres. - Intégration avec Vercel AI SDK — utilisez une interface de chat pour les réponses conversationnelles et
agent.executepour 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.