สร้างแชทบอท AI สุดล้ำด้วย Next.js, Vercel AI SDK และ Google Gemini: ฉบับสมบูรณ์

2025-03-24T07:46:33.000+00:00

สวัสดีครับทุกคน! วันนี้เราจะมาสร้างแชทบอท AI เป็นของตัวเองกันครับ! ไม่ต้องกังวลว่าจะยากเกินไป เพราะเราจะใช้เครื่องมือที่ใช้งานง่าย และผมจะอธิบายทุกขั้นตอนอย่างละเอียด พร้อมแล้วก็ลุยกันเลย!

Deploy with Vercel

Stack ที่เราจะใช้:

เตรียมโปรเจกต์ Next.js ให้พร้อม

เปิด Terminal หรือ Command Prompt แล้วรันคำสั่งนี้:

npx create-next-app ai-chatbot

คำสั่งนี้จะสร้างโปรเจกต์ Next.js ใหม่ชื่อว่า ai-chatbot ให้เราครับ

จากนั้น จะมีคำถามให้เราตั้งค่าโปรเจกต์เล็กน้อย:

เมื่อตั้งค่าเสร็จแล้ว ให้เข้าไปในโฟลเดอร์โปรเจกต์ของเรา แล้วเปิดโปรแกรมเขียนโค้ดขึ้นมาเลย

เริ่มต้นด้วยการทำ Backend

Backend คือส่วนที่จะจัดการการสื่อสารกับ Gemini AI Model ครับ

  1. ทำการติดตั้ง package ที่จำเป็นดังต่อไปนี้:
npm install ai @ai-sdk/google
  1. สร้างไฟล์ route.ts: สร้างไฟล์ใหม่ชื่อ route.ts ไว้ในโฟลเดอร์ app/api/chat (ถ้ายังไม่มีโฟลเดอร์ api/chat ให้สร้างขึ้นมาก่อน)

แล้วเขียนโค้ดใน route.ts ดังต่อไปนี้:

import { google } from "@ai-sdk/google";
import { streamText } from "ai";
import { NextRequest } from "next/server";

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

  const result = streamText({
    model: google("gemini-2.0-flash-001"),
    messages,
  });

  return result.toDataStreamResponse();
}

อธิบายโค้ด:

เท่านี้เราก็จะได้ backend แล้วครับ

มาทำฝั่งหน้าบ้านกันต่อ

Frontend คือส่วนที่ผู้ใช้จะเห็นและโต้ตอบกับแชทบอทของเรา และเมื่อเราสร้างโปรเจค Next.js เราจะได้ไฟล์ app/page.tsx แถมมาด้วย

ให้เราลบโค้ดที่อยู่ในไฟล์นี้ แล้วแทนที่ด้วยโค้ดต่อไปนี้:

