AIGatewayService.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. <?php
  2. namespace app\service;
  3. use think\Db;
  4. use think\Queue;
  5. class AIGatewayService{
  6. /**
  7. * 接口访问配置
  8. *
  9. * 每个模块包含:
  10. * - api_key:API 调用密钥(Token)
  11. * - api_url:对应功能的服务端地址
  12. */
  13. protected $config = [
  14. //图生文-gemini-2.5-flash-preview
  15. 'imgtotxt' => [
  16. 'api_key' => 'sk-LVcDfTx5SYK6pWiGpfcAN2KA0LunymnMiYSVfzUKQXrjlkZv',
  17. 'api_url' => 'https://chatapi.onechats.top/v1/chat/completions'
  18. ],
  19. //文生文-gtp-4
  20. 'txttotxtgtp' => [
  21. 'api_key' => 'sk-fxlawqVtbbQbNW0wInR3E4wsLo5JHozDC2XOHzMa711su6ss',
  22. 'api_url' => 'https://chatapi.onechats.top/v1/chat/completions'
  23. ],
  24. //文生文-gemini-2.0-flash
  25. 'txttotxtgemini' => [
  26. 'api_key' => 'sk-cqfCZFiiSIdpDjIHLMBbH6uWfeg7iVsASvlubjrNEmfUXbpX',
  27. 'api_url' => 'https://chatapi.onechats.top/v1/chat/completions'
  28. ],
  29. //文生图-dall-e-3
  30. 'txttoimg' => [
  31. // 'api_key' => 'sk-MB6SR8qNaTjO80U7HJl4ztivX3zQKPgKVka9oyfVSXIkHSYZ',
  32. 'api_key' => 'sk-iURfrAgzAjhZ4PpPLwzmWIAhM7zKfrkwDvyxk4RVBQ4ouJNK',
  33. 'api_url' => 'https://chatapi.onechats.ai/v1/images/generations'
  34. ]
  35. ];
  36. /**
  37. * 图生文
  38. * @param string $imageUrl 图像 URL,支持公网可访问地址
  39. * @param string $prompt 对图像的提问内容或提示文本
  40. */
  41. public function callGptApi($imageUrl, $prompt)
  42. {
  43. //方式一
  44. $data = [
  45. "model" => "gemini-2.5-flash-lite-preview-06-17",
  46. "messages" => [[
  47. "role" => "user",
  48. "content" => [
  49. ["type" => "text", "text" => $prompt],
  50. ["type" => "image_url", "image_url" => [
  51. "url" => $imageUrl,
  52. "detail" => "auto"
  53. ]]
  54. ]
  55. ]],
  56. "max_tokens" => 1000
  57. ];
  58. //方式二
  59. // $data = [
  60. // "model" => "gpt-4-vision-preview",
  61. // "messages" => [[
  62. // "role" => "user",
  63. // "content" => [
  64. // ["type" => "text", "text" => $prompt],
  65. // ["type" => "image_url", "image_url" => [
  66. // "url" => $imageUrl,
  67. // "detail" => "auto"
  68. // ]]
  69. // ]
  70. // ]],
  71. // "max_tokens" => 1000
  72. // ];
  73. return $this->callApi($this->config['imgtotxt']['api_url'], $this->config['imgtotxt']['api_key'], $data);
  74. }
  75. /**
  76. * 文生文
  77. * @param string $prompt 用户输入的文本提示内容
  78. */
  79. public function txtGptApi($prompt,$txttotxt_selectedOption)
  80. {
  81. if (empty($prompt)) {
  82. throw new \Exception("Prompt 不允许为空");
  83. }
  84. //判断使用模型
  85. if ($txttotxt_selectedOption === 'gemini-2.0-flash') {
  86. $data = [
  87. 'model' => 'gemini-2.0-flash',
  88. 'messages' => [
  89. ['role' => 'user', 'content' => $prompt]
  90. ],
  91. 'temperature' => 0.7,
  92. 'max_tokens' => 1024
  93. ];
  94. return $this->callApi(
  95. $this->config['txttotxtgemini']['api_url'],
  96. $this->config['txttotxtgemini']['api_key'],
  97. $data
  98. );
  99. }else if ($txttotxt_selectedOption === 'gpt-4') {
  100. $data = [
  101. 'model' => 'gpt-4',
  102. 'messages' => [
  103. ['role' => 'user', 'content' => $prompt]
  104. ],
  105. 'temperature' => 0.7,
  106. 'max_tokens' => 1024
  107. ];
  108. return $this->callApi(
  109. $this->config['txttotxtgtp']['api_url'],
  110. $this->config['txttotxtgtp']['api_key'],
  111. $data
  112. );
  113. }
  114. }
  115. /**
  116. * 文生图
  117. *
  118. * @param string $prompt 提示文本,用于指导图像生成(最长建议 1000 字符)
  119. * @param string $selectedOption 模型名称,例如 'dall-e-3' 或其他兼容模型
  120. *
  121. * 默认参数说明(适用于所有模型):
  122. * - n: 1(生成 1 张图)
  123. * - size: '1024x1024'(标准正方形图像)
  124. * - quality: 'hd'(高清质量)
  125. * - style: 'vivid'(鲜明风格)
  126. *
  127. * response_format 参数差异:
  128. * - 若模型为 'dall-e-3':返回 base64 图像,字段为 `b64_json`
  129. * - 其他模型默认返回图像 URL,字段为 `url`
  130. *
  131. * ⚠️ 注意:使用此方法后,需在 Job/TextToImageJob.php 中按 response_format 判断提取方式:
  132. * 提取 url 图像
  133. * $base64Image = $dalle1024['data'][0]['url'] ?? null;
  134. * 提取 base64 图像
  135. * $base64Image = $dalle1024['data'][0]['b64_json'] ?? null;
  136. *
  137. * @return array 返回接口响应,成功时包含 'data' 字段,失败时包含 'error' 信息
  138. */
  139. public function callDalleApi($prompt, $selectedOption)
  140. {
  141. if ($selectedOption === 'dall-e-3') {
  142. $data = [
  143. 'prompt' => $prompt,
  144. 'model' => $selectedOption,
  145. 'n' => 1,
  146. // 'size' => '512x512',
  147. 'size' => '1024x1024',
  148. 'quality' => 'hd',
  149. 'style' => 'vivid',
  150. 'response_format' => 'url',
  151. ];
  152. } else if ($selectedOption === 'black-forest-labs/FLUX.1-kontext-pro') {
  153. $data = [
  154. 'prompt' => $prompt,
  155. 'model' => $selectedOption,
  156. 'n' => 1,
  157. // 'size' => '512x512',
  158. 'size' => '1024x1024',
  159. 'quality' => 'hd',
  160. 'style' => 'vivid',
  161. 'response_format' => 'url',
  162. ];
  163. }else{
  164. $data = [
  165. 'prompt' => $prompt,
  166. 'model' => $selectedOption,
  167. 'n' => 1,
  168. // 'size' => '512x512',
  169. 'size' => '1024x1024',
  170. 'quality' => 'hd',
  171. 'style' => 'vivid',
  172. 'response_format' => 'url',
  173. ];
  174. }
  175. return $this->callApi($this->config['txttoimg']['api_url'],$this->config['txttoimg']['api_key'],$data);
  176. }
  177. /**
  178. * 图生图
  179. * @param string $prompt 用户输入的文本提示内容
  180. * @param string $new_image_url 原图路径
  181. * @param array $options 可选参数,可覆盖默认配置
  182. * @return array
  183. */
  184. public function imgtoimgGptApi($prompt, $new_image_url, $options = [])
  185. {
  186. $imgPath = ROOT_PATH . 'public/' . $new_image_url;
  187. if (!file_exists($imgPath)) {
  188. return ['code' => 1, 'msg' => '原图不存在:' . $new_image_url];
  189. }
  190. // 默认参数配置
  191. $defaultParams = [
  192. 'prompt' => $prompt,
  193. 'steps' => 15,
  194. 'cfg_scale' => 7,
  195. 'denoising_strength' => 0.2,
  196. 'width' => 679,
  197. 'height' => 862,
  198. 'resize_mode' => 2,
  199. 'sampler_name' => 'DPM++ 2M SDE Heun',
  200. 'seed' => -1, // 使用-1表示随机种子
  201. 'inpaint_full_res' => true,
  202. 'inpainting_fill' => 1,
  203. 'override_settings' => [
  204. 'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]',
  205. 'sd_vae' => 'anything-v4.5.vae.pt',
  206. 'CLIP_stop_at_last_layers' => 7
  207. ],
  208. 'override_settings_restore_afterwards' => true
  209. ];
  210. // 合并用户自定义选项
  211. $params = array_merge($defaultParams, $options);
  212. // 原图 base64 编码
  213. $imgData = file_get_contents($imgPath);
  214. $base64Img = base64_encode($imgData);
  215. $params['init_images'] = ['data:image/png;base64,' . $base64Img];
  216. $apiUrl = "http://20.0.17.188:45001/sdapi/v1/img2img";
  217. $headers = ['Content-Type: application/json'];
  218. $ch = curl_init();
  219. curl_setopt($ch, CURLOPT_URL, $apiUrl);
  220. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  221. curl_setopt($ch, CURLOPT_POST, true);
  222. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  223. curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
  224. curl_setopt($ch, CURLOPT_TIMEOUT, 90);
  225. $response = curl_exec($ch);
  226. if (curl_errno($ch)) {
  227. $error = curl_error($ch);
  228. curl_close($ch);
  229. return ['code' => 1, 'msg' => '请求失败:' . $error];
  230. }
  231. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  232. curl_close($ch);
  233. if ($httpCode !== 200) {
  234. return ['code' => 1, 'msg' => 'API请求失败,HTTP状态码:' . $httpCode];
  235. }
  236. $data = json_decode($response, true);
  237. if (json_last_error() !== JSON_ERROR_NONE) {
  238. return ['code' => 1, 'msg' => 'API返回数据解析失败:' . json_last_error_msg()];
  239. }
  240. if (!isset($data['images'][0])) {
  241. $errorMsg = $data['error'] ?? $data['message'] ?? '未知错误';
  242. return ['code' => 1, 'msg' => 'API未返回图像数据:' . $errorMsg];
  243. }
  244. return [
  245. 'code' => 0,
  246. 'msg' => '图像生成成功',
  247. 'data' => [
  248. 'url' => $data['images'][0]
  249. ]
  250. ];
  251. }
  252. /**
  253. * 图片高清放大
  254. * @param string $imageRelPath 原图相对路径(相对 public)
  255. * @param array $options 可选参数,可覆盖默认放大配置
  256. * @return array
  257. */
  258. /**
  259. * 图片高清放大(不保存,返回 base64)
  260. * @param string $imageRelPath 原图相对路径(相对 public)
  261. * @param array $options 可选参数
  262. * @return array
  263. */
  264. public function imgtogqGptApi($imageRelPath, $options = [])
  265. {
  266. $imgPath = ROOT_PATH . 'public/' . $imageRelPath;
  267. if (!file_exists($imgPath)) {
  268. return ['code' => 1, 'msg' => '原图不存在:' . $imageRelPath];
  269. }
  270. $defaultParams = [
  271. 'resize_mode' => 0,
  272. 'show_extras_results' => true,
  273. 'gfpgan_visibility' => 0,
  274. 'codeformer_visibility' => 0,
  275. 'codeformer_weight' => 0,
  276. 'upscaling_resize' => 2.45,
  277. 'upscaling_crop' => true,
  278. 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B',
  279. 'upscaler_2' => 'None',
  280. 'extras_upscaler_2_visibility' => 0,
  281. 'upscale_first' => false
  282. ];
  283. $params = array_merge($defaultParams, $options);
  284. try {
  285. $imgData = file_get_contents($imgPath);
  286. if ($imgData === false) {
  287. throw new Exception('无法读取图片文件');
  288. }
  289. $params['image'] = base64_encode($imgData);
  290. } catch (Exception $e) {
  291. return ['code' => 1, 'msg' => '图片读取失败:' . $e->getMessage()];
  292. }
  293. $apiUrl = "http://20.0.17.188:45001/sdapi/v1/extra-single-image";
  294. $headers = ['Content-Type: application/json'];
  295. $ch = curl_init();
  296. curl_setopt_array($ch, [
  297. CURLOPT_URL => $apiUrl,
  298. CURLOPT_RETURNTRANSFER => true,
  299. CURLOPT_POST => true,
  300. CURLOPT_HTTPHEADER => $headers,
  301. CURLOPT_POSTFIELDS => json_encode($params),
  302. CURLOPT_TIMEOUT => 120
  303. ]);
  304. $response = curl_exec($ch);
  305. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  306. $curlErr = curl_error($ch);
  307. curl_close($ch);
  308. if ($curlErr) {
  309. return ['code' => 1, 'msg' => '请求失败:' . $curlErr];
  310. }
  311. if ($httpCode !== 200) {
  312. return ['code' => 1, 'msg' => 'API请求失败,HTTP状态码:' . $httpCode];
  313. }
  314. $data = json_decode($response, true);
  315. if (json_last_error() !== JSON_ERROR_NONE) {
  316. return ['code' => 1, 'msg' => 'API返回数据解析失败:' . json_last_error_msg()];
  317. }
  318. if (empty($data['image'])) {
  319. return ['code' => 1, 'msg' => '接口未返回有效的图像数据'];
  320. }
  321. return [
  322. 'code' => 0,
  323. 'msg' => '高清图生成成功',
  324. 'data' => [
  325. 'base64_image' => $data['image'],
  326. 'original_size' => strlen($imgData),
  327. 'processed_size' => strlen(base64_decode($data['image']))
  328. ]
  329. ];
  330. }
  331. // public function imgtogqGptApi($imageRelPath, $options = [])
  332. // {
  333. // // 构造图片路径
  334. // $imgPath = ROOT_PATH . 'public/' . $imageRelPath;
  335. //
  336. // if (!file_exists($imgPath)) {
  337. // return ['code' => 1, 'msg' => '原图不存在:' . $imageRelPath];
  338. // }
  339. //
  340. // // 默认放大配置
  341. // $defaultParams = [
  342. // 'resize_mode' => 0,
  343. // 'show_extras_results' => true,
  344. // 'gfpgan_visibility' => 0,
  345. // 'codeformer_visibility' => 0,
  346. // 'codeformer_weight' => 0,
  347. // 'upscaling_resize' => 2.45,
  348. // 'upscaling_crop' => true,
  349. // 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B',
  350. // 'upscaler_2' => 'None',
  351. // 'extras_upscaler_2_visibility' => 0,
  352. // 'upscale_first' => false
  353. // ];
  354. //
  355. // // 合并配置参数
  356. // $params = array_merge($defaultParams, $options);
  357. //
  358. // // 编码原始图片
  359. // try {
  360. // $imgData = file_get_contents($imgPath);
  361. // if ($imgData === false) {
  362. // throw new Exception('无法读取图片文件');
  363. // }
  364. // $params['image'] = base64_encode($imgData);
  365. // } catch (Exception $e) {
  366. // return ['code' => 1, 'msg' => '图片读取失败:' . $e->getMessage()];
  367. // }
  368. //
  369. // $apiUrl = "http://20.0.17.188:45001/sdapi/v1/extra-single-image";
  370. // $headers = ['Content-Type: application/json'];
  371. //
  372. // // 调用接口
  373. // $ch = curl_init();
  374. // curl_setopt_array($ch, [
  375. // CURLOPT_URL => $apiUrl,
  376. // CURLOPT_RETURNTRANSFER => true,
  377. // CURLOPT_POST => true,
  378. // CURLOPT_HTTPHEADER => $headers,
  379. // CURLOPT_POSTFIELDS => json_encode($params),
  380. // CURLOPT_TIMEOUT => 120
  381. // ]);
  382. //
  383. // $response = curl_exec($ch);
  384. // $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  385. // $curlErr = curl_error($ch);
  386. // curl_close($ch);
  387. //
  388. // // 网络请求失败
  389. // if ($curlErr) {
  390. // return ['code' => 1, 'msg' => '请求失败:' . $curlErr];
  391. // }
  392. //
  393. // // 状态码错误
  394. // if ($httpCode !== 200) {
  395. // return ['code' => 1, 'msg' => 'API请求失败,HTTP状态码:' . $httpCode];
  396. // }
  397. //
  398. // // 解析响应
  399. // $data = json_decode($response, true);
  400. // if (json_last_error() !== JSON_ERROR_NONE) {
  401. // return ['code' => 1, 'msg' => 'API返回数据解析失败:' . json_last_error_msg()];
  402. // }
  403. //
  404. // if (empty($data['image'])) {
  405. // return ['code' => 1, 'msg' => '接口未返回有效的图像数据'];
  406. // }
  407. //
  408. // // 保存新图片
  409. // try {
  410. // $baseName = pathinfo($imageRelPath, PATHINFO_FILENAME);
  411. // $ext = pathinfo($imageRelPath, PATHINFO_EXTENSION);
  412. // $outputDir = 'uploads/extra_image/';
  413. // $outputPath = ROOT_PATH . 'public/' . $outputDir;
  414. //
  415. // if (!is_dir($outputPath)) {
  416. // mkdir($outputPath, 0755, true);
  417. // }
  418. //
  419. // $saveFileName = $baseName . '-hd.' . $ext;
  420. // $saveFullPath = $outputPath . $saveFileName;
  421. // $resultImg = base64_decode($data['image']);
  422. //
  423. // if ($resultImg === false || file_put_contents($saveFullPath, $resultImg) === false) {
  424. // throw new Exception('保存图片失败');
  425. // }
  426. //
  427. // return [
  428. // 'code' => 0,
  429. // 'msg' => '高清图生成成功',
  430. // 'data' => [
  431. // 'url' => '/' . $outputDir . $saveFileName,
  432. // 'original_size' => filesize($imgPath),
  433. // 'processed_size' => filesize($saveFullPath),
  434. // 'resolution' => getimagesize($saveFullPath)
  435. // ]
  436. // ];
  437. // } catch (Exception $e) {
  438. // return ['code' => 1, 'msg' => '保存失败:' . $e->getMessage()];
  439. // }
  440. // }
  441. /**
  442. * 通用 API 调用方法(支持重试机制)
  443. *
  444. * @param string $url 接口地址
  445. * @param string $apiKey 授权密钥(Bearer Token)
  446. * @param array $data 请求数据(JSON 格式)
  447. *
  448. * 功能说明:
  449. * - 使用 cURL 发送 POST 请求到指定 API 接口
  450. * - 设置请求头和超时时间等参数
  451. * - 支持最多重试 2 次,当接口调用失败时自动重试
  452. * - 返回成功时解析 JSON 响应为数组
  453. *
  454. * 异常处理:
  455. * - 若全部重试失败,将抛出异常并包含最后一次错误信息
  456. *
  457. * @return array 接口响应数据(成功时返回解析后的数组)
  458. * @throws \Exception 接口请求失败时抛出异常
  459. */
  460. public function callApi($url, $apiKey, $data)
  461. {
  462. $maxRetries = 2;
  463. $attempt = 0;
  464. $lastError = '';
  465. $httpCode = 0;
  466. $apiErrorDetail = '';
  467. while ($attempt <= $maxRetries) {
  468. try {
  469. $ch = curl_init();
  470. curl_setopt_array($ch, [
  471. CURLOPT_URL => $url,
  472. CURLOPT_RETURNTRANSFER => true,
  473. CURLOPT_POST => true,
  474. CURLOPT_POSTFIELDS => json_encode($data),
  475. CURLOPT_HTTPHEADER => [
  476. 'Content-Type: application/json',
  477. 'Authorization: Bearer ' . $apiKey
  478. ],
  479. CURLOPT_TIMEOUT => 120,
  480. CURLOPT_SSL_VERIFYPEER => true,
  481. CURLOPT_SSL_VERIFYHOST => 2,
  482. CURLOPT_CONNECTTIMEOUT => 30,
  483. CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  484. CURLOPT_FAILONERROR => true
  485. ]);
  486. $response = curl_exec($ch);
  487. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  488. $curlError = curl_error($ch);
  489. if ($response === false) {
  490. throw new \Exception("请求发送失败: " . $curlError);
  491. }
  492. $result = json_decode($response, true);
  493. // 检查API返回的错误
  494. if (isset($result['error'])) {
  495. $apiErrorDetail = $result['error']['message'] ?? '';
  496. $errorType = $result['error']['type'] ?? '';
  497. // 常见错误类型映射
  498. $errorMessages = [
  499. 'invalid_request_error' => '请求参数错误',
  500. 'authentication_error' => '认证失败',
  501. 'rate_limit_error' => '请求频率过高',
  502. 'insufficient_quota' => '额度不足',
  503. 'billing_not_active' => '账户未开通付费',
  504. 'content_policy_violation' => '内容违反政策'
  505. ];
  506. $friendlyMessage = $errorMessages[$errorType] ?? 'API服务错误';
  507. throw new \Exception("{$friendlyMessage}: {$apiErrorDetail}");
  508. }
  509. if ($httpCode !== 200) {
  510. // HTTP状态码映射
  511. $statusMessages = [
  512. 400 => '请求参数不合法',
  513. 401 => 'API密钥无效或权限不足',
  514. 403 => '访问被拒绝',
  515. 404 => 'API端点不存在',
  516. 429 => '请求过于频繁,请稍后再试',
  517. 500 => '服务器内部错误',
  518. 503 => '服务暂时不可用'
  519. ];
  520. $statusMessage = $statusMessages[$httpCode] ?? "HTTP错误({$httpCode})";
  521. throw new \Exception($statusMessage);
  522. }
  523. curl_close($ch);
  524. return $result;
  525. } catch (\Exception $e) {
  526. $lastError = $e->getMessage();
  527. $attempt++;
  528. if ($attempt <= $maxRetries) {
  529. sleep(pow(2, $attempt));
  530. } else {
  531. // 最终失败时的详细错误信息
  532. $errorDetails = [
  533. '错误原因' => $this->getErrorCause($httpCode, $apiErrorDetail),
  534. '解决方案' => $this->getErrorSolution($httpCode),
  535. '请求参数' => json_encode($data, JSON_UNESCAPED_UNICODE),
  536. 'HTTP状态码' => $httpCode,
  537. '重试次数' => $attempt
  538. ];
  539. throw new \Exception("API请求失败\n" .
  540. "失败说明: " . $errorDetails['错误原因'] . "\n" .
  541. "建议解决方案: " . $errorDetails['解决方案'] . "\n" .
  542. "技术详情: HTTP {$httpCode} - " . $lastError);
  543. }
  544. }
  545. }
  546. }
  547. /**
  548. * 根据错误类型获取友好的错误原因
  549. */
  550. private function getErrorCause($httpCode, $apiError)
  551. {
  552. $causes = [
  553. 401 => 'API密钥无效、过期或没有访问权限',
  554. 400 => $apiError ?: '请求参数不符合API要求',
  555. 429 => '已达到API调用频率限制',
  556. 403 => '您的账户可能没有开通相关服务权限',
  557. 500 => 'OpenAI服务器处理请求时出错'
  558. ];
  559. return $causes[$httpCode] ?? '未知错误,请检查网络连接和API配置';
  560. }
  561. /**
  562. * 根据错误类型获取解决方案建议
  563. */
  564. private function getErrorSolution($httpCode)
  565. {
  566. $solutions = [
  567. 401 => '1. 检查API密钥是否正确 2. 确认密钥是否有访问权限 3. 尝试创建新密钥',
  568. 400 => '1. 检查请求参数 2. 验证提示词内容 3. 参考API文档修正参数',
  569. 429 => '1. 等待1分钟后重试 2. 升级账户提高限额 3. 优化调用频率',
  570. 403 => '1. 检查账户状态 2. 确认是否已开通付费 3. 联系OpenAI支持',
  571. 500 => '1. 等待几分钟后重试 2. 检查OpenAI服务状态页'
  572. ];
  573. return $solutions[$httpCode] ?? '1. 检查网络连接 2. 查看服务日志 3. 联系技术支持';
  574. }
  575. }