انتقل إلى المحتوى

شات بوت بالذكاء الاصطناعي لموقعك مع Claude أو GPT أو Gemini

أضف شات بوت إلى أي موقع بنافذة محادثة صغيرة وخادم Node.js يحفظ مفتاح الـ API سرًا، مع كود مُختبَر وحدود أمان وحساب التكلفة.

آخر تحديث: 1 نوفمبر 2026نسخة Markdown

لإضافة شات بوت إلى موقعك، ضع في صفحتك نافذة محادثة صغيرة، واجعل خادمك هو الذي يستدعي النموذج. ترسل النافذة كل سؤال إلى خادمك، فيضيف إليه المفتاح والتعليمات، ولا يصل المفتاح إلى المتصفح أبدًا. وبمفتاح من 5abeer تصل مكتبة OpenAI إلى نماذج Claude وGPT وGemini بأقل من أسعارها الرسمية.

ما الذي تحتاجه لبناء شات بوت لموقعك؟#

لماذا لا يستدعي المتصفح الـ API مباشرة؟#

كل ما يحمّله المتصفح يستطيع الزائر قراءته. فالمفتاح المكتوب في جافاسكريبت صفحتك يظهر في مصدرها، ومن ينسخه يستطيع إنفاق رصيدك. ويوصي مرجع OpenAI بالشيء نفسه لمفاتيحه: لا تشارك المفتاح مع غيرك، ولا تكشفه في أي كود يعمل عند المستخدم، مثل المتصفحات أو التطبيقات.

لذلك يتكوّن روبوت المحادثة من ثلاثة أجزاء:

  1. نافذة المحادثة: قسم HTML وسكربت قصير في صفحتك، ترسل كل سؤال إلى /api/chat على خادمك.
  2. خادمك: تطبيق Express صغير يفحص كل طلب، ويضيف المفتاح والتعليمات، ويستدعي النموذج، ثم يعيد الإجابة وحدها.
  3. واجهة 5abeer API: تشغّل النموذج وتخصم ثمن التوكنات من رصيدك.

وتبقى التعليمات، أي موجّه النظام، على الخادم أيضًا، حيث لا يستطيع الزائر قراءتها.

كيف تربط خادمك بنماذج Claude أو GPT أو Gemini؟#

تعمل 5abeer بصيغة Chat Completions من OpenAI، لذلك يستخدم الخادم مكتبة OpenAI الرسمية مع عنوان أساس ومفتاح مختلفين. وبين Claude وGPT وGemini لا يتغير إلا معرّف النموذج ومزوّد المفتاح. والإعداد نفسه يشغّل بوت تليجرام بالذكاء الاصطناعي.

متوافق مع OpenAIhttps://api.5abeer.ai/v1Authorization: Bearer <KEY>
متوافق مع Anthropichttps://api.5abeer.aix-api-key: <KEY>
Geminihttps://api.5abeer.ai/v1betax-goog-api-key: <KEY>

أرسل أولًا طلبًا تجريبيًا واحدًا بمفتاحك. إذا جاءت الإجابة، فالمفتاح والنموذج جاهزان. والصيغة مشروحة في Chat Completions.

curl --request POST "https://api.5abeer.ai/v1/chat/completions" \
  --header "Authorization: Bearer $KHABEER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"model":"claude-sonnet-5.5","messages":[{"role":"user","content":"اكتب فكرة منتج في سطر واحد"}]}'
الرد
{
  "id": "chatcmpl-9f2",
  "object": "chat.completion",
  "model": "claude-sonnet-5.5",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "مرحبًا! كيف أساعدك اليوم؟"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 21,
    "completion_tokens": 12,
    "total_tokens": 33
  }
}

المفتاح يُقرأ من متغير البيئة ⁦KHABEER_API_KEY⁩، فلا يظهر في الكود.

كيف يبدو كود الخادم؟#

الخادم ملف واحد، يقدّم نافذة المحادثة من مجلد public ويجيب عن POST /api/chat.

server.mjs
import express from "express";
import OpenAI from "openai";

