diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 1037012c8..ed036bf82 100755 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -1927,6 +1927,50 @@ class UsersController extends AbstractController ]); } + /** + * @api {get} api/users/bot/list 32. 机器人列表 + * + * @apiDescription 需要token身份,获取我的机器人列表 + * @apiVersion 1.0.0 + * @apiGroup users + * @apiName bot__list + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function bot__list() + { + // 获取当前认证用户 + $user = User::auth(); + + // 使用连表查询一次性获取所有机器人数据 + $bots = User::join('user_bots', 'user_bots.bot_id', '=', 'users.userid') + ->where('user_bots.userid', $user->userid) + ->select([ + 'users.userid', + 'users.nickname', + 'users.userimg', + 'user_bots.clear_day', + 'user_bots.webhook_url' + ]) + ->orderByDesc('id') + ->get() + ->toArray(); + foreach ($bots as &$bot) { + $bot['id'] = $bot['userid']; + $bot['name'] = $bot['nickname']; + $bot['avatar'] = $bot['userimg']; + $bot['system_name'] = UserBot::systemBotName($bot['name']); + unset($bot['userid'], $bot['nickname'], $bot['userimg']); + } + + // 返回成功响应,将机器人列表包装在list字段中 + return Base::retSuccess('success', [ + 'list' => $bots + ]); + } + /** * @api {get} api/users/bot/info 32. 机器人信息 * @@ -1979,14 +2023,14 @@ class UsersController extends AbstractController } /** - * @api {post} api/users/bot/edit 33. 编辑机器人 + * @api {post} api/users/bot/edit 33. 添加、编辑机器人 * * @apiDescription 需要token身份,编辑 我的机器人 或 管理员修改系统机器人 信息 * @apiVersion 1.0.0 * @apiGroup users * @apiName bot__edit * - * @apiParam {Number} id 机器人ID + * @apiParam {Number} [id] 机器人ID(编辑时必填,留空为添加) * @apiParam {String} [name] 机器人名称 * @apiParam {String} [avatar] 机器人头像 * @apiParam {Number} [clear_day] 清理天数(仅 我的机器人) @@ -2001,10 +2045,19 @@ class UsersController extends AbstractController $user = User::auth(); // $botId = intval(Request::input('id')); - $botUser = User::whereUserid($botId)->whereBot(1)->first(); - if (empty($botUser)) { - return Base::retError('机器人不存在'); + if (empty($botId)) { + $res = UserBot::newbot($user->userid, trim(Request::input('name'))); + if (Base::isError($res)) { + return $res; + } + $botUser = $res['data']; + } else { + $botUser = User::whereUserid($botId)->whereBot(1)->first(); + if (empty($botUser)) { + return Base::retError('机器人不存在'); + } } + // $userBot = UserBot::whereBotId($botUser->userid)->whereUserid($user->userid)->first(); if (empty($userBot)) { if (UserBot::systemBotName($botUser->email)) { @@ -2061,7 +2114,57 @@ class UsersController extends AbstractController $data['clear_day'] = $userBot->clear_day; $data['webhook_url'] = $userBot->webhook_url; } - return Base::retSuccess('修改成功', $data); + return Base::retSuccess($botId ? '修改成功' : '添加成功', $data); + } + + /** + * @api {get} api/users/bot/delete 33. 删除机器人 + * + * @apiDescription 需要token身份 + * @apiVersion 1.0.0 + * @apiGroup users + * @apiName bot__delete + * + * @apiParam {Number} id 机器人ID + * @apiParam {String} remark 删除备注 + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function bot__delete() + { + $user = User::auth(); + // + $botId = intval(Request::input('id')); + $remark = trim(Request::input('remark')); + // + if (empty($remark)) { + return Base::retError('请输入删除备注'); + } + if (mb_strlen($remark) > 255) { + return Base::retError('删除备注长度限制255个字'); + } + // + $botUser = User::whereUserid($botId)->whereBot(1)->first(); + if (empty($botUser)) { + return Base::retError('机器人不存在'); + } + $userBot = UserBot::whereBotId($botUser->userid)->whereUserid($user->userid)->first(); + if (empty($userBot)) { + if (UserBot::systemBotName($botUser->email)) { + // 系统机器人(仅限管理员) + return Base::retError('系统机器人不能删除'); + } else { + // 其他用户的机器人(仅限主人) + return Base::retError('不是你的机器人'); + } + } + // + if (!$botUser->deleteUser($remark)) { + return Base::retError('删除失败'); + } + return Base::retSuccess('删除成功'); } /** diff --git a/app/Models/UserBot.php b/app/Models/UserBot.php index 2b6a00b31..6c50c54d2 100644 --- a/app/Models/UserBot.php +++ b/app/Models/UserBot.php @@ -452,4 +452,39 @@ class UserBot extends AbstractModel default => [], }; } + + /** + * 创建我的机器人 + * @param $userid + * @param $botName + * @return array + */ + public static function newbot($userid, $botName) + { + if (User::select(['users.*']) + ->join('user_bots', 'users.userid', '=', 'user_bots.bot_id') + ->where('users.bot', 1) + ->where('user_bots.userid', $userid) + ->count() >= 50) { + return Base::retError("超过最大创建数量。"); + } + if (strlen($botName) < 2 || strlen($botName) > 20) { + return Base::retError("机器人名称由2-20个字符组成。"); + } + $data = User::botGetOrCreate("user-" . Base::generatePassword(), [ + 'nickname' => $botName + ], $userid); + if (empty($data)) { + return Base::retError("创建失败。"); + } + $dialog = WebSocketDialog::checkUserDialog($data, $userid); + if ($dialog) { + WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [ + 'type' => '/hello', + 'title' => '创建成功。', + 'data' => $data, + ], $data->userid); + } + return Base::retSuccess("创建成功。", $data); + } } diff --git a/app/Tasks/BotReceiveMsgTask.php b/app/Tasks/BotReceiveMsgTask.php index 023641bf6..a3530789c 100644 --- a/app/Tasks/BotReceiveMsgTask.php +++ b/app/Tasks/BotReceiveMsgTask.php @@ -208,32 +208,11 @@ class BotReceiveMsgTask extends AbstractTask * 创建 */ case '/newbot': - if (User::select(['users.*']) - ->join('user_bots', 'users.userid', '=', 'user_bots.bot_id') - ->where('users.bot', 1) - ->where('user_bots.userid', $msg->userid) - ->count() >= 50) { - $content = "超过最大创建数量。"; - break; - } - if (strlen($array[1]) < 2 || strlen($array[1]) > 20) { - $content = "机器人名称由2-20个字符组成。"; - break; - } - $data = User::botGetOrCreate("user-" . Base::generatePassword(), [ - 'nickname' => $array[1] - ], $msg->userid); - if (empty($data)) { - $content = "创建失败。"; - break; - } - $dialog = WebSocketDialog::checkUserDialog($data, $msg->userid); - if ($dialog) { - WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [ - 'type' => '/hello', - 'title' => '创建成功。', - 'data' => $data, - ], $data->userid); // todo 未能在任务end事件来发送任务 + $res = UserBot::newbot($msg->userid, $array[1]); + if (Base::isError($res)) { + $content = $res['msg']; + } else { + $data = $res['data']; } break; diff --git a/resources/assets/js/pages/manage/application.vue b/resources/assets/js/pages/manage/application.vue index c1bb9c3da..5537ff212 100644 --- a/resources/assets/js/pages/manage/application.vue +++ b/resources/assets/js/pages/manage/application.vue @@ -40,7 +40,71 @@ - + + +
+
+ {{ $L('我的机器人') }} +

