بالنسبة لروبوت دردشة Next.js الذي يحتاج إلى عدة مزودي نماذج، فإن أنسب واجهة خلفية هي التي تُركّز المصادقة وتسمح للتطبيق باختيار النموذج لكل طلب. توفر CometAPI هذا النمط: يحتفظ الخادم بمفتاح API واحد وعنوان الأساس المتوافق مع OpenAI https://api.cometapi.com/v1، بينما يختار حقل model في الطلب أحد نماذج GPT أو Claude أو Gemini أو غيرها من نماذج الدردشة المتاحة.
يبني هذا الدليل مشروع App Router عملياً مع قائمة سماح للنماذج على الخادم، ومعالج مسار للبث، ومُحدِّد نموذج في المتصفح، ومتغيرات بيئة محمية، وواجهة دردشة كاملة، وإرشادات النشر. لن يصل مفتاح CometAPI إلى المتصفح مطلقاً.
ما هو تطبيق دردشة ذكاء اصطناعي متعدد النماذج؟
يضع هيكل الدردشة متعددة النماذج واجهة خلفية يتحكم فيها التطبيق بين واجهة المستخدم وعدة مزودي نماذج. في هذا المشروع، يرسل المتصفح المحادثة إلى /api/chat، بينما يحتفظ الخادم باعتماد CometAPI بشكل خاص، ويتحقق من صحة معرّف النموذج المطلوب، ويُمرِّر الطلب عبر واجهة برمجة تطبيقات متوافقة واحدة.
كيف يعمل تبديل النماذج في Next.js؟
يُعد تبديل النماذج قرار توجيه (routing). يرسل المتصفح معرّف نموذج مُعتمداً مع المحادثة إلى /api/chat. يضيف معالج المسار اعتماد CometAPI، ويستدعي POST /v1/chat/completions، ويبث استجابة النموذج المحدد مرة أخرى إلى المتصفح.
لا يجعل الطرف المشترك كل النماذج متطابقة. قد تختلف أسلوب المخرجات، سلوك الأدوات، المعلمات المدعومة، حدود السياق، والتسعير. احتفظ بمعرّفات النماذج في قائمة سماح على الخادم واختبر كل مسار باستخدام مطالبات التطبيق نفسها قبل إتاحته للمستخدمين.
ماذا ستبني؟
يحتوي التطبيق النهائي على واجهة خلفية Next.js واحدة وثلاثة مسارات دردشة قابلة للاختيار:
| معرّف النموذج | مثال على الدور | سعر CometAPI |
|---|---|---|
| gemini-3.7-flash | دردشة بحجم مرتفع وحساسة للتكلفة | $0.60 للمدخلات / $3.00 للمخرجات |
| claude-opus-5 | استدلال متميز مُركّز | $4 للمدخلات / $20 للمخرجات |
| gpt-5.6 | gpt-5.6 هو مسار CometAPI العام لـ GPT-5.6 ويطابق حالياً فئة Sol. | $3.2 للمدخلات / $16 للمخرجات في فئة السياق القصير |
ملاحظة التسعير: الأسعار المذكورة أدناه بالدولار الأمريكي لكل مليون رمز، وقد تم التحقق منها في 21 أغسطس 2026، وقد تتغير. تحقّق دائماً من الأسعار الحالية على صفحات النماذج قبل الاستخدام الإنتاجي. الجدول مثال توجيه، وليس تصنيف جودة. يستخدم GPT-5.6 فئة سعر أعلى فوق 272,000 رمز. راجع صفحات النماذج المرتبطة Gemini 3.7 Flash، وClaude Opus 5، وGPT-5.6 للأسعار المؤرخة المستخدمة هنا.
تسعّر CometAPI النماذج بشفافية مقابل واجهات مقدمي الخدمات الرسمية. النماذج ذات التسعير الرسمي الموحد — OpenAI، Claude، Gemini وما شابه — تُحتسب بالرمز بمعدل 0.8:1 مقارنة بالسعر الرسمي، أي خصم 20%. النماذج التي لا تمتلك واجهات رسمية (MidJourney، Kling، Luma) تُحتسب لكل نداء وفق أسعار CometAPI المحددة، مع خصم 20% أيضاً. راجع CometAPI Pricing Guide لصيغة الهامش، وأسعار كل نموذج، ووحدات الفوترة.
تغطي المسارات الثلاثة مفاضلات مختلفة. يعدّ Gemini 3.7 Flash عامل Google الفعال، مع إدخال متعدد الوسائط ونافذة سياق 1,048,576 رمزاً لسيناريوهات الدردشة واسعة الحجم، والبرمجة، وتدفقات المعرفة. Claude Opus 5 هو نموذج الاستدلال المتقدم من Anthropic، قوي في التحليل متعدد الخطوات، والبرمجة، والكتابة الطويلة الدقيقة بسعر مميز. GPT-5.6 هو نموذج OpenAI العام، يوازن بين الاستدلال، واستخدام الأدوات، والصياغة عبر نافذة سياق واسعة لحركة الإنتاج اليومية.
قبل أن تبدأ
تحتاج إلى Node.js وnpm وحساب CometAPI ومفتاح API على الخادم من CometAPI Quick Start. كانت معرّفات النماذج الثلاثة المحددة أعلاه متاحة في الكتالوج الحي في 21 أغسطس 2026، ولم تكن موسومة بأنها قادمة، وتوفّر POST /v1/chat/completions.
يستخدم هذا الدليل الإعدادات المشتركة التالية:
- مفتاح API:
COMETAPI_API_KEY - عنوان الأساس:
https://api.cometapi.com/v1 - نقطة الكتالوج:
GEThttps://api.cometapi.com/api/models
الخطوة 1: أنشئ تطبيق Next.js
npx create-next-app@latest multi-model-chat --ts --app --eslintcd multi-model-chatnpm run dev
لا تحتاج إلى SDK خاص بمزوّد النماذج لهذا الإصدار. يستخدم الخادم واجهة fetch المضمنة ويعيد تمرير تدفق Server-Sent Events الصاعد.
الخطوة 2: احتفظ بمفتاح CometAPI على الخادم
أنشئ ملف .env.local في جذر المشروع:
COMETAPI_API_KEY=replace_with_your_cometapi_keyCOMETAPI_BASE_URL=https://api.cometapi.com/v1
لا تُسبق المفتاح بـ NEXT_PUBLIC_. يقوم Next.js فقط بكشف المتغيرات ذات هذا السابقة لحزم المتصفح؛ ينتمي مفتاح API إلى معالج المسار على الخادم.
الخطوة 3: عرّف سياسة النماذج
ضع المعرّفات المسموح بها في الواجهة الخلفية، لا في القائمة المنسدلة فقط. يمكن للمستخدم تجاوز عناصر تحكم المتصفح واستدعاء مسارك مباشرةً، لذا يجب على الخادم رفض قيم النماذج غير المعروفة.
const ALLOWED_MODELS = [ "gemini-3.7-flash", "claude-opus-5", "gpt-5.6",] as const;type ModelId = (typeof ALLOWED_MODELS)[number];
سيستخدم العميل نفس المعرّفات الثلاثة لمُحدد النماذج، بينما يظل الخادم هو المصدر الموثوق.
الخطوة 4: بث CometAPI عبر Route Handler
أنشئ app/api/chat/route.ts. يتحقق المسار من صحة حمولة دردشة نصّية فقط، يستدعي CometAPI باستخدام stream: true، ويعيد تدفق الأحداث الصاعد دون كشف الاعتماد.
export const runtime = "nodejs";export const dynamic = "force-dynamic";const ALLOWED_MODELS = [ "gemini-3.7-flash", "claude-opus-5", "gpt-5.6",] as const;type ModelId = (typeof ALLOWED_MODELS)[number];type ChatMessage = { role: "system" | "user" | "assistant"; content: string;};function isModelId(value: unknown): value is ModelId { return ( typeof value === "string" && (ALLOWED_MODELS as readonly string[]).includes(value) );}function isChatMessage(value: unknown): value is ChatMessage { if (typeof value !== "object" || value === null) return false; const message = value as Record<string, unknown>; return ( ["system", "user", "assistant"].includes(String(message.role)) && typeof message.content === "string" && message.content.length > 0 && message.content.length <= 20_000 );}export async function POST(request: Request) { const apiKey = process.env.COMETAPI_API_KEY; const baseUrl = ( process.env.COMETAPI_BASE_URL || "https://api.cometapi.com/v1" ).replace(/\/$/, ""); if (!apiKey) { return Response.json( { error: "COMETAPI_API_KEY is not configured." }, { status: 500 }, ); } let payload: { model?: unknown; messages?: unknown }; try { payload = await request.json(); } catch { return Response.json({ error: "Invalid JSON body." }, { status: 400 }); } if (!isModelId(payload.model)) { return Response.json({ error: "Unsupported model ID." }, { status: 400 }); } if ( !Array.isArray(payload.messages) || payload.messages.length === 0 || payload.messages.length > 50 || !payload.messages.every(isChatMessage) ) { return Response.json( { error: "messages must contain 1 to 50 valid text messages." }, { status: 400 }, ); } const upstream = await fetch(`${baseUrl}/chat/completions`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: payload.model, messages: payload.messages, stream: true, }), cache: "no-store", signal: request.signal, }); if (!upstream.ok) { const requestId = upstream.headers.get("x-request-id"); console.error("CometAPI request failed", { status: upstream.status, requestId, }); return Response.json( { error: "The selected model request failed.", status: upstream.status, requestId, }, { status: upstream.status }, ); } if (!upstream.body) { return Response.json( { error: "The model returned no response body." }, { status: 502 }, ); } return new Response(upstream.body, { status: 200, headers: { "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache, no-transform", }, });}
يمرر المعالج إشارة قطع اتصال المتصفح إلى الأعلى، لذا يمكن أن يؤدي إغلاق الطلب إلى إيقاف التوليد غير الضروري. كما يعيد خطأً مُعقّماً إلى العميل مع إبقاء تفاصيل التشخيص في سجلات الخادم.
الخطوة 5: إضافة اختيار النموذج وتحليل التدفق
أنشئ مكوّناً عميلاً في app/page.tsx. يُرسل النموذج المُختار مع المحادثة، يُحلّل كل حدث data:، ويضيف نص delta.content التزايدي إلى آخر رسالة للمساعد.
لا يستدعي المتصفح CometAPI مباشرةً. وجهته الوحيدة هي مسارك /api/chat، الذي يحتفظ بالمفتاح سرياً ويفرض قائمة السماح.
الشفرة الأساسية للمشروع
معالج المسار أعلاه هو الملف الكامل app/api/chat/route.ts. أضف الصفحة والمخطط العام وملف الأنماط التاليين لإكمال المشروع القابل للتشغيل.
app/page.tsx
"use client";import { FormEvent, useState } from "react";const MODEL_OPTIONS = [ { id: "gemini-3.7-flash", label: "Gemini 3.7 Flash" }, { id: "claude-opus-5", label: "Claude Opus 5" }, { id: "gpt-5.6", label: "GPT-5.6" },] as const;type Role = "user" | "assistant";type Message = { role: Role; content: string };function textFromSseLine(line: string): string { const trimmed = line.trim(); if (!trimmed.startsWith("data:")) return ""; const data = trimmed.slice(5).trim(); if (!data || data === "[DONE]") return ""; try { const event = JSON.parse(data); return event.choices?.[0]?.delta?.content ?? ""; } catch { return ""; }}export default function Home() { const [model, setModel] = useState("gemini-3.7-flash"); const [messages, setMessages] = useState<Message[]>([]); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); function appendAssistantText(text: string) { if (!text) return; setMessages((current) => { const next = [...current]; const lastIndex = next.length - 1; if (lastIndex >= 0 && next[lastIndex].role === "assistant") { next[lastIndex] = { ...next[lastIndex], content: next[lastIndex].content + text, }; } return next; }); } async function sendMessage(event: FormEvent<HTMLFormElement>) { event.preventDefault(); const content = input.trim(); if (!content || loading) return; const outgoing: Message[] = [...messages, { role: "user", content }]; setMessages([...outgoing, { role: "assistant", content: "" }]); setInput(""); setError(""); setLoading(true); try { const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model, messages: outgoing }), }); if (!response.ok) { const body = await response.json().catch(() => ({})); throw new Error(body.error || `Request failed with ${response.status}`); } if (!response.body) throw new Error("Streaming is not available."); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; while (true) { const { value, done } = await reader.read(); buffer += decoder.decode(value, { stream: !done }); const lines = buffer.split("\n"); buffer = lines.pop() ?? ""; for (const line of lines) { appendAssistantText(textFromSseLine(line)); } if (done) { appendAssistantText(textFromSseLine(buffer)); break; } } } catch (requestError) { setError( requestError instanceof Error ? requestError.message : "Request failed.", ); } finally { setLoading(false); } } return ( <main className="shell"> <section className="chat"> <header> <p className="eyebrow">Next.js + CometAPI</p> <h1>Multi-model chat</h1> <label> Model <select value={model} onChange={(event) => setModel(event.target.value)} disabled={loading} > {MODEL_OPTIONS.map((option) => ( <option key={option.id} value={option.id}> {option.label} </option> ))} </select> </label> </header> <div className="messages" aria-live="polite"> {messages.length === 0 ? ( <p className="empty">Choose a model and send a message.</p> ) : ( messages.map((message, index) => ( <article className={message.role} key={`${message.role}-${index}`}> <b>{message.role === "user" ? "You" : "Assistant"}</b> <p>{message.content || "…"}</p> </article> )) )} </div> <form onSubmit={sendMessage}> <textarea value={input} onChange={(event) => setInput(event.target.value)} placeholder="Ask something…" rows={3} maxLength={20_000} /> <button disabled={loading || !input.trim()} type="submit"> {loading ? "Streaming…" : "Send"} </button> </form> {error ? <p className="error">{error}</p> : null} </section> </main> );}
app/layout.tsx
import type { Metadata } from "next";import "./globals.css";export const metadata: Metadata = { title: "Multi-Model Chat", description: "A streaming Next.js chat app powered by CometAPI.",};export default function RootLayout({ children,}: Readonly<{ children: React.ReactNode }>) { return ( <html lang="en"> <body>{children}</body> </html> );}
app/globals.css
:root { color-scheme: dark; font-family: Arial, sans-serif; background: #07111f; color: #eef4ff;}* { box-sizing: border-box; }body { margin: 0; }button, select, textarea { font: inherit; }/* Layout shell and chat card */.shell { min-height: 100vh; display: grid; place-items: center; padding: 32px 16px; }.chat { width: min(820px, 100%); background: #0d1b2e; border: 1px solid #223957; border-radius: 20px; padding: 24px; }/* Message list and bubbles */.messages { min-height: 360px; display: grid; align-content: start; gap: 12px; margin: 24px 0; }.messages article { max-width: 85%; padding: 12px 14px; border-radius: 14px; white-space: pre-wrap; }.user { justify-self: end; background: #164f8f; }.assistant { justify-self: start; background: #182a42; }/* Form controls, buttons, and error states follow the same dark theme. */
الخطوة 6: شغّل التطبيق وانشره
npm run dev
افتح http://localhost:3000، اختر نموذجاً، وأرسل رسالة. لنشر Node.js على بيئة إنتاج، أضف COMETAPI_API_KEY وCOMETAPI_BASE_URL إلى إعدادات بيئة الخادم للمضيف، ثم نفّذ:
npm run buildnpm run start
استخدم منصة استضافة تدعم الاستجابات المتدفقة. لا يمكن للتصدير الساكن تشغيل معالج المسار /api/chat.
اختبار مسار البث
مع تشغيل خادم التطوير، استدعِ الواجهة الخلفية مباشرةً:
curl -N http://localhost:3000/api/chat \ -H "Content-Type: application/json" \ -d '{ "model": "gemini-3.7-flash", "messages": [ {"role": "user", "content": "Explain model routing in two sentences."} ] }'
يُعيد الطلب الناجح Server-Sent Events. قد تختلف المعرّفات والنصوص الدقيقة، لكن التدفق يتبع هذا الشكل:
data: {"choices":[{"delta":{"content":"Model"}}]}data: {"choices":[{"delta":{"content":" routing"}}]}data: [DONE]
يقرأ محلّل المتصفح كل حدث SSE، ويستخرج choices[0].delta.content، ويضيف النص المتدفق إلى رسالة المساعد على هيئة كتل متتابعة عند وصولها.
أخطاء تكامل شائعة
| العَرَض | السبب | الحل |
|---|---|---|
| خطأ مصادقة 401 | مفتاح الخادم مفقود أو غير صالح | اضبط COMETAPI_API_KEY؛ لا تكشفه باستخدام NEXT_PUBLIC_. |
| 404 أو مسار خاطئ | عنوان الأساس يفتقد /v1 | استخدم https://api.cometapi.com/v1. |
| 400 نموذج غير مدعوم | المعرّف ليس في قائمة السماح على الخادم | استخدم معرّف نموذج نصّي حي ودقيق، وحدّث كلا المُحدِّدَيْن. |
| تظهر الإجابة دفعة واحدة | المضيف أو الوكيل يقوم بتموين (buffer) التدفق | عطّل تحويل الاستجابة واستخدم نشر Node.js يدعم البث. |
تهيئة واجهة الدردشة الخلفية للإنتاج
- صادِق مستخدميك أنت. لا تكشف مساراً عاماً يستهلك الأرصدة لحركة مجهولة.
- طبّق تحديد المعدل حسب المستخدم وعنوان IP. قَيِّد التدفقات المتزامنة، والطلبات في الدقيقة، وعدد الرسائل، وطول الرسالة.
- احتفظ بقائمة سماح النماذج على الخادم. مُحدِّد المتصفح للتسهيل، وليس حدّاً أمنياً.
- تحقّق من الكتالوج الحي أثناء النشر. استعلم
GEThttps://api.cometapi.com/api/modelsوأفشل الإصدار إذا كان معرّف مُهيّأ قادماً أو غير متاح أو يفتقد نقطة chat-completions. - تتبّع التكلفة حسب المسار. سجّل النموذج المُختار، ومعرّف الطلب، والزمن، واستخدام الرموز، ومعرّف المستخدم. اضبط حصص المفاتيح أو حدود الإنفاق في لوحة CometAPI حيثما لزم.
- تعامل مع قطع الاتصال ومهلات الانتظار. حافظ على
request.signal، واضبط مهلة للتطبيق، وأوقف العمل عند مغادرة العميل. - لا تُخْفِ أخطاء التهيئة عبر بديل (fallback). اعرض استجابات 400 و401. استخدم نموذجاً آخر فقط لمجموعة محدودة من الإخفاقات القابلة لإعادة المحاولة وفقط عندما يكون مخطط الطلب متوافقاً؛ راجع CometAPI Model Fallback Guide لسلسلة بدائل ذات طبقتين (CometAPI أساسي → نموذج بديل لـ CometAPI → المزوّد الرسمي).
- شَفِّر السجلات. لا تتضمن مفاتيح API أو المطالبات الكاملة أو المخرجات الحساسة للنموذج في سجلات أخطاء الإنتاج.
واجهة خلفية Next.js واحدة، وخيارات نماذج متعددة
ينتمي تبديل النموذج إلى سياسة الخادم لديك، وليس إلى حسابات مزوّدين منفصلة. يمكن لمعالج مسار Next.js الاحتفاظ بمفتاح CometAPI واحد بشكل خاص، وقبول معرّف نموذج مُعتمد لكل طلب، وبث النموذج المحدد عبر نقطة نهاية متوافقة مع OpenAI. تظل الواجهة الأمامية بسيطة، بينما تحتفظ الواجهة الخلفية بالتحكم في الوصول والتحقق والرصد والتكلفة. يصل نفس الحساب والمفتاح أيضاً إلى واجهات CometAPI الأصلية للصور والفيديو — مثل Flux لتوليد الصور وKling لتوليد الفيديو — لذا يمكن توسيع واجهة الدردشة الخلفية هذه إلى تدفقات متعددة الوسائط دون تكامل ثانٍ.
استخدم الدليل العام لنماذج CometAPI لاكتشاف النماذج وGET https://api.cometapi.com/api/models للتحقق الآلي من التوجيه.
أسئلة شائعة
هل يمكنني استخدام عدة نماذج ذكاء اصطناعي في تطبيق Next.js واحد؟
نعم. احتفظ بمسار خادم واحد ومرِّر معرّف نموذج من قائمة سماح مع كل طلب. يمكن لواجهة المتصفح تقديم خيارات النماذج، بينما يتحكم الخادم في المعرّفات المقبولة.
كيف أنتقل بين GPT وClaude وGemini؟
أرسل معرّف النموذج المُختار في جسم الطلب. يتحقق الخادم منه مقابل قائمة السماح ويعيد تمرير حمولة الدردشة نفسها إلى النموذج المُختار عبر الواجهة المتوافقة مع OpenAI.
أين يجب أن أخزّن مفتاح CometAPI API الخاص بي؟
اخزنه في متغير بيئة على الخادم مثل COMETAPI_API_KEY. لا تكشفه عبر متغير NEXT_PUBLIC_ أو في شيفرة على العميل.
هل تجعل واجهة متوافقة مع OpenAI كل النماذج قابلة للاستبدال؟
لا. شكل الطلب قابل للنقل، لكن النماذج قد تختلف في المعلمات المدعومة، وحدود السياق، وسلوك الأدوات، وأسلوب الإخراج، والزمن، والسعر. اختبر كل نموذج في قائمة السماح باستخدام مطالبات إنتاجك.
هل يمكن لمعالجات مسارات Next.js بث استجابات الذكاء الاصطناعي؟
نعم. يمكن لمعالج المسار إعادة تدفق Server-Sent Events الصاعد مع نوع محتوى text/event-stream، بشرط أن تدعم منصة النشر وأي وكيل أمامها عدم تموين الاستجابة.
هل يمكنني نشر هذا التطبيق كموقع Next.js ساكن؟
لا. لا يستطيع التصدير الساكن تشغيل معالج المسار /api/chat أو حماية مفتاح API. استخدم تشغيل Node.js أو خادماً متوافقاً يدعم الاستجابات المتدفقة.