"use client";

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

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

  return (
    <main className="flex flex-col items-center p-4 min-h-screen">
      <div className="w-full max-w-md space-y-2">
        {messages.map((message) => (
          <div key={message.id} className="whitespace-pre-wrap">
            <strong>{message.role === "user" ? "You:" : "AI:"}</strong> {" "}
            {message.parts.map((part, i) =>
              part.type === "text" ? <span key={i}>{part.text}</span> : null
            )}
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit} className="w-full max-w-md mt-4">
        <input
          className="w-full p-2 border rounded"
          value={input}
          placeholder="Type a message..."
          onChange={handleInputChange}
        />
      </form>
    </main>
  );
}

ตามล่า API KEY

ก่อนจะเริ่มแชทได้ เราต้องมี API Key จาก Google ก่อนครับ โดย:

  1. เข้าไปที่ https://aistudio.google.com/apikey
  2. ทำการกด Create API Key:
  1. เลือก Google Cloud Project:
    • ถ้าคุณมีโปรเจกต์อยู่แล้ว ให้เลือกโปรเจกต์นั้น
    • ถ้ายังไม่มี ให้สร้างโปรเจกต์ใหม่ และอย่าลืมตั้งค่า Billing ให้เรียบร้อย (Google Cloud Project จำเป็นต้องมีการผูกวิธีการชำระเงิน ถึงแม้ว่าเราจะใช้ Gemini API ในโควต้าฟรีก็ตาม)
  1. คลิก "Create API key in existing project"
  2. ทำการคัดลอก API Key มา: Google จะแสดง API Key ให้เรา ให้คัดลอกเก็บไว้
  1. เก็บ API Key ไว้ในไฟล์ .env
  1. เพิ่ม API Key ลงในไฟล์:

สำคัญ: ไฟล์ .env ใช้เก็บข้อมูลที่เป็นความลับ เช่น API Key เราจะไม่ commit ไฟล์นี้ขึ้น Git

ทำการทดสอบ

  1. ทดสอบโดยการรันคำสั่ง:
npm run dev
  1. เปิดเว็บเบราว์เซอร์: ไปที่ http://localhost:3000 (หรือ URL ที่ Next.js บอก)
  1. ลองแชท: พิมพ์อะไรก็ได้ แล้วกด Enter ถ้าทุกอย่างถูกต้อง Gemini จะตอบกลับมา!

ถ้า Gemini ตอบกลับ แสดงว่าทุกอย่างทำงานได้ถูกต้อง

ปรับปรุงหน้าตาแชทบอทให้สวยงาม

ตอนนี้แชทบอทของเราใช้งานได้แล้ว แต่หน้าตายังดูธรรมดาไปหน่อย เรามาเพิ่มความสวยงามกันดีกว่าครับ

  1. ติดตั้ง react-textarea-autosize: ช่วยให้ช่องพิมพ์ข้อความขยายขนาดได้อัตโนมัติ
npm install react-textarea-autosize
  1. ติดตั้งและตั้งค่า Shadcn/UI: เป็น Component Library ที่สวยงามและใช้งานง่าย
npx shadcn init
  1. แล้วทำการรันคำสั่งต่อไปนี้:
npx shadcn add button
  1. แก้ไขโค้ดในไฟล์ `app/page.tsx` ให้เป็นไปดังตัวอย่างต่อไปนี้:
"use client";

import type React from "react";

import { useChat } from "@ai-sdk/react";
import { useRef, useEffect } from "react";
import { Loader2, ArrowUp, Ghost } from "lucide-react";
import { Button } from "@/components/ui/button";
import Textarea from "react-textarea-autosize";
import { cn } from "@/lib/utils";

export default function ChatbotUI() {
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const { messages, input, handleInputChange, handleSubmit, status } =
    useChat({
      onFinish: () => {
        textareaRef.current?.focus();
      },
    });
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const formRef = useRef<HTMLFormElement>(null);

  // Handle form submission
  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (input.trim() && status === "ready") {
      handleSubmit(e);
      // Focus back on textarea after submission
      setTimeout(() => {
        textareaRef.current?.focus();
      }, 100);
    }
  };

  // Handle keyboard shortcuts
  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      formRef.current?.requestSubmit();
    }
  };

  // Scroll to bottom when messages change
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

  return (
    <div className="flex flex-col w-full rounded-lg">
      <div className="fixed inset-0 -z-10 pointer-events-none">
        <div className="absolute inset-0 bg-gradient-to-b from-background via-background/90 to-background" />
        <div className="absolute right-0 top-0 h-[300px] w-[300px] md:h-[500px] md:w-[500px] bg-blue-500/10 blur-[100px]" />
        <div className="absolute bottom-0 left-0 h-[300px] w-[300px] md:h-[500px] md:w-[500px] dark:bg-purple-500/10 bg-purple-500/15 blur-[100px]" />
      </div>
      <div className="flex-1 pr-4 mb-8 flex p-4 flex-col h-full max-w-4xl w-full mx-auto">
        <div className="space-y-6 pb-4">
          {messages.length === 0 ? (
            <div className="flex flex-col items-center justify-center h-[70vh] text-muted-foreground">
              <Ghost className="mb-2 size-6" />
              <p className="text-center">
                No messages yet. Start a conversation!
              </p>
            </div>
          ) : (
            messages.map((message) => (
              <div
                key={message.id}
                className={cn(
                  "flex items-start gap-3",
                  message.role === "user" ? "justify-end" : "justify-start"
                )}
              >
                <div
                  className={cn(
                    "rounded-lg px-4 py-3 max-w-[80%]",
                    message.role === "user"
                      ? "bg-primary text-primary-foreground"
                      : "bg-muted border"
                  )}
                >
                  {message.parts.map((part, i) =>
                    part.type === "text" ? (
                      <div key={i} className="whitespace-pre-wrap">
                        {part.text}
                      </div>
                    ) : null
                  )}
                </div>
              </div>
            ))
          )}
          {status === "streaming" && (
            <div className="flex items-start gap-3">
              <div className="rounded-lg px-4 py-3 max-w-[80%] bg-muted">
                <Loader2 className="h-4 w-4 animate-spin" />
              </div>
            </div>
          )}
          <div ref={messagesEndRef} />
        </div>
      </div>
      <form
        ref={formRef}
        onSubmit={onSubmit}
        className="fixed bottom-0 right-0 left-0 max-w-4xl w-full mx-auto"
      >
        <div className="relative bg-background border-x border-t rounded-t-xl shadow-xl">
          <Textarea
            ref={textareaRef}
            value={input}
            onChange={handleInputChange}
            onKeyDown={handleKeyDown}
            placeholder="Type a message..."
            className="min-h-[60px] mb-0 resize-none pr-12 py-3 w-full px-4 focus:outline-0"
            maxRows={5}
            disabled={status === "submitted"}
          />
          <div className="absolute right-2 top-2">
            <Button
              type="submit"
              size="icon"
              disabled={!input.trim() || status === "submitted"}
              className="size-8"
            >
              {status === "submitted" ? (
                <Loader2 className="h-4 w-4 animate-spin" />
              ) : (
                <ArrowUp className="h-4 w-4" />
              )}
            </Button>
          </div>
        </div>
      </form>
    </div>
  );
}

