perf: 机器人支持新会话

This commit is contained in:
kuaifan 2025-07-25 11:17:12 +08:00
parent b406e22695
commit 0b86fa7bee
9 changed files with 85 additions and 62 deletions

View File

@ -3291,15 +3291,7 @@ class DialogController extends AbstractController
$dialog = WebSocketDialog::checkDialog($dialog_id);
}
//
if ($dialog->type != 'user') {
return Base::retError('当前对话不支持');
}
//
$hasAiUser = WebSocketDialogUser::join('users as u', 'web_socket_dialog_users.userid', '=', 'u.userid')
->where('dialog_id', $dialog->id)
->where('u.email', 'like', 'ai-%@bot.system')
->exists();
if (!$hasAiUser) {
if (!$dialog->isSessionDialog()) {
return Base::retError('当前对话不支持');
}
//
@ -3312,7 +3304,6 @@ class DialogController extends AbstractController
//
$session = WebSocketDialogSession::create([
'dialog_id' => $dialog->id,
'status' => 1,
'title' => '',
]);
$session->save();

View File

@ -376,7 +376,7 @@ class UsersController extends AbstractController
public function info__departments()
{
$user = User::auth();
// 获取部门列表
$list = UserDepartment::select(['id', 'owner_userid', 'parent_id', 'name'])
->whereIn('id', $user->department)
@ -2103,6 +2103,11 @@ class UsersController extends AbstractController
* @apiParam {Number} [id] 机器人ID编辑时必填留空为添加
* @apiParam {String} [name] 机器人名称
* @apiParam {String} [avatar] 机器人头像
* @apiParam {Number} [session] 开启新会话功能(仅 我的机器人)
* - 1开启、0关闭, 默认0
* - 此参数仅在添加机器人时有效
* - 开启后,机器人对话窗口会出现新会话菜单和历史会话菜单
* - 开启后webhook_url 消息会多一个 session_id 字段
* @apiParam {Number} [clear_day] 清理天数(仅 我的机器人)
* @apiParam {String} [webhook_url] Webhook地址 我的机器人)
*
@ -2115,8 +2120,9 @@ class UsersController extends AbstractController
$user = User::auth();
//
$botId = intval(Request::input('id'));
$session = intval(Request::input('session'));
if (empty($botId)) {
$res = UserBot::newbot($user->userid, trim(Request::input('name')));
$res = UserBot::newBot($user->userid, trim(Request::input('name')), (bool)$session);
if (Base::isError($res)) {
return $res;
}

View File

@ -174,10 +174,9 @@ class User extends AbstractModel
return UserDepartment::where('owner_userid', $this->userid)->exists();
}
/**
* 获取机器人所有者
* @return int|mixed
* @return int
*/
public function getBotOwner()
{
@ -782,18 +781,17 @@ class User extends AbstractModel
/**
* 是否机器人
* @param $userid
* @return bool|mixed
* @return bool
*/
public static function isBot($userid)
{
if (empty($userid)) {
return false;
}
$userid = intval($userid);
if (RequestContext::has("isBot_" . $userid)) {
return RequestContext::get("isBot_" . $userid);
}
return (bool)User::find($userid)?->bot;
// 这个不会有变化,所以可以使用永久缓存
return (bool)Cache::rememberForever('is-bot-user-' . $userid, function () use ($userid) {
return (bool)User::find($userid)?->bot;
});
}
/**

View File

@ -180,28 +180,8 @@ class UserBot extends AbstractModel
];
default:
if (preg_match('/^ai-(.*?)@bot\.system$/', $email, $match)) {
if (!Base::judgeClientVersion('0.42.62')) {
return [
'key' => '%3A.clear',
'label' => Doo::translate('清空上下文')
];
}
$aibotSetting = Base::setting('aibotSetting');
$aibotModel = $aibotSetting[$match[1] . '_model'];
$aibotModels = Setting::AIModels2Array($aibotSetting[$match[1] . '_models']);
if (empty($aibotModels)) {
return [];
}
return [
[
'key' => '~ai-model-select',
'label' => Doo::translate('选择模型'),
'config' => [
'model' => $aibotModel,
'models' => $aibotModels
]
],
if (preg_match('/^(ai-|user-session-)(.*?)@bot\.system$/', $email, $match)) {
$menus = [
[
'key' => '~ai-session-create',
'label' => Doo::translate('开启新会话'),
@ -211,6 +191,27 @@ class UserBot extends AbstractModel
'label' => Doo::translate('历史会话'),
]
];
if ($match[1] === "ai-") {
$aibotSetting = Base::setting('aibotSetting');
$aibotModel = $aibotSetting[$match[1] . '_model'];
$aibotModels = Setting::AIModels2Array($aibotSetting[$match[1] . '_models']);
if ($aibotModels) {
$menus = array_merge(
[
[
'key' => '~ai-model-select',
'label' => Doo::translate('选择模型'),
'config' => [
'model' => $aibotModel,
'models' => $aibotModels
]
]
],
$menus
);
}
}
return $menus;
}
return [];
}
@ -445,11 +446,12 @@ class UserBot extends AbstractModel
/**
* 创建我的机器人
* @param $userid
* @param $botName
* @param int $userid 创建人userid
* @param string $botName 机器人名称
* @param bool $sessionSupported 是否支持会话
* @return array
*/
public static function newbot($userid, $botName)
public static function newBot($userid, $botName, $sessionSupported = false)
{
if (User::select(['users.*'])
->join('user_bots', 'users.userid', '=', 'user_bots.bot_id')
@ -461,7 +463,8 @@ class UserBot extends AbstractModel
if (strlen($botName) < 2 || strlen($botName) > 20) {
return Base::retError("机器人名称由2-20个字符组成。");
}
$data = User::botGetOrCreate("user-" . Base::generatePassword(), [
$botType = ($sessionSupported ? "user-session-" : "user-normal-") . Base::generatePassword();
$data = User::botGetOrCreate($botType, [
'nickname' => $botName
], $userid);
if (empty($data)) {
@ -469,6 +472,15 @@ class UserBot extends AbstractModel
}
$dialog = WebSocketDialog::checkUserDialog($data, $userid);
if ($dialog) {
if ($sessionSupported) {
$dialogSession = WebSocketDialogSession::create([
'dialog_id' => $dialog->id,
'title' => 'Default',
]);
$dialogSession->save();
$dialog->session_id = $dialogSession->id;
$dialog->save();
}
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
'type' => '/hello',
'title' => '创建成功。',

View File

@ -705,6 +705,27 @@ class WebSocketDialog extends AbstractModel
return WebSocketDialogUser::whereDialogId($this->id)->where('userid', '>', 0)->count() === 1;
}
/**
* 检查是否支持创建会话
* @return bool
*/
public function isSessionDialog()
{
// 这个不会有变化,所以可以使用永久缓存
return Cache::rememberForever('is-session-dialog-' . $this->id, function () {
if ($this->type !== 'user') {
return false;
}
$data = $this->dialogUserBuilder()->get();
foreach ($data as $item) {
if (preg_match('/^(ai-|user-session-)(.*?)@bot\.system$/', $item->email)) {
return true;
}
}
return false;
});
}
/**
* 检查是否是AI对话
* @return bool
@ -799,6 +820,7 @@ class WebSocketDialog extends AbstractModel
WebSocketDialogUser::createInstance([
'dialog_id' => $dialog->id,
'userid' => $value,
'bot' => User::isBot($value) ? 1 : 0,
'important' => !in_array($group_type, ['user', 'all']),
'last_at' => in_array($group_type, ['user', 'department', 'all']) ? Carbon::now() : null,
])->save();
@ -835,16 +857,17 @@ class WebSocketDialog extends AbstractModel
WebSocketDialogUser::createInstance([
'dialog_id' => $dialog->id,
'userid' => $user->userid,
'bot' => User::isBot($user->userid) ? 1 : 0,
])->save();
WebSocketDialogUser::createInstance([
'dialog_id' => $dialog->id,
'userid' => $receiver,
'bot' => User::isBot($receiver) ? 1 : 0,
])->save();
//
if ($user->isAiBot() || User::find($receiver)?->isAiBot()) {
$session = WebSocketDialogSession::create([
'dialog_id' => $dialog->id,
'status' => 1,
'title' => '',
]);
$session->save();

View File

@ -205,7 +205,7 @@ class BotReceiveMsgTask extends AbstractTask
* 创建
*/
case '/newbot':
$res = UserBot::newbot($msg->userid, $array[1]);
$res = UserBot::newBot($msg->userid, $array[1]);
if (Base::isError($res)) {
$content = $res['msg'];
} else {
@ -381,6 +381,7 @@ class BotReceiveMsgTask extends AbstractTask
default => '不支持的指令',
};
if ($type == '/api') {
$msgData['email'] = $botUser->email;
$msgData['version'] = Base::getVersion();
} elseif ($type == '/help') {
$msgData['manager'] = $isManager;
@ -522,6 +523,7 @@ class BotReceiveMsgTask extends AbstractTask
'text' => $command,
'reply_text' => $replyText,
'token' => User::generateToken($botUser),
'session_id' => $dialog->session_id,
'dialog_id' => $dialog->id,
'dialog_type' => $dialog->type,
'msg_id' => $msg->id,

View File

@ -20,6 +20,7 @@
<p><span class="mark-color">text</span>: {{$L("消息文本")}}</p>
<p><span class="mark-color">reply_text</span>: {{$L("回复/引用消息文本")}}</p>
<p><span class="mark-color">token</span>: {{$L("机器人Token")}}</p>
<p v-if="/^(ai-|user-session-)/.test(msg.email)"><span class="mark-color">session_id</span>: {{$L("会话ID")}}</p>
<p><span class="mark-color">dialog_id</span>: {{$L("对话ID")}}</p>
<p><span class="mark-color">dialog_type</span>: {{$L("对话类型")}}</p>
<p><span class="mark-color">msg_id</span>: {{$L("消息ID")}}</p>

View File

@ -1917,13 +1917,6 @@ export default {
//
case "~ai-session-create":
if (!this.isAiBot) {
return
}
//
this.$store.dispatch("clearDialogMsgs", {
id: this.dialogId
});
//
this.$store.dispatch("call", {
url: 'dialog/session/create',
@ -1940,9 +1933,6 @@ export default {
//
case "~ai-session-history":
if (!this.isAiBot) {
return
}
this.sessionHistoryData = {
dialog_id: this.dialogId,
name: this.dialogData.name,

View File

@ -250,10 +250,6 @@ export default {
const processQueue = async () => {
try {
for (const user of this.aiUser) {
//
this.$store.dispatch("clearDialogMsgs", {
id: this.dialogId
});
//
await this.$store.dispatch("call", {
url: 'dialog/session/create',
@ -261,6 +257,10 @@ export default {
userid: user.userid,
},
});
//
await this.$store.dispatch("clearDialogMsgs", {
id: this.dialogId
});
}
resolve();
} catch (error) {