iT邦幫忙

2024 iThome 鐵人賽

DAY 26
0
自我挑戰組

從零打造客製化 AI 聊天機器人系列 第 26

[從零打造客製化 AI 聊天機器人] 建立旅遊即時聊天機器人1 (開發OpenAI聊天室功能)

  • 分享至 

  • xImage
  •  

從今天開始就要再一步步建立旅遊即時聊天機器人給大家看啦~
細節的部份可以看之前的文章,從今天開始主要是要把之前教的全部融合再一起。

今天要實作 OpenAI 初步的聊天室功能啦!
FastAPI 與 Azure OpenAI 串接建立 WebSocket 後端,並在 Next.js 中使用 WebSocket 與後端通信的前端頁面。

1. FastAPI 和 Azure OpenAI 的 WebSocket 後端

基於 FastAPI 的 WebSocket 服務端,該服務會與 Azure OpenAI 的 chat API 進行交互,並將結果發送回客戶端。
openai_config 注意是 AzureOpenAI 的金鑰

from fastapi import FastAPI
from openai import AzureOpenAI
from fastapi import FastAPI, WebSocket
from openai_config import *

app = FastAPI()  # 產生 FastAPI物件

client = AzureOpenAI(
  azure_endpoint = azure_endpoint,
  api_key=api_key,
  api_version=api_version
)


@app.websocket("/chatbot")
async def websocket_endpoint(websocket: WebSocket):
    # 接受前端的 websocket 連接請求
    await websocket.accept()
    try:
        while True:
            
            # 等待前端訊息
            data = await websocket.receive_text()
            prompt = data.strip()
            message_text =[
                {"role": "system", "content": "你是一個AI助理, 幫人類回答問題"},
                {"role": "user", "content": prompt}
            ]

            # 生成式 ai 回覆
            response = client.chat.completions.create(
                model=model_name,
                messages = message_text,
                temperature=0,
                max_tokens=150,
                top_p=0.95,
                frequency_penalty=0,
                presence_penalty=0,
                stop=None
            )

             # 發送給前端
            await websocket.send_text(response.choices[0].message.content)
    except Exception as e:
        print(f"WebSocket connection closed with exception: {e}")

https://ithelp.ithome.com.tw/upload/images/20241009/20169415YXzxA9sGZL.png

2. Next.js 中建立 WebSocket Hook:

頁面會使用這個 useWebSocket Hook 來管理 WebSocket 連接並與 FastAPI 進行通信。

import { useEffect, useState } from 'react';

type Message = {
  role: 'User' | 'AI';
  content: string;
};

type WebSocketHook = {
  messages: Message[];
  sendMessage: (message: string) => void;
  loading: boolean;
};

const useWebSocket = (url: string): WebSocketHook => {
  const [socket, setSocket] = useState<WebSocket | null>(null);
  const [messages, setMessages] = useState<Message[]>([
    { role: 'AI', content: '您好,我是AideAI,有什麼可以幫你的嗎?' }
  ]);
  const [loading, setLoading] = useState<boolean>(false);

  useEffect(() => {
    const ws = new WebSocket(url);
    setSocket(ws);

    ws.onopen = () => {
      setLoading(false);
      console.log('已連接到 WebSocket');
    };

    ws.onmessage = (event: MessageEvent) => {
      setLoading(false);
      setMessages((prevMessages) => {
        const updatedMessages = [...prevMessages];
        updatedMessages.splice(-1, 1); // 移除最後的 "loading" 訊息
        return [...updatedMessages, { role: 'AI', content: event.data }];
      });
      console.log('收到伺服器的回應:', event.data);
    };

    ws.onclose = () => {
      console.log('WebSocket 連接已關閉');
    };

    ws.onerror = (error: Event) => {
      console.error('WebSocket 發生錯誤:', error);
    };

    // 在組件卸載時關閉 WebSocket
    return () => {
      ws.close();
    };
  }, [url]);

  // 發送消息的函數
  const sendMessage = (message: string) => {
    if (socket && socket.readyState === WebSocket.OPEN) {
      setLoading(true);
      socket.send(message);
      setMessages((prevMessages) => [
        ...prevMessages,
        { role: 'User', content: message },
        { role: 'AI', content: 'loading' } // 添加一個 "loading" 消息表示 AI 正在回應
      ]);
    }
  };

  return { messages, sendMessage, loading };
};