const client = new OpenAI({ apiKey: process.env.KHABEER_API_KEY, baseURL: "https://api.5abeer.ai/v1" });
const MODEL = process.env.CHAT_MODEL || "claude-sonnet-5.5";

// The assistant's instructions stay on the server: visitors can't read or change them.
const SYSTEM = `You are the website assistant of Example Store.
Answer only questions about the store, in the visitor's language, briefly and in plain text.
Opening hours: 9:00 to 21:00, Saturday to Thursday. Shipping takes 2 to 4 days.
If you don't know the answer, say so and suggest the contact page.`;

const MAX_MESSAGES = 11; // the last 5 questions and answers, plus the new question
const MAX_CHARS = 4000; // per message
const LIMIT = 20; // requests per visitor IP every 10 minutes
const hits = new Map();
setInterval(() => hits.clear(), 10 * 60 * 1000).unref();

function clean(messages) {
  if (!Array.isArray(messages) || messages.length === 0 || messages.length > MAX_MESSAGES) return null;
  const out = [];
  for (const m of messages) {
    if (!m || (m.role !== "user" && m.role !== "assistant") || typeof m.content !== "string") return null;
    out.push({ role: m.role, content: m.content.slice(0, MAX_CHARS) });
  }
  return out[0].role === "user" && out.at(-1).role === "user" ? out : null;
}

const app = express();
// app.set("trust proxy", 1); // behind Nginx, Caddy or a load balancer: see the notes below
app.use(express.json());
app.use(express.static("public"));

app.post("/api/chat", async (req, res) => {
  const count = (hits.get(req.ip) ?? 0) + 1;
  hits.set(req.ip, count);
  if (count > LIMIT) return res.status(429).json({ error: "Too many messages. Try again in a few minutes." });

  const messages = clean(req.body?.messages);
  if (!messages) return res.status(400).json({ error: "Invalid conversation." });

  try {
    const reply = await client.chat.completions.create({
      model: MODEL,
      messages: [{ role: "system", content: SYSTEM }, ...messages],
      max_tokens: 800,
    });
    const answer = (reply.choices[0]?.message?.content ?? "").trim();
    if (!answer) throw new Error("empty answer");
    res.json({ answer });
  } catch (error) {
    console.error("model error:", error.message);
    res.status(502).json({ error: "The assistant can't answer right now. Try again in a minute." });
  }
});

app.listen(process.env.PORT || 3000, () => console.log("Chat server running on port", process.env.PORT || 3000));

كيف تبدو نافذة المحادثة؟#

النافذة ملفان في مجلد public: صفحة فيها قسم المحادثة، وسكربت يرسل الأسئلة.

public/index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Chat with us</title>
  <style>
    .chat { max-width: 28rem; font: 16px/1.5 system-ui, sans-serif; border: 1px solid #767676; border-radius: 0.75rem; padding: 0.75rem; }
    .chat-log { height: 20rem; overflow-y: auto; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.25rem; }
    .chat-log p { margin: 0; padding: 0.5rem 0.75rem; border-radius: 0.75rem; max-width: 85%; white-space: pre-wrap; }
    .chat-log .user { align-self: flex-end; background: #1d4ed8; color: #fff; }
    .chat-log .assistant { align-self: flex-start; background: #f1f5f9; color: #111; }
    .chat form { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }
    .chat label { flex-basis: 100%; }
    .chat input { flex: 1; min-width: 0; font: inherit; padding: 0.5rem; }
    .chat button { font: inherit; padding: 0.5rem 1rem; }
    .chat :focus-visible { outline: 3px solid #1d4ed8; outline-offset: 2px; }
  </style>
</head>
<body>
  <section class="chat" aria-labelledby="chat-title">
    <h2 id="chat-title">Ask our assistant</h2>
    <div id="chat-log" class="chat-log" role="log" aria-label="Conversation" tabindex="0"></div>
    <form id="chat-form">
      <label for="chat-input">Your question</label>
      <input id="chat-input" name="question" autocomplete="off" maxlength="2000" required>
      <button type="submit">Send</button>
    </form>
  </section>
  <script type="module" src="chat.js"></script>
</body>
</html>
public/chat.js
const log = document.getElementById("chat-log");
const form = document.getElementById("chat-form");
const input = document.getElementById("chat-input");
const button = form.querySelector("button");
const turns = [];

function add(role, text) {
  const p = document.createElement("p");
  p.className = role;
  p.textContent = text; // text only: a reply can never inject HTML into the page
  log.append(p);
  log.scrollTop = log.scrollHeight;
  return p;
}

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const question = input.value.trim();
  if (!question) return;
  input.value = "";
  button.disabled = true;
  add("user", question);
  const reply = add("assistant", "Thinking…");
  let text = "Sorry, something went wrong. Please try again.";
  try {
    const res = await fetch("/api/chat", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ messages: [...turns, { role: "user", content: question }] }),
    });
    const data = await res.json().catch(() => ({}));
    if (res.ok && data.answer) {
      text = data.answer;
      turns.push({ role: "user", content: question }, { role: "assistant", content: data.answer });
      turns.splice(0, turns.length - 10); // keep the last 5 questions and answers
    } else if (data.error) {
      text = data.error;
    }
  } catch {
    // network error: keep the default message
  }
  reply.textContent = text;
  button.disabled = false;
  input.focus();
});

