perf: 优化机器人消息接收处理任务

This commit is contained in:
kuaifan 2025-07-29 15:13:24 +08:00
parent b914164a77
commit cdc27004bf

View File

@ -22,56 +22,85 @@ use Exception;
use DB; use DB;
/** /**
* 推送会话消息 * 机器人消息接收处理任务
* Class BotReceiveMsgTask *
* @package App\Tasks * @package App\Tasks
*/ */
class BotReceiveMsgTask extends AbstractTask class BotReceiveMsgTask extends AbstractTask
{ {
protected $userid; // 机器人ID protected $userid; // 机器人ID
protected $msgId; // 消息ID protected $msgId; // 消息ID
protected $mention; // 是否提及 protected $mention; // 是否提及(机器人被@,不含@所有人)
protected $mentionOther; // 是否提及其他人 protected $mentionOther; // 是否提及其他人
protected $client = []; // 客户端信息(版本、语言、平台) protected $client = []; // 客户端信息(版本、语言、平台)
/**
* 构造函数
* 初始化机器人消息处理任务的相关参数
*
* @param int $userid 机器人用户ID
* @param int $msgId 消息ID
* @param array $mentions 提及的用户ID数组
* @param array $client 客户端信息(版本、语言、平台等)
*/
public function __construct($userid, $msgId, $mentions, $client = []) public function __construct($userid, $msgId, $mentions, $client = [])
{ {
parent::__construct(...func_get_args()); parent::__construct(...func_get_args());
$this->userid = $userid; $this->userid = $userid;
$this->msgId = $msgId; $this->msgId = $msgId;
$this->mention = array_intersect([$userid], $mentions) ? 1 : 0; // 是否提及(不含@所有人) $this->mention = array_intersect([$userid], $mentions) ? 1 : 0;
$this->mentionOther = array_diff($mentions, [0, $userid]) ? 1 : 0; // 是否提及其他人 $this->mentionOther = array_diff($mentions, [0, $userid]) ? 1 : 0;
$this->client = is_array($client) ? $client : []; $this->client = is_array($client) ? $client : [];
} }
/**
* 任务开始执行
* 验证机器人用户和消息的有效性,然后处理机器人接收到的消息
*/
public function start() public function start()
{ {
// 判断是否是机器人用户
$botUser = User::whereUserid($this->userid)->whereBot(1)->first(); $botUser = User::whereUserid($this->userid)->whereBot(1)->first();
if (empty($botUser)) { if (empty($botUser)) {
return; return;
} }
// 判断消息是否存在
$msg = WebSocketDialogMsg::with(['user'])->find($this->msgId); $msg = WebSocketDialogMsg::with(['user'])->find($this->msgId);
if (empty($msg)) { if (empty($msg)) {
return; return;
} }
// 标记消息已读
$msg->readSuccess($botUser->userid); $msg->readSuccess($botUser->userid);
if (!$msg->user?->bot) {
$this->botReceiveBusiness($msg, $botUser); // 判断消息是否是机器人发送的则不处理,避免循环
} if (!$msg->user || $msg->user->bot) {
return;
} }
// 处理机器人消息
$this->processMessage($msg, $botUser);
}
/**
* 任务结束回调
* 当前为空实现,可在此处添加清理逻辑
*/
public function end() public function end()
{ {
} }
/** /**
* 机器人处理消息 * 处理机器人接收到的消息
* @param WebSocketDialogMsg $msg * 根据消息类型和机器人类型执行相应的处理逻辑
* @param User $botUser *
* @param WebSocketDialogMsg $msg 接收到的消息对象
* @param User $botUser 机器人用户对象
* @return void * @return void
*/ */
private function botReceiveBusiness(WebSocketDialogMsg $msg, User $botUser) private function processMessage(WebSocketDialogMsg $msg, User $botUser)
{ {
// 位置消息(仅支持签到机器人) // 位置消息(仅支持签到机器人)
if ($msg->type === 'location') { if ($msg->type === 'location') {
@ -88,16 +117,14 @@ class BotReceiveMsgTask extends AbstractTask
} }
// 提取指令 // 提取指令
try { $sendText = $this->extractMessageContent($msg);
$command = $this->extractCommand($msg, $botUser, $this->mention); $replyText = null;
if (empty($command)) { if ($msg->reply_id && $replyMsg = WebSocketDialogMsg::find($msg->reply_id)) {
return; $replyText = $this->extractMessageContent($replyMsg);
} }
} catch (Exception $e) {
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [ // 没有提取到指令,则不处理
'type' => 'content', if (empty($sendText) && empty($replyText)) {
'content' => $e->getMessage() ?: "指令解析失败。",
], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
return; return;
} }
@ -107,22 +134,22 @@ class BotReceiveMsgTask extends AbstractTask
return; return;
} }
// 如果是群聊,未提及丹提及其他人 // 如果是群聊,@别人但是没有@自己,则不处理
if ($dialog->type === 'group' && !$this->mention && $this->mentionOther) { if ($dialog->type === 'group' && $this->mentionOther && !$this->mention) {
return; return;
} }
// 推送Webhook // 推送Webhook
$this->botWebhookBusiness($command, $msg, $botUser, $dialog); $this->handleWebhookRequest($sendText, $replyText, $msg, $dialog, $botUser);
// 仅支持用户会话 // 如果不是用户对话,则只处理到这里
if ($dialog->type !== 'user') { if ($dialog->type !== 'user') {
return; return;
} }
// 签到机器人 // 签到机器人
if ($botUser->email === 'check-in@bot.system') { if ($botUser->email === 'check-in@bot.system') {
$content = UserBot::checkinBotQuickMsg($command, $msg->userid); $content = UserBot::checkinBotQuickMsg($sendText, $msg->userid);
if ($content) { if ($content) {
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [ WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [
'type' => 'content', 'type' => 'content',
@ -133,7 +160,7 @@ class BotReceiveMsgTask extends AbstractTask
// 隐私机器人 // 隐私机器人
if ($botUser->email === 'anon-msg@bot.system') { if ($botUser->email === 'anon-msg@bot.system') {
$array = UserBot::anonBotQuickMsg($command); $array = UserBot::anonBotQuickMsg($sendText);
if ($array) { if ($array) {
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [ WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [
'type' => 'content', 'type' => 'content',
@ -144,7 +171,7 @@ class BotReceiveMsgTask extends AbstractTask
} }
// 管理机器人 // 管理机器人
if (str_starts_with($command, '/')) { if (str_starts_with($sendText, '/')) {
// 判断是否是机器人管理员 // 判断是否是机器人管理员
if ($botUser->email === 'bot-manager@bot.system') { if ($botUser->email === 'bot-manager@bot.system') {
$isManager = true; $isManager = true;
@ -159,7 +186,7 @@ class BotReceiveMsgTask extends AbstractTask
} }
// 指令处理 // 指令处理
$array = Base::newTrim(explode(" ", "{$command} ")); $array = Base::newTrim(explode(" ", "{$sendText} "));
$type = $array[0]; $type = $array[0];
$data = []; $data = [];
$content = ""; $content = "";
@ -195,7 +222,7 @@ class BotReceiveMsgTask extends AbstractTask
case '/hello': case '/hello':
case '/info': case '/info':
$botId = $isManager ? $array[1] : $botUser->userid; $botId = $isManager ? $array[1] : $botUser->userid;
$data = $this->botOne($botId, $msg->userid); $data = $this->getBotInfo($botId, $msg->userid);
if (!$data) { if (!$data) {
$content = "机器人不存在。"; $content = "机器人不存在。";
} }
@ -223,7 +250,7 @@ class BotReceiveMsgTask extends AbstractTask
$content = "机器人名称由2-20个字符组成。"; $content = "机器人名称由2-20个字符组成。";
break; break;
} }
$data = $this->botOne($botId, $msg->userid); $data = $this->getBotInfo($botId, $msg->userid);
if ($data) { if ($data) {
$data->nickname = $nameString; $data->nickname = $nameString;
$data->az = Base::getFirstCharter($nameString); $data->az = Base::getFirstCharter($nameString);
@ -240,7 +267,7 @@ class BotReceiveMsgTask extends AbstractTask
*/ */
case '/deletebot': case '/deletebot':
$botId = $isManager ? $array[1] : $botUser->userid; $botId = $isManager ? $array[1] : $botUser->userid;
$data = $this->botOne($botId, $msg->userid); $data = $this->getBotInfo($botId, $msg->userid);
if ($data) { if ($data) {
$data->deleteUser('delete bot'); $data->deleteUser('delete bot');
} else { } else {
@ -253,7 +280,7 @@ class BotReceiveMsgTask extends AbstractTask
*/ */
case '/token': case '/token':
$botId = $isManager ? $array[1] : $botUser->userid; $botId = $isManager ? $array[1] : $botUser->userid;
$data = $this->botOne($botId, $msg->userid); $data = $this->getBotInfo($botId, $msg->userid);
if ($data) { if ($data) {
User::generateToken($data); User::generateToken($data);
} else { } else {
@ -266,7 +293,7 @@ class BotReceiveMsgTask extends AbstractTask
*/ */
case '/revoke': case '/revoke':
$botId = $isManager ? $array[1] : $botUser->userid; $botId = $isManager ? $array[1] : $botUser->userid;
$data = $this->botOne($botId, $msg->userid); $data = $this->getBotInfo($botId, $msg->userid);
if ($data) { if ($data) {
$data->encrypt = Base::generatePassword(6); $data->encrypt = Base::generatePassword(6);
$data->password = Doo::md5s(Base::generatePassword(32), $data->encrypt); $data->password = Doo::md5s(Base::generatePassword(32), $data->encrypt);
@ -282,7 +309,7 @@ class BotReceiveMsgTask extends AbstractTask
case '/clearday': case '/clearday':
$botId = $isManager ? $array[1] : $botUser->userid; $botId = $isManager ? $array[1] : $botUser->userid;
$clearDay = $isManager ? $array[2] : $array[1]; $clearDay = $isManager ? $array[2] : $array[1];
$data = $this->botOne($botId, $msg->userid); $data = $this->getBotInfo($botId, $msg->userid);
if ($data) { if ($data) {
$userBot = UserBot::whereBotId($botId)->whereUserid($msg->userid)->first(); $userBot = UserBot::whereBotId($botId)->whereUserid($msg->userid)->first();
if ($userBot) { if ($userBot) {
@ -303,7 +330,7 @@ class BotReceiveMsgTask extends AbstractTask
case '/webhook': case '/webhook':
$botId = $isManager ? $array[1] : $botUser->userid; $botId = $isManager ? $array[1] : $botUser->userid;
$webhookUrl = $isManager ? $array[2] : $array[1]; $webhookUrl = $isManager ? $array[2] : $array[1];
$data = $this->botOne($botId, $msg->userid); $data = $this->getBotInfo($botId, $msg->userid);
if (strlen($webhookUrl) > 255) { if (strlen($webhookUrl) > 255) {
$content = "webhook地址最长仅支持255个字符。"; $content = "webhook地址最长仅支持255个字符。";
} elseif ($data) { } elseif ($data) {
@ -326,7 +353,7 @@ class BotReceiveMsgTask extends AbstractTask
case '/dialog': case '/dialog':
$botId = $isManager ? $array[1] : $botUser->userid; $botId = $isManager ? $array[1] : $botUser->userid;
$nameKey = $isManager ? $array[2] : $array[1]; $nameKey = $isManager ? $array[2] : $array[1];
$data = $this->botOne($botId, $msg->userid); $data = $this->getBotInfo($botId, $msg->userid);
if ($data) { if ($data) {
$list = DB::table('web_socket_dialog_users as u') $list = DB::table('web_socket_dialog_users as u')
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at']) ->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
@ -338,7 +365,7 @@ class BotReceiveMsgTask extends AbstractTask
->orderByDesc('u.last_at') ->orderByDesc('u.last_at')
->take(20) ->take(20)
->get() ->get()
->map(function($item) use ($data) { ->map(function ($item) use ($data) {
return WebSocketDialog::synthesizeData($item, $data->userid); return WebSocketDialog::synthesizeData($item, $data->userid);
}) })
->all(); ->all();
@ -392,26 +419,35 @@ class BotReceiveMsgTask extends AbstractTask
} }
/** /**
* 机器人处理 Webhook * 处理机器人Webhook请求
* @param string $command * 根据机器人类型AI机器人或用户机器人发送相应的Webhook请求
* @param WebSocketDialogMsg $msg *
* @param User $botUser * @param string $sendText 发送的文本内容
* @param WebSocketDialog $dialog * @param string $replyText 回复的文本内容
* @param WebSocketDialogMsg $msg 消息对象
* @param WebSocketDialog $dialog 对话对象
* @param User $botUser 机器人用户对象
* @return void * @return void
*/ */
private function botWebhookBusiness(string $command, WebSocketDialogMsg $msg, User $botUser, WebSocketDialog $dialog) private function handleWebhookRequest($sendText, $replyText, WebSocketDialogMsg $msg, WebSocketDialog $dialog, User $botUser)
{ {
$serverUrl = 'http://nginx'; $webhookUrl = null;
$userBot = null;
$extras = ['timestamp' => time()]; $extras = ['timestamp' => time()];
$replyText = null;
$errorContent = null; try {
if ($botUser->isAiBot($type)) { if ($botUser->isAiBot($type)) {
// AI机器人 // AI机器人
if (Base::val($msg->msg, 'forward_data.leave')) { if (Base::val($msg->msg, 'forward_data.leave')) {
// AI机器人不处理带有留言的转发消息因为他要处理那条留言消息 // AI机器人不处理带有留言的转发消息因为他要处理那条留言消息
return; return;
} }
if (in_array($this->client['platform'], ['win', 'mac', 'web']) && !Base::judgeClientVersion("0.41.11", $this->client['version'])) {
throw new Exception('当前客户端版本低所需版本≥v0.41.11)。');
}
if (!Apps::isInstalled('ai')) {
throw new Exception('应用「AI Robot」未安装');
}
// 整理机器人参数
$setting = Base::setting('aibotSetting'); $setting = Base::setting('aibotSetting');
$extras = [ $extras = [
'model_type' => match ($type) { 'model_type' => match ($type) {
@ -423,7 +459,7 @@ class BotReceiveMsgTask extends AbstractTask
'api_key' => $setting[$type . '_key'], 'api_key' => $setting[$type . '_key'],
'base_url' => $setting[$type . '_base_url'], 'base_url' => $setting[$type . '_base_url'],
'agency' => $setting[$type . '_agency'], 'agency' => $setting[$type . '_agency'],
'server_url' => $serverUrl, 'server_url' => 'http://nginx',
]; ];
if ($setting[$type . '_temperature']) { if ($setting[$type . '_temperature']) {
$extras['temperature'] = floatval($setting[$type . '_temperature']); $extras['temperature'] = floatval($setting[$type . '_temperature']);
@ -458,69 +494,58 @@ class BotReceiveMsgTask extends AbstractTask
} }
if ($type === 'ollama') { if ($type === 'ollama') {
if (empty($extras['base_url'])) { if (empty($extras['base_url'])) {
$errorContent = '机器人未启用。'; throw new Exception('机器人未启用。');
} }
if (empty($extras['api_key'])) { if (empty($extras['api_key'])) {
$extras['api_key'] = Base::strRandom(6); $extras['api_key'] = Base::strRandom(6);
} }
} }
if (empty($extras['api_key'])) { if (empty($extras['api_key'])) {
$errorContent = '机器人未启用。'; throw new Exception('机器人未启用。');
} }
if (in_array($this->client['platform'], ['win', 'mac', 'web']) && !Base::judgeClientVersion("0.41.11", $this->client['version'])) { $this->generateSystemPromptForAI($msg->userid, $dialog, $extras);
$errorContent = '当前客户端版本低所需版本≥v0.41.11)。'; // 转换提及格式
} $sendText = $this->convertMentionForAI($sendText);
if (!Apps::isInstalled('ai')) { $replyText = $this->convertMentionForAI($replyText);
$errorContent = '应用「AI Robot」未安装'; if ($replyText) {
} $sendText = <<<EOF
if ($msg->reply_id > 0) {
$replyCommand = $this->extractReplyCommand($msg->reply_id, $botUser);
if (Base::isError($replyCommand)) {
$errorContent = $replyCommand['msg'];
} else {
$command = <<<EOF
<quoted_content> <quoted_content>
{$replyCommand['data']} {$replyText}
</quoted_content> </quoted_content>
The content within the above quoted_content tags is a citation. The content within the above quoted_content tags is a citation.
{$command} {$sendText}
EOF; EOF;
} }
} $webhookUrl = "http://nginx/ai/chat";
$this->AIGenerateSystemMessage($msg->userid, $dialog, $extras);
$webhookUrl = "{$serverUrl}/ai/chat";
} else { } else {
// 用户机器人 // 用户机器人
if ($botUser->isUserBot() && str_starts_with($command, '/')) { if ($botUser->isUserBot() && str_starts_with($sendText, '/')) {
// 用户机器人不处理指令类型命令 // 用户机器人不处理指令类型命令
return; return;
} }
if ($msg->reply_id > 0) {
$replyCommand = $this->extractReplyCommand($msg->reply_id, $botUser);
if (Base::isSuccess($replyCommand)) {
$replyText = $replyCommand['data'] ?: '';
}
}
$userBot = UserBot::whereBotId($botUser->userid)->first(); $userBot = UserBot::whereBotId($botUser->userid)->first();
$webhookUrl = $userBot?->webhook_url; if ($userBot) {
$userBot->webhook_num++;
$userBot->save();
$webhookUrl = $userBot->webhook_url;
} }
if ($errorContent) {
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [
'type' => 'content',
'content' => $errorContent,
], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
return;
} }
if (!preg_match("/^https?:\/\//", $webhookUrl)) { if (!preg_match("/^https?:\/\//", $webhookUrl)) {
return; return;
} }
} catch (\Exception $e) {
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [
'type' => 'content',
'content' => $e->getMessage(),
], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
return;
}
// //
try { try {
$data = [ $data = [
'text' => $command, 'text' => $sendText,
'reply_text' => $replyText, 'reply_text' => $replyText,
'token' => User::generateToken($botUser), 'token' => User::generateToken($botUser),
'session_id' => $dialog->session_id, 'session_id' => $dialog->session_id,
@ -535,6 +560,7 @@ class BotReceiveMsgTask extends AbstractTask
]; ];
// 添加用户信息 // 添加用户信息
$userInfo = User::find($msg->userid); $userInfo = User::find($msg->userid);
if ($userInfo) {
$data['msg_user'] = [ $data['msg_user'] = [
'userid' => $userInfo->userid, 'userid' => $userInfo->userid,
'email' => $userInfo->email, 'email' => $userInfo->email,
@ -543,14 +569,14 @@ class BotReceiveMsgTask extends AbstractTask
'lang' => $userInfo->lang, 'lang' => $userInfo->lang,
'token' => User::generateTokenNoDevice($userInfo, now()->addHour()), 'token' => User::generateTokenNoDevice($userInfo, now()->addHour()),
]; ];
$res = Ihttp::ihttp_post($webhookUrl, $data, 30);
if ($userBot) {
$userBot->webhook_num++;
$userBot->save();
} }
if ($res['data'] && $data = Base::json2array($res['data'])) { // 请求Webhook
$result = Ihttp::ihttp_post($webhookUrl, $data, 30);
if ($result['data'] && $data = Base::json2array($result['data'])) {
if ($data['code'] != 200 && $data['message']) { if ($data['code'] != 200 && $data['message']) {
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $res['data']['message']], $botUser->userid, false, false, true); WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', [
'text' => $result['data']['message']
], $botUser->userid, false, false, true);
} }
} }
} catch (\Throwable $th) { } catch (\Throwable $th) {
@ -565,85 +591,124 @@ class BotReceiveMsgTask extends AbstractTask
} }
/** /**
* 获取机器人信息 * 提取消息内容
* @param $botId * 根据消息类型(文件、文本等)提取相应的内容文本
* @param $userid *
* @return User * @param WebSocketDialogMsg $msg 消息对象
* @return string 提取出的消息文本内容
*/ */
private function botOne($botId, $userid) private function extractMessageContent(WebSocketDialogMsg $msg)
{ {
$botId = intval($botId); switch ($msg->type) {
$userid = intval($userid); case "file":
if ($botId > 0) { // 提取文件消息
return User::select([ $msgData = Base::json2array($msg->getRawOriginal('msg'));
'users.*', return $this->convertMentionFormat("path", $msgData['path'], $msgData['name']);
'user_bots.clear_day',
'user_bots.clear_at',
'user_bots.webhook_url',
'user_bots.webhook_num'
])
->join('user_bots', 'users.userid', '=', 'user_bots.bot_id')
->where('users.bot', 1)
->where('user_bots.bot_id', $botId)
->where('user_bots.userid', $userid)
->first();
}
return null;
}
/** case "text":
* 提取消息指令(提取消息内容) // 提取文本消息
* @param WebSocketDialogMsg $msg $original = $msg->msg['text'] ?: '';
* @param User $botUser if (empty($original)) {
* @param bool $mention
* @return string
* @throws Exception
*/
private function extractCommand(WebSocketDialogMsg $msg, User $botUser, bool $mention = false)
{
if ($msg->type !== 'text') {
return ''; return '';
} }
$original = $msg->msg['text'] ?: ''; // 提取快捷键
if ($mention) {
$original = preg_replace("/<span class=\"mention user\" data-id=\"(\d+)\">(.*?)<\/span>/", "", $original);
}
if (preg_match("/<span[^>]*?data-quick-key=([\"'])([^\"']+?)\\1[^>]*?>(.*?)<\/span>/is", $original, $match)) { if (preg_match("/<span[^>]*?data-quick-key=([\"'])([^\"']+?)\\1[^>]*?>(.*?)<\/span>/is", $original, $match)) {
$command = $match[2]; $command = $match[2] ?? '';
if (str_starts_with($command, '%3A.')) { $command = preg_replace("/^%3A\.?/", ":", $command);
$command = ":" . substr($command, 4); $command = trim($command);
} if ($command) {
return $command; return $command;
} }
}
// 提及任务、文件、报告
$original = preg_replace_callback_array([
// 用户
"/<span class=\"mention user\" data-id=\"(\d+)\">(.*?)<\/span>/" => function () {
return "";
},
if ($botUser->isAiBot()) {
// AI 机器人
$contents = [];
if (preg_match_all("/<span class=\"mention task\" data-id=\"(\d+)\">(.*?)<\/span>/", $original, $match)) {
// 任务 // 任务
$taskIds = Base::newIntval($match[1]); "/<span class=\"mention task\" data-id=\"(\d+)\">(.*?)<\/span>/" => function ($match) {
foreach ($taskIds as $index => $taskId) { return $this->convertMentionFormat("task", $match[1], $match[2]);
$taskInfo = ProjectTask::with(['content'])->whereId($taskId)->first(); },
if (!$taskInfo) {
throw new Exception("任务不存在或已被删除"); // 文件
"/<a class=\"mention file\" href=\"([^\"']+?)\"[^>]*?>(.*?)<\/a>/" => function ($match) {
if (preg_match("/single\/file\/(.*?)$/", $match[1], $subMatch)) {
return $this->convertMentionFormat("file", $subMatch[1], $match[2]);
} }
$taskName = addslashes($taskInfo->name) . " (ID:{$taskId})"; return "";
$taskContext = implode("\n", $taskInfo->AIContext()); },
$contents[] = "<task_content path=\"{$taskName}\">\n{$taskContext}\n</task_content>";
$original = str_replace($match[0][$index], "'{$taskName}' (see below for task_content tag)", $original); // 报告
"/<a class=\"mention report\" href=\"([^\"']+?)\"[^>]*?>(.*?)<\/a>/" => function ($match) {
if (preg_match("/single\/report\/detail\/(.*?)$/", $match[1], $subMatch)) {
return $this->convertMentionFormat("report", $subMatch[1], $match[2]);
}
return "";
},
], $original);
// 转成 markdown 并返回
return Base::html2markdown($original);
default:
// 其他类型消息不处理
return '';
} }
} }
if (preg_match_all("/<a class=\"mention ([^'\"]*)\" href=\"([^\"']+?)\"[^>]*?>[~%]([^>]*)<\/a>/", $original, $match)) {
// 文件、报告 /**
$urlPaths = $match[2]; * 转换提及消息格式
foreach ($urlPaths as $index => $urlPath) { * 将提及的任务、文件、报告等转换为统一的格式 [type#key#name]
*
* @param string $type 提及类型task、file、report、path
* @param string $key 提及对象的唯一标识
* @param string $name 提及对象的显示名称
* @return string 格式化后的提及字符串
*/
private function convertMentionFormat($type, $key, $name)
{
$key = preg_replace('/[#\[\]]/', '', $key);
$name = preg_replace('/[#\[\]]/', '', $name);
return "[{$type}#{$key}#{$name}]";
}
/**
* 为AI机器人转换提及消息格式
* 将提及的任务、文件、报告转换为AI可理解的格式并提取相关内容
*
* @param string $original 原始消息文本
* @return string 转换后的消息文本,包含相关内容的标签
* @throws Exception 当提及的对象不存在或读取失败时抛出异常
*/
private function convertMentionForAI($original)
{
$array = [];
$original = preg_replace_callback('/\[([^#\[\]]+)#([^#\[\]]+)#([^#\[\]]*)\]/', function ($match) use (&$array) {
// 初始化 tag 内容
$pathTag = null; $pathTag = null;
$pathName = null; $pathName = null;
$pathContent = null; $pathContent = null;
// 根据 type 提取 tag 内容
switch ($match[1]) {
// 任务
case 'task':
$taskInfo = ProjectTask::with(['content'])->whereId(intval($match[2]))->first();
if (!$taskInfo) {
throw new Exception("任务不存在或已被删除");
}
$pathTag = "task_content";
$pathName = addslashes($taskInfo->name) . " (ID:{$taskInfo->id})";
$pathContent = implode("\n", $taskInfo->AIContext());
break;
// 文件 // 文件
if (preg_match("/single\/file\/(.*?)$/", $urlPath, $fileMatch)) { case 'file':
$fileInfo = FileContent::idOrCodeToContent($fileMatch[1]); $fileInfo = FileContent::idOrCodeToContent($match[2]);
if (!$fileInfo || !isset($fileInfo->content['url'])) { if (!$fileInfo || !isset($fileInfo->content['url'])) {
throw new Exception("文件不存在或已被删除"); throw new Exception("文件不存在或已被删除");
} }
@ -656,86 +721,68 @@ class BotReceiveMsgTask extends AbstractTask
throw new Exception("文件读取失败:" . $fileResult['msg']); throw new Exception("文件读取失败:" . $fileResult['msg']);
} }
$pathTag = "file_content"; $pathTag = "file_content";
$pathName = addslashes($match[3][$index]) . " (ID:{$fileInfo->id})"; $pathName = addslashes($match[3]) . " (ID:{$fileInfo->id})";
$pathContent = $fileResult['data']; $pathContent = $fileResult['data'];
break;
// 文件路径
case 'path':
$urlPath = public_path($match[2]);
if (!file_exists($urlPath)) {
throw new Exception("文件不存在或已被删除");
} }
$fileResult = TextExtractor::extractFile($urlPath);
if (Base::isError($fileResult)) {
throw new Exception("文件读取失败:" . $fileResult['msg']);
}
$pathTag = "file_content";
$pathName = addslashes($match[3]);
$pathContent = $fileResult['data'];
break;
// 报告 // 报告
elseif (preg_match("/single\/report\/detail\/(.*?)$/", $urlPath, $reportMatch)) { case 'report':
$reportInfo = Report::idOrCodeToContent($reportMatch[1]); $reportInfo = Report::idOrCodeToContent($match[2]);
if (!$reportInfo) { if (!$reportInfo) {
throw new Exception("报告不存在或已被删除"); throw new Exception("报告不存在或已被删除");
} }
$pathTag = "report_content"; $pathTag = "report_content";
$pathName = addslashes($match[3][$index]) . " (ID:{$reportInfo->id})"; $pathName = addslashes($match[3]) . " (ID:{$reportInfo->id})";
$pathContent = $reportInfo->content; $pathContent = $reportInfo->content;
break;
} }
// 如果提取到 tag 内容,则添加到 contents 数组中
if ($pathTag) { if ($pathTag) {
$contents[] = "<{$pathTag} path=\"{$pathName}\">\n{$pathContent}\n</{$pathTag}>"; $array[] = "<{$pathTag} path=\"{$pathName}\">\n{$pathContent}\n</{$pathTag}>";
$original = str_replace($match[0][$index], "'{$pathName}' (see below for {$pathTag} tag)", $original); return "`{$pathName}` (see below for {$pathTag} tag)";
} }
return "";
}, $original);
// 添加 tag 内容
if ($array) {
$original .= "\n\n" . implode("\n\n", $array);
} }
}
$original = Base::html2markdown($original);
if ($contents) {
// 添加tag内容
$original .= "\n\n" . implode("\n\n", $contents);
}
return $original; return $original;
} elseif ($botUser->isUserBot()) {
// 用户机器人
return Base::html2markdown($original);
} else {
// 其他机器人(系统)
return trim(strip_tags($original));
}
} }
/** /**
* 提取回复消息指令 * 为AI机器人生成系统提示词
* @param $id * 根据对话类型(用户对话、项目群、任务群、部门群等)生成相应的系统提示词
* @param User $botUser *
* @return array * @param int|null $userid 用户ID
*/ * @param WebSocketDialog $dialog 对话对象
private function extractReplyCommand($id, User $botUser) * @param array $extras 额外参数数组通过引用传递以修改system_message
{
$replyMsg = WebSocketDialogMsg::find($id);
$replyCommand = null;
if ($replyMsg) {
switch ($replyMsg->type) {
case 'text':
try {
$replyCommand = $this->extractCommand($replyMsg, $botUser);
} catch (Exception) {
return Base::retError('error', "引用消息解析失败。");
}
break;
case 'file':
if ($botUser->isAiBot()) {
$msgData = Base::json2array($replyMsg->getRawOriginal('msg'));
$fileResult = TextExtractor::extractFile(public_path($msgData['path']));
if (Base::isError($fileResult)) {
return Base::retError('error', $fileResult['msg']);
} else {
$replyCommand = $fileResult['data'];
}
}
break;
}
}
return Base::retSuccess('success', $replyCommand);
}
/**
* 生成AI系统提示词
* @param int|null $userid
* @param WebSocketDialog $dialog
* @param array $extras
* @return void * @return void
*/ */
private function AIGenerateSystemMessage(int|null $userid, WebSocketDialog $dialog, array &$extras) private function generateSystemPromptForAI($userid, WebSocketDialog $dialog, array &$extras)
{ {
$system_messages = []; $system_messages = [];
switch ($dialog->type) { switch ($dialog->type) {
// 用户对话
case "user": case "user":
$aiPrompt = WebSocketDialogConfig::where([ $aiPrompt = WebSocketDialogConfig::where([
'dialog_id' => $dialog->id, 'dialog_id' => $dialog->id,
@ -746,10 +793,13 @@ class BotReceiveMsgTask extends AbstractTask
$extras['system_message'] = $aiPrompt; $extras['system_message'] = $aiPrompt;
} }
break; break;
// 群组对话
case "group": case "group":
switch ($dialog->group_type) { switch ($dialog->group_type) {
// 用户群
case 'user': case 'user':
break; break;
// 项目群
case 'project': case 'project':
$projectInfo = Project::whereDialogId($dialog->id)->first(); $projectInfo = Project::whereDialogId($dialog->id)->first();
if ($projectInfo) { if ($projectInfo) {
@ -772,6 +822,7 @@ class BotReceiveMsgTask extends AbstractTask
EOF; EOF;
} }
break; break;
// 任务群
case 'task': case 'task':
$taskInfo = ProjectTask::with(['content'])->whereDialogId($dialog->id)->first(); $taskInfo = ProjectTask::with(['content'])->whereDialogId($dialog->id)->first();
if ($taskInfo) { if ($taskInfo) {
@ -791,12 +842,14 @@ class BotReceiveMsgTask extends AbstractTask
EOF; EOF;
} }
break; break;
// 部门群
case 'department': case 'department':
$userDepartment = UserDepartment::whereDialogId($dialog->id)->first(); $userDepartment = UserDepartment::whereDialogId($dialog->id)->first();
if ($userDepartment) { if ($userDepartment) {
$system_messages[] = "当前我在【{$userDepartment->name}】的部门群聊中"; $system_messages[] = "当前我在【{$userDepartment->name}】的部门群聊中";
} }
break; break;
// 全体成员群
case 'all': case 'all':
$system_messages[] = "当前我在【全体成员】的群聊中"; $system_messages[] = "当前我在【全体成员】的群聊中";
break; break;
@ -807,7 +860,36 @@ class BotReceiveMsgTask extends AbstractTask
array_unshift($system_messages, $extras['system_message']); array_unshift($system_messages, $extras['system_message']);
} }
if ($system_messages) { if ($system_messages) {
$extras['system_message'] = implode("\n\n====\n\n", Base::newTrim($system_messages)); $extras['system_message'] = implode("\n\n----------------\n\n", Base::newTrim($system_messages));
} }
} }
/**
* 获取机器人信息
* 根据机器人ID和用户ID获取机器人的详细信息包括清理设置和webhook配置
*
* @param int $botId 机器人ID
* @param int $userid 用户ID
* @return User|null 机器人用户对象如果不存在则返回null
*/
private function getBotInfo($botId, $userid)
{
$botId = intval($botId);
$userid = intval($userid);
if ($botId > 0) {
return User::select([
'users.*',
'user_bots.clear_day',
'user_bots.clear_at',
'user_bots.webhook_url',
'user_bots.webhook_num'
])
->join('user_bots', 'users.userid', '=', 'user_bots.bot_id')
->where('users.bot', 1)
->where('user_bots.bot_id', $botId)
->where('user_bots.userid', $userid)
->first();
}
return null;
}
} }