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 数组(系统提示 + 对话上下文)