اختبرنا هذه الملفات الثلاثة في 2026-11-01 على Node.js 22 وExpress 5.3 ومكتبة openai بالإصدار 7.47، أمام بديل محلي للـ API وفي متصفح حقيقي، بمفتاح وهمي.

كيف تشغّله على جهازك؟#

ضع server.mjs في مجلد، وضع index.html وchat.js في مجلد public داخله. ثم شغّل هذه الأوامر في ذلك المجلد:

macOS / Linux
npm install express openai
export KHABEER_API_KEY="YOUR_5ABEER_API_KEY"
node server.mjs
Windows
npm install express openai
$env:KHABEER_API_KEY="YOUR_5ABEER_API_KEY"
node server.mjs

افتح http://localhost:3000 واكتب سؤالًا. لا تفتح index.html بنقرة مزدوجة، فالنافذة تحتاج إلى الخادم. ولتغيير النموذج أو المنفذ، اضبط CHAT_MODEL أو PORT بالطريقة التي ضبطت بها المفتاح.

كيف يعمل الكود؟#

  • التعليمات: يحدد SYSTEM للنموذج وظيفة المساعد. ضع بيانات نشاطك مكان بيانات المتجر في المثال.
  • الفحص: يقبل الخادم 11 رسالة على الأكثر من المستخدم والمساعد، تبدأ بسؤال وتنتهي بسؤال، ويقتطع كل رسالة عند 4000 حرف. وأي شيء آخر، ومنه رسالة نظام يرسلها الزائر، يعيد الخطأ 400 دون استدعاء النموذج.
  • الذاكرة: ترسل النافذة آخر 5 أسئلة وإجاباتها مع كل سؤال جديد، فتنجح أسئلة المتابعة.
  • حد المعدل: لكل عنوان IP عشرون طلبًا كل 10 دقائق. والطلب الحادي والعشرون يعيد الخطأ 429 ولا يكلّف شيئًا. ويبقى العدّاد في الذاكرة، فيعود إلى الصفر عند إعادة تشغيل الخادم.
  • الأخطاء: تعيد المكتبة المحاولة مرتين من تلقاء نفسها عند تجاوز حد المعدل أو انتهاء المهلة أو أخطاء الخادم. وإذا فشل الطلب بعدها، يرى الزائر اعتذارًا قصيرًا ويسجّل الخادم الخطأ.
  • يحدّ max_tokens: 800 من طول الإجابة في نماذج Claude وGemini، وتتجاهله نماذج GPT، كما في المعاملات.
  • الإخراج الآمن: تكتب النافذة الإجابات عبر textContent، فلا يعمل أي HTML تحمله الإجابة داخل صفحتك.
  • سهولة الوصول: المحادثة منطقة role="log" لها اسم، تعلن قارئات الشاشة رسائلها الجديدة، ولحقل الإدخال تسمية ظاهرة.

