معظم أدوات أتمتة المتصفح تعمل كسائح يحمل كاميرا — تلتقط لقطة شاشة، ترسلها إلى نموذج رؤية، تخمّن إحداثيات البكسل، ثم تنقر. تغيير CSS واحد يكسر التخمين. Page Agent من علي بابا يسلك المسار المعاكس: يقرأ DOM مباشرة، تماماً كما يفعل المطوّر حين يفتح لوحة Elements. النتيجة مكتبة TypeScript تُحوّل أي واجهة ويب إلى شيء يمكن لنموذج لغوي قيادته بجمل عادية — دون Chrome headless، ودون عملية خلفية، ودون رموز صورة.
يرشدك هذا الدليل خطوة بخطوة لدمج Page Agent في تطبيق Next.js 15 لبناء لوحة مساعد ذكاء اصطناعي عائمة. في النهاية، يكتب المستخدم "أضف مهمة باسم نشر الخلفية تنتهي الجمعة، أولوية عالية" — فيملأ العميل النموذج ويرسله تلقائياً.
المتطلبات المسبقة
قبل البدء، تأكد من توافر:
- Node.js 20 أو أحدث
- إلمام أساسي بـ React وTypeScript
- مفتاح API من مزوّد متوافق مع OpenAI (OpenAI أو Anthropic أو DeepSeek أو نموذج محلي عبر Ollama)
- npm 10 أو pnpm 9 فأعلى
ما الذي ستبنيه
لوحة إدارة مهام بـ Next.js 15 مع لوحة مساعد عائمة مدعومة بـ Page Agent. يقرأ المساعد DOM الحي لتطبيقك ويفسّر الأوامر بالعربية أو الإنجليزية — ملء الحقول، والنقر على الأزرار، وتفعيل مربعات الاختيار — دون لقطات شاشة أو تخمين إحداثيات.
كيف يعمل Page Agent داخلياً
عند استدعاء agent.execute(task)، يقوم وقت تشغيل Page Agent بما يلي:
- تجفيف DOM إلى هيكل نصي مضغوط يُسمى FlatDomTree — خريطة مسطّحة لكل عنصر تفاعلي ودوره الدلالي.
- إرسال الشجرة مع المهمة بلغة طبيعية إلى نقطة نهاية LLM المُعدّة.
- استقبال إجراء منظّم من النموذج (نقر، كتابة، تمرير، اختيار).
- تطبيق الإجراء على عقدة DOM الحقيقية، ثم التكرار حتى اكتمال المهمة.
لا يلزم نموذج رؤية لأن استنتاج النص على DOM مضغوط أسرع وأرخص من استنتاج الصورة على لقطة شاشة.
الخطوة 1: إنشاء مشروع Next.js
أنشئ تطبيق Next.js 15 جديداً بـ TypeScript وTailwind:
npx create-next-app@latest page-agent-demo --typescript --app --tailwind
cd page-agent-demoثبّت Page Agent:
npm install page-agentالخطوة 2: إعداد متغيرات البيئة
أنشئ ملف .env.local في جذر المشروع:
NEXT_PUBLIC_LLM_API_KEY=مفتاحك_هنا
NEXT_PUBLIC_LLM_BASE_URL=https://api.openai.com/v1
NEXT_PUBLIC_LLM_MODEL=gpt-4o-miniالبادئة NEXT_PUBLIC_ تكشف هذه القيم للمتصفح، وهو أمر ضروري لأن Page Agent يعمل كلياً على جانب العميل. سنتناول لاحقاً كيفية نقل مفتاح API إلى جانب الخادم.
الخطوة 3: بناء لوحة المهام
أنشئ صفحة إدارة المهام في app/dashboard/page.tsx. لاحظ سمات id الصريحة على كل عنصر في النموذج — يلتقطها تجفيف DOM الخاص بـ Page Agent كمراسي دلالية، مما يجعل الاستهداف أكثر موثوقية.
"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">مدير المهام</h1>
<section id="task-form" className="bg-gray-50 p-4 rounded-lg mb-8">
<input
id="task-title"
placeholder="عنوان المهمة"
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">منخفضة</option>
<option value="medium">متوسطة</option>
<option value="high">عالية</option>
</select>
<button
id="add-task-btn"
onClick={addTask}
className="w-full bg-blue-600 text-white rounded p-2"
>
إضافة مهمة
</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>
);
}الخطوة 4: إنشاء مكوّن المساعد الذكي
أنشئ components/Copilot.tsx. نمط Singleton لـ agentInstance يضمن إعادة استخدام كائن واحد من Page Agent عبر إعادات الرسم، مما يحافظ على حالة الصفحة الداخلية بين الأوامر المتتالية.
"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: "ar-SA",
});
}
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"
? "جاهز"
: status === "running"
? "يعمل…"
: status === "done"
? `تم: ${lastTask}`
: "حدث خطأ — حاول إعادة صياغة المهمة";
return (
<div className="fixed bottom-4 left-4 w-80 bg-white shadow-xl rounded-xl p-4 border" dir="rtl">
<p className="text-xs font-semibold text-gray-500 mb-2">المساعد الذكي</p>
<textarea
rows={2}
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="أخبر المساعد بما تريد…"
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" ? "يعمل…" : "تشغيل"}
</button>
<p className="text-xs text-gray-400 mt-2">{statusText}</p>
</div>
);
}الخطوة 5: دمج المساعد في اللوحة
حدّث app/dashboard/page.tsx لاستيراد وعرض المساعد:
import { Copilot } from "@/components/Copilot";أضفه بعد وسم الإغلاق </main> داخل return:
return (
<>
<main className="max-w-2xl mx-auto p-8">
{/* محتوى اللوحة الحالي */}
</main>
<Copilot />
</>
);الخطوة 6: استخدام نموذج محلي عبر Ollama
إذا كنت تفضّل إعداداً محلياً بالكامل دون تكاليف API سحابية، فإن Page Agent متوافق مع أي خادم متوافق مع OpenAI بما في ذلك Ollama. ثبّت Ollama وحمّل Qwen 2.5 7B — النموذج الذي توصي به وثائق علي بابا لمهام Page Agent:
ollama pull qwen2.5:7bحدّث .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 بسهولة على 8 جيجابايت من ذاكرة الوصول العشوائي، ويتعامل مع مهام ملء النماذج بدقة، ولا يكلّف شيئاً لكل رمز مميز.
الخطوة 7: اختبار المساعد
شغّل خادم التطوير:
npm run devافتح http://localhost:3000/dashboard. في لوحة المساعد، جرّب هذه الأوامر:
- "أضف مهمة باسم إصلاح خطأ تسجيل الدخول تنتهي 2026-08-01 بأولوية عالية" — يملأ العميل الحقول الثلاثة وينقر على إضافة مهمة.
- "ضع علامة تم على جميع المهام" — يفعّل العميل كل مربع اختيار بالتسلسل.
- "أضف ثلاث مهام: نشر API، كتابة الاختبارات، وتحديث الوثائق، جميعها بأولوية متوسطة" — تنفيذ متعدد الخطوات.
الخطوة 8: تقييد نطاق المساعد
في الإنتاج قد ترغب في حصر المساعد في قسم معين من الصفحة لمنع التفاعلات غير المقصودة. مرّر عنصر DOM كـ context:
const formEl = document.getElementById("task-form");
await agent.execute("املأ النموذج بتفاصيل المهمة", {
context: formEl ?? undefined,
});عند توفير عنصر context، يغطي FlatDomTree الشجرة الفرعية لذلك العنصر فقط، مما يقلل استخدام الرموز المميزة ويُضيّق نطاق إجراءات المساعد.
الخطوة 9: إضافة وكيل CORS على الخادم
كشف مفتاح API في متغيرات NEXT_PUBLIC_ مقبول في التطوير لكنه خطر أمني في الإنتاج. واجهات API السحابية تحجب أيضاً طلبات المتصفح المباشرة بأخطاء CORS. الحل هو معالج مسار Next.js يوكّل الطلب من جانب الخادم.
أنشئ 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);
}انقل البيانات الحساسة إلى متغيرات جانب الخادم فقط (بدون بادئة NEXT_PUBLIC_):
LLM_BASE_URL=https://api.openai.com/v1
LLM_API_KEY=مفتاحك_الحقيقيثم وجّه عميل Page Agent إلى الوكيل:
NEXT_PUBLIC_LLM_BASE_URL=/api/llm
NEXT_PUBLIC_LLM_API_KEY=proxy
NEXT_PUBLIC_LLM_MODEL=gpt-4o-miniاستكشاف الأخطاء وإصلاحها
المساعد لا يجد الحقل. أضف وسم label مرئياً أو placeholder أو id للعنصر. Page Agent يقرأ النص الدلالي؛ حقل إدخال بدون تسمية ولا placeholder غير مرئي له.
الإجراءات تعمل لكن حالة React لا تتحدث. Page Agent يطلق أحداث المتصفح الأصلية (click, input, change). تأكد من أن مكوناتك تستجيب لأحداث DOM الأصلية.
تكاليف الرموز المميزة مرتفعة. انتقل إلى Qwen 2.5 7B عبر Ollama للاستنتاج المحلي المجاني، أو استخدم خيار context لتقليص حجم FlatDomTree.
الخطوات التالية
- أتمتة عبر تبويبات — ثبّت امتداد Chrome الاختياري لـ Page Agent للتنسيق عبر تبويبات متعددة.
- إدخال صوتي — وجّه نصوص Web Speech API مباشرة إلى
agent.executeللحصول على مساعد بدون لمس. - الدمج مع Vercel AI SDK — استخدم واجهة محادثة للردود النصية واعتمد على
agent.executeللتلاعب بالواجهة. - استكشاف
@page-agent/core— للتحكم منخفض المستوى في توليد FlatDomTree وإرسال الإجراءات.
خلاصة
يُزيل Page Agent من علي بابا العبء التحتي الذي جعل مساعدي الذكاء الاصطناعي داخل التطبيق مكلفين تاريخياً. بقراءة DOM بدلاً من لقطات الشاشة، يحقق تحكماً دقيقاً وحيادياً تجاه النماذج في حزمة لا تتجاوز 50 كيلوبايت دون الحاجة إلى عملية خلفية. في أقل من 30 دقيقة، أصبح لديك مساعد لغة طبيعية يعمل داخل تطبيق Next.js حقيقي — يعمل مع أي نموذج متوافق مع OpenAI، من GPT-4o-mini إلى Qwen 2.5 المستضاف محلياً.