อธิบายการเปลี่ยนแปลง:

เพียงเท่านี้คุณก็จะได้แชทบอทแล้ว!

ปรับบุคลิกให้แชทบอท (System Instruction)

ตอนนี้แชทบอทของเราอาจจะยังตอบไม่ค่อยเป็นธรรมชาติ หรือสลับภาษาไปมา เช่น:

เราสามารถปรับปรุงได้โดยการเพิ่ม "System Instruction" หรือคำสั่งระบบ เพื่อบอก Gemini ว่าเราอยากให้แชทบอทมีบุคลิกแบบไหน:

คุณคือผู้ช่วยของผู้ใช้งานชื่อว่า Gemini เป็นเพศหญิง มีความเป็นมิตร เข้าถึงง่าย และน่าเชื่อถือ ที่ถูกออกแบบมาเพื่อช่วยเหลือผู้ใช้ในชีวิตประจำวัน

## บุคลิกภาพและโทน

- **พูดคุยแบบเป็นกันเอง** เหมือนเพื่อนที่ดี ใช้ภาษาทั่วไป หลีกเลี่ยงคำศัพท์เทคนิคหรือภาษาที่เป็นทางการเกินไป
- **สนทนาอย่างเป็นธรรมชาติ** โต้ตอบเหมือนคนจริงๆ ใช้คำพูดกระชับ แต่อบอุ่น ไม่จำเป็นต้องเป็นทางการเสมอไป
- **สร้างความรู้สึกเป็นส่วนตัว** จดจำรายละเอียดที่ผู้ใช้แชร์และอ้างถึงในการสนทนาต่อๆ ไป
- **แสดงความเห็นอกเห็นใจ** เข้าใจความรู้สึกและสถานการณ์ของผู้ใช้ ตอบสนองด้วยความเข้าใจ
- **มีอารมณ์ขัน** แต่เข้าใจจังหวะและความเหมาะสม ไม่พยายามตลกในสถานการณ์จริงจัง

## การให้ความช่วยเหลือ

- **รอบคอบเป็นพิเศษ** ตรวจสอบข้อเท็จจริงก่อนให้คำตอบ หากไม่แน่ใจให้บอกตรงๆ และแนะนำวิธีหาข้อมูลเพิ่มเติม
- **ให้คำแนะนำที่ปฏิบัติได้จริง** แนะนำวิธีแก้ปัญหาที่ทำได้จริงในชีวิตประจำวัน ไม่ซับซ้อนเกินไป
- **นำเสนอทางเลือก** เสนอหลายวิธีแก้ปัญหาเมื่อเป็นไปได้ พร้อมข้อดีและข้อเสียของแต่ละวิธี

## การตอบสนอง

- **โต้ตอบอย่างรวดเร็ว** ให้คำตอบที่กระชับ ตรงประเด็น ไม่เยิ่นเย้อ
- **ถามคำถามเพื่อทำความเข้าใจ** เมื่อต้องการข้อมูลเพิ่มเติมเพื่อให้คำแนะนำที่ดีที่สุด
- **ยอมรับข้อจำกัด** หากไม่สามารถช่วยได้ ให้บอกอย่างตรงไปตรงมาและแนะนำแหล่งข้อมูลอื่นๆ
- **ปรับให้เข้ากับสถานการณ์** เข้าใจบริบทและความต้องการเฉพาะของผู้ใช้
- **สนับสนุนการเรียนรู้** ช่วยอธิบายแนวคิดใหม่ๆ ในรูปแบบที่เข้าใจง่าย

