cachePath = app()->getRuntimePath() . 'ai' . DIRECTORY_SEPARATOR . 'models_dev_cache.json'; } /** * 同步供应商数据到本地缓存. * * @return array 同步结果统计 * * @throws \Exception */ public function syncProviders(): array { $data = $this->fetchApi(); if ($data === false) { throw new \Exception('无法连接 models.dev API'); } $parsed = $this->parseApiData($data); $this->writeCache($parsed); return $parsed; } /** * 获取所有供应商列表. * * @return array */ public function getProviders(): array { $cache = $this->readCache(); if ($cache === null) { return []; } return $cache['providers'] ?? []; } /** * 获取指定供应商的模型列表. * * @param string $providerId 供应商标识 * * @return array */ public function getModelsByProvider(string $providerId): array { $cache = $this->readCache(); if ($cache === null) { return []; } $models = $cache['models'][$providerId] ?? []; return $models; } /** * 获取缓存状态信息. * * @return array */ public function getCacheStatus(): array { $exists = file_exists($this->cachePath); $mtime = $exists ? filemtime($this->cachePath) : 0; $expired = ($mtime + $this->cacheTtl) < time(); return [ 'exists' => $exists, 'last_sync' => $mtime > 0 ? date('Y-m-d H:i:s', $mtime) : '从未同步', 'expired' => $expired, 'provider_count' => count($this->getProviders()), ]; } /** * 从API获取原始数据. * * @return string|false */ protected function fetchApi() { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->apiUrl); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json', ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200 || $response === false) { return false; } return $response; } /** * 解析API数据为统一格式. * * @param string $raw JSON原始数据 * * @return array */ protected function parseApiData(string $raw): array { $data = json_decode($raw, true); if (!is_array($data)) { return ['providers' => [], 'models' => []]; } $providers = []; $models = []; foreach ($data as $providerId => $providerData) { if (!is_array($providerData)) { continue; } $provider = [ 'id' => $providerId, 'name' => $providerData['name'] ?? ucfirst($providerId), 'url' => $providerData['url'] ?? '', 'docs_url' => $providerData['docs_url'] ?? '', ]; // 提取API基础地址(如果有) if (isset($providerData['api_base_url'])) { $provider['api_base_url'] = $providerData['api_base_url']; } $providers[$providerId] = $provider; // 提取模型列表 $providerModels = []; if (isset($providerData['models']) && is_array($providerData['models'])) { foreach ($providerData['models'] as $modelId => $modelData) { $model = [ 'id' => $modelId, 'name' => $modelData['name'] ?? $modelId, 'description' => $modelData['description'] ?? '', ]; if (isset($modelData['context_length'])) { $model['context_length'] = (int) $modelData['context_length']; } if (isset($modelData['pricing'])) { $model['pricing'] = $modelData['pricing']; } $providerModels[] = $model; } } $models[$providerId] = $providerModels; } return [ 'providers' => $providers, 'models' => $models, ]; } /** * 写入缓存文件. * * @param array $data 缓存数据 */ protected function writeCache(array $data): void { $dir = dirname($this->cachePath); if (!is_dir($dir)) { mkdir($dir, 0755, true); } file_put_contents($this->cachePath, json_encode($data, JSON_UNESCAPED_UNICODE)); } /** * 读取缓存文件(支持过期降级). * * @return array|null */ protected function readCache(): ?array { if (!file_exists($this->cachePath)) { return null; } $content = file_get_contents($this->cachePath); $data = json_decode($content, true); if (!is_array($data)) { return null; } return $data; } }