From 01ff10385a40b614061e9ee7c5ce726cb0384998 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Sat, 26 Jul 2025 12:01:37 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20AI=20=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/SystemController.php | 48 +++++++- app/Models/Setting.php | 31 +++-- app/Models/UserBot.php | 2 +- app/Module/Extranet.php | 47 ++++---- ..._07_26_112113_create_ai_settings_table.php | 34 ++++++ .../setting/components/SystemAiAssistant.vue | 109 ++++++++++++++++++ .../setting/components/SystemSetting.vue | 4 +- .../assets/js/pages/manage/setting/system.vue | 8 +- 8 files changed, 241 insertions(+), 42 deletions(-) create mode 100644 database/migrations/2025_07_26_112113_create_ai_settings_table.php create mode 100644 resources/assets/js/pages/manage/setting/components/SystemAiAssistant.vue diff --git a/app/Http/Controllers/Api/SystemController.php b/app/Http/Controllers/Api/SystemController.php index 01c6f26be..a1c779ae6 100755 --- a/app/Http/Controllers/Api/SystemController.php +++ b/app/Http/Controllers/Api/SystemController.php @@ -107,10 +107,10 @@ class SystemController extends AbstractController } } if ($all['voice2text'] == 'open' && !Setting::AIOpen()) { - return Base::retError('开启语音转文字功能需要在应用中开启 ChatGPT AI 机器人。'); + return Base::retError('开启语音转文字功能需要先设置 AI 助理。'); } if ($all['translation'] == 'open' && !Setting::AIOpen()) { - return Base::retError('开启翻译功能需要在应用中开启 ChatGPT AI 机器人。'); + return Base::retError('开启翻译功能需要先设置 AI 助理。'); } if ($all['system_alias'] == env('APP_NAME')) { $all['system_alias'] = ''; @@ -285,6 +285,48 @@ class SystemController extends AbstractController return Base::retSuccess('success', $setting ?: json_decode('{}')); } + /** + * @api {get} api/system/setting/ai 04. AI助手设置(限管理员) + * + * @apiVersion 1.0.0 + * @apiGroup system + * @apiName setting__ai + * + * @apiParam {String} type + * - get: 获取(默认) + * - save: 保存设置(参数:['ai_provider', 'ai_api_key', 'ai_api_url', 'ai_proxy']) + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function setting__ai() + { + User::auth('admin'); + // + $type = trim(Request::input('type')); + if ($type == 'save') { + if (env("SYSTEM_SETTING") == 'disabled') { + return Base::retError('当前环境禁止修改'); + } + $all = Base::newTrim(Request::input()); + foreach ($all as $key => $value) { + if (!in_array($key, [ + 'ai_provider', + 'ai_api_key', + 'ai_api_url', + 'ai_proxy', + ])) { + unset($all[$key]); + } + } + $setting = Base::setting('aiSetting', Base::newTrim($all)); + } else { + $setting = Base::setting('aiSetting'); + } + // + return Base::retSuccess('success', $setting ?: json_decode('{}')); + } + /** * @api {get} api/system/setting/aibot 04. 获取会议设置、保存AI机器人设置(限管理员) * @@ -393,7 +435,7 @@ class SystemController extends AbstractController } return Extranet::ollamaModels($baseUrl, $key, $agency); } - $models = Setting::AIDefaultModels($type); + $models = Setting::AIBotDefaultModels($type); if (empty($models)) { return Base::retError('未找到默认模型'); } diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 681fa7873..dcb672103 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -48,6 +48,7 @@ class Setting extends AbstractModel } $value = Base::json2array($value); switch ($this->name) { + // 系统设置 case 'system': $value['system_alias'] = $value['system_alias'] ?: env('APP_NAME'); $value['image_compress'] = $value['image_compress'] ?: 'open'; @@ -58,11 +59,21 @@ class Setting extends AbstractModel } break; + // 文件设置 case 'fileSetting': $value['permission_pack_type'] = $value['permission_pack_type'] ?: 'all'; $value['permission_pack_userids'] = is_array($value['permission_pack_userids']) ? $value['permission_pack_userids'] : []; break; + // AI 助手设置 + case 'aiSetting': + $value['ai_provider'] = $value['ai_provider'] ?: 'openai'; + $value['ai_api_key'] = $value['ai_api_key'] ?: ''; + $value['ai_api_url'] = $value['ai_api_url'] ?: ''; + $value['ai_proxy'] = $value['ai_proxy'] ?: ''; + break; + + // AI 机器人设置 case 'aibotSetting': if ($value['claude_token'] && empty($value['claude_key'])) { $value['claude_key'] = $value['claude_token']; @@ -81,12 +92,12 @@ class Setting extends AbstractModel $content = array_filter($content); } if (empty($content)) { - $content = self::AIDefaultModels($aiName); + $content = self::AIBotDefaultModels($aiName); } $content = implode("\n", $content); break; case 'model': - $models = Setting::AIModels2Array($array[$key . 's'], true); + $models = Setting::AIBotModels2Array($array[$key . 's'], true); $content = in_array($content, $models) ? $content : ($models[0] ?? ''); break; case 'temperature': @@ -105,22 +116,20 @@ class Setting extends AbstractModel } /** - * 是否开启AI - * @param $ai + * 是否开启 AI 助理 * @return bool */ - public static function AIOpen($ai = 'openai') + public static function AIOpen() { - $array = Base::setting('aibotSetting'); - return !!$array[$ai . '_key']; + return !!Base::settingFind('aiSetting', 'ai_api_key'); } /** - * AI默认模型 + * AI 机器人默认模型 * @param string $ai * @return array */ - public static function AIDefaultModels($ai = 'openai') + public static function AIBotDefaultModels($ai = 'openai') { return match ($ai) { 'openai' => [ @@ -205,12 +214,12 @@ class Setting extends AbstractModel } /** - * AI模型转数组 + * AI 机器人模型转数组 * @param $models * @param bool $retValue * @return array */ - public static function AIModels2Array($models, $retValue = false) + public static function AIBotModels2Array($models, $retValue = false) { $list = is_array($models) ? $models : explode("\n", $models); $array = []; diff --git a/app/Models/UserBot.php b/app/Models/UserBot.php index b8d2fc420..5cb77887c 100644 --- a/app/Models/UserBot.php +++ b/app/Models/UserBot.php @@ -194,7 +194,7 @@ class UserBot extends AbstractModel if ($match[1] === "ai-") { $aibotSetting = Base::setting('aibotSetting'); $aibotModel = $aibotSetting[$match[1] . '_model']; - $aibotModels = Setting::AIModels2Array($aibotSetting[$match[1] . '_models']); + $aibotModels = Setting::AIBotModels2Array($aibotSetting[$match[1] . '_models']); if ($aibotModels) { $menus = array_merge( [ diff --git a/app/Module/Extranet.php b/app/Module/Extranet.php index 5f8eb5dd5..eea8aa6a2 100644 --- a/app/Module/Extranet.php +++ b/app/Module/Extranet.php @@ -2,6 +2,7 @@ namespace App\Module; +use App\Models\Setting; use Cache; use Carbon\Carbon; use Illuminate\Support\Arr; @@ -24,25 +25,25 @@ class Extranet return Base::retError("语音文件不存在"); } $systemSetting = Base::setting('system'); - $aibotSetting = Base::setting('aibotSetting'); - if ($systemSetting['voice2text'] !== 'open' || empty($aibotSetting['openai_key'])) { + $aiSetting = Base::setting('aiSetting'); + if ($systemSetting['voice2text'] !== 'open' || !Setting::AIOpen()) { return Base::retError("语音转文字功能未开启"); } $extra = [ 'Content-Type' => 'multipart/form-data', - 'Authorization' => 'Bearer ' . $aibotSetting['openai_key'], + 'Authorization' => 'Bearer ' . $aiSetting['ai_api_key'], ]; - if ($aibotSetting['openai_agency']) { - $extra['CURLOPT_PROXY'] = $aibotSetting['openai_agency']; - $extra['CURLOPT_PROXYTYPE'] = str_contains($aibotSetting['openai_agency'], 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; + if ($aiSetting['ai_proxy']) { + $extra['CURLOPT_PROXY'] = $aiSetting['ai_proxy']; + $extra['CURLOPT_PROXYTYPE'] = str_contains($aiSetting['ai_proxy'], 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; } $post = array_merge($extParams, [ 'file' => new \CURLFile($filePath), 'model' => 'whisper-1', ]); $cacheKey = "openAItranscriptions::" . md5($filePath . '_' . Base::array2json($extra) . '_' . Base::array2json($extParams)); - $result = Cache::remember($cacheKey, Carbon::now()->addDays(), function() use ($extra, $post) { - $res = Ihttp::ihttp_request('https://api.openai.com/v1/audio/transcriptions', $post, $extra, 15); + $result = Cache::remember($cacheKey, Carbon::now()->addDays(), function() use ($aiSetting, $extra, $post) { + $res = Ihttp::ihttp_request(($aiSetting['ai_api_url'] ?: 'https://api.openai.com/v1') . '/audio/transcriptions', $post, $extra, 15); if (Base::isError($res)) { return Base::retError("语音转文字失败", $res); } @@ -67,17 +68,17 @@ class Extranet public static function openAItranslations($text, $targetLanguage) { $systemSetting = Base::setting('system'); - $aibotSetting = Base::setting('aibotSetting'); - if ($systemSetting['translation'] !== 'open' || empty($aibotSetting['openai_key'])) { + $aiSetting = Base::setting('aiSetting'); + if ($systemSetting['translation'] !== 'open' || !Setting::AIOpen()) { return Base::retError("翻译功能未开启"); } $extra = [ 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . $aibotSetting['openai_key'], + 'Authorization' => 'Bearer ' . $aiSetting['ai_api_key'], ]; - if ($aibotSetting['openai_agency']) { - $extra['CURLOPT_PROXY'] = $aibotSetting['openai_agency']; - $extra['CURLOPT_PROXYTYPE'] = str_contains($aibotSetting['openai_agency'], 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; + if ($aiSetting['ai_proxy']) { + $extra['CURLOPT_PROXY'] = $aiSetting['ai_proxy']; + $extra['CURLOPT_PROXYTYPE'] = str_contains($aiSetting['ai_proxy'], 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; } $post = json_encode([ "model" => "gpt-4o-mini", @@ -101,8 +102,8 @@ class Extranet ] ]); $cacheKey = "openAItranslations::" . md5(Base::array2json($extra) . '_' . Base::array2json($post)); - $result = Cache::remember($cacheKey, Carbon::now()->addDays(), function() use ($extra, $post) { - $res = Ihttp::ihttp_request('https://api.openai.com/v1/chat/completions', $post, $extra, 15); + $result = Cache::remember($cacheKey, Carbon::now()->addDays(), function() use ($aiSetting, $extra, $post) { + $res = Ihttp::ihttp_request(($aiSetting['ai_api_url'] ?: 'https://api.openai.com/v1') . '/chat/completions', $post, $extra, 15); if (Base::isError($res)) { return Base::retError("翻译失败", $res); } @@ -131,19 +132,19 @@ class Extranet */ public static function openAIGenerateTitle($text) { - $aibotSetting = Base::setting('aibotSetting'); - if (empty($aibotSetting['openai_key'])) { + $aiSetting = Base::setting('aiSetting'); + if (!Setting::AIOpen()) { return Base::retError("AI接口未配置"); } $extra = [ 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . $aibotSetting['openai_key'], + 'Authorization' => 'Bearer ' . $aiSetting['ai_api_key'], ]; - if ($aibotSetting['openai_agency']) { - $extra['CURLOPT_PROXY'] = $aibotSetting['openai_agency']; - $extra['CURLOPT_PROXYTYPE'] = str_contains($aibotSetting['openai_agency'], 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; + if ($aiSetting['ai_proxy']) { + $extra['CURLOPT_PROXY'] = $aiSetting['ai_proxy']; + $extra['CURLOPT_PROXYTYPE'] = str_contains($aiSetting['ai_proxy'], 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; } - $res = Ihttp::ihttp_request('https://api.openai.com/v1/chat/completions', json_encode([ + $res = Ihttp::ihttp_request(($aiSetting['ai_api_url'] ?: 'https://api.openai.com/v1') . '/chat/completions', json_encode([ "model" => "gpt-4o-mini", "messages" => [ [ diff --git a/database/migrations/2025_07_26_112113_create_ai_settings_table.php b/database/migrations/2025_07_26_112113_create_ai_settings_table.php new file mode 100644 index 000000000..05240a1d9 --- /dev/null +++ b/database/migrations/2025_07_26_112113_create_ai_settings_table.php @@ -0,0 +1,34 @@ + 'openai', + 'ai_api_key' => $setting['openai_key'], + 'ai_api_url' => $setting['openai_base_url'], + 'ai_proxy' => $setting['openai_agency'], + ]); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // This migration does not need to be reversible + } +} diff --git a/resources/assets/js/pages/manage/setting/components/SystemAiAssistant.vue b/resources/assets/js/pages/manage/setting/components/SystemAiAssistant.vue new file mode 100644 index 000000000..265bb1b69 --- /dev/null +++ b/resources/assets/js/pages/manage/setting/components/SystemAiAssistant.vue @@ -0,0 +1,109 @@ + + + + diff --git a/resources/assets/js/pages/manage/setting/components/SystemSetting.vue b/resources/assets/js/pages/manage/setting/components/SystemSetting.vue index c3eed78ce..54ede1119 100644 --- a/resources/assets/js/pages/manage/setting/components/SystemSetting.vue +++ b/resources/assets/js/pages/manage/setting/components/SystemSetting.vue @@ -187,7 +187,7 @@ {{$L('开启')}} {{$L('关闭')}} -
{{$L('长按语音消息可转换成文字。')}} ({{$L('需要在应用中开启 ChatGPT AI 机器人')}})
+
{{$L('长按语音消息可转换成文字。')}} ({{$L('需要先设置 AI 助理')}})
{{$L('关闭语音转文字功能。')}}
@@ -195,7 +195,7 @@ {{$L('开启')}} {{$L('关闭')}} -
{{$L('长按文本消息可翻译成当前设置的语言。')}} ({{$L('需要在应用中开启 ChatGPT AI 机器人')}})
+
{{$L('长按文本消息可翻译成当前设置的语言。')}} ({{$L('需要先设置 AI 助理')}})
{{$L('关闭文本消息翻译功能。')}}
diff --git a/resources/assets/js/pages/manage/setting/system.vue b/resources/assets/js/pages/manage/setting/system.vue index 0fffb108c..5ee2a77bc 100644 --- a/resources/assets/js/pages/manage/setting/system.vue +++ b/resources/assets/js/pages/manage/setting/system.vue @@ -2,7 +2,7 @@
- + @@ -13,6 +13,9 @@ + + +
@@ -22,9 +25,10 @@ import SystemSetting from "./components/SystemSetting"; import SystemTaskPriority from "./components/SystemTaskPriority"; import SystemColumnTemplate from "./components/SystemColumnTemplate"; import SystemFileSetting from "./components/SystemFileSetting"; +import SystemAiAssistant from "./components/SystemAiAssistant"; export default { - components: {SystemColumnTemplate, SystemTaskPriority, SystemSetting, SystemFileSetting}, + components: {SystemColumnTemplate, SystemTaskPriority, SystemSetting, SystemFileSetting, SystemAiAssistant}, data() { return { tabAction: 'setting',