یک چت‌بات حداقلی سه بخش دارد: یک صفحه‌ی HTML با کادر پیام، یک سرور Express که پیام را می‌گیرد، و یک درخواست از سرور به API مدل زبانی. کلید API فقط روی سرور می‌ماند. کد زیر عمداً ساده است تا ساختار دیده شود؛ قالب دقیق درخواست را از مستندات سرویسی که انتخاب می‌کنید بردارید.

ساختار پروژه

chatbot/
  package.json
  server.js
  public/
    index.html

package.json

{
  "name": "chatbot",
  "type": "module",
  "scripts": { "start": "node server.js" },
  "dependencies": { "express": "^4.19.0", "express-rate-limit": "^7.0.0" },
  "engines": { "node": ">=20" }
}

server.js

import express from 'express';
import rateLimit from 'express-rate-limit';

const app = express();
app.use(express.json({ limit: '10kb' }));
app.use(express.static('public'));

// هر آی‌پی: حداکثر ۲۰ پیام در ۱۰ دقیقه
app.use('/api/chat', rateLimit({ windowMs: 10 * 60 * 1000, max: 20 }));

const SYSTEM = 'تو دستیار پشتیبانی سایت هستی. کوتاه و دقیق جواب بده. اگر نمی‌دانی بگو نمی‌دانم.';

app.post('/api/chat', async (req, res) => {
  const message = String(req.body.message || '').slice(0, 1000);
  if (!message) return res.status(400).json({ error: 'empty' });
  try {
    const r = await fetch(process.env.AI_API_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.AI_API_KEY}`
      },
      // بدنه را طبق مستندات سرویس خودتان تنظیم کنید
      body: JSON.stringify({
        model: process.env.AI_MODEL,
        max_tokens: 500,
        messages: [
          { role: 'system', content: SYSTEM },
          { role: 'user', content: message }
        ]
      }),
      signal: AbortSignal.timeout(30000)
    });
    if (!r.ok) throw new Error('upstream ' + r.status);
    const data = await r.json();
    // مسیر متن جواب در هر سرویس فرق دارد
    res.json({ reply: data.choices?.[0]?.message?.content ?? '' });
  } catch (e) {
    console.error(e.message);
    res.status(502).json({ error: 'الان نمی‌توانم جواب بدهم.' });
  }
});

app.listen(process.env.PORT || 3000);

public/index.html

<div id="log"></div>
<form id="f"><input id="m" required><button>ارسال</button></form>
<script>
const log = document.getElementById('log');
const add = (who, text) => {
  const p = document.createElement('p');
  p.textContent = who + ': ' + text;   // textContent، نه innerHTML
  log.appendChild(p);
};
document.getElementById('f').addEventListener('submit', async (e) => {
  e.preventDefault();
  const m = document.getElementById('m');
  add('شما', m.value);
  const r = await fetch('/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ message: m.value })
  });
  const d = await r.json();
  add('دستیار', d.reply || d.error);
  m.value = '';
});
</script>

نکته‌هایی که در همین کد رعایت شده

  • کلید در متغیر محیطی است، نه در کد.
  • طول پیام و طول جواب محدود است.
  • محدودیت نرخ برای هر آی‌پی.
  • مهلت ۳۰ ثانیه برای درخواست.
  • جواب با textContent نمایش داده می‌شود تا کد مخرب اجرا نشود.
  • دستور سیستمی روی سرور است و کاربر نمی‌تواند عوضش کند.

استقرار روی هاست Node.js

  1. پروژه را (بدون node_modules) به هاست ببرید.
  2. اپلیکیشن را با فایل شروع server.js بسازید و وابستگی‌ها را نصب کنید.
  3. سه متغیر محیطی را وارد کنید: AI_API_URL، AI_API_KEY، AI_MODEL.
  4. اپلیکیشن را اجرا کنید و دامنه را باز کنید.

راهنماها: راه‌اندازی اپلیکیشن، هاست Node.js چیست، پلن‌ها: هاست Node.js رایکت.

قدم‌های بعدی

  • حافظه‌ی گفتگو: چند پیام آخر را هم همراه درخواست بفرستید.
  • جواب از محتوای خودتان: متن سوالات متداول را در دستور سیستمی بگذارید یا بخش مرتبط را قبل از درخواست پیدا و ضمیمه کنید.
  • نمایش تدریجی جواب (استریم).
  • ثبت گفتگوها برای بهبود، با رعایت حریم خصوصی.

سوال‌های رایج

از سرور به API وصل نمی‌شود.

اول با یک درخواست ساده از همان سرور امتحان کنید که آن آدرس در دسترس است.

کلید را چطور امن‌تر نگه دارم؟

نگهداری امن کلید API.

بدون برنامه‌نویسی هم می‌شود؟

چت‌بات روی سایت وردپرسی.