كيف تضيفه إلى موقع قائم؟#

  1. انسخ العنصر <section class="chat"> إلى صفحتك، وقواعد <style> إلى ملف التنسيق.
  2. أضف وسم السكربت الخاص بملف chat.js قبل </body>، مع مسار الملف في موقعك. ولأنه يعمل وحدةً مستقلة، فلا يتعارض مع سكربتات موقعك الأخرى.
  3. اجعل المسار /api/chat في موقعك يصل إلى خادم Node.js.

أبسط طريقة للخطوة الثالثة نطاق واحد: في خادم الويب، مثل Nginx أو Caddy، أو في إعدادات الوكيل لدى الاستضافة، حوّل /api/chat إلى خادم Node.js. وعندها تعمل النافذة كما هي، دون CORS. وإذا كان الخادم على نطاق آخر، فضع عنوانه الكامل في chat.js، واسمح بطلبات CORS من أصل موقعك وحده، حتى لا تستخدم مواقع أخرى روبوت المحادثة من صفحاتها.

وللصفحة العربية، اضبط lang="ar" dir="rtl" على العنصر html، وترجم النصوص الظاهرة في index.html وchat.js، ورسائل الخطأ في server.mjs. وتنتقل الفقاعات إلى الجهة الصحيحة من تلقاء نفسها، ويطلب SYSTEM أصلًا أن تأتي الإجابة بلغة الزائر.

ما الذي يتغير خلف وكيل عكسي أو موزّع أحمال؟#

يعدّ حد المعدل الطلبات حسب req.ip، ويأخذه Express افتراضيًا من الاتصال نفسه. وخلف Nginx أو Caddy أو موزّع أحمال يكون هذا عنوان الوكيل، فيشترك كل الزوار في عدّاد واحد.

والحل في السطر المعلّق داخل server.mjs: اضبط trust proxy على عدد الوكلاء الذين يسبقون التطبيق، وهو في الغالب 1. وهذه القيم كما في دليل Express، راجعناه في 2026-11-01:

قيمة trust proxyمن أين يأتي عنوان IP للزائر
غير مضبوطة (الافتراضي)الاتصال المباشر
رقم، مثل 1العنوان الذي يبعد هذا العدد من الخطوات على الأكثر، بقراءة X-Forwarded-For من آخره إلى أوله
trueأول عنوان في X-Forwarded-For

لا تستخدم true إلا إذا كان آخر وكيل لديك يستبدل قيمة X-Forwarded-For بقيمته. وإلا، كما يحذّر الدليل، قد يتمكن العميل من وضع أي قيمة يريدها، فيتجاوز الزائر الحد. وإذا شغّلت عدة نسخ من الخادم، فاحفظ العدّادات في مخزن مشترك، مثل Redis.

كم يكلّف شات بوت الموقع؟#

لا تضيف النافذة ولا Express أي رسوم، فأنت تدفع ثمن الخادم وتوكنات النموذج. ويرسل كل سؤال التعليمات والمحادثة الأخيرة والرسالة الجديدة. وفي هذا المثال يجيب روبوت المحادثة عن 3000 سؤال في الشهر، بنحو 1500 توكن إدخال و300 توكن إخراج لكل سؤال:

مثال: تكلفة ⁦Claude Sonnet 5.5⁩

4.5 مليون توكن إدخال و900 ألف توكن إخراج

سعرنا‏270.00 ج.م‏أقل من السعر الرسمي بنسبة 70%‎
السعر الرسمي‏900.00 ج.م‏

السعر الرسمي من صفحة المزوّد، بتاريخ 19 أكتوبر 2026.

وهذا سعر التوكن في النموذج الافتراضي:

سعر ⁦Claude Sonnet 5.5⁩ لكل مليون توكن
الإدخال‏30 ج.م‏السعر الرسمي: ‏100 ج.م‏
الإخراج‏150 ج.م‏السعر الرسمي: ‏500 ج.م‏

