From a49c0aea4714f24143ec696ac1665ccc8d9ad7ca Mon Sep 17 00:00:00 2001 From: kuaifan Date: Mon, 24 Mar 2025 15:25:02 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E4=BA=BAWebhook=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/UsersController.php | 5 +- app/Models/User.php | 12 + app/Models/UserBot.php | 10 - app/Module/Base.php | 21 +- app/Tasks/BotReceiveMsgTask.php | 218 ++++++++++-------- .../DialogView/template/bot-api.vue | 1 + 6 files changed, 154 insertions(+), 113 deletions(-) diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index ea0a8e20f..da12aac34 100755 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -629,12 +629,11 @@ class UsersController extends AbstractController User::auth(); // $type = trim(Request::input('type')); - $botName = "ai-{$type}"; - if (!UserBot::isAiBot("{$botName}@bot.system")) { + if (!UserBot::systemBotName($type)) { return Base::retError('AI机器人不存在'); } // - $botUser = User::botGetOrCreate($botName); + $botUser = User::botGetOrCreate("ai-{$type}"); if (empty($botUser)) { return Base::retError('AI机器人不存在'); } diff --git a/app/Models/User.php b/app/Models/User.php index 8ed329d50..bf21664e5 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -255,6 +255,18 @@ class User extends AbstractModel return false; } + /** + * 返回是否用户机器人 + * @return bool + */ + public function isUserBot() + { + if (preg_match('/^user-(.*?)@bot\.system$/', $this->email)) { + return true; + } + return false; + } + /** * 判断是否管理员 */ diff --git a/app/Models/UserBot.php b/app/Models/UserBot.php index 6c50c54d2..4d07fd5e5 100644 --- a/app/Models/UserBot.php +++ b/app/Models/UserBot.php @@ -55,16 +55,6 @@ class UserBot extends AbstractModel return str_ends_with($email, '@bot.system') && self::systemBotName($email); } - /** - * 判断是否系统AI机器人 - * @param $email - * @return bool - */ - public static function isAiBot($email) - { - return str_starts_with($email, 'ai-') && self::isSystemBot($email); - } - /** * 系统机器人名称 * @param $name string 邮箱 或 邮箱前缀 diff --git a/app/Module/Base.php b/app/Module/Base.php index dee106d7f..9e73125b0 100755 --- a/app/Module/Base.php +++ b/app/Module/Base.php @@ -9,7 +9,7 @@ use App\Services\RequestContext; use Cache; use Carbon\Carbon; use League\CommonMark\CommonMarkConverter; -use League\CommonMark\Exception\CommonMarkException; +use League\HTMLToMarkdown\HtmlConverter; use Overtrue\Pinyin\Pinyin; use Redirect; use Request; @@ -2977,11 +2977,26 @@ class Base */ public static function markdown2html($markdown) { - $converter = new CommonMarkConverter(); try { + $converter = new CommonMarkConverter(); return $converter->convert($markdown); - } catch (CommonMarkException $e) { + } catch (\League\CommonMark\Exception\CommonMarkException $e) { return $markdown; } } + + /** + * html 转 MD(markdown) + * @param $html + * @return mixed|string + */ + public static function html2markdown($html) + { + try { + $converter = new HtmlConverter(); + return $converter->convert($html); + } catch (\Exception) { + return $html; + } + } } diff --git a/app/Tasks/BotReceiveMsgTask.php b/app/Tasks/BotReceiveMsgTask.php index 952460bca..ce10a23fa 100644 --- a/app/Tasks/BotReceiveMsgTask.php +++ b/app/Tasks/BotReceiveMsgTask.php @@ -92,7 +92,7 @@ class BotReceiveMsgTask extends AbstractTask // 提取指令 try { - $command = $this->extractCommand($msg, $botUser->isAiBot(), $this->mention); + $command = $this->extractCommand($msg, $botUser, $this->mention); if (empty($command)) { return; } @@ -406,6 +406,7 @@ class BotReceiveMsgTask extends AbstractTask $serverUrl = 'http://nginx'; $userBot = null; $extras = []; + $replyText = null; $errorContent = null; if ($botUser->isAiBot($type)) { // AI机器人 @@ -456,32 +457,13 @@ class BotReceiveMsgTask extends AbstractTask } if ($msg->reply_id > 0) { - $replyMsg = WebSocketDialogMsg::find($msg->reply_id); - $replyCommand = null; - if ($replyMsg) { - switch ($replyMsg->type) { - case 'text': - try { - $replyCommand = $this->extractCommand($replyMsg, true); - } catch (Exception) { - $errorContent = "引用消息解析失败。"; - } - break; - case 'file': - $msgData = Base::json2array($replyMsg->getRawOriginal('msg')); - $fileResult = TextExtractor::extractFile(public_path($msgData['path'])); - if (Base::isError($fileResult)) { - $errorContent = $fileResult['msg']; - } else { - $replyCommand = $fileResult['data']; - } - break; - } - } - if ($replyCommand) { + $replyCommand = $this->extractReplyCommand($msg->reply_id, $botUser); + if (Base::isError($replyCommand)) { + $errorContent = $replyCommand['msg']; + } else { $command = << - {$replyCommand} + {$replyCommand['data']} The content within the above quoted_content tags is a citation. @@ -494,9 +476,17 @@ class BotReceiveMsgTask extends AbstractTask $webhookUrl = "{$serverUrl}/ai/chat"; } else { // 用户机器人 - if (str_starts_with($command, '/')) { + if ($botUser->isUserBot() && str_starts_with($command, '/')) { + // 用户机器人不处理指令类型命令 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(); $webhookUrl = $userBot?->webhook_url; } @@ -514,6 +504,7 @@ class BotReceiveMsgTask extends AbstractTask try { $data = [ 'text' => $command, + 'reply_text' => $replyText, 'token' => User::generateToken($botUser), 'dialog_id' => $dialog->id, 'dialog_type' => $dialog->type, @@ -575,12 +566,12 @@ class BotReceiveMsgTask extends AbstractTask /** * 提取消息指令(提取消息内容) * @param WebSocketDialogMsg $msg - * @param bool $isAiBot + * @param User $botUser * @param bool $mention * @return string * @throws Exception */ - private function extractCommand(WebSocketDialogMsg $msg, bool $isAiBot = false, bool $mention = false) + private function extractCommand(WebSocketDialogMsg $msg, User $botUser, bool $mention = false) { if ($msg->type !== 'text') { return ''; @@ -597,80 +588,113 @@ class BotReceiveMsgTask extends AbstractTask } return $command; } - if (!$isAiBot) { + + if ($botUser->isAiBot()) { + // AI 机器人 + $contents = []; + if (preg_match_all("/(.*?)<\/span>/", $original, $match)) { + // 任务 + $taskIds = Base::newIntval($match[1]); + foreach ($taskIds as $index => $taskId) { + $taskInfo = ProjectTask::with(['content'])->whereId($taskId)->first(); + if (!$taskInfo) { + throw new Exception("任务不存在或已被删除"); + } + $taskName = addslashes($taskInfo->name) . " (ID:{$taskId})"; + $taskContext = implode("\n", $taskInfo->AIContext()); + $contents[] = "\n{$taskContext}\n"; + $original = str_replace($match[0][$index], "'{$taskName}' (see below for task_content tag)", $original); + } + } + if (preg_match_all("/]*?>[~%]([^>]*)<\/a>/", $original, $match)) { + // 文件、报告 + $urlPaths = $match[2]; + foreach ($urlPaths as $index => $urlPath) { + $pathTag = null; + $pathName = null; + $pathContent = null; + // 文件 + if (preg_match("/single\/file\/(.*?)$/", $urlPath, $fileMatch)) { + $fileInfo = FileContent::idOrCodeToContent($fileMatch[1]); + if (!$fileInfo || !isset($fileInfo->content['url'])) { + throw new Exception("文件不存在或已被删除"); + } + $urlPath = public_path($fileInfo->content['url']); + 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][$index]) . " (ID:{$fileInfo->id})"; + $pathContent = $fileResult['data']; + } + // 报告 + elseif (preg_match("/single\/report\/detail\/(.*?)$/", $urlPath, $reportMatch)) { + $reportInfo = Report::idOrCodeToContent($reportMatch[1]); + if (!$reportInfo) { + throw new Exception("报告不存在或已被删除"); + } + $pathTag = "report_content"; + $pathName = addslashes($match[3][$index]) . " (ID:{$reportInfo->id})"; + $pathContent = $reportInfo->content; + } + if ($pathTag) { + $contents[] = "<{$pathTag} path=\"{$pathName}\">\n{$pathContent}\n"; + $original = str_replace($match[0][$index], "'{$pathName}' (see below for {$pathTag} tag)", $original); + } + } + } + $original = Base::html2markdown($original); + if ($contents) { + // 添加tag内容 + $original .= "\n\n" . implode("\n\n", $contents); + } + return $original; + } elseif ($botUser->isUserBot()) { + // 用户机器人 + return Base::html2markdown($original); + } else { + // 其他机器人(系统) return trim(strip_tags($original)); } + } - $contents = []; - // 任务 - if (preg_match_all("/(.*?)<\/span>/", $original, $match)) { - $taskIds = Base::newIntval($match[1]); - foreach ($taskIds as $index => $taskId) { - $taskInfo = ProjectTask::with(['content'])->whereId($taskId)->first(); - if (!$taskInfo) { - throw new Exception("任务不存在或已被删除"); - } - $taskName = addslashes($taskInfo->name) . " (ID:{$taskId})"; - $taskContext = implode("\n", $taskInfo->AIContext()); - $contents[] = "\n{$taskContext}\n"; - $original = str_replace($match[0][$index], "'{$taskName}' (see below for task_content tag)", $original); + /** + * 提取回复消息指令 + * @param $id + * @param User $botUser + * @return array + */ + private function extractReplyCommand($id, User $botUser) + { + $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; } } - // 文件、报告 - if (preg_match_all("/]*?>[~%]([^>]*)<\/a>/", $original, $match)) { - $urlPaths = $match[2]; - foreach ($urlPaths as $index => $urlPath) { - $pathTag = null; - $pathName = null; - $pathContent = null; - // 文件 - if (preg_match("/single\/file\/(.*?)$/", $urlPath, $fileMatch)) { - $fileInfo = FileContent::idOrCodeToContent($fileMatch[1]); - if (!$fileInfo || !isset($fileInfo->content['url'])) { - throw new Exception("文件不存在或已被删除"); - } - $urlPath = public_path($fileInfo->content['url']); - 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][$index]) . " (ID:{$fileInfo->id})"; - $pathContent = $fileResult['data']; - } - // 报告 - elseif (preg_match("/single\/report\/detail\/(.*?)$/", $urlPath, $reportMatch)) { - $reportInfo = Report::idOrCodeToContent($reportMatch[1]); - if (!$reportInfo) { - throw new Exception("报告不存在或已被删除"); - } - $pathTag = "report_content"; - $pathName = addslashes($match[3][$index]) . " (ID:{$reportInfo->id})"; - $pathContent = $reportInfo->content; - } - if ($pathTag) { - $contents[] = "<{$pathTag} path=\"{$pathName}\">\n{$pathContent}\n"; - $original = str_replace($match[0][$index], "'{$pathName}' (see below for {$pathTag} tag)", $original); - } - } - } - if ($msg->msg['type'] !== 'md') { - // 转换为Markdown - try { - $converter = new HtmlConverter(); - $original = $converter->convert($original); - } catch (\Exception) { - throw new Exception("Failed to convert HTML to Markdown"); - } - } - if ($contents) { - // 添加tag内容 - $original .= "\n\n" . implode("\n\n", $contents); - } - return $original ?: ''; + return Base::retSuccess('success', $replyCommand); } /** diff --git a/resources/assets/js/pages/manage/components/DialogView/template/bot-api.vue b/resources/assets/js/pages/manage/components/DialogView/template/bot-api.vue index 2eba96e55..85554a888 100644 --- a/resources/assets/js/pages/manage/components/DialogView/template/bot-api.vue +++ b/resources/assets/js/pages/manage/components/DialogView/template/bot-api.vue @@ -18,6 +18,7 @@

{{$L("Webhook说明")}}:

{{$L("机器人收到消息后会将消息POST推送到Webhook地址,请求超时为10秒,请求参数如下")}}:

text: {{$L("消息文本")}}

+

reply_text: {{$L("回复/引用消息文本")}}

token: {{$L("机器人Token")}}

dialog_id: {{$L("对话ID")}}

dialog_type: {{$L("对话类型")}}