چت بات هوش مصنوعی با AI SDK UI


هوک useChat ایجاد رابط کاربری مکالمه‌ای برای برنامه‌ی چت‌بات شما را بسیار آسان می‌کند. این هوک امکان استریم پیام‌های چت از ارائه‌دهنده هوش مصنوعی را فراهم می‌کند، وضعیت چت (chat state) را مدیریت می‌نماید و با رسیدن پیام‌های جدید، رابط کاربری را به‌صورت خودکار به‌روزرسانی می‌کند.

به‌طور خلاصه، هوک useChat امکانات زیر را ارائه می‌دهد:

  • استریم پیام‌ها: تمامی پیام‌ها از ارائه‌دهنده هوش مصنوعی به صورت بلادرنگ به رابط کاربری چت ارسال می‌شوند
  • مدیریت stateها: این هوک stateهای مربوط به ورودی، پیام‌ها، وضعیت کلی، خطا و موارد دیگر را برای شما مدیریت می‌کند
  • یکپارچه‌سازی: به‌سادگی می‌توانید هوش مصنوعی چت خود را در هر طراحی یا چیدمان رابط کاربری با کمترین تلاش ادغام کنید

در این راهنما، شما خواهید آموخت چگونه از هوک useChat برای ساخت یک برنامه چت‌بات با استریم پیام‌های بلادرنگ (real-time) استفاده کنید.


مثال

در پروژه NextJS خود در مسیر app/page.tsx، قطعه کد زیر را قرار دهید:

کپی
// npm i @ai-sdk/react@^1.2.12

'use client';

import { useChat } from '@ai-sdk/react';

export default function Page() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({});

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.content}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input name="prompt" value={input} onChange={handleInputChange} />
        <button type="submit">Submit</button>
      </form>
    </>
  );
}

در مسیر app/api/chat/route.ts، قطعه کد زیر را قرار دهید:

کپی
// npm add @ai-sdk/openai@^1 ai@^4

import { createOpenAI } from '@ai-sdk/openai';
import { streamText } from 'ai';

const my_model = createOpenAI({
  baseURL: process.env.BASE_URL!,
  apiKey: process.env.LIARA_API_KEY!,
});

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: my_model('openai/gpt-4o-mini'),
    system: 'You are a helpful assistant.',
    messages,
  });

  return result.toDataStreamResponse();
}

متغیرهای محیطی BASE_URL و LIARA_API_KEY همان baseUrl سرویس هوش مصنوعی لیارا و کلید API لیارا هستند که باید در بخش متغیرهای محیطی برنامه خود، آن‌ها را تنظیم کنید.

پیام‌های رابط کاربری اکنون دارای ویژگی جدیدی به نام parts هستند که شامل بخش‌های مختلف پیام می‌باشد. پیشنهاد می‌شود برای نمایش پیام‌ها از ویژگی parts به جای content استفاده کنید. ویژگی parts از انواع مختلف پیام پشتیبانی می‌کند، از جمله متن (text)، فراخوانی tool و نتیجه tool، و امکان ایجاد رابط‌های کاربری چت پیچیده‌تر و انعطاف‌پذیرتر را فراهم می‌آورد.

در کامپوننت Page، هوک useChat هر بار که کاربر پیامی ارسال می‌کند، درخواست را به endpoint ارائه‌دهنده هوش مصنوعی شما می‌فرستد. پیام‌ها سپس به‌صورت بلادرنگ (real-time) استریم شده و در رابط کاربری چت نمایش داده می‌شوند. این ویژگی تجربه چتی را فراهم می‌کند که در آن کاربر می‌تواند پاسخ هوش مصنوعی را به محض آماده شدن مشاهده کند، بدون آنکه نیاز باشد منتظر دریافت کامل پاسخ بماند.


رابط کاربری سفارشی

هوک useChat همچنین روش‌هایی برای مدیریت وضعیت پیام‌های چت و ورودی‌ها از طریق کدنویسی فراهم می‌کند، امکان نمایش وضعیت (status) را می‌دهد و قابلیت به‌روزرسانی پیام‌ها را بدون نیاز به تعامل مستقیم کاربر ارائه می‌دهد.

Status

هوک useChat یک status بازمی‌گرداند که می‌تواند مقادیر زیر را داشته باشد:

  • submitted: پیام به API ارسال شده و در انتظار شروع استریم پاسخ هستیم
  • streaming: پاسخ به‌صورت فعال از API استریم می‌شود و chunkهای داده دریافت می‌شوند
  • ready: پاسخ کامل دریافت و پردازش شده است؛ کاربر می‌تواند پیام جدیدی ارسال کند
  • error: در طول درخواست API خطایی رخ داده و درخواست به صورت کامل انجام نشده است

می‌توانید از status برای اهداف زیر استفاده کنید:

  • نمایش یک spinner بارگذاری هنگام پردازش پیام کاربر توسط چت‌بات
  • نمایش یک دکمه Stop برای متوقف کردن پیام جاری
  • غیرفعال کردن دکمه ارسال (submit button)

در مسیر app/page.tsx، قطعه کد زیر را قرار دهید:

کپی
'use client';

import { useChat } from '@ai-sdk/react';

