mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-14 12:42:51 +00:00
perf: 添加ChatGPT、Claude智能机器人
This commit is contained in:
parent
efdc4c5229
commit
569145196e
@ -658,7 +658,7 @@ class DialogController extends AbstractController
|
|||||||
$userid = intval(Request::input('userid'));
|
$userid = intval(Request::input('userid'));
|
||||||
$stream_url = trim(Request::input('stream_url'));
|
$stream_url = trim(Request::input('stream_url'));
|
||||||
//
|
//
|
||||||
if ($userid < 1 || !str_starts_with($stream_url, 'http')) {
|
if ($userid <= 0) {
|
||||||
return Base::retError('参数错误');
|
return Base::retError('参数错误');
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Models\WebSocketDialog;
|
||||||
|
use App\Models\WebSocketDialogMsg;
|
||||||
use Request;
|
use Request;
|
||||||
use Session;
|
use Session;
|
||||||
use Response;
|
use Response;
|
||||||
@ -228,6 +230,72 @@ class SystemController extends AbstractController
|
|||||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} api/system/setting/aibot 03. 获取会议设置、保存AI机器人设置(限管理员)
|
||||||
|
*
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup system
|
||||||
|
* @apiName setting__aibot
|
||||||
|
*
|
||||||
|
* @apiParam {String} type
|
||||||
|
* - get: 获取(默认)
|
||||||
|
* - save: 保存设置(参数:['openai_key', 'openai_agency', 'claude_token', 'claude_agency'])
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function setting__aibot()
|
||||||
|
{
|
||||||
|
$user = User::auth('admin');
|
||||||
|
//
|
||||||
|
$type = trim(Request::input('type'));
|
||||||
|
$setting = Base::setting('aibotSetting');
|
||||||
|
if ($type == 'save') {
|
||||||
|
if (env("SYSTEM_SETTING") == 'disabled') {
|
||||||
|
return Base::retError('当前环境禁止修改');
|
||||||
|
}
|
||||||
|
$all = Request::input();
|
||||||
|
foreach ($all as $key => $value) {
|
||||||
|
if (!in_array($key, [
|
||||||
|
'openai_key',
|
||||||
|
'openai_agency',
|
||||||
|
'claude_token',
|
||||||
|
'claude_agency',
|
||||||
|
])) {
|
||||||
|
unset($all[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$backup = $setting;
|
||||||
|
$setting = Base::setting('aibotSetting', Base::newTrim($all));
|
||||||
|
//
|
||||||
|
if ($backup['openai_key'] != $setting['openai_key']) {
|
||||||
|
$botUser = User::botGetOrCreate('ai-openai');
|
||||||
|
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
|
||||||
|
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($backup['claude_token'] != $setting['claude_token']) {
|
||||||
|
$botUser = User::botGetOrCreate('ai-claude');
|
||||||
|
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
|
||||||
|
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
if (env("SYSTEM_SETTING") == 'disabled') {
|
||||||
|
foreach ([
|
||||||
|
'openai_key',
|
||||||
|
'openai_agency',
|
||||||
|
'claude_token',
|
||||||
|
'claude_agency',
|
||||||
|
] as $item) {
|
||||||
|
$setting[$item] = substr($setting[$item], 0, 4) . str_repeat('*', strlen($setting[$item]) - 8) . substr($setting[$item], -4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} api/system/setting/checkin 04. 获取签到设置、保存签到设置(限管理员)
|
* @api {get} api/system/setting/checkin 04. 获取签到设置、保存签到设置(限管理员)
|
||||||
*
|
*
|
||||||
|
|||||||
@ -533,6 +533,10 @@ class User extends AbstractModel
|
|||||||
return url("images/avatar/default_anon.png");
|
return url("images/avatar/default_anon.png");
|
||||||
case 'approval-alert@bot.system':
|
case 'approval-alert@bot.system':
|
||||||
return url("images/avatar/default_approval.png");
|
return url("images/avatar/default_approval.png");
|
||||||
|
case 'ai-openai@bot.system':
|
||||||
|
return url("images/avatar/default_openai.png");
|
||||||
|
case 'ai-claude@bot.system':
|
||||||
|
return url("images/avatar/default_claude.png");
|
||||||
case 'bot-manager@bot.system':
|
case 'bot-manager@bot.system':
|
||||||
return url("images/avatar/default_bot.png");
|
return url("images/avatar/default_bot.png");
|
||||||
}
|
}
|
||||||
@ -619,6 +623,12 @@ class User extends AbstractModel
|
|||||||
case 'approval-alert':
|
case 'approval-alert':
|
||||||
$update['nickname'] = '审批';
|
$update['nickname'] = '审批';
|
||||||
break;
|
break;
|
||||||
|
case 'ai-openai':
|
||||||
|
$update['nickname'] = 'ChatGPT';
|
||||||
|
break;
|
||||||
|
case 'ai-claude':
|
||||||
|
$update['nickname'] = 'Claude';
|
||||||
|
break;
|
||||||
case 'bot-manager':
|
case 'bot-manager':
|
||||||
$update['nickname'] = '机器人管理';
|
$update['nickname'] = '机器人管理';
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -81,22 +81,7 @@ class BotReceiveMsgTask extends AbstractTask
|
|||||||
if ($command
|
if ($command
|
||||||
&& !str_starts_with($command, '/')
|
&& !str_starts_with($command, '/')
|
||||||
&& ($dialog->type === 'user' || $this->mention)) {
|
&& ($dialog->type === 'user' || $this->mention)) {
|
||||||
$userBot = UserBot::whereBotId($botUser->userid)->first();
|
$this->botManagerWebhook($command, $msg, $botUser, $dialog);
|
||||||
if ($userBot && preg_match("/^https*:\/\//", $userBot->webhook_url)) {
|
|
||||||
Ihttp::ihttp_post($userBot->webhook_url, [
|
|
||||||
'text' => $command,
|
|
||||||
'token' => User::generateToken($botUser),
|
|
||||||
'dialog_id' => $dialog->id,
|
|
||||||
'dialog_type' => $dialog->type,
|
|
||||||
'msg_id' => $msg->id,
|
|
||||||
'msg_uid' => $msg->userid,
|
|
||||||
'mention' => $this->mention ? 1 : 0,
|
|
||||||
'bot_uid' => $botUser->userid,
|
|
||||||
'version' => Base::getVersion(),
|
|
||||||
], 10);
|
|
||||||
$userBot->webhook_num++;
|
|
||||||
$userBot->save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ($dialog->type !== 'user') {
|
if ($dialog->type !== 'user') {
|
||||||
return;
|
return;
|
||||||
@ -366,6 +351,76 @@ class BotReceiveMsgTask extends AbstractTask
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机器人处理 Webhook
|
||||||
|
* @param string $command
|
||||||
|
* @param WebSocketDialogMsg $msg
|
||||||
|
* @param User $botUser
|
||||||
|
* @param WebSocketDialog $dialog
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function botManagerWebhook(string $command, WebSocketDialogMsg $msg, User $botUser, WebSocketDialog $dialog)
|
||||||
|
{
|
||||||
|
$serverUrl = 'http://' . env('APP_IPPR') . '.3';
|
||||||
|
$userBot = null;
|
||||||
|
$extras = [];
|
||||||
|
switch ($botUser->email) {
|
||||||
|
// ChatGPT 机器人
|
||||||
|
case 'ai-openai@bot.system':
|
||||||
|
$setting = Base::setting('aibotSetting');
|
||||||
|
$webhookUrl = "{$serverUrl}/ai/openai/send";
|
||||||
|
$extras = [
|
||||||
|
'openai_key' => $setting['openai_key'],
|
||||||
|
'openai_agency' => $setting['openai_agency'],
|
||||||
|
'server_url' => $serverUrl,
|
||||||
|
];
|
||||||
|
if (empty($extras['openai_key'])) {
|
||||||
|
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => 'Robot disabled'], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Claude 机器人
|
||||||
|
case 'ai-claude@bot.system':
|
||||||
|
$setting = Base::setting('aibotSetting');
|
||||||
|
$webhookUrl = "{$serverUrl}/ai/claude/send";
|
||||||
|
$extras = [
|
||||||
|
'claude_token' => $setting['claude_token'],
|
||||||
|
'claude_agency' => $setting['claude_agency'],
|
||||||
|
'server_url' => $serverUrl,
|
||||||
|
];
|
||||||
|
if (empty($extras['claude_token'])) {
|
||||||
|
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => 'Robot disabled'], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// 其他机器人
|
||||||
|
default:
|
||||||
|
$userBot = UserBot::whereBotId($botUser->userid)->first();
|
||||||
|
$webhookUrl = $userBot?->webhook_url;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!preg_match("/^https*:\/\//", $webhookUrl)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
Ihttp::ihttp_post($webhookUrl, [
|
||||||
|
'text' => $command,
|
||||||
|
'token' => User::generateToken($botUser),
|
||||||
|
'dialog_id' => $dialog->id,
|
||||||
|
'dialog_type' => $dialog->type,
|
||||||
|
'msg_id' => $msg->id,
|
||||||
|
'msg_uid' => $msg->userid,
|
||||||
|
'mention' => $this->mention ? 1 : 0,
|
||||||
|
'bot_uid' => $botUser->userid,
|
||||||
|
'version' => Base::getVersion(),
|
||||||
|
'extras' => Base::array2json($extras)
|
||||||
|
], 10);
|
||||||
|
if ($userBot) {
|
||||||
|
$userBot->webhook_num++;
|
||||||
|
$userBot->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $botId
|
* @param $botId
|
||||||
* @param $userid
|
* @param $userid
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\WebSocketDialog;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateDefaultRobot extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
User::botGetOrCreate('ai-openai');
|
||||||
|
User::botGetOrCreate('ai-claude');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
php:
|
php:
|
||||||
container_name: "dootask-php-${APP_ID}"
|
container_name: "dootask-php-${APP_ID}"
|
||||||
image: "kuaifan/php:swoole-8.0.rc10"
|
image: "kuaifan/php:swoole-8.0.rc11"
|
||||||
shm_size: "2gb"
|
shm_size: "2gb"
|
||||||
ulimits:
|
ulimits:
|
||||||
core:
|
core:
|
||||||
@ -12,6 +12,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./docker/crontab/crontab.conf:/etc/supervisor/conf.d/crontab.conf
|
- ./docker/crontab/crontab.conf:/etc/supervisor/conf.d/crontab.conf
|
||||||
- ./docker/php/php.conf:/etc/supervisor/conf.d/php.conf
|
- ./docker/php/php.conf:/etc/supervisor/conf.d/php.conf
|
||||||
|
- ./docker/ai/ai.conf:/etc/supervisor/conf.d/ai.conf
|
||||||
- ./docker/php/php.ini:/usr/local/etc/php/php.ini
|
- ./docker/php/php.ini:/usr/local/etc/php/php.ini
|
||||||
- ./docker/log/supervisor:/var/log/supervisor
|
- ./docker/log/supervisor:/var/log/supervisor
|
||||||
- ./:/var/www
|
- ./:/var/www
|
||||||
|
|||||||
1
docker/ai/.gitignore
vendored
Normal file
1
docker/ai/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.cache
|
||||||
10
docker/ai/ai.conf
Normal file
10
docker/ai/ai.conf
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[program:ai]
|
||||||
|
directory=/var/www/docker/ai
|
||||||
|
command=/usr/lib/doo/doo_cli ai
|
||||||
|
numprocs=1
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startretries=100
|
||||||
|
user=root
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/var/log/supervisor/%(program_name)s.log
|
||||||
@ -183,6 +183,15 @@ server {
|
|||||||
proxy_set_header Content-Length $request_length;
|
proxy_set_header Content-Length $request_length;
|
||||||
proxy_pass http://service/api/approve/verifyToken;
|
proxy_pass http://service/api/approve/verifyToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# AI
|
||||||
|
location /ai/ {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Scheme $scheme;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_pass http://php:8881/;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include /etc/nginx/conf.d/conf.d/*.conf;
|
include /etc/nginx/conf.d/conf.d/*.conf;
|
||||||
|
|||||||
BIN
public/images/avatar/default_claude.png
Normal file
BIN
public/images/avatar/default_claude.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
BIN
public/images/avatar/default_openai.png
Normal file
BIN
public/images/avatar/default_openai.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<div class="setting-component-item">
|
||||||
|
<Form ref="formData" :model="formData" :rules="ruleData" label-width="auto" @submit.native.prevent>
|
||||||
|
<div class="block-setting-box">
|
||||||
|
<h3>{{ $L('ChatGTP') }}</h3>
|
||||||
|
<div class="form-box">
|
||||||
|
<FormItem label="Key" prop="openai_key">
|
||||||
|
<Input :maxlength="255" v-model="formData.openai_key"/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem :label="$L('使用代理')" prop="openai_agency">
|
||||||
|
<Input :maxlength="500" v-model="formData.openai_agency" :placeholder="$L('支持 http 或 socks 代理')"/>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="block-setting-box">
|
||||||
|
<h3>{{ $L('Claude') }}</h3>
|
||||||
|
<div class="form-box">
|
||||||
|
<FormItem label="Token" prop="claude_token">
|
||||||
|
<Input :maxlength="255" v-model="formData.claude_token"/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem :label="$L('使用代理')" prop="claude_agency">
|
||||||
|
<Input :maxlength="500" v-model="formData.claude_agency" :placeholder="$L('支持 http 或 socks 代理')"/>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
<div class="setting-footer">
|
||||||
|
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{ $L('提交') }}</Button>
|
||||||
|
<Button :loading="loadIng > 0" @click="resetForm" style="margin-left: 8px">{{ $L('重置') }}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "SystemAibot",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loadIng: 0,
|
||||||
|
formData: {},
|
||||||
|
ruleData: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.systemSetting();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
submitForm() {
|
||||||
|
this.$refs.formData.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
this.systemSetting(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
resetForm() {
|
||||||
|
this.formData = $A.cloneJSON(this.formDatum_bak);
|
||||||
|
},
|
||||||
|
|
||||||
|
systemSetting(save) {
|
||||||
|
this.loadIng++;
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'system/setting/aibot?type=' + (save ? 'save' : 'all'),
|
||||||
|
data: this.formData,
|
||||||
|
}).then(({data}) => {
|
||||||
|
if (save) {
|
||||||
|
$A.messageSuccess('修改成功');
|
||||||
|
}
|
||||||
|
this.formData = data;
|
||||||
|
this.formDatum_bak = $A.cloneJSON(this.formData);
|
||||||
|
}).catch(({msg}) => {
|
||||||
|
if (save) {
|
||||||
|
$A.modalError(msg);
|
||||||
|
}
|
||||||
|
}).finally(_ => {
|
||||||
|
this.loadIng--;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -10,6 +10,9 @@
|
|||||||
<TabPane :label="$L('项目模板')" name="columnTemplate">
|
<TabPane :label="$L('项目模板')" name="columnTemplate">
|
||||||
<SystemColumnTemplate/>
|
<SystemColumnTemplate/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
<TabPane :label="$L('AI机器人')" name="aibot">
|
||||||
|
<SystemAibot/>
|
||||||
|
</TabPane>
|
||||||
<TabPane :label="$L('会议功能')" name="meeting">
|
<TabPane :label="$L('会议功能')" name="meeting">
|
||||||
<SystemMeeting/>
|
<SystemMeeting/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
@ -38,9 +41,11 @@ import SystemAppPush from "./components/SystemAppPush";
|
|||||||
import SystemMeeting from "./components/SystemMeeting";
|
import SystemMeeting from "./components/SystemMeeting";
|
||||||
import SystemCheckin from "./components/SystemCheckin";
|
import SystemCheckin from "./components/SystemCheckin";
|
||||||
import SystemThirdAccess from "./components/SystemThirdAccess";
|
import SystemThirdAccess from "./components/SystemThirdAccess";
|
||||||
|
import SystemAibot from "./components/SystemAibot.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
SystemAibot,
|
||||||
SystemThirdAccess,
|
SystemThirdAccess,
|
||||||
SystemCheckin,
|
SystemCheckin,
|
||||||
SystemMeeting,
|
SystemMeeting,
|
||||||
|
|||||||
BIN
resources/assets/statics/public/images/avatar/default_claude.png
Normal file
BIN
resources/assets/statics/public/images/avatar/default_claude.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
BIN
resources/assets/statics/public/images/avatar/default_openai.png
Normal file
BIN
resources/assets/statics/public/images/avatar/default_openai.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
Loading…
x
Reference in New Issue
Block a user