Files
chatroom/app/Http/Controllers/Admin/AiProviderController.php

280 lines
8.8 KiB
PHP
Raw Normal View History

<?php
/**
* 文件功能AI 厂商配置管理控制器(管理后台)
*
* 提供完整的 AI 厂商 CRUD 管理功能,包括:
* - 列表/新增/编辑/删除 AI 厂商配置
* - 切换启用/禁用状态
* - 设置默认厂商(互斥,同时只有一个默认)
* - 全局开关控制机器人是否启用
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AiProviderConfig;
use App\Models\Sysparam;
use App\Services\ChatStateService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\View\View;
class AiProviderController extends Controller
{
/**
* 构造函数
*/
public function __construct(
private readonly ChatStateService $chatState,
) {}
/**
* 显示 AI 厂商配置列表页面
*
* 列出所有已配置的 AI 厂商,并显示全局开关状态。
*
* @return View AI 厂商管理页面
*/
public function index(): View
{
$providers = AiProviderConfig::orderBy('sort_order')->get();
$chatbotEnabled = Sysparam::getValue('chatbot_enabled', '0') === '1';
return view('admin.ai-providers.index', compact('providers', 'chatbotEnabled'));
}
/**
* 新增 AI 厂商配置
*
* @param Request $request 请求对象
* @return RedirectResponse 重定向到列表页
*/
public function store(Request $request): RedirectResponse
{
$data = $request->validate([
'provider' => 'required|string|max:50',
'name' => 'required|string|max:100',
'api_key' => 'required|string',
'api_endpoint' => 'required|url|max:255',
'model' => 'required|string|max:100',
'temperature' => 'nullable|numeric|min:0|max:2',
'max_tokens' => 'nullable|integer|min:100|max:32000',
'sort_order' => 'nullable|integer|min:0',
]);
// 加密 API Key
$data['api_key'] = Crypt::encryptString($data['api_key']);
$data['temperature'] = $data['temperature'] ?? 0.3;
$data['max_tokens'] = $data['max_tokens'] ?? 2048;
$data['sort_order'] = $data['sort_order'] ?? 0;
$data['is_enabled'] = true;
$data['is_default'] = false;
AiProviderConfig::create($data);
return redirect()->route('admin.ai-providers.index')
->with('success', '已成功添加 AI 厂商配置!');
}
/**
* 更新 AI 厂商配置
*
* @param Request $request 请求对象
* @param int $id 厂商配置 ID
* @return RedirectResponse 重定向到列表页
*/
public function update(Request $request, int $id): RedirectResponse
{
$provider = AiProviderConfig::findOrFail($id);
$data = $request->validate([
'provider' => 'required|string|max:50',
'name' => 'required|string|max:100',
'api_key' => 'nullable|string',
'api_endpoint' => 'required|url|max:255',
'model' => 'required|string|max:100',
'temperature' => 'nullable|numeric|min:0|max:2',
'max_tokens' => 'nullable|integer|min:100|max:32000',
'sort_order' => 'nullable|integer|min:0',
]);
// 只在用户提供了新 API Key 时才更新(空值表示不修改)
if (! empty($data['api_key'])) {
$data['api_key'] = Crypt::encryptString($data['api_key']);
} else {
unset($data['api_key']);
}
$provider->update($data);
return redirect()->route('admin.ai-providers.index')
->with('success', "已更新 {$provider->name} 的配置!");
}
/**
* 切换 AI 厂商的启用/禁用状态
*
* @param int $id 厂商配置 ID
* @return JsonResponse 操作结果
*/
public function toggleEnabled(int $id): JsonResponse
{
$provider = AiProviderConfig::findOrFail($id);
$provider->is_enabled = ! $provider->is_enabled;
$provider->save();
$status = $provider->is_enabled ? '启用' : '禁用';
return response()->json([
'status' => 'success',
'message' => "{$provider->name}{$status}",
'is_enabled' => $provider->is_enabled,
]);
}
/**
* 设置指定厂商为默认使用
*
* 互斥操作:将其他厂商的 is_default 全部置为 false
*
* @param int $id 厂商配置 ID
* @return JsonResponse 操作结果
*/
public function setDefault(int $id): JsonResponse
{
$provider = AiProviderConfig::findOrFail($id);
// 先将所有厂商的默认标记清除
AiProviderConfig::where('is_default', true)->update(['is_default' => false]);
// 设置当前厂商为默认
$provider->is_default = true;
$provider->is_enabled = true; // 默认的必须是启用状态
$provider->save();
return response()->json([
'status' => 'success',
'message' => "{$provider->name} 已设为默认 AI 厂商",
]);
}
/**
* 切换聊天机器人全局开关
*
* 通过 sysparam chatbot_enabled 参数控制是否在聊天室中显示 AI 机器人。
*
* @param Request $request 请求对象
* @return JsonResponse 操作结果
*/
public function toggleChatBot(Request $request): JsonResponse
{
$current = Sysparam::getValue('chatbot_enabled', '0');
$newValue = $current === '1' ? '0' : '1';
// 更新 sysparam
Sysparam::updateOrCreate(
['alias' => 'chatbot_enabled'],
[
'body' => $newValue,
'guidetxt' => 'AI聊天机器人开关1=开启0=关闭)',
]
);
// 刷新缓存
$this->chatState->setSysParam('chatbot_enabled', $newValue);
Sysparam::clearCache('chatbot_enabled');
$status = $newValue === '1' ? '开启' : '关闭';
return response()->json([
'status' => 'success',
'message' => "聊天机器人已{$status}",
'enabled' => $newValue === '1',
]);
}
/**
* 测试指定 AI 厂商的接口连通性
*
* 发送一条简短的测试消息,返回响应结果和耗时,用于验证配置是否正确。
*
* @param int $id 厂商配置 ID
* @return JsonResponse 测试结果
*/
public function testConnection(int $id): JsonResponse
{
$provider = AiProviderConfig::findOrFail($id);
$apiKey = $provider->getDecryptedApiKey();
$base = rtrim($provider->api_endpoint, '/');
$endpoint = str_ends_with($base, '/v1')
? $base.'/chat/completions'
: $base.'/v1/chat/completions';
$startTime = microtime(true);
try {
$response = \Illuminate\Support\Facades\Http::withToken($apiKey)
->timeout(60) // Ollama 本地模型冷启动较慢,给 60s
->post($endpoint, [
'model' => $provider->model,
'messages' => [
['role' => 'user', 'content' => '请用一句话介绍你自己。'],
],
'max_tokens' => 64,
]);
$ms = (int) ((microtime(true) - $startTime) * 1000);
$data = $response->json();
if (! $response->successful()) {
return response()->json([
'ok' => false,
'message' => "HTTP {$response->status()}{$response->body()}",
'ms' => $ms,
]);
}
$reply = $data['choices'][0]['message']['content'] ?? '(无回复内容)';
return response()->json([
'ok' => true,
'message' => trim($reply),
'ms' => $ms,
'model' => $data['model'] ?? $provider->model,
]);
} catch (\Illuminate\Http\Client\ConnectionException $e) {
$ms = (int) ((microtime(true) - $startTime) * 1000);
return response()->json([
'ok' => false,
'message' => '连接失败:'.$e->getMessage(),
'ms' => $ms,
]);
}
}
/**
* 删除 AI 厂商配置
*
* @param int $id 厂商配置 ID
* @return RedirectResponse 重定向到列表页
*/
public function destroy(int $id): RedirectResponse
{
$provider = AiProviderConfig::findOrFail($id);
$name = $provider->name;
$provider->delete();
return redirect()->route('admin.ai-providers.index')
->with('success', "已删除 {$name}");
}
}