export default useWebSocket;

3. Next.js 前端頁面

在 Next.js 頁面中使用 WebSocket Hook,並建立聊天介面。



import React, { useState } from 'react';
import Image from 'next/image';
import useWebSocket from '../../hooks/useWebSocket';

const ChatLoading = () => {
    return (
        <div className='flex space-x-2 justify-center items-center dark:invert h-full'>
            <div className='h-2 w-2 bg-gray-500 rounded-full animate-bounce [animation-delay:-0.3s]'></div>
            <div className='h-2 w-2 bg-gray-500 rounded-full animate-bounce [animation-delay:-0.15s]'></div>
            <div className='h-2 w-2 bg-gray-500 rounded-full animate-bounce'></div>
        </div>
    )
}

const ChatBot: React.FC = () => {
  const { messages, sendMessage, loading } = useWebSocket('ws://localhost:8000/chatbot');
  const [input, setInput] = useState<string>('');

  const handleSend = () => {
    if (input.trim()) {
      sendMessage(input);
      setInput(''); // 清空輸入框
    }
  };

  return (
    <div className='flex flex-col relative w-full'>
    <header className='flex p-2 items-center'><p className='font-bold ml-1 text-xl'>Taipei Tourism Chatbot</p></header>
      <div style={{ height: 'calc(100vh - 46px)' }} className='overflow-y-auto'>
        {messages.map((msg, index) => (
          <div key={index} className='flex flex-col space-y-4'>
            {msg.role === 'AI' ? (
              <div className='flex space-x-2 justify-start bg-slate-100 p-4 items-center'>
                <div className='bg-sky-600 rounded-lg w-10 h-10 flex items-center justify-center flex-shrink-0'>
                  <Image src='/chatbot.png' alt='robot' width={30} height={30} />
                </div>
                <div>
                  {msg.content === 'loading' ? <ChatLoading /> : msg.content}
                </div>
              </div>
            ) : (
              <div className='flex space-x-2 justify-end p-4 items-center'>
                <div>{msg.content}</div>
                <div className='bg-green-400 rounded-lg w-10 h-10 flex items-center justify-center'>
                  <Image src='/avatar.png' alt='user' width={25} height={25} />
                </div>
              </div>
            )}
          </div>
        ))}
      </div>
      <footer className='p-4 bottom-0 absolute w-full flex flex-col items-center'>
        <div className='flex w-1/2'>
          <input
            value={input}
            onChange={(e) => setInput(e.target.value)}
            className='p-2 rounded-l w-full outline-none bg-gray-100'
            placeholder='請輸入要詢問的台北景點'
          />
          <button className='rounded-r bg-gray-100' onClick={handleSend}>
            <Image src='/send.png' alt='send' width={30} height={30} className='p-1' />
          </button>
        </div>
      </footer>
    </div>
  );
};

export default ChatBot;
  • FastAPI 串接 Azure OpenAI:通過 WebSocket 從前端獲取用戶輸入,並使用 Azure OpenAI 生成回應,然後通過 WebSocket 發回前端。
  • Next.js 前端使用 WebSocket:在 Next.js 中使用 WebSocket 進行雙向通信,顯示用戶和 AI 之間的對話,並在回應未到達之前顯示一個 "loading" 狀態。

https://ithelp.ithome.com.tw/upload/images/20241009/20169415dqy4Fvc251.pnghttps://ithelp.ithome.com.tw/upload/images/20241009/20169415jodYpikGEv.png


上一篇
[從零打造客製化 AI 聊天機器人] 撰寫 WebSocket 伺服器 (2)
下一篇
[從零打造客製化 AI 聊天機器人] 建立旅遊即時聊天機器人2 (建立意圖和景點的向量資料庫)
系列文
從零打造客製化 AI 聊天機器人30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言