السعر الرسمي من صفحة المزوّد، بتاريخ 19 أكتوبر 2026.

ولتنفق أقل، اختر نموذجًا أصغر من صفحة النماذج، أو اختصر SYSTEM، أو اخفض max_tokens. وقارن بين المزوّدين في صفحة الـ API وصفحات Claude API وOpenAI API وGemini API، والباقات في صفحة الأسعار.

وخصّص لروبوت المحادثة مفتاحًا له حد إنفاق، كما في تحكّم في كل مفتاح. وعند بلوغ الحد تفشل الطلبات بالخطأ API_KEY_QUOTA_EXHAUSTED ويرى الزوار الاعتذار، فلا يتجاوز يوم مزدحم أو استخدام مسيء المبلغ الذي حددته.

كيف تحمي روبوت المحادثة؟#

  • احفظ المفتاح في متغير بيئة على الخادم، ولا تضعه أبدًا في الصفحة أو في chat.js أو في مستودع كود. وإذا تسرّب، فعطّله وأنشئ مفتاحًا جديدًا.
  • إذا كان للخادم عنوان ثابت، فأضف إلى مفتاحه قائمة عناوين IP مسموح بها، كما في تحكّم في كل مفتاح.
  • يستطيع أي شخص استدعاء /api/chat دون المرور بصفحتك. ويحدّ حد المعدل وحد الإنفاق مما قد يكلّفه ذلك.
  • يستطيع الزائر تعديل السجل الذي ترسله النافذة، أو محاولة إقناع المساعد بترك تعليماته. فلا تضع في SYSTEM أسرارًا أو بيانات عملاء، وتعامل مع الإجابات على أنها معلومات لا وعود.
  • قدّم الصفحة عبر HTTPS، وأخبر الزوار أن مساعدًا بالذكاء الاصطناعي هو الذي يجيبهم.

أسئلة شائعة#

هل يستطيع روبوت المحادثة استخدام GPT أو Gemini بدل Claude؟#

نعم. اضبط CHAT_MODEL على معرّف نموذج من صفحة النماذج، واستخدم مفتاحًا لمزوّد ذلك النموذج. فكل مفتاح يغطي مزوّدًا واحدًا، واستدعاء نموذج GPT بمفتاح Claude يعيد الخطأ MODEL_PLATFORM_NOT_ALLOWED.

هل يجيب من محتوى موقعي؟#

المحتوى القصير، مثل مواعيد العمل والشحن والأسئلة المتكررة، يتسع له SYSTEM. أما الكتالوج الكبير، فابحث فيه عند كل سؤال وأرسل معه الأجزاء المتعلقة به وحدها. فالنموذج لا يعرف إلا ما ترسله إليه.

هل يمكن أن تظهر الإجابة كلمة بكلمة؟#

نعم، بالبث المتدفق: اضبط stream: true على الخادم، ومرّر الأجزاء إلى النافذة فور وصولها، كما في البث المتدفق. وينتظر هذا الدليل الإجابة كاملة ليبقى الكود قصيرًا.

هل يعمل على ووردبريس أو منصات بناء المواقع؟#

نعم، إذا كانت منصتك تقبل HTML وسكربتات من عندك، في كتلة HTML مخصصة مثلًا. ويعمل خادم Node.js على أي استضافة تدعم Node.js.

ماذا يحدث عندما ينفد رصيدي؟#

تفشل الطلبات بالخطأ INSUFFICIENT_BALANCE، ويرى الزوار الاعتذار حتى تشحن رصيدك.

كيف أدفع ثمن الـ API؟#

تشحن رصيدك في خبير بأي طريقة من طرق الدفع، وتُخصم طلبات روبوت المحادثة من هذا الرصيد.

هل 5abeer جزء من OpenAI أو Anthropic أو Google؟#

لا. 5abeer خدمة مستقلة لا تتبع أيًّا منها.

المصادر#

راجعناها في 2026-11-01:

ابدأ#

أنشئ مفتاح Claude، وضعه في KHABEER_API_KEY، ثم شغّل الخادم.