isAuth = false; parent::__construct(); } /** * 解析指定供应商的调用配置 * * @return array{provider:string,api_url:string,api_key:string,model:string,timeout:float,platform_id:int,api_key_id:int} */ public function resolve(?string $providerOverride = null): array { $sys = SystemConfigService::getInstance(); $provider = strtolower(trim((string) ( $providerOverride ?: $sys->getValue(self::CFG_PROVIDER, '') ?: config('ai.default_provider', 'deepseek') ))); $modelOverride = trim((string) $sys->getValue(self::CFG_MODEL, '')); $keyId = (int) $sys->getValue(self::CFG_API_KEY_ID, '0'); $platform = AiPlatformModel::where('code', $provider) ->where('deleted_at', 0) ->where('status', 1) ->first(); $apiUrl = ''; $model = ''; $apiKey = ''; $platformId = 0; $timeout = 60.0; if ($platform) { $platformId = (int) $platform->id; $apiUrl = trim((string) $platform->api_url); // 模型:系统配置覆盖 → 模型表默认 → 模型表首个启用 if ($modelOverride !== '') { $model = $modelOverride; } else { $defaultModel = AiModelModel::where('platform_id', $platformId) ->where('deleted_at', 0) ->where('status', 1) ->where('is_default', 1) ->value('code'); if ($defaultModel) { $model = trim((string) $defaultModel); } else { $firstModel = AiModelModel::where('platform_id', $platformId) ->where('deleted_at', 0) ->where('status', 1) ->orderByDesc('sort') ->orderBy('id') ->value('code'); $model = trim((string) ($firstModel ?: '')); } } // 指定密钥优先;否则默认密钥;再否则取该平台启用中的第一条 $keyQuery = AiApiKeyModel::where('platform_id', $platformId) ->where('deleted_at', 0) ->where('status', 1); if ($keyId > 0) { $keyRow = (clone $keyQuery)->where('id', $keyId)->first(); } else { $keyRow = null; } if (!$keyRow) { $keyRow = (clone $keyQuery)->where('is_default', 1)->first() ?: $keyQuery->orderByDesc('sort')->orderBy('id')->first(); } if ($keyRow) { $apiKey = $this->decryptApiKey((string) $keyRow->api_key); $keyId = (int) $keyRow->id; } } // 回落 env,保证未录入密钥时仍可用旧配置 $envCfg = config('ai.' . $provider, []); if ($apiUrl === '') { $apiUrl = (string) ($envCfg['api_url'] ?? ''); } if ($model === '') { $model = (string) ($envCfg['model'] ?? ''); } if ($apiKey === '') { if ($provider === 'spark') { $apiKey = (string) ($envCfg['password'] ?? ''); } else { $apiKey = (string) ($envCfg['api_key'] ?? ''); } } $timeout = (float) ($envCfg['timeout'] ?? 60); return [ 'provider' => $provider, 'api_url' => $apiUrl, 'api_key' => $apiKey, 'model' => $model, 'timeout' => $timeout, 'platform_id' => $platformId, 'api_key_id' => $keyId, ]; } /** * 库内 AES 解密 api_key;非加密串原样返回(兼容过渡) * 委托 FieldEncryptService,与 nl_ai_api_key 密文格式一致 */ public function decryptApiKey(string $cipher): string { return FieldEncryptService::getInstance()->decryptFromStorage($cipher, true); } /** * 明文入库前 AES 加密 * 委托 FieldEncryptService,写出 nl_ase_256_ 前缀密文 */ public function encryptApiKey(string $plain): string { return FieldEncryptService::getInstance()->encryptForStorage($plain); } /** * 系统配置页:启用中的平台卡片 + 模型 + 密钥选项 * 为什么组装成卡片结构:前端 C 端风格按平台展示,减少二次请求 */ public function optionsForConfig(): array { $platforms = AiPlatformModel::where('deleted_at', 0) ->where('status', 1) ->orderByDesc('sort') ->orderBy('id') ->get() ->toArray(); $models = AiModelModel::where('deleted_at', 0) ->where('status', 1) ->orderByDesc('is_default') ->orderByDesc('sort') ->orderBy('id') ->get(['id', 'platform_id', 'code', 'name', 'description', 'is_default']) ->toArray(); $keys = AiApiKeyModel::where('deleted_at', 0) ->where('status', 1) ->orderByDesc('is_default') ->orderByDesc('sort') ->orderBy('id') ->get(['id', 'platform_id', 'name', 'is_default']) ->toArray(); $modelMap = []; foreach ($models as $m) { $pid = (int) $m['platform_id']; $modelMap[$pid][] = [ 'id' => (int) $m['id'], 'label' => $m['name'] . '(' . $m['code'] . ')', 'value' => (string) $m['code'], 'code' => (string) $m['code'], 'name' => (string) $m['name'], 'description' => (string) ($m['description'] ?? ''), 'is_default' => (int) ($m['is_default'] ?? 0), ]; } $keyMap = []; foreach ($keys as $k) { $pid = (int) $k['platform_id']; $name = trim((string) ($k['name'] ?? '')); if ($name === '') { $name = '密钥#' . (int) $k['id']; } $keyMap[$pid][] = [ 'id' => (int) $k['id'], 'key_name' => $name, 'name' => $name, 'label' => $name, 'value' => (int) $k['id'], 'is_default' => (int) ($k['is_default'] ?? 0), ]; } $cards = []; foreach ($platforms as $p) { $id = (int) $p['id']; $platModels = $modelMap[$id] ?? []; $defaultModel = ''; foreach ($platModels as $pm) { if ((int) $pm['is_default'] === 1) { $defaultModel = $pm['code']; break; } } if ($defaultModel === '' && !empty($platModels[0]['code'])) { $defaultModel = (string) $platModels[0]['code']; } $cards[] = [ 'id' => $id, 'code' => $p['code'], 'name' => $p['name'], 'logo' => $p['logo'], 'description' => $p['description'], 'default_model' => $defaultModel, 'api_url' => $p['api_url'], 'models' => $platModels, 'keys' => $keyMap[$id] ?? [], ]; } $sys = SystemConfigService::getInstance(); return [ 'platforms' => $cards, 'active' => [ 'provider' => (string) $sys->getValue(self::CFG_PROVIDER, config('ai.default_provider', 'deepseek')), 'api_key_id' => (int) $sys->getValue(self::CFG_API_KEY_ID, '0'), 'model' => (string) $sys->getValue(self::CFG_MODEL, ''), ], ]; } /** * 保存默认运行时组合(平台 / 模型 / 密钥) * 为什么校验平台存在:避免写入无效编码导致代码生成 AI 调用失败 */ public function saveActiveConfig(string $provider, string $model, int $apiKeyId): array { $provider = strtolower(trim($provider)); $model = trim($model); if ($provider === '') { $this->utils->errorThrow('请选择 AI 平台'); } $platform = AiPlatformModel::where('code', $provider) ->where('deleted_at', 0) ->where('status', 1) ->first(); if (!$platform) { $this->utils->errorThrow('AI 平台不存在或已禁用'); } if ($model === '') { $this->utils->errorThrow('请选择模型'); } $modelExists = AiModelModel::where('platform_id', (int) $platform->id) ->where('code', $model) ->where('deleted_at', 0) ->where('status', 1) ->exists(); if (!$modelExists) { $this->utils->errorThrow('模型不属于当前平台或已禁用'); } if ($apiKeyId > 0) { $keyExists = AiApiKeyModel::where('id', $apiKeyId) ->where('platform_id', (int) $platform->id) ->where('deleted_at', 0) ->where('status', 1) ->exists(); if (!$keyExists) { $this->utils->errorThrow('密钥不属于当前平台或已禁用'); } } $sys = SystemConfigService::getInstance(); $sys->setValue(self::CFG_PROVIDER, $provider, '当前启用的 AI 平台编码'); $sys->setValue(self::CFG_MODEL, $model, '当前启用的模型编码'); $sys->setValue(self::CFG_API_KEY_ID, (string) $apiKeyId, '当前启用的 API 密钥 ID'); return [ 'provider' => $provider, 'model' => $model, 'api_key_id' => $apiKeyId, ]; } }