## ข้อควรหลีกเลี่ยง

- **ไม่แสร้งว่ารู้ทุกอย่าง** ยอมรับเมื่อไม่รู้คำตอบ
- **ไม่พูดวกวนหรือใช้ภาษาซับซ้อนโดยไม่จำเป็น** รักษาการสื่อสารให้ชัดเจนและเข้าใจง่าย

ให้คุณคัดลอก prompt ด้านบนไปใส่ใน app/api/chat/route.ts ดังตัวอย่างต่อไปนี้

import { google } from "@ai-sdk/google";
import { streamText } from "ai";
import { NextRequest } from "next/server";

const systemInstruction = `คุณคือผู้ช่วยของผู้ใช้งานชื่อว่า Gemini เป็นเพศหญิง มีความเป็นมิตร เข้าถึงง่าย และน่าเชื่อถือ ที่ถูกออกแบบมาเพื่อช่วยเหลือผู้ใช้ในชีวิตประจำวัน

## บุคลิกภาพและโทน

- **พูดคุยแบบเป็นกันเอง** เหมือนเพื่อนที่ดี ใช้ภาษาทั่วไป หลีกเลี่ยงคำศัพท์เทคนิคหรือภาษาที่เป็นทางการเกินไป
- **สนทนาอย่างเป็นธรรมชาติ** โต้ตอบเหมือนคนจริงๆ ใช้คำพูดกระชับ แต่อบอุ่น ไม่จำเป็นต้องเป็นทางการเสมอไป
- **สร้างความรู้สึกเป็นส่วนตัว** จดจำรายละเอียดที่ผู้ใช้แชร์และอ้างถึงในการสนทนาต่อๆ ไป
- **แสดงความเห็นอกเห็นใจ** เข้าใจความรู้สึกและสถานการณ์ของผู้ใช้ ตอบสนองด้วยความเข้าใจ
- **มีอารมณ์ขัน** แต่เข้าใจจังหวะและความเหมาะสม ไม่พยายามตลกในสถานการณ์จริงจัง

## การให้ความช่วยเหลือ

- **รอบคอบเป็นพิเศษ** ตรวจสอบข้อเท็จจริงก่อนให้คำตอบ หากไม่แน่ใจให้บอกตรงๆ และแนะนำวิธีหาข้อมูลเพิ่มเติม
- **ให้คำแนะนำที่ปฏิบัติได้จริง** แนะนำวิธีแก้ปัญหาที่ทำได้จริงในชีวิตประจำวัน ไม่ซับซ้อนเกินไป
- **นำเสนอทางเลือก** เสนอหลายวิธีแก้ปัญหาเมื่อเป็นไปได้ พร้อมข้อดีและข้อเสียของแต่ละวิธี

## การตอบสนอง

- **โต้ตอบอย่างรวดเร็ว** ให้คำตอบที่กระชับ ตรงประเด็น ไม่เยิ่นเย้อ
- **ถามคำถามเพื่อทำความเข้าใจ** เมื่อต้องการข้อมูลเพิ่มเติมเพื่อให้คำแนะนำที่ดีที่สุด
- **ยอมรับข้อจำกัด** หากไม่สามารถช่วยได้ ให้บอกอย่างตรงไปตรงมาและแนะนำแหล่งข้อมูลอื่นๆ
- **ปรับให้เข้ากับสถานการณ์** เข้าใจบริบทและความต้องการเฉพาะของผู้ใช้
- **สนับสนุนการเรียนรู้** ช่วยอธิบายแนวคิดใหม่ๆ ในรูปแบบที่เข้าใจง่าย

## ข้อควรหลีกเลี่ยง

- **ไม่แสร้งว่ารู้ทุกอย่าง** ยอมรับเมื่อไม่รู้คำตอบ
- **ไม่พูดวกวนหรือใช้ภาษาซับซ้อนโดยไม่จำเป็น** รักษาการสื่อสารให้ชัดเจนและเข้าใจง่าย
`

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

  const result = streamText({
    model: google("gemini-1.5-flash-002"),
    messages,
    system: systemInstruction
  });

  return result.toDataStreamResponse();
}

ทำการทดลองสนทนาอีกครั้ง:

โมเดลภาษาของเราก็จะมีความเป็นธรรมชาติมากขึ้นกว่าเดิมเยอะเลย!

เลือกโมเดลภาษาที่ใช่สำหรับคุณ

แน่นอนครับว่า Gemini 2.0 จะมีอยู่ 2 ตัวหลักๆคือ Gemini Pro และ Gemini Flash ซึ่งสามารถสรุปความแตกต่างได้ดังต่อไปนี้

นี่คือตารางสรุปความแตกต่างระหว่าง Gemini 2.0 Flash และ Gemini 2.0 Pro ในภาษาที่เข้าใจง่าย สำหรับผู้ใช้ทั่วไป:

คุณสมบัติGemini 2.0 FlashGemini 2.0 Proเหมาะกับใคร
ความเร็วและการตอบสนองรวดเร็ว เหมาะสำหรับการใช้งานที่ต้องการการตอบสนองแบบทันที เช่น แชทบอท, ผู้ช่วยเสียงช้ากว่า Flash เล็กน้อย แต่ยังถือว่าเร็วผู้ที่ต้องการความรวดเร็วในการใช้งาน และผู้ที่ใช้แอปพลิเคชันที่ต้องการการตอบสนองแบบเรียลไทม์
ความสามารถในการเข้าใจและตอบคำถามเข้าใจคำถามทั่วไปได้ดี ตอบคำถามได้ถูกต้องแม่นยำเข้าใจคำถามที่ซับซ้อนได้ดีกว่า ให้เหตุผลได้ดีกว่าผู้ที่ต้องการคำตอบที่ถูกต้องแม่นยำ และผู้ที่ต้องการให้ AI ช่วยวิเคราะห์ข้อมูลที่ซับซ้อน
ความสามารถในการเขียนโค้ดเขียนโค้ดได้ แต่ไม่เท่า Proเขียนโค้ดได้ดีกว่า เข้าใจโค้ดที่ซับซ้อนได้ดีกว่านักพัฒนาซอฟต์แวร์, โปรแกรมเมอร์, และผู้ที่ต้องการให้ AI ช่วยเขียนโค้ด
ความสามารถในการทำงานกับข้อมูลขนาดใหญ่ทำงานกับข้อมูลขนาดใหญ่ได้ แต่ไม่เท่า Proทำงานกับข้อมูลขนาดใหญ่ได้ดีกว่าผู้ที่ต้องการให้ AI ช่วยวิเคราะห์ข้อมูลจำนวนมาก, สรุปเอกสาร, และระบุแนวโน้ม
ความสามารถในการทำงานกับสื่อหลากหลายรูปแบบ (มัลติโมดัล)ทำงานกับข้อความ, รูปภาพ, วิดีโอ, และเสียงได้ทำงานกับข้อความ, รูปภาพ, วิดีโอ, และเสียงได้ผู้ที่ต้องการให้ AI ทำงานกับสื่อหลากหลายรูปแบบ เช่น สร้างคำบรรยายภาพ, ถอดเสียง, หรือวิเคราะห์วิดีโอ
ราคาถูกกว่า Proแพงกว่า Flashผู้ที่ต้องการใช้งาน AI ในราคาที่เข้าถึงได้ และผู้ที่ต้องการใช้งาน AI แบบทั่วไป
สถานะการใช้งานพร้อมใช้งานทั่วไปอยู่ในช่วงทดลองผู้ใช้งานทั่วไป

สรุปง่ายๆ:

หวังว่าตารางนี้จะช่วยให้คุณเข้าใจความแตกต่างระหว่าง Gemini 2.0 Flash และ Gemini 2.0 Pro ได้ง่ายขึ้นนะครับ

ทั้งนี้ทั้งนั้น ทาง Google ได้เปิดให้เราใช้งาน มีแผนการให้บริการ 2 แบบ คือ

แน่นอนว่าเราใช้งานได้ฟรีๆ แต่อาจจะแชทถี่ๆไม่ได้ ถ้าคุณอยากแชทถี่ๆ แนะนำให้อัพเกรดไปใช้ "ระดับแบบชำระเงิน" โดยดูรายละเอียดได้ที่นี่

ในส่วนของวิธีการเลือกโมเดล ให้คุณไปที่ไฟล์ app/api/chat/route.ts ในส่วนของ model คุณสามารถเปลี่ยนได้สองแบบ

แบบแรก เลือกใช้ Gemini 2.0 Flash

แบบที่สอง เลือกใช้ Gemini 2.0 Pro

เอาขึ้น Vercel

สำหรับใครที่อยากนำไปจริง สามารถคลิกปุ่มด้านล่างนี้ได้เลยครับ

Deploy with Vercel

แค่นี้เอง! ตอนนี้คุณก็มีแชทบอท AI ที่ทั้งสวยงามและฉลาด พร้อมใช้งานแล้วครับ! 🎉