export default function Page() {
  const { messages, input, handleInputChange, handleSubmit, status, stop } =
    useChat({});

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.content}
        </div>
      ))}

      {(status === 'submitted' || status === 'streaming') && (
        <div>
          {status === 'submitted' && <Spinner />}
          <button type="button" onClick={() => stop()}>
            Stop
          </button>
        </div>
      )}

      <form onSubmit={handleSubmit}>
        <input
          name="prompt"
          value={input}
          onChange={handleInputChange}
          disabled={status !== 'ready'}
        />
        <button type="submit">Submit</button>
      </form>
    </>
  );
}

می‌توانید کامپوننت Spinner را با کامپوننت دلخواه خود جایگزین کنید؛ یا اینکه در مسیر components/Spinner.tsx، قطعه کد زیر را قرار دهید:

کپی
export default function Spinner() {
  return (
    <div className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]">
      <span className="sr-only">Loading...</span>
    </div>
  );
}

Error State

به‌طور مشابه، error state نمایانگر آبجکت خطایی است که هنگام درخواست fetch ایجاد شده است. این state می‌تواند برای نمایش پیام خطا، غیرفعال کردن دکمه ارسال یا نمایش دکمه تلاش مجدد (retry) استفاده شود.

توصیه می‌شود پیام خطای عمومی به کاربر نمایش داده شود، مانند: "مشکلی پیش آمد". این یک شیوه‌ی مناسب برای جلوگیری از افشای اطلاعات داخلی سرور محسوب می‌شود.

کپی
'use client';

import { useChat } from '@ai-sdk/react';

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit, error, reload } =
    useChat({});

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.role}: {m.content}
        </div>
      ))}

      {error && (
        <>
          <div>An error occurred.</div>
          <button type="button" onClick={() => reload()}>
            Retry
          </button>
        </>
      )}

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          disabled={error != null}
        />
      </form>
    </div>
  );
}

ویرایش پیام‌ها

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

تابع setMessages می‌تواند به شما در انجام این کارها کمک کند:

کپی
const { messages, setMessages, ... } = useChat()

const handleDelete = (id) => {
  setMessages(messages.filter(message => message.id !== id))
}

return <>
  {messages.map(message => (
    <div key={message.id}>
      {message.role === 'user' ? 'User: ' : 'AI: '}
      {message.content}
      <button onClick={() => handleDelete(message.id)}>Delete</button>
    </div>
  ))}
  ...

می‌توانید messages و setMessages را مانند یک جفت state و setState در React در نظر بگیرید.

ورودی کنترل شده

در مثال اولیه، ما از callbackهای handleSubmit و handleInputChange برای مدیریت تغییرات ورودی و ارسال فرم استفاده کرده‌ایم. این روش برای موارد رایج کاربردی است، اما شما می‌توانید از APIهای کنترل‌نشده (uncontrolled) برای سناریوهای پیشرفته‌تر مانند اعتبارسنجی فرم یا ایجاد کامپوننت‌های سفارشی نیز، استفاده کنید.

مثال زیر نشان می‌دهد چگونه می‌توان از APIهای دقیق‌تر مانند setInput و append همراه با کامپوننت‌های ورودی و دکمه ارسال سفارشی استفاده کرد:

کپی
const { input, setInput, append } = useChat()

return <>
  <MyCustomInput value={input} onChange={value => setInput(value)} />
  <MySubmitButton onClick={() => {
    // Send a new message to the AI provider
    append({
      role: 'user',
      content: input,
    })
  }}/>
  ...

لغو و بازتولید

یکی دیگر از سناریوهای رایج، متوقف کردن پاسخی است که هنوز از ارائه‌دهنده هوش مصنوعی در حال استریم شدن است. شما می‌توانید این کار را با فراخوانی تابع stop که توسط هوک useChat بازگردانده می‌شود، انجام دهید.

کپی
const { stop, status, ... } = useChat()

return <>
  <button onClick={stop} disabled={!(status === 'streaming' || status === 'submitted')}>Stop</button>
  ...

زمانی که کاربر روی دکمه Stop کلیک می‌کند، درخواست fetch متوقف می‌شود. این کار از مصرف غیرضروری منابع جلوگیری کرده و تجربه کاربری برنامه چت‌بات شما را بهبود می‌بخشد.

به‌طور مشابه، شما می‌توانید از ارائه‌دهنده هوش مصنوعی بخواهید پیام آخر را دوباره پردازش کند، با فراخوانی تابع reload که توسط هوک useChat بازگردانده می‌شود:

کپی
const { reload, status, ... } = useChat()

return <>
  <button onClick={reload} disabled={!(status === 'ready' || status === 'error')}>Regenerate</button>
  ...
</>

زمانی که کاربر روی دکمه Regenerate کلیک می‌کند، ارائه‌دهنده هوش مصنوعی پیام آخر را دوباره تولید کرده و پیام فعلی را به‌طور متقابل جایگزین می‌کند.

محدودسازی به‌روزرسانی‌های رابط کاربری

این ویژگی در حال حاضر فقط برای React در دسترس است.

به‌صورت پیش‌فرض، هر بار که یک chunk جدید دریافت می‌شود، هوک useChat باعث رندر مجدد رابط کاربری می‌شود. شما می‌توانید با استفاده از experimental_throttle به محدودسازی (throttle) به‌روزرسانی‌های UI بپردازید.

کپی
const { messages, ... } = useChat({
  // Throttle the messages and data updates to 50ms:
  experimental_throttle: 50
})