前回、前々回でRAGを自作し、APIサーバを立てることが出来ましたので、これを使って簡単なチャットアプリを作成します。
利用イメージはこんな感じです。
Reactの部分だけ載せておきますので、その他の部分は自由に作ってください。
チャットアプリのプログラム
私はLaravelとReactで作成しました。全体のデザイン部分でtailwind、チャットのデザイン部分でchat-ui-kit-reactを使用しています。
LaravelとReactとユーザーの認証機能を使うのであればこちらのリファレンスが参考になるかもしれません。
以下に、コードを記載します。工夫したところは、1. レスポンスをちょっとずつ表示するところと、2. RAGのAPIにオンラインかオフラインかチェックする機能をつけたので、それを利用して、RAGがオンラインかオフラインかわかるようにしたところですかね。
なお、AIChatContainer.jsx
にはその親要素からuser_id
を含むuser
変数が渡されるものと想定しています。
また、RAGからのレスポンスにはマークダウンの強調表示(**)が含まれている場合がありますが、このチャットは対応していません。一応プロンプトで指示しているのですが、含まれている場合があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# AIChatContainer.jsx import React, { useEffect, useState, } from 'react'; import styles from "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css"; import { MainContainer, ChatContainer, ConversationHeader, MessageList, Message, MessageInput, Status, } from "@chatscope/chat-ui-kit-react"; // RAGAPIのhealthチェック const healthCheck = async () => { return await fetch('http://localhost:8100/health') .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { console.log('Health check status:', data.status); if (data.status === 'ok') { console.log('The server is online.'); return true; } else { console.log('The server is offline or not healthy.'); return false; } }) .catch(error => { console.error('There was a problem with the fetch operation:', error); return false; }); } // データの取得(非同期ジェネレータ) async function* fetchData(user, message) { const completion = await fetch( `http://localhost:8100/chat`, { headers: { "Content-Type": "application/json", }, method: "POST", // APIのリクエストボディ(ユーザーID、ユーザーのメッセージ、システムのメッセージ) body: JSON.stringify({ user_id: user.user_id, user: message, system: "Please transrate your response in Japanese. And must answer only Japanese response.", }), } ); // リーダーの取得 const reader = completion.body?.getReader(); // エラー処理 if (completion.status !== 200 || !reader) { console.log(completion.status); throw new Error("Request failed"); } const decoder = new TextDecoder("utf-8"); let done = false; // トークンをひとつずつ取り出す while (!done) { const { done: readDone, value } = await reader.read(); // 最後まで読み込んだら終了 if (readDone) { done = readDone; // リーダーのロックの解除 reader.releaseLock(); } else { const token = decoder.decode(value, { stream: true }); yield token; } } } export default function AIChatContainer({ user, }) { // ロード画面 const [isLoading, setIsLoading] = useState(false); const [isHealth, setIsHealth] = useState(false); useEffect(() => { async function getHealthCheck() { // ロード画面 setIsLoading(true); // ヘルスチェック const healthResponse = await healthCheck(); setIsHealth(healthResponse); console.log(healthResponse); // ロード画面 setIsLoading(false); } getHealthCheck(); }, []); return ( <> <div className="p-3 max-xl"> <div className="w-[600px] sm:w-[1000px] mx-auto pt-3 px-1 sm:px-6"> {!isLoading && <AIChatContent user={user} errors={errors} isHealth={isHealth}/>} </div> </div> </> ); } // ヘッダーの情報 const AIInfo = ({isHealth}) => { return ( <div className='flex flex-col justify-center items-start'> <div className='flex'> <span className='font-bold'>RAGチャット</span> <Status status={isHealth ? "available": "dnd"} size="xs" name={isHealth ? "オンライン": "オフライン"} style={{fontSize:"0.85em" ,marginLeft: "0.5em"}}/> </div> <span className='inline-block text-sm'> model: gemma-2-2B-jpn </span> </div> ) } function AIChatContent({ user, isHealth }) { // メッセージの初期値 const [messages, setMessages] = useState([{ message: "こんにちは、何でも質問してください!", sentTime: "just now", sender: "Joe", direction: "incoming", position: 'single' }]); // 送信ボタン押下時の処理 const onSend = async (innerHtml, textContent, message) => { console.log("onSend", innerHtml, textContent, message); // 送信メッセージの中身 const sendMessage = { message: message, sentTime: "just now", sender: "me", direction: "outgoing", position: 'last' } // 仮の空欄返答メッセージの中身 const tentativeMessages = { message: "", sentTime: "just now", sender: "Joe", direction: "incoming", position: 'single' }; // 追加 setMessages([...messages, sendMessage, tentativeMessages]); // chatAPIの呼び出し const generator = fetchData(user, message); try { let message = ""; // トークンをひとつずつ取り出す for await (let token of generator) { // トークンをメッセージに追加 message += token; // 返答メッセージの中身 const replyMessage = { message: message, sentTime: "just now", sender: "Joe", direction: "incoming", position: 'single' }; // 追加 setMessages([...messages, sendMessage, replyMessage]); } // エラー処理 } catch (error) { console.log(error); const replyMessage = { message: "エラーが発生しました。", sentTime: "just now", sender: "Joe", direction: "incoming", position: 'single' }; setMessages([...messages, sendMessage, replyMessage]); } let replyMessage = {}; } return ( <div className="relative h-[600px]"> <MainContainer className='border-none'> <ChatContainer > <ConversationHeader> <ConversationHeader.Content info="model: gemma-2-2B-jpn" userName="RAGチャット" > <AIInfo isHealth={isHealth}/> </ConversationHeader.Content> </ConversationHeader> <MessageList> {messages.map((message, index) => ( <Message key={index} model={{ type: "text", message: message.message, sentTime: message.sentTime, sender: message.sender, direction: message.direction, position: message.position, }} /> ))} </MessageList> <MessageInput placeholder="メッセージを入力して下さい" attachButton={false} onSend={onSend} disabled={!isHealth}/> </ChatContainer> </MainContainer> </div> ) } |
まとめ
今だったらLaravelではなく、Next.jsとかの方が人気なんでしょうか?詳しくないのでわかりませんが、いずれにしても、Reactでチャットアプリを作れば、社内RAGとしての使い勝手はよりアップするのではないでしょうか。
コメント