تاريخ النشر: 16 يوليو 2026
في مقالة استخدام Prompt API المضمّنة مع Vercel AI
SDK، تعرّفت على أربعة عناصر أساسية للإنشاء،
وهي generateText() وstreamText() والرمز المختلط والناتج المنظَّم باستخدام
Output.object()، وكلها تعمل بواسطة @browser-ai/core. في هذه التجربة، ستنشئ واجهة مستخدم أكثر تفاعلية، وهي واجهة مستخدم كاملة لمحادثة البث المباشر تعمل بالكامل في المتصفّح، مع إمكانية الرجوع تلقائيًا إلى نموذج مستند إلى السحابة الإلكترونية عندما لا تكون Prompt API متاحة.
ما الذي تعمل على إنشائه؟
واجهة محادثة React تنفّذ ما يلي:
- يستخدم الخطاف
useChatفي حزمة تطوير البرامج (SDK) من Vercel AI لبث المحادثة المترابطة. - تشغيل حلقة النموذج في المتصفّح بدون الحاجة إلى خادم خلفي
- يتم الرجوع إلى Gemini 2.5 Flash تلقائيًا عندما لا يتوفّر Prompt API.
- تعرض هذه السمة ردود المساعد بتنسيق Markdown المتوافق مع GitHub، وتتعامل مع الرموز غير المكتملة أثناء البث.
- تعرض هذه السمة تأثيرًا لامعًا "جارٍ التفكير…" أثناء انتظار الرمز المميز الأول.
- التمرير التلقائي إلى الرسائل الجديدة، مع زر للتمرير إلى الأسفل عند التمرير إلى الأعلى
المهام التابعة الإضافية
بالإضافة إلى ai و@browser-ai/core و@ai-sdk/google، تحتاج واجهة مستخدم المحادثة إلى React، وعمليات الربط الخاصة بـ React في حزمة تطوير البرامج (SDK) الخاصة بالذكاء الاصطناعي، وبعض حِزم Markdown:
npm install react react-dom @ai-sdk/react
npm install react-markdown remark-gfm harden-react-markdown
npm install -D @types/react @types/react-dom
بالنسبة إلى واجهة المستخدم، أضِف أيضًا Tailwind CSS وبعض أدوات المكوّنات وLucide للرموز:
npm install -D tailwindcss postcss autoprefixer
npm install clsx tailwind-merge lucide-react
إعدادات التصويت: إنشاء إدخالات متعددة
يحتوي المشروع على index.html. أضِف chat.html كنقطة دخول ثانية
وأعِدّ Vite لإنشاء كليهما:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
resolve: { alias: { '@': resolve(__dirname, './src') } },
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
chat: resolve(__dirname, 'chat.html'),
},
},
},
});
chat.html هو الحد الأدنى، أي <div id="root"> وعلامة برمجية فقط:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Built-in AI Chatbot</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/chat.tsx"></script>
</body>
</html>
الاختيار التلقائي للنموذج
في برنامج الدردشة الآلي، يتخذ التطبيق هذا القرار تلقائيًا: فهو يجرب النموذج المضمّن أولاً، ثم يعود إلى السحابة الإلكترونية إذا لم تكن Prompt API متاحة.
ويتم ذلك عند مدّة تحميل الوحدة، قبل أن يتم تحميل React، وبالتالي يكون الوكيل جاهزًا عندما يكتب المستخدم رسالته الأولى:
const agentPromise: Promise<ToolLoopAgent> = (async () => {
const builtIn = browserAI();
let model: any = builtIn;
if (typeof builtIn.availability === 'function') {
const availability = await builtIn.availability();
if (availability === 'unavailable') {
const { createGoogleGenerativeAI } = await import('@ai-sdk/google');
model = createGoogleGenerativeAI({ apiKey })('gemini-2.5-flash');
} else if (availability === 'downloadable') {
await builtIn.createSessionWithProgress(() => {});
}
}
return new ToolLoopAgent({ model, instructions: 'You are a helpful assistant.' });
})();
الجزء الجديد هو ToolLoopAgent؛ تجريد حزمة تطوير البرامج (SDK) الخاصة بالذكاء الاصطناعي الذي يدير حلقة محادثة مترابطة فوق أي نموذج. يأخذ هذا الإعداد النموذج ومطالبة النظام، ويتعامل مع عملية تبادل المعلومات داخليًا.
ربط الوكيل بـ useChat
يتواصل خطاف useChat الخاص بحزمة @ai-sdk/react عادةً مع نقطة نهاية HTTP.
بالنسبة إلى الاستدلال من جهة المتصفح، استخدِم DirectChatTransport بدلاً من ذلك. يتم تشغيل حلقة
ToolLoopAgent بالكامل في المتصفّح بدون أي خادم:
const transport = useMemo(() => new DirectChatTransport({ agent }), [agent]);
const { messages, sendMessage, status, stop } = useChat({ transport });
useMemo مهم لأنّ DirectChatTransport يحمل حالة المحادثة، لذا يجب أن يكون مرجعًا ثابتًا. تؤدي إعادة إنشائه في كل عملية عرض إلى إعادة ضبط المحادثة.
يمنحك useChat ما يلي:
Messages: المحادثة الكاملة بتنسيقUIMessage[]، ويتضمّن كل منها مصفوفةroleوparts-
sendMessage({ text }): يرسل هذا الإجراء ردًا جديدًا من المستخدم ويبدأ ببث الرد Status:'idle'|'submitted'|'streaming'|'error'-
Stop: إلغاء عملية إنشاء قيد التقدّم
عرض الرسائل
تحتوي كل رسالة على مصفوفة parts. بالنسبة إلى هذا البوت، يهمّنا فقط الأجزاء type:
'text'. تظهر رسائل المستخدمين على شكل فقاعة محاذية لليسار، بينما تظهر رسائل المساعد محاذية لليمين مع رمز:
const ChatMessage = ({ message, isStreaming }: { message: UIMessage; isStreaming: boolean }) => {
const isUser = message.role === 'user';
const textParts = message.parts.map((part, i) => {
if (part.type !== 'text') return null;
if (isUser) return <span key={i}>{part.text}</span>;
return <Response key={i} parseIncompleteMarkdown={isStreaming}>{part.text}</Response>;
});
if (isUser) {
return (
<div className="flex flex-col items-end gap-2 animate-fade-up">
<MessageContent className="w-fit max-w-[min(80%,56ch)] ...">
{textParts}
</MessageContent>
</div>
);
}
return (
<div className="flex items-start gap-3">
<AIIcon />
<MessageContent className="text-[13px] leading-[1.65]">{textParts}</MessageContent>
</div>
);
};
MessageContent وResponse هما عنصرا ذكاء اصطناعي.
وهي عبارة عن مكوّنات مصدرية بنمط shadcn يمكنك نسخها إلى مشروعك بدلاً من تثبيتها من npm. تلتف Response حول react-markdown
مع remark-gfm لتنسيق Markdown المتوافق مع GitHub (الجداول وقوائم المهام
والتشطيب) وharden-react-markdown لتنظيف الروابط والصور في
نتائج الذكاء الاصطناعي.
تكون قيمة السمة parseIncompleteMarkdown هي true أثناء استمرار بث الرسالة. أثناء البث، قد يكتب النموذج **bold ولكنّه لن يغلق ** بعد، ما يؤدي إلى ترك رمز مميّز معلّق سيتم عرضه على شكل علامات نجمية حرفية. يؤدي parseIncompleteMarkdown إلى إغلاق أي ** و__ و` و~~ مفتوحة،
ويقتطع بدايات روابط [ المعلقة ليبقى الناتج المعروض نظيفًا في كل
جزء إضافي.
الحالة "جارٍ التفكير…"
بين إرسال الرسالة واستلام الرمز المميز الأول، يكون status هو
'submitted'. خلال هذه الفترة، يعرض التطبيق تأثيرًا متلألئًا متحركًا:
{status === 'submitted' && messages.at(-1)?.role !== 'assistant' && (
<ThinkingMessage />
)}
يمنع الشرط messages.at(-1)?.role !== 'assistant' ظهور التأثير المتلألئ مرة أخرى بعد بدء بث رسالة "المساعد".
تستخدم ThinkingMessage المكوّن Shimmer: وهو <span> يتضمّن تدرّجًا لونيًا متحركًا
باستخدام background-clip: text، ما يمنح النص "جارٍ التفكير…" تأثير تمييز
شامل.
الانتقال التلقائي
عند وصول محتوى جديد، ينتقل التطبيق إلى أسفل الصفحة، ولكن فقط إذا كان المستخدم في أسفل الصفحة. سيكون من المزعج أن يتم إبعادهم عن المحتوى الذي يقرأونه في منتصف المحادثة.
const [isAtBottom, setIsAtBottom] = useState(true);
useEffect(() => {
if (isAtBottom) endRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages, status, isAtBottom]);
const handleScroll = () => {
const el = containerRef.current;
if (!el) return;
setIsAtBottom(el.scrollHeight - el.scrollTop - el.clientHeight < 50);
};
يظهر زر عائم للانتقال إلى أسفل الصفحة عندما تكون قيمة isAtBottom هي false، ويختفي تدريجيًا عندما يعود المستخدم إلى أسفل الصفحة.
مساحة الإدخال
يتم تغيير حجم مساحة النص تلقائيًا أثناء الكتابة من خلال إعادة ضبط ارتفاعها على auto في كل حدث إدخال ثم ضبطه على scrollHeight. يتم إرسال الطلب عند الضغط على Enter
(وليس Shift+Enter)، وأثناء عرض الرد تدريجيًا، يتم استبدال زر "إرسال" بزر "إيقاف" الذي يستدعي stop():
<textarea
onInput={(e) => {
const el = e.currentTarget;
el.style.height = 'auto';
el.style.height = `${el.scrollHeight}px`;
}}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (input.trim() && !isStreaming) {
sendMessage({ text: input });
setInput('');
}
}
}}
/>;
{
isStreaming ? (
<Button variant="outline" onClick={stop}>
Stop
</Button>
) : (
<Button type="submit" disabled={!input.trim()}>
Send
</Button>
);
}
تركيب عنصر مع حالة تحميل
بما أنّ agentPromise غير متزامن، يجب الانتظار إلى أن يكتمل قبل عرض Chat. يحلّف برنامج تضمين App الوعد ويعرض أداة تحميل في الوقت نفسه:
function App() {
const [agent, setAgent] = (useState < ToolLoopAgent) | (null > null);
useEffect(() => {
agentPromise.then(setAgent);
}, []);
if (!agent) {
return (
<div className="flex h-dvh items-center justify-center">
<Loader size={20} />
</div>
);
}
return <Chat agent={agent} />;
}
بعد أن يحلّ الوكيل المشكلة، سواء كان ذلك من خلال تشغيل النموذج المضمّن على الفور أو الانتظار إلى حين تنزيل النموذج، يختفي مؤشر التحميل وتظهر واجهة مستخدم المحادثة.
عرض توضيحي
العرض التوضيحي المباشر هو برنامج دردشة آلي يعمل بكامل وظائفه مباشرةً في متصفحك. اكتب رسالة واضغط على Enter. إذا كانت واجهة Prompt API متاحة، سيتم بث الرد مباشرةً من النموذج الذي يعمل على الجهاز فقط بدون الحاجة إلى طلب شبكة. إذا كان المتصفّح لا يتيح استخدام Prompt API، سيتم تلقائيًا استخدام Gemini 2.5 Flash. جرِّب أن تطلب منه شرح شيء ما في قائمة أو كتابة مقتطف الرمز أو استخدام تنسيق Markdown. يتم عرض الردود مع مجموعات الرموز المنسَّقة والجداول والرموز المضمّنة بدون الحاجة إلى أي إعدادات إضافية.

الخاتمة
خلال هذين المقالتين، تعرّفت على النطاق الكامل لما تتيحه حزمة تطوير البرامج (SDK) من Vercel AI باستخدام واجهة برمجة التطبيقات Prompt API المضمّنة في المتصفح، بدءًا من العناصر الأساسية لإنشاء المحتوى الأولي وصولاً إلى واجهة دردشة متدفقة مصقولة.
في استخدام Prompt API المضمّنة مع Vercel AI SDK، تعرّفت على كيفية استخدام generateText() وstreamText() لإنشاء نص غير متواصل ومتواصل، وكيفية طلب إخراج JSON منظَّم باستخدام Output.object()، وكيفية كتابة رمز برمجي مختلط يختار بين النموذج المضمّن ومزوّد خدمة سحابي في وقت التشغيل بدون إجراء أي تغييرات على منطق الإنشاء.
في هذا المستند، أخذت هذه اللبنات الأساسية نفسها وضمّنتها في واجهة مستخدم كاملة تستند إلى React: ToolLoopAgent لإدارة حلقة المحادثة، وuseChat مع DirectChatTransport لبث الردود مباشرةً في المتصفح، ومكوّنات AI Elements لعرض ردود Markdown بشكل واضح عند وصولها، وكل ذلك مع توفير خيار احتياطي تلقائي على السحابة الإلكترونية عندما لا تتوفّر Prompt API.
والنتيجة هي عرضان توضيحيان يعملان بالكامل في المتصفّح، بدون الحاجة إلى أي خادم خلفي:
- استخدام Prompt API المضمّنة مع العرض التوضيحي لحزمة Vercel AI SDK: إنشاء النصوص، والبث، والناتج المنظَّم جنبًا إلى جنب
- استخدام واجهة مستخدم Vercel AI SDK وعناصر الذكاء الاصطناعي مع العرض التوضيحي لواجهة Prompt API: روبوت دردشة كامل البث مع عرض Markdown والوضع الداكن