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