perf: 添加ChatGPT、Claude智能机器人

This commit is contained in:
kuaifan 2023-07-27 16:00:16 +08:00
parent efdc4c5229
commit 569145196e
15 changed files with 290 additions and 18 deletions

View File

@ -658,7 +658,7 @@ class DialogController extends AbstractController
$userid = intval(Request::input('userid'));
$stream_url = trim(Request::input('stream_url'));
//
if ($userid < 1 || !str_starts_with($stream_url, 'http')) {
if ($userid <= 0) {
return Base::retError('参数错误');
}
//

View File

@ -2,6 +2,8 @@
namespace App\Http\Controllers\Api;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use Request;
use Session;
use Response;
@ -228,6 +230,72 @@ class SystemController extends AbstractController
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. 获取签到设置、保存签到设置(限管理员)
*

View File

@ -533,6 +533,10 @@ class User extends AbstractModel
return url("images/avatar/default_anon.png");
case 'approval-alert@bot.system':
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':
return url("images/avatar/default_bot.png");
}
@ -619,6 +623,12 @@ class User extends AbstractModel
case 'approval-alert':
$update['nickname'] = '审批';
break;
case 'ai-openai':
$update['nickname'] = 'ChatGPT';
break;
case 'ai-claude':
$update['nickname'] = 'Claude';
break;
case 'bot-manager':
$update['nickname'] = '机器人管理';
break;

View File

@ -81,22 +81,7 @@ class BotReceiveMsgTask extends AbstractTask
if ($command
&& !str_starts_with($command, '/')
&& ($dialog->type === 'user' || $this->mention)) {
$userBot = UserBot::whereBotId($botUser->userid)->first();
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();
}
$this->botManagerWebhook($command, $msg, $botUser, $dialog);
}
if ($dialog->type !== 'user') {
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 $userid

View File

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

View File

@ -3,7 +3,7 @@ version: '3'
services:
php:
container_name: "dootask-php-${APP_ID}"
image: "kuaifan/php:swoole-8.0.rc10"
image: "kuaifan/php:swoole-8.0.rc11"
shm_size: "2gb"
ulimits:
core:
@ -12,6 +12,7 @@ services:
volumes:
- ./docker/crontab/crontab.conf:/etc/supervisor/conf.d/crontab.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/log/supervisor:/var/log/supervisor
- ./:/var/www

1
docker/ai/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.cache

10
docker/ai/ai.conf Normal file
View 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

View File

@ -183,6 +183,15 @@ server {
proxy_set_header Content-Length $request_length;
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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

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

View File

@ -10,6 +10,9 @@
<TabPane :label="$L('项目模板')" name="columnTemplate">
<SystemColumnTemplate/>
</TabPane>
<TabPane :label="$L('AI机器人')" name="aibot">
<SystemAibot/>
</TabPane>
<TabPane :label="$L('会议功能')" name="meeting">
<SystemMeeting/>
</TabPane>
@ -38,9 +41,11 @@ import SystemAppPush from "./components/SystemAppPush";
import SystemMeeting from "./components/SystemMeeting";
import SystemCheckin from "./components/SystemCheckin";
import SystemThirdAccess from "./components/SystemThirdAccess";
import SystemAibot from "./components/SystemAibot.vue";
export default {
components: {
SystemAibot,
SystemThirdAccess,
SystemCheckin,
SystemMeeting,

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB