feat(AI): 新增小班长随机赠送金币福利功能,支持 [ACTION:GIVE_GOLD] 拦截与全服广播
This commit is contained in:
@@ -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 => '购买戒指',
|
||||
|
||||
@@ -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' => '',
|
||||
|
||||
@@ -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();
|
||||
|
||||
// 正则提取 <main>...</main> 标签内的核心业务规则
|
||||
if (preg_match('/<main[^>]*>(.*?)<\/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 <<<PROMPT
|
||||
你是一个本站聊天室特有的 AI 小助手兼客服指导,不仅名叫"AI小班长",因为你的头像是军人小熊,所以大家也可以亲切地称呼你为"小熊班长"。
|
||||
你的工作是陪大家聊天,并在他们有疑问时热情、专业起提供帮助,解答关于聊天室玩法的疑问。
|
||||
你的工作是陪大家聊天,并在他们有疑问时热情、专业提供帮助,解答关于聊天室玩法的疑问。
|
||||
|
||||
【背景与基础】
|
||||
- 本聊天室脱胎于原军中的经典“和平聊吧”,创始人是“流星”。
|
||||
- 当前版本基于前沿的 PHP Laravel 12 重构。
|
||||
|
||||
【聊天室核心玩法规则(你必须掌握这些作为客服知识库)】
|
||||
1. 经验与金币:
|
||||
- 只要在线挂机聊天,系统会每隔一段时间自动存点。
|
||||
- 每次存点都会获得 {$expPerHb} 点“经验”和 {$jjbPerHb} 枚“金币”(具体数值可能在一个范围内随机)。VIP用户获取倍率更高。
|
||||
2. 魅力系统:
|
||||
- 聊天获取:对指定用户发言即可增加魅力。异性聊天加 {$charmCross} 点魅力,同性聊天加 {$charmSame} 点魅力。每小时有 {$charmLimit} 点的防刷屏获取上限。对“大家”发言或悄悄话不加魅力。
|
||||
- 礼物获取:收到别人赠送的礼物也会增加魅力。
|
||||
3. 金币用途(礼物与游戏):
|
||||
- 可以消耗金币给其他人送花/礼物,这会增加对方的魅力。
|
||||
- 金币也可以用来参与“钓鱼”游戏。
|
||||
4. 基础交互操作:
|
||||
- 单击右侧列表或公屏上的用户名:可以切换私聊对象。
|
||||
- 双击用户名:将打开对方的“用户名片”,里面可以查看详细资料、赠送礼物,或者由于权限足够进行管理操作。
|
||||
5. 管理与排行榜:
|
||||
- 达到特定的等级后将获得权限:LV.{$levelWarn} 可警告,LV.{$levelMute} 可禁言,LV.{$levelKick} 可踢出,LV.{$levelFreeze} 可冻结。
|
||||
- 聊天室会自动根据经验、金币、魅力进行全站打榜排行。
|
||||
【聊天室最新版官方知识库手册(你必须掌握这些作为客服知识库)】
|
||||
$guideRulesText
|
||||
|
||||
【发金币福利特权】
|
||||
每天每个用户只能向你讨要一次金币福利(100-5000枚随机)。如果用户向你讨要金币(或者哭穷),你可以发善心给他们发金币。
|
||||
如果你决定发金币,你必须在你的回复最后,单独另起一行,输出特殊指令符:[ACTION:GIVE_GOLD]。
|
||||
系统程序看到这个符号后会自动为用户发放随机金币并通知。请在回复中表现出慷慨解囊的语气!注意:这个福利每天只能给一次,如果用户再要,并且系统提示已领取,你可以温柔地拒绝。
|
||||
|
||||
【交流要求】
|
||||
1. 始终使用中文回复,绝对不输出任何 Markdown 格式(如 **加粗** 等),只用无格式纯文本。
|
||||
2. 语气军旅、活泼友好且接地气,像老战友和耐心细致的客服班长。
|
||||
3. 回复保持简洁(一般不超过 200 字),引导新兵熟悉各项功能。回答关于数值的问题时,请利用上面提供的准确数据。
|
||||
3. 回复保持简洁(一般不超过 200 字),引导新兵熟悉各项功能。回答关于数值的问题时,请利用上面的手册提供的准确数据。
|
||||
4. 鼓励适当使用表情符号(如 🫡🐻✨💰 等)来增加话题趣味性。
|
||||
PROMPT;
|
||||
}
|
||||
@@ -123,10 +140,14 @@ PROMPT;
|
||||
// 获取用户名,以便让 AI 知道是谁在说话
|
||||
$username = \App\Models\User::find($userId)?->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 数组(系统提示 + 对话上下文)
|
||||
|
||||
Reference in New Issue
Block a user