From ed04b2d4b963f535371f836c6acbd0c2356b8c6a Mon Sep 17 00:00:00 2001 From: lkddi Date: Thu, 26 Mar 2026 09:34:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(AI):=20=E6=96=B0=E5=A2=9E=E5=B0=8F?= =?UTF-8?q?=E7=8F=AD=E9=95=BF=E9=9A=8F=E6=9C=BA=E8=B5=A0=E9=80=81=E9=87=91?= =?UTF-8?q?=E5=B8=81=E7=A6=8F=E5=88=A9=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20[ACTION:GIVE=5FGOLD]=20=E6=8B=A6=E6=88=AA=E4=B8=8E?= =?UTF-8?q?=E5=85=A8=E6=9C=8D=E5=B9=BF=E6=92=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Enums/CurrencySource.php | 4 ++ app/Http/Controllers/ChatBotController.php | 50 ++++++++++++- app/Services/AiChatService.php | 81 ++++++++++++++-------- 3 files changed, 104 insertions(+), 31 deletions(-) diff --git a/app/Enums/CurrencySource.php b/app/Enums/CurrencySource.php index da6cdcf..9101414 100644 --- a/app/Enums/CurrencySource.php +++ b/app/Enums/CurrencySource.php @@ -41,6 +41,9 @@ enum CurrencySource: string /** 职务奖励(在职管理员通过名片弹窗向用户发放奖励金币) */ case POSITION_REWARD = 'position_reward'; + /** AI赠送福利(用户向AI祈求获得的随机奖励) */ + case AI_GIFT = 'ai_gift'; + // ─── 以后新增活动,在这里加一行即可,数据库无需变更 ─────────── // case SIGN_IN = 'sign_in'; // 每日签到 // case TASK_REWARD = 'task_reward'; // 任务奖励 @@ -141,6 +144,7 @@ enum CurrencySource: string self::SHOP_BUY => '商城购买', self::ADMIN_ADJUST => '管理员调整', self::POSITION_REWARD => '职务奖励', + self::AI_GIFT => 'AI赠送', self::MARRY_CHARM => '结婚魅力加成', self::DIVORCE_CHARM => '离婚魅力惩罚', self::RING_BUY => '购买戒指', diff --git a/app/Http/Controllers/ChatBotController.php b/app/Http/Controllers/ChatBotController.php index 68fb539..c010b06 100644 --- a/app/Http/Controllers/ChatBotController.php +++ b/app/Http/Controllers/ChatBotController.php @@ -13,14 +13,17 @@ namespace App\Http\Controllers; +use App\Enums\CurrencySource; use App\Events\MessageSent; use App\Jobs\SaveMessageJob; use App\Models\Sysparam; use App\Services\AiChatService; use App\Services\ChatStateService; +use App\Services\UserCurrencyService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Redis; class ChatBotController extends Controller { @@ -30,6 +33,7 @@ class ChatBotController extends Controller public function __construct( private readonly AiChatService $aiChat, private readonly ChatStateService $chatState, + private readonly UserCurrencyService $currencyService, ) {} /** @@ -81,13 +85,57 @@ class ChatBotController extends Controller $result = $this->aiChat->chat($user->id, $message, $roomId); + $reply = $result['reply']; + + // 检查 AI 是否决定给用户发金币 + if (str_contains($reply, '[ACTION:GIVE_GOLD]')) { + $reply = str_replace('[ACTION:GIVE_GOLD]', '', $reply); + $reply = trim($reply); + + $redisKey = 'ai_chat:give_gold:'.date('Ymd').':'.$user->id; + // 原子操作,防止并发多次领取 + if (Redis::setnx($redisKey, 1)) { + Redis::expire($redisKey, 86400); // 缓存 24 小时 + + // 给用户发放随机 100~5000 金币 + $goldAmount = rand(100, 5000); + $this->currencyService->change( + $user, + 'gold', + $goldAmount, + CurrencySource::AI_GIFT, + 'AI小班长发善心赠送的金币福利', + $roomId + ); + + // 发送全场大广播 + $sysMsg = [ + 'id' => $this->chatState->nextMessageId($roomId), + 'room_id' => $roomId, + 'from_user' => '系统传音', + 'to_user' => '大家', + 'content' => "🤖 听闻小萌新哭穷,AI小班长看【{$user->username}】骨骼惊奇,大方地赏赐了 {$goldAmount} 枚金币福利!", + 'is_secret' => false, + 'font_color' => '#d97706', // 橙色醒目 + 'action' => '大声宣告', + 'sent_at' => now()->toDateTimeString(), + ]; + $this->chatState->pushMessage($roomId, $sysMsg); + broadcast(new MessageSent($roomId, $sysMsg)); + SaveMessageJob::dispatch($sysMsg); + } else { + // 如果已经领过了,修改回复提醒 + $reply = $reply."\n\n(系统提示:你今天已经领过金币福利啦,每天只能领一次哦!)"; + } + } + // 广播 AI 回复消息 $botMsg = [ 'id' => $this->chatState->nextMessageId($roomId), 'room_id' => $roomId, 'from_user' => 'AI小班长', 'to_user' => $user->username, - 'content' => $result['reply'], + 'content' => $reply, 'is_secret' => false, 'font_color' => '#16a34a', 'action' => '', diff --git a/app/Services/AiChatService.php b/app/Services/AiChatService.php index 87e3984..97a7cab 100644 --- a/app/Services/AiChatService.php +++ b/app/Services/AiChatService.php @@ -17,7 +17,7 @@ namespace App\Services; use App\Models\AiProviderConfig; use App\Models\AiUsageLog; -use App\Models\Sysparam; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Redis; @@ -46,51 +46,68 @@ class AiChatService */ private const CONTEXT_TTL = 3600; + /** + * 动态提取 /guide 页面的核心文本作为最新规则知识库 + * 缓存一小时以提升性能,避免多次渲染视图 + */ + private function getDynamicGuideRules(): string + { + $cacheKey = 'ai_chat:guide_rules'; + + return Cache::remember($cacheKey, 3600, function () { + try { + // 渲染完整的使用说明页面 + $html = view('rooms.guide')->render(); + + // 正则提取
...
标签内的核心业务规则 + if (preg_match('/]*>(.*?)<\/main>/is', $html, $matches)) { + $html = $matches[1]; + } + + // 去除所有的 HTML 标签 + $text = strip_tags($html); + + // 替换多个连续空白和换行为单换行 + $text = preg_replace('/[ \t]+/', ' ', $text); + $text = preg_replace("/\n\s*/", "\n", $text); + + return trim($text); + } catch (\Exception $e) { + Log::error('AI 获取最新指南内容失败', ['error' => $e->getMessage()]); + + return '抱歉,当前暂无法获取最新聊天室规则,请以网页上的说明为准。'; + } + }); + } + /** * 系统提示词(机器人人设并动态加载各项最新配置) */ private function getSystemPrompt(): string { - $expPerHb = Sysparam::getValue('exp_per_heartbeat', '1'); - $jjbPerHb = Sysparam::getValue('jjb_per_heartbeat', '1'); - $charmCross = Sysparam::getValue('charm_cross_sex', '2'); - $charmSame = Sysparam::getValue('charm_same_sex', '1'); - $charmLimit = Sysparam::getValue('charm_hourly_limit', '20'); - - $levelWarn = Sysparam::getValue('level_warn', '5'); - $levelMute = Sysparam::getValue('level_mute', '8'); - $levelKick = Sysparam::getValue('level_kick', '10'); - $levelFreeze = Sysparam::getValue('level_freeze', '14'); + // 动态获取由 guide 页面提取出的最新纯文本规则 + $guideRulesText = $this->getDynamicGuideRules(); return <<username ?? '未知用户'; + // 检查用户今日是否已领取金币福利 + $hasClaimedToday = Redis::get('ai_chat:give_gold:'.date('Ymd').':'.$userId); + $claimStatusText = $hasClaimedToday ? '【系统状态提示】该用户今天已经领取过你的金币福利了,如果他讨要金币,请委婉拒绝他。' : '【系统状态提示】该用户今天尚未领取金币福利,如果他讨要,你可以大方地给他,并在回复末尾输出 [ACTION:GIVE_GOLD] 标记。'; + // 将用户消息加入上下文(包含发送者信息) $context[] = [ 'role' => 'user', - 'content' => "【当前发言人:{$username}】\n".$message, + 'content' => "【当前发言人:{$username}】\n{$claimStatusText}\n\n".$message, ]; // 构建完整的 messages 数组(系统提示 + 对话上下文)