[ 'api_key' => 'sk-LVcDfTx5SYK6pWiGpfcAN2KA0LunymnMiYSVfzUKQXrjlkZv', 'api_url' => 'https://chatapi.onechats.top/v1/chat/completions' ], //文生文-gtp-4 'txttotxtgtp' => [ 'api_key' => 'sk-fxlawqVtbbQbNW0wInR3E4wsLo5JHozDC2XOHzMa711su6ss', 'api_url' => 'https://chatapi.onechats.top/v1/chat/completions' ], //文生文-gemini-2.0-flash 'txttotxtgemini' => [ 'api_key' => 'sk-cqfCZFiiSIdpDjIHLMBbH6uWfeg7iVsASvlubjrNEmfUXbpX', 'api_url' => 'https://chatapi.onechats.top/v1/chat/completions' ], //文生图-dall-e-3 'txttoimg' => [ // 'api_key' => 'sk-MB6SR8qNaTjO80U7HJl4ztivX3zQKPgKVka9oyfVSXIkHSYZ', 'api_key' => 'sk-iURfrAgzAjhZ4PpPLwzmWIAhM7zKfrkwDvyxk4RVBQ4ouJNK', 'api_url' => 'https://chatapi.onechats.ai/v1/images/generations' ], 'submitimage' => [ 'api_key' => 'sk-iURfrAgzAjhZ4PpPLwzmWIAhM7zKfrkwDvyxk4RVBQ4ouJNK', 'api_url' => 'https://chatapi.onechats.ai/mj/submit/imagine' ] ]; /** * 图生文 * @param string $imageUrl 图像 URL,支持公网可访问地址 * @param string $prompt 对图像的提问内容或提示文本 */ public function callGptApi($imageUrl, $prompt) { //方式一 $data = [ "model" => "gemini-2.5-flash-lite-preview-06-17", "messages" => [[ "role" => "user", "content" => [ ["type" => "text", "text" => $prompt], ["type" => "image_url", "image_url" => [ "url" => $imageUrl, "detail" => "auto" ]] ] ]], "max_tokens" => 1000 ]; //方式二 // $data = [ // "model" => "gpt-4-vision-preview", // "messages" => [[ // "role" => "user", // "content" => [ // ["type" => "text", "text" => $prompt], // ["type" => "image_url", "image_url" => [ // "url" => $imageUrl, // "detail" => "auto" // ]] // ] // ]], // "max_tokens" => 1000 // ]; return $this->callApi($this->config['imgtotxt']['api_url'], $this->config['imgtotxt']['api_key'], $data); } /** * 文生文 * @param string $prompt 用户输入的文本提示内容 */ public function txtGptApi($prompt,$txttotxt_selectedOption) { if (empty($prompt)) { throw new \Exception("Prompt 不允许为空"); } //判断使用模型 if ($txttotxt_selectedOption === 'gemini-2.0-flash') { $data = [ 'model' => 'gemini-2.0-flash', 'messages' => [ ['role' => 'user', 'content' => $prompt] ], 'temperature' => 0.7, 'max_tokens' => 1024 ]; return $this->callApi( $this->config['txttotxtgemini']['api_url'], $this->config['txttotxtgemini']['api_key'], $data ); }else if ($txttotxt_selectedOption === 'gpt-4') { $data = [ 'model' => 'gpt-4', 'messages' => [ ['role' => 'user', 'content' => $prompt] ], 'temperature' => 0.7, 'max_tokens' => 1024 ]; return $this->callApi( $this->config['txttotxtgtp']['api_url'], $this->config['txttotxtgtp']['api_key'], $data ); } } /** * 文生图 * * @param string $prompt 提示文本,用于指导图像生成(最长建议 1000 字符) * @param string $selectedOption 模型名称,例如 'dall-e-3' 或其他兼容模型 * * 默认参数说明(适用于所有模型): * - n: 1(生成 1 张图) * - size: '1024x1024'(标准正方形图像) * - quality: 'hd'(高清质量) * - style: 'vivid'(鲜明风格) * * response_format 参数差异: * - 若模型为 'dall-e-3':返回 base64 图像,字段为 `b64_json` * - 其他模型默认返回图像 URL,字段为 `url` * * ⚠️ 注意:使用此方法后,需在 Job/TextToImageJob.php 中按 response_format 判断提取方式: * 提取 url 图像 * $base64Image = $dalle1024['data'][0]['url'] ?? null; * 提取 base64 图像 * $base64Image = $dalle1024['data'][0]['b64_json'] ?? null; * * @return array 返回接口响应,成功时包含 'data' 字段,失败时包含 'error' 信息 */ public function callDalleApi($prompt, $selectedOption) { if ($selectedOption === 'dall-e-3') { $data = [ 'prompt' => $prompt, 'model' => $selectedOption, 'n' => 1, 'size' => '1024x1024', 'quality' => 'hd', 'style' => 'vivid', 'response_format' => 'url', ]; return $this->callApi($this->config['txttoimg']['api_url'],$this->config['txttoimg']['api_key'],$data); } else if ($selectedOption === 'black-forest-labs/FLUX.1-kontext-pro') { $data = [ 'prompt' => $prompt, 'model' => $selectedOption, 'n' => 1, 'size' => '1024x1024', 'quality' => 'hd', 'style' => 'vivid', 'response_format' => 'url', ]; return $this->callApi($this->config['txttoimg']['api_url'],$this->config['txttoimg']['api_key'],$data); } else if ($selectedOption === 'gpt-image-1') { $data = [ 'prompt' => $prompt, 'model' => $selectedOption, 'n' => 1, 'size' => '1024x1024', 'quality' => 'hd', 'style' => 'vivid', 'response_format' => 'url', ]; return $this->callApi($this->config['txttoimg']['api_url'],$this->config['txttoimg']['api_key'],$data); } else if ($selectedOption === 'MID_JOURNEY') { $data = [ 'botType' => $selectedOption, 'prompt' => $prompt, 'base64Array' => [], 'accountFilter' => [ 'channelId' => "", 'instanceId' => "", 'modes' => [], 'remark' => "", 'remix' => true, 'remixAutoConsidered' => true ], 'notifyHook' => "", 'state' => "" ]; return $this->callApi($this->config['submitimage']['api_url'],$this->config['submitimage']['api_key'],$data); }else{ echo '其他文生图模型参数配置'; } } /** * 图生图 * @param string $prompt 用户输入的文本提示内容 * @param string $new_image_url 原图路径 * @param array $options 可选参数,可覆盖默认配置 * @return array */ public function txt2imgWithControlNet($prompt, $controlImgUrl, $options = []) { $apiUrl = "http://20.0.17.188:45001/sdapi/v1/txt2img"; $headers = ['Content-Type: application/json']; $imgPath = ROOT_PATH . 'public/' . ltrim($controlImgUrl, '/'); if (!file_exists($imgPath)) { return ['code' => 1, 'msg' => '图片不存在:' . $controlImgUrl]; } $imgData = file_get_contents($imgPath); $base64Img = 'data:image/png;base64,' . base64_encode($imgData); $params = [ 'prompt' => $prompt, 'negative_prompt' => '(deformed, distorted, disfigured:1.3), poorly drawn, bad anatomy', 'steps' => 20, 'sampler_name' => 'DPM++ 2M SDE', 'cfg_scale' => 7, 'seed' => -1, 'width' => 1024, 'height' => 1303, 'override_settings' => [ 'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting', 'sd_vae' => 'vae-ft-mse-840000-ema-pruned', 'CLIP_stop_at_last_layers' => 2 ], 'clip_skip' => 2, 'alwayson_scripts' => [ 'controlnet' => [ 'args' => [[ 'enabled' => true, 'input_image' => $base64Img, 'module' => 'inpaint_only+lama', 'model' => 'control_v11p_sd15_inpaint_fp16 [be8bc0ed]', 'weight' => 1, 'resize_mode' => 'Resize and Fill', 'pixel_perfect' => false, 'control_mode' => 'ControlNet is more important', 'starting_control_step' => 0, 'ending_control_step' => 1 ]] ] ] ]; if (!empty($options)) { $params = array_merge($params, $options); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $apiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params, JSON_UNESCAPED_UNICODE)); curl_setopt($ch, CURLOPT_TIMEOUT, 180); $response = curl_exec($ch); $error = curl_error($ch); curl_close($ch); if ($error) { return ['code' => 1, 'msg' => '请求失败:' . $error]; } $data = json_decode($response, true); if (!isset($data['images'][0])) { return ['code' => 1, 'msg' => '接口未返回图像数据']; } return [ 'code' => 0, 'msg' => '成功', 'data' => [ 'base64' => $data['images'][0], 'info' => $data['info'] ?? '' ] ]; } // 第一阶段:图生图 public function upscaleWithImg2Img($prompt, $imgPath) { if (!file_exists($imgPath)) { return ['code' => 1, 'msg' => '原图不存在:' . $imgPath]; } // 获取原始图像尺寸 [$origWidth, $origHeight] = getimagesize($imgPath); if (!$origWidth || !$origHeight) { return ['code' => 1, 'msg' => '无法识别图片尺寸']; } // 按2倍尺寸计算目标大小 $targetWidth = $origWidth * 2; $targetHeight = $origHeight * 2; // 编码图像为 base64 $imgData = file_get_contents($imgPath); $base64Img = 'data:image/png;base64,' . base64_encode($imgData); // 构造参数 $params = [ 'init_images' => [$base64Img], 'prompt' => $prompt, 'steps' => 20, 'sampler_name' => 'DPM++ 2M SDE Heun', 'cfg_scale' => 7, 'seed' => 1669863506, 'width' => $targetWidth, 'height' => $targetHeight, 'denoising_strength' => 0.2, 'clip_skip' => 2, 'override_settings' => [ 'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]', 'sd_vae' => 'vae-ft-mse-840000-ema-pruned.safetensors', 'CLIP_stop_at_last_layers' => 2 ], 'override_settings_restore_afterwards' => true ]; $apiUrl = "http://20.0.17.188:45001/sdapi/v1/img2img"; $headers = ['Content-Type: application/json']; // 发起请求 $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $apiUrl, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => json_encode($params), CURLOPT_TIMEOUT => 180 ]); $response = curl_exec($ch); $error = curl_error($ch); curl_close($ch); if ($error) return ['code' => 1, 'msg' => '图生图请求失败:' . $error]; $data = json_decode($response, true); if (!isset($data['images'][0])) return ['code' => 1, 'msg' => '图生图接口未返回图像']; return ['code' => 0, 'data' => ['base64' => $data['images'][0]]]; } // 第二阶段:高清超分 public function imgtogqGptApi($imageRelPath, $options = []) { $imgPath = ROOT_PATH . 'public/' . $imageRelPath; if (!file_exists($imgPath)) { return ['code' => 1, 'msg' => '原图不存在:' . $imageRelPath]; } $defaultParams = [ 'resize_mode' => 0, 'show_extras_results' => true, 'gfpgan_visibility' => 0, 'codeformer_visibility' => 0, 'codeformer_weight' => 0, 'upscaling_resize' => 1.62, 'upscaling_crop' => true, 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B', 'upscaler_2' => 'None', 'extras_upscaler_2_visibility' => 0, 'upscale_first' => false ]; $params = array_merge($defaultParams, $options); try { $imgData = file_get_contents($imgPath); if ($imgData === false) { throw new Exception('无法读取图片文件'); } $params['image'] = base64_encode($imgData); } catch (Exception $e) { return ['code' => 1, 'msg' => '图片读取失败:' . $e->getMessage()]; } $apiUrl = "http://20.0.17.188:45001/sdapi/v1/extra-single-image"; $headers = ['Content-Type: application/json']; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $apiUrl, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => json_encode($params), CURLOPT_TIMEOUT => 120 ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlErr = curl_error($ch); curl_close($ch); if ($curlErr) { return ['code' => 1, 'msg' => '请求失败:' . $curlErr]; } if ($httpCode !== 200) { return ['code' => 1, 'msg' => 'API请求失败,HTTP状态码:' . $httpCode]; } $data = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { return ['code' => 1, 'msg' => 'API返回数据解析失败:' . json_last_error_msg()]; } if (empty($data['image'])) { return ['code' => 1, 'msg' => '接口未返回有效的图像数据']; } return [ 'code' => 0, 'msg' => '高清图生成成功', 'data' => [ 'base64_image' => $data['image'], 'original_size' => strlen($imgData), 'processed_size' => strlen(base64_decode($data['image'])) ] ]; } // public function imgtogqGptApi($imageRelPath, $options = []) // { // // 构造图片路径 // $imgPath = ROOT_PATH . 'public/' . $imageRelPath; // // if (!file_exists($imgPath)) { // return ['code' => 1, 'msg' => '原图不存在:' . $imageRelPath]; // } // // // 默认放大配置 // $defaultParams = [ // 'resize_mode' => 0, // 'show_extras_results' => true, // 'gfpgan_visibility' => 0, // 'codeformer_visibility' => 0, // 'codeformer_weight' => 0, // 'upscaling_resize' => 2.45, // 'upscaling_crop' => true, // 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B', // 'upscaler_2' => 'None', // 'extras_upscaler_2_visibility' => 0, // 'upscale_first' => false // ]; // // // 合并配置参数 // $params = array_merge($defaultParams, $options); // // // 编码原始图片 // try { // $imgData = file_get_contents($imgPath); // if ($imgData === false) { // throw new Exception('无法读取图片文件'); // } // $params['image'] = base64_encode($imgData); // } catch (Exception $e) { // return ['code' => 1, 'msg' => '图片读取失败:' . $e->getMessage()]; // } // // $apiUrl = "http://20.0.17.188:45001/sdapi/v1/extra-single-image"; // $headers = ['Content-Type: application/json']; // // // 调用接口 // $ch = curl_init(); // curl_setopt_array($ch, [ // CURLOPT_URL => $apiUrl, // CURLOPT_RETURNTRANSFER => true, // CURLOPT_POST => true, // CURLOPT_HTTPHEADER => $headers, // CURLOPT_POSTFIELDS => json_encode($params), // CURLOPT_TIMEOUT => 120 // ]); // // $response = curl_exec($ch); // $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); // $curlErr = curl_error($ch); // curl_close($ch); // // // 网络请求失败 // if ($curlErr) { // return ['code' => 1, 'msg' => '请求失败:' . $curlErr]; // } // // // 状态码错误 // if ($httpCode !== 200) { // return ['code' => 1, 'msg' => 'API请求失败,HTTP状态码:' . $httpCode]; // } // // // 解析响应 // $data = json_decode($response, true); // if (json_last_error() !== JSON_ERROR_NONE) { // return ['code' => 1, 'msg' => 'API返回数据解析失败:' . json_last_error_msg()]; // } // // if (empty($data['image'])) { // return ['code' => 1, 'msg' => '接口未返回有效的图像数据']; // } // // // 保存新图片 // try { // $baseName = pathinfo($imageRelPath, PATHINFO_FILENAME); // $ext = pathinfo($imageRelPath, PATHINFO_EXTENSION); // $outputDir = 'uploads/extra_image/'; // $outputPath = ROOT_PATH . 'public/' . $outputDir; // // if (!is_dir($outputPath)) { // mkdir($outputPath, 0755, true); // } // // $saveFileName = $baseName . '-hd.' . $ext; // $saveFullPath = $outputPath . $saveFileName; // $resultImg = base64_decode($data['image']); // // if ($resultImg === false || file_put_contents($saveFullPath, $resultImg) === false) { // throw new Exception('保存图片失败'); // } // // return [ // 'code' => 0, // 'msg' => '高清图生成成功', // 'data' => [ // 'url' => '/' . $outputDir . $saveFileName, // 'original_size' => filesize($imgPath), // 'processed_size' => filesize($saveFullPath), // 'resolution' => getimagesize($saveFullPath) // ] // ]; // } catch (Exception $e) { // return ['code' => 1, 'msg' => '保存失败:' . $e->getMessage()]; // } // } /** * 通用 API 调用方法(支持重试机制) * * @param string $url 接口地址 * @param string $apiKey 授权密钥(Bearer Token) * @param array $data 请求数据(JSON 格式) * * 功能说明: * - 使用 cURL 发送 POST 请求到指定 API 接口 * - 设置请求头和超时时间等参数 * - 支持最多重试 2 次,当接口调用失败时自动重试 * - 返回成功时解析 JSON 响应为数组 * * 异常处理: * - 若全部重试失败,将抛出异常并包含最后一次错误信息 * * @return array 接口响应数据(成功时返回解析后的数组) * @throws \Exception 接口请求失败时抛出异常 */ public function callApi($url, $apiKey, $data) { $maxRetries = 2; $attempt = 0; $lastError = ''; $httpCode = 0; $apiErrorDetail = ''; while ($attempt <= $maxRetries) { try { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($data), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $apiKey ], CURLOPT_TIMEOUT => 120, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CONNECTTIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_FAILONERROR => true ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); if ($response === false) { throw new \Exception("请求发送失败: " . $curlError); } $result = json_decode($response, true); // 检查API返回的错误 if (isset($result['error'])) { $apiErrorDetail = $result['error']['message'] ?? ''; $errorType = $result['error']['type'] ?? ''; // 常见错误类型映射 $errorMessages = [ 'invalid_request_error' => '请求参数错误', 'authentication_error' => '认证失败', 'rate_limit_error' => '请求频率过高', 'insufficient_quota' => '额度不足', 'billing_not_active' => '账户未开通付费', 'content_policy_violation' => '内容违反政策' ]; $friendlyMessage = $errorMessages[$errorType] ?? 'API服务错误'; throw new \Exception("{$friendlyMessage}: {$apiErrorDetail}"); } if ($httpCode !== 200) { // HTTP状态码映射 $statusMessages = [ 400 => '请求参数不合法', 401 => 'API密钥无效或权限不足', 403 => '访问被拒绝', 404 => 'API端点不存在', 429 => '请求过于频繁,请稍后再试', 500 => '服务器内部错误', 503 => '服务暂时不可用' ]; $statusMessage = $statusMessages[$httpCode] ?? "HTTP错误({$httpCode})"; throw new \Exception($statusMessage); } curl_close($ch); return $result; } catch (\Exception $e) { $lastError = $e->getMessage(); $attempt++; if ($attempt <= $maxRetries) { sleep(pow(2, $attempt)); } else { // 最终失败时的详细错误信息 $errorDetails = [ '错误原因' => $this->getErrorCause($httpCode, $apiErrorDetail), '解决方案' => $this->getErrorSolution($httpCode), '请求参数' => json_encode($data, JSON_UNESCAPED_UNICODE), 'HTTP状态码' => $httpCode, '重试次数' => $attempt ]; throw new \Exception("API请求失败\n" . "失败说明: " . $errorDetails['错误原因'] . "\n" . "建议解决方案: " . $errorDetails['解决方案'] . "\n" . "技术详情: HTTP {$httpCode} - " . $lastError); } } } } /** * 根据错误类型获取友好的错误原因 */ private function getErrorCause($httpCode, $apiError) { $causes = [ 401 => 'API密钥无效、过期或没有访问权限', 400 => $apiError ?: '请求参数不符合API要求', 429 => '已达到API调用频率限制', 403 => '您的账户可能没有开通相关服务权限', 500 => 'OpenAI服务器处理请求时出错' ]; return $causes[$httpCode] ?? '未知错误,请检查网络连接和API配置'; } /** * 根据错误类型获取解决方案建议 */ private function getErrorSolution($httpCode) { $solutions = [ 401 => '1. 检查API密钥是否正确 2. 确认密钥是否有访问权限 3. 尝试创建新密钥', 400 => '1. 检查请求参数 2. 验证提示词内容 3. 参考API文档修正参数', 429 => '1. 等待1分钟后重试 2. 升级账户提高限额 3. 优化调用频率', 403 => '1. 检查账户状态 2. 确认是否已开通付费 3. 联系OpenAI支持', 500 => '1. 等待几分钟后重试 2. 检查OpenAI服务状态页' ]; return $solutions[$httpCode] ?? '1. 检查网络连接 2. 查看服务日志 3. 联系技术支持'; } }