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 厂商的接口连通性 * * 通过 GET /v1/models 检查端点可达性与 API Key 有效性,毫秒级响应, * 不触发模型推理,避免经 Cloudflare 代理时因推理耗时导致 524 超时。 * * @param int $id 厂商配置 ID * @return JsonResponse 测试结果(含可用模型列表) */ public function testConnection(int $id): JsonResponse { $provider = AiProviderConfig::findOrFail($id); $apiKey = $provider->getDecryptedApiKey(); $base = rtrim($provider->api_endpoint, '/'); // 拼接 /v1/models 端点(检查连通性,不触发推理) $modelsUrl = str_ends_with($base, '/v1') ? $base.'/models' : $base.'/v1/models'; $startTime = microtime(true); try { $response = \Illuminate\Support\Facades\Http::withToken($apiKey) ->timeout(10) ->get($modelsUrl); $ms = (int) ((microtime(true) - $startTime) * 1000); if (! $response->successful()) { return response()->json([ 'ok' => false, 'message' => "HTTP {$response->status()}:{$response->body()}", 'ms' => $ms, ]); } $data = $response->json(); // 提取可用模型列表(兼容 Ollama 和 OpenAI 格式) $models = collect($data['models'] ?? $data['data'] ?? []) ->pluck('id') ->filter() ->values() ->toArray(); $modelList = count($models) > 0 ? implode('、', array_slice($models, 0, 5)).(count($models) > 5 ? ' 等' : '') : $provider->model; return response()->json([ 'ok' => true, 'message' => "接口连通正常,可用模型:{$modelList}", 'ms' => $ms, 'models' => $models, ]); } 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}!"); } }