feat: 添加我的机器人管理

This commit is contained in:
kuaifan 2025-03-22 18:19:39 +08:00
parent d6ca66aa2f
commit dd899a3e13
8 changed files with 411 additions and 86 deletions

View File

@ -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('删除成功');
}
/**

View File

@ -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);
}
}

View File

@ -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;

View File

@ -40,7 +40,71 @@
</div>
</div>
<!--AI-->
<!--MY BOT-->
<DrawerOverlay v-model="mybotShow" placement="right" :size="720">
<div v-if="mybotShow" class="ivu-modal-wrap-apply">
<div class="ivu-modal-wrap-apply-title">
{{ $L('我的机器人') }}
<p @click="applyClick({value: 'mybot-add'}, {id: 0})">{{$L('添加机器人')}}</p>
</div>
<div class="ivu-modal-wrap-apply-body full-body">
<div v-if="mybotList.length === 0" class="empty-data">
<Loading v-if="mybotLoad"/>
<span>{{$L('您没有创建机器人')}}</span>
</div>
<ul v-else class="ivu-modal-wrap-ul">
<li v-for="(item, key) in mybotList" :key="key">
<div class="modal-item-img">
<img :src="item.avatar">
</div>
<div class="modal-item-info">
<h4>{{ item.name }}</h4>
<div class="modal-item-mybot">
<p><span>ID:</span>{{item.id}}</p>
<p><span>{{$L('清理时间')}}:</span>{{item.clear_day}}</p>
<p><span>Webhook:</span>{{item.webhook_url || '-'}}</p>
</div>
<div class="modal-item-btns">
<Button icon="md-chatbubbles" @click="applyClick({value: 'mybot-chat'}, item)">{{ $L('开始聊天') }}</Button>
<Button icon="md-create" @click="applyClick({value: 'mybot-add'}, item)">{{ $L('修改') }}</Button>
<Button icon="md-trash" @click="applyClick({value: 'mybot-del'}, item)">{{ $L('删除') }}</Button>
</div>
</div>
</li>
</ul>
</div>
</div>
</DrawerOverlay>
<!--MY BOT 设置-->
<Modal
v-model="mybotModifyShow"
:title="$L(mybotModifyData.id > 0 ? '修改机器人' : '添加机器人')"
:mask-closable="false">
<Form :model="mybotModifyData" v-bind="formOptions" @submit.native.prevent>
<Alert v-if="mybotModifyData.system_name" type="error" style="margin-bottom:18px">{{$L(`正在修改系统机器人${mybotModifyData.system_name}`)}}</Alert>
<FormItem prop="avatar" :label="$L('头像')">
<ImgUpload v-model="mybotModifyData.avatar" :num="1" :width="512" :height="512" whcut="cover"/>
</FormItem>
<FormItem prop="name" :label="$L('名称')">
<Input v-model="mybotModifyData.name" :maxlength="20" />
</FormItem>
<FormItem prop="clear_day" :label="$L('消息保留')">
<Input v-model="mybotModifyData.clear_day" :maxlength="3" type="number">
<div slot="append">{{$L('天')}}</div>
</Input>
</FormItem>
<FormItem prop="webhook_url" label="Webhook">
<Input v-model="mybotModifyData.webhook_url" :maxlength="255" :show-word-limit="0.9" type="textarea" />
</FormItem>
</Form>
<div slot="footer" class="adaption">
<Button type="default" @click="mybotModifyShow=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="mybotModifyLoad > 0" @click="onMybotModify">{{$L('保存')}}</Button>
</div>
</Modal>
<!--AI BOT-->
<DrawerOverlay v-model="aibotShow" placement="right" :size="720">
<div v-if="aibotShow" class="ivu-modal-wrap-apply">
<div class="ivu-modal-wrap-apply-title">
@ -70,7 +134,7 @@
</div>
</DrawerOverlay>
<!--AI 设置-->
<!--AI BOT 设置-->
<DrawerOverlay v-model="aibotSettingShow" placement="right" :size="950">
<div v-if="aibotSettingShow" class="ivu-modal-wrap-apply">
<div class="ivu-modal-wrap-apply-title">
@ -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", {

View File

@ -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}) => {

View File

@ -842,6 +842,40 @@ export default {
emitter.emit('userActive', {type: 'cache', data});
},
/**
* 修改机器人信息
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
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

View File

@ -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;

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" viewBox="0 0 48 48"><defs><clipPath id="master_svg0_3054_25405"><rect x="12" y="12" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="48" height="48" rx="12" fill="#0D4196" fill-opacity="1"/><g clip-path="url(#master_svg0_3054_25405)"><g><g><rect x="12" y="26.31494140625" width="1.8119423389434814" height="7.173644542694092" rx="0.9059711694717407" fill="#FFFFFF" fill-opacity="1"/></g><g><rect x="34.18804931640625" y="26.31494140625" width="1.8119423389434814" height="7.173644542694092" rx="0.9059711694717407" fill="#FFFFFF" fill-opacity="1"/></g><g><path d="M29.53465,20.54139L31.35485,17.38422C31.41525,17.38422,31.47695,17.39383,31.53875,17.39383C32.931349999999995,17.39648,33.97485,16.118949999999998,33.69415,14.75489C33.41345,13.390835,31.95045,12.629057,30.67195,13.181321C29.393549999999998,13.733585,28.94525,15.32101,29.74605,16.4604L27.76795,19.89211C25.287950000000002,19.2077,22.669629999999998,19.20201,20.18666,19.87564L18.21274,16.46315C19.0135,15.32376,18.56525,13.73633,17.28679,13.184066C16.00834,12.631803,14.545278,13.393581,14.2646052,14.75764C13.983932,16.1217,15.02738,17.39923,16.42001,17.39657C16.48179,17.39657,16.542180000000002,17.39657,16.60258,17.386960000000002L18.417270000000002,20.529040000000002C16.05625,21.62718,14.483158,23.3979,14.36648,25.4172L14.356871,25.4172L14.356871,33.289500000000004C14.356871,34.7254,15.52087,35.889399999999995,16.95673,35.889399999999995L30.96905,35.889399999999995C32.40495,35.889399999999995,33.56895,34.7254,33.56895,33.289500000000004L33.56895,25.4172L33.559349999999995,25.4172C33.444050000000004,23.4048,31.88195,21.636789999999998,29.53465,20.54139ZM27.063850000000002,29.5105L20.86888,29.5105C19.89243,29.5105,19.10087,28.718899999999998,19.10087,27.7425C19.10087,26.766,19.89243,25.9745,20.86888,25.9745L27.05695,25.9745C28.03335,25.9745,28.82495,26.766,28.82495,27.7425C28.82495,28.718899999999998,28.03335,29.5105,27.05695,29.5105L27.063850000000002,29.5105Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB