From f5d8a593c960881944a754bfefbf62be704fea3c Mon Sep 17 00:00:00 2001 From: lkddi Date: Thu, 26 Feb 2026 22:11:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9AAI=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=85=AC=E5=BC=80=E5=B9=BF=E6=92=AD=EF=BC=8C?= =?UTF-8?q?=E6=89=80=E6=9C=89=E4=BA=BA=E5=8F=AF=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatBotController 将用户提问和AI回复都广播到聊天室 - 前端不再本地渲染AI消息,由WebSocket广播统一处理 - AI小助手加入系统用户列表(公告样式展示) - 思考中提示延迟500ms显示在包厢窗口,避免排序混乱 --- app/Http/Controllers/ChatBotController.php | 38 ++++++++++++- .../views/chat/partials/scripts.blade.php | 57 ++++--------------- 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/app/Http/Controllers/ChatBotController.php b/app/Http/Controllers/ChatBotController.php index 7e66fcd..0ed03b6 100644 --- a/app/Http/Controllers/ChatBotController.php +++ b/app/Http/Controllers/ChatBotController.php @@ -13,8 +13,11 @@ namespace App\Http\Controllers; +use App\Events\MessageSent; +use App\Jobs\SaveMessageJob; use App\Models\Sysparam; use App\Services\AiChatService; +use App\Services\ChatStateService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -22,10 +25,11 @@ use Illuminate\Support\Facades\Auth; class ChatBotController extends Controller { /** - * 构造函数:注入 AI 聊天服务 + * 构造函数:注入 AI 聊天服务和聊天状态服务 */ public function __construct( private readonly AiChatService $aiChat, + private readonly ChatStateService $chatState, ) {} /** @@ -59,8 +63,40 @@ class ChatBotController extends Controller $roomId = $request->input('room_id'); try { + // 先广播用户的提问消息 + $userMsg = [ + 'id' => $this->chatState->nextMessageId($roomId), + 'room_id' => $roomId, + 'from_user' => $user->username, + 'to_user' => 'AI小助手', + 'content' => $message, + 'is_secret' => false, + 'font_color' => '#000000', + 'action' => '', + 'sent_at' => now()->toDateTimeString(), + ]; + $this->chatState->pushMessage($roomId, $userMsg); + broadcast(new MessageSent($roomId, $userMsg)); + SaveMessageJob::dispatch($userMsg); + $result = $this->aiChat->chat($user->id, $message, $roomId); + // 广播 AI 回复消息 + $botMsg = [ + 'id' => $this->chatState->nextMessageId($roomId), + 'room_id' => $roomId, + 'from_user' => 'AI小助手', + 'to_user' => $user->username, + 'content' => $result['reply'], + 'is_secret' => false, + 'font_color' => '#16a34a', + 'action' => '', + 'sent_at' => now()->toDateTimeString(), + ]; + $this->chatState->pushMessage($roomId, $botMsg); + broadcast(new MessageSent($roomId, $botMsg)); + SaveMessageJob::dispatch($botMsg); + return response()->json([ 'status' => 'success', 'reply' => $result['reply'], diff --git a/resources/views/chat/partials/scripts.blade.php b/resources/views/chat/partials/scripts.blade.php index 0b89c6d..799ab98 100644 --- a/resources/views/chat/partials/scripts.blade.php +++ b/resources/views/chat/partials/scripts.blade.php @@ -264,7 +264,7 @@ const timeStr = msg.sent_at || ''; // 系统用户名列表(不可被选为聊天对象) - const systemUsers = ['钓鱼播报', '星海小博士', '系统传音', '系统公告']; + const systemUsers = ['钓鱼播报', '星海小博士', '系统传音', '系统公告', 'AI小助手']; // 用户名(单击切换发言对象,双击查看资料;系统用户仅显示文本) const clickableUser = (uName, color) => { if (systemUsers.includes(uName)) { @@ -1011,26 +1011,15 @@ } chatBotSending = true; - const now = new Date(); - const timeStr = now.getHours().toString().padStart(2, '0') + ':' + - now.getMinutes().toString().padStart(2, '0') + ':' + - now.getSeconds().toString().padStart(2, '0'); - - // 显示用户发送的消息 - const userDiv = document.createElement('div'); - userDiv.className = 'msg-line'; - userDiv.innerHTML = `${window.chatContext.username}` + - `对🤖AI小助手说:` + - `${escapeHtml(content)}` + - ` (${timeStr})`; - container2.appendChild(userDiv); - // 显示"思考中"提示 + // 延迟显示"思考中",让广播消息先到达 const thinkDiv = document.createElement('div'); thinkDiv.className = 'msg-line'; thinkDiv.innerHTML = '🤖 AI小助手 正在思考中...'; - container2.appendChild(thinkDiv); - if (autoScroll) container2.scrollTop = container2.scrollHeight; + setTimeout(() => { + container2.appendChild(thinkDiv); + if (autoScroll) container2.scrollTop = container2.scrollHeight; + }, 500); try { const res = await fetch(window.chatContext.chatBotUrl, { @@ -1049,47 +1038,25 @@ const data = await res.json(); - // 移除"思考中"提示 + // 移除"思考中"提示(消息已通过广播显示) thinkDiv.remove(); - const replyTime = new Date(); - const replyTimeStr = replyTime.getHours().toString().padStart(2, '0') + ':' + - replyTime.getMinutes().toString().padStart(2, '0') + ':' + - replyTime.getSeconds().toString().padStart(2, '0'); - - if (res.ok && data.status === 'success') { - // 显示机器人回复 - const botDiv = document.createElement('div'); - botDiv.className = 'msg-line'; - botDiv.style.background = '#f0fdf4'; - botDiv.style.borderLeft = '3px solid #22c55e'; - botDiv.style.padding = '4px 8px'; - botDiv.style.margin = '2px 0'; - botDiv.style.borderRadius = '4px'; - botDiv.innerHTML = `🤖 AI小助手` + - `对${window.chatContext.username}说:` + - `${escapeHtml(data.reply)}` + - ` (${replyTimeStr})` + - ` [${data.provider}]`; - container2.appendChild(botDiv); - } else { - // 显示错误信息 + if (!res.ok || data.status !== 'success') { const errDiv = document.createElement('div'); errDiv.className = 'msg-line'; - errDiv.innerHTML = `🤖【AI小助手】${data.message || '回复失败,请稍后重试'}` + - ` (${replyTimeStr})`; - container2.appendChild(errDiv); + errDiv.innerHTML = `🤖【AI小助手】${data.message || '回复失败,请稍后重试'}`; + container.appendChild(errDiv); } } catch (e) { thinkDiv.remove(); const errDiv = document.createElement('div'); errDiv.className = 'msg-line'; errDiv.innerHTML = '🤖【AI小助手】网络连接错误,请稍后重试'; - container2.appendChild(errDiv); + container.appendChild(errDiv); } chatBotSending = false; - if (autoScroll) container2.scrollTop = container2.scrollHeight; + scrollToBottom(); } /**