{{$L('添加机器人')}}

+
+
+
+ + {{$L('您没有创建机器人')}} +
+
    +
  • + + +
  • +
+
+
+
+ + + +
+ {{$L(`正在修改系统机器人:${mybotModifyData.system_name}`)}} + + + + + + + + +
{{$L('天')}}
+ +
+ + + +
+
+ + +
+
+ +
@@ -70,7 +134,7 @@
- +
@@ -248,9 +312,11 @@ import SystemEmailSetting from "./setting/components/SystemEmailSetting"; import SystemAppPush from "./setting/components/SystemAppPush"; import emitter from "../../store/events"; import {AIBotList, AIModelNames} from "../../store/ai"; +import ImgUpload from "../../components/ImgUpload.vue"; export default { components: { + ImgUpload, UserSelect, DrawerOverlay, SystemAibot, @@ -266,8 +332,15 @@ export default { applyList: [], applyListTypes: ['base', 'admin'], // - aibotList: AIBotList, + mybotShow: false, + mybotList: [], + mybotLoad: 0, + mybotModifyShow: false, + mybotModifyData: {}, + mybotModifyLoad: 0, + // aibotShow: false, + aibotList: AIBotList, aibotSettingShow: false, aibotTabAction: "openai", aibotDialogSearchLoad: "", @@ -308,6 +381,7 @@ export default { 'approveUnreadNumber', 'cacheDialogs', 'windowOrientation', + 'formOptions', ]), isExistAdminList() { return this.applyList.map(h => h.type).indexOf('admin') !== -1; @@ -321,14 +395,15 @@ export default { methods: { initList() { let applyList = [ - { value: "approve", label: "审批中心", sort: 3 }, - { value: "report", label: "工作报告", sort: 5 }, - { value: "okr", label: "OKR 管理", sort: 4 }, - { value: "robot", label: "AI 机器人", sort: 6 }, - { value: "signin", label: "签到打卡", sort: 7 }, - { value: "meeting", label: "在线会议", sort: 8 }, - { value: "word-chain", label: "群接龙", sort: 9 }, - { value: "vote", label: "群投票", sort: 10 }, + { value: "approve", label: "审批中心", sort: 30 }, + { value: "report", label: "工作报告", sort: 50 }, + { value: "okr", label: "OKR 管理", sort: 40 }, + { value: "mybot", label: "我的机器人", sort: 55 }, + { value: "robot", label: "AI 机器人", sort: 60 }, + { value: "signin", label: "签到打卡", sort: 70 }, + { value: "meeting", label: "在线会议", sort: 80 }, + { value: "word-chain", label: "群接龙", sort: 90 }, + { value: "vote", label: "群投票", sort: 100 }, ]; if (this.systemConfig.server_closeai === 'close') { applyList = applyList.filter(h => h.value !== 'robot'); @@ -336,32 +411,32 @@ export default { // wap模式 if (this.windowOrientation == 'landscape') { // 横屏模式 - applyList.push({ value: "scan", label: "扫一扫", show: $A.isEEUiApp, sort: 13 }) + applyList.push({ value: "scan", label: "扫一扫", show: $A.isEEUiApp, sort: 130 }) } else { // 竖屏模式 applyList.push(...[ - { value: "calendar", label: "日历", sort: 1 }, - { value: "file", label: "文件", sort: 2 }, - { value: "addProject", label: "创建项目", sort: 11 }, - { value: "addTask", label: "添加任务", sort: 12 }, - { value: "scan", label: "扫一扫", show: $A.isEEUiApp, sort: 13 }, - { value: "setting", label: "设置", sort: 14 } + { value: "calendar", label: "日历", sort: 10 }, + { value: "file", label: "文件", sort: 20 }, + { value: "addProject", label: "创建项目", sort: 110 }, + { value: "addTask", label: "添加任务", sort: 120 }, + { value: "scan", label: "扫一扫", show: $A.isEEUiApp, sort: 130 }, + { value: "setting", label: "设置", sort: 140 } ]) } // 管理员 let adminApplyList = []; if (!this.userIsAdmin) { if (this.userInfo.department_owner) { - adminApplyList.push({ value: "okrAnalyze", label: "OKR 结果", sort: 15 }) + adminApplyList.push({ value: "okrAnalyze", label: "OKR 结果", sort: 150 }) } } else { adminApplyList.push(...[ - { value: "okrAnalyze", label: "OKR 结果", sort: 15 }, - { value: "ldap", label: "LDAP", sort: 16 }, - { value: "mail", label: "邮件通知", sort: 17 }, - { value: "appPush", label: "APP 推送", sort: 18 }, - { value: "complaint", label: "举报管理", sort: 19 }, - { value: "allUser", label: "团队管理", sort: 20 }, + { value: "okrAnalyze", label: "OKR 结果", sort: 150 }, + { value: "ldap", label: "LDAP", sort: 160 }, + { value: "mail", label: "邮件通知", sort: 170 }, + { value: "appPush", label: "APP 推送", sort: 180 }, + { value: "complaint", label: "举报管理", sort: 190 }, + { value: "allUser", label: "团队管理", sort: 200 }, ]) } adminApplyList = adminApplyList.map((h) => { @@ -369,15 +444,7 @@ export default { return h; }); // - this.applyList = [...applyList, ...adminApplyList].sort((a, b) => { - if (a.sort < b.sort) { - return -1; - } else if (a.sort > b.sort) { - return 1; - } else { - return 0; - } - }); + this.applyList = [...applyList, ...adminApplyList].sort((a, b) => a.sort - b.sort); }, getLogoClass(name) { name = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); @@ -413,6 +480,19 @@ export default { case 'report': emitter.emit('openReport', area == 'badge' ? 'receive' : 'my'); break; + case 'mybot': + this.getMybot(); + this.mybotShow = true; + break; + case 'mybot-chat': + this.chatMybot(area.id); + break; + case 'mybot-add': + this.addMybot(area); + break; + case 'mybot-del': + this.delMybot(area); + break; case 'robot': this.getAITags(); this.aibotShow = true; @@ -448,6 +528,82 @@ export default { } this.$emit("on-click", item.value) }, + // 获取我的机器人 + getMybot() { + this.mybotLoad++ + this.$store.dispatch("call", { + url: 'users/bot/list', + }).then(({data}) => { + this.mybotList = data.list; + }).finally(_ => { + this.mybotLoad-- + }); + }, + // 与我的机器人聊天 + chatMybot(userid) { + this.$store.dispatch("openDialogUserid", userid).then(_ => { + this.mybotShow = false; + this.goForward({name: 'manage-messenger', params: {dialogAction: 'dialog'}}) + }).catch(({msg}) => { + $A.modalError(msg || this.$L('打开会话失败')) + }); + }, + // 添加修改我的机器人 + addMybot(info) { + this.mybotModifyData = $A.cloneJSON(info) + this.mybotModifyShow = true; + }, + // 删除我的机器人 + delMybot(info) { + $A.modalInput({ + title: `删除机器人:${info.name}`, + placeholder: `请输入备注原因`, + okText: "删除", + okType: "error", + onOk: remark => { + if (!remark) { + return `请输入备注原因` + } + return new Promise((resolve, reject) => { + this.$store.dispatch("call", { + url: 'users/bot/delete', + data: { + id: info.id, + remark + } + }).then(({msg}) => { + const index = this.mybotList.findIndex(item => item.id === info.id); + if (index > -1) { + this.mybotList.splice(index, 1); + } + $A.messageSuccess(msg); + resolve(); + }).catch(({msg}) => { + reject(msg); + }); + }) + } + }); + }, + // 添加/修改我的机器人 + onMybotModify() { + this.mybotModifyLoad++ + this.$store.dispatch("editUserBot", this.mybotModifyData).then(({data, msg}) => { + const index = this.mybotList.findIndex(item => item.id === data.id); + if (index > -1) { + this.mybotList.splice(index, 1, data); + } else { + this.mybotList.unshift(data); + } + this.mybotModifyShow = false; + this.mybotModifyData = {}; + $A.messageSuccess(msg); + }).catch(({msg}) => { + $A.modalError(msg); + }).finally(_ => { + this.mybotModifyLoad--; + }); + }, // 获取AI标签 getAITags() { this.$store.dispatch("call", { diff --git a/resources/assets/js/pages/manage/components/DialogWrapper.vue b/resources/assets/js/pages/manage/components/DialogWrapper.vue index 50b974804..8de4c5d1d 100644 --- a/resources/assets/js/pages/manage/components/DialogWrapper.vue +++ b/resources/assets/js/pages/manage/components/DialogWrapper.vue @@ -2794,27 +2794,15 @@ export default { if (this.modifyData.userid) { // 个人头像(机器人) this.modifyLoad++; - this.$store.dispatch("call", { - url: 'users/bot/edit', - data: { - id: this.modifyData.userid, - avatar: this.modifyData.avatar, - name: this.modifyData.name, - clear_day: this.modifyData.clear_day, - webhook_url: this.modifyData.webhook_url, - }, - method: 'post' - }).then(({data, msg}) => { + this.$store.dispatch("editUserBot", { + id: this.modifyData.userid, + avatar: this.modifyData.avatar, + name: this.modifyData.name, + clear_day: this.modifyData.clear_day, + webhook_url: this.modifyData.webhook_url, + dialog_id: this.modifyData.dialog_id + }).then(({msg}) => { $A.messageSuccess(msg); - this.$store.dispatch("saveUserBasic", { - userid: this.modifyData.userid, - nickname: data.name, - userimg: data.avatar, - }); - this.$store.dispatch("saveDialog", { - id: this.modifyData.dialog_id, - name: data.name - }); this.modifyShow = false; this.modifyData = {}; }).catch(({msg}) => { diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js index 36c7c210b..fc35b1bfb 100644 --- a/resources/assets/js/store/actions.js +++ b/resources/assets/js/store/actions.js @@ -842,6 +842,40 @@ export default { emitter.emit('userActive', {type: 'cache', data}); }, + /** + * 修改机器人信息 + * @param dispatch + * @param data + * @returns {Promise} + */ + editUserBot({dispatch}, data) { + return new Promise((resolve, reject) => { + let dialogId = 0 + if (data.dialog_id) { + dialogId = data.dialog_id; + delete data.dialog_id; + } + dispatch("call", { + url: 'users/bot/edit', + data, + method: 'post' + }).then(({data, msg}) => { + dispatch("saveUserBasic", { + userid: data.id, + nickname: data.name, + userimg: data.avatar, + }); + if (dialogId) { + dispatch("saveDialog", { + id: dialogId, + name: data.name + }); + } + resolve({data, msg}) + }).catch(reject); + }) + }, + /** * 设置用户信息 * @param dispatch diff --git a/resources/assets/sass/pages/page-apply.scss b/resources/assets/sass/pages/page-apply.scss index 0e79655f9..adc03de04 100644 --- a/resources/assets/sass/pages/page-apply.scss +++ b/resources/assets/sass/pages/page-apply.scss @@ -207,6 +207,10 @@ background-image: url("../images/application/report.svg"); } + &.mybot { + background-image: url("../images/application/mybot.svg"); + } + &.robot { background-image: url("../images/application/robot.svg"); } @@ -375,6 +379,20 @@ -webkit-line-clamp: 2; } + .modal-item-mybot { + display: flex; + row-gap: 4px; + flex-direction: column; + margin-bottom: 12px; + > p { + display: flex; + > span { + opacity: 0.8; + padding-right: 6px; + } + } + } + .modal-item-tags { display: flex; flex-wrap: wrap; @@ -436,6 +454,17 @@ height: 0; overflow: auto; + .empty-data { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + > span { + opacity: 0.8; + } + } + @media (max-width: 500px) { padding: 8px 0; diff --git a/resources/assets/statics/public/images/application/mybot.svg b/resources/assets/statics/public/images/application/mybot.svg new file mode 100644 index 000000000..ceeb151fc --- /dev/null +++ b/resources/assets/statics/public/images/application/mybot.svg @@ -0,0 +1 @@ +