success('请求成功'); } /** * 图生文接口 */ public function img_to_txt() { $params = $this->request->param(); $prompt = trim($params['prompt']); $old_path = trim($params['path']); $model = trim($params['model']); $status_val = trim($params['status_val']); // 参数验证 if (empty($prompt) || empty($old_path) || empty($model)) { return json([ 'code' => 1, 'msg' => '缺少必要参数', 'data' => null ]); } $aiGateway = new AIGatewayService(); // 获取图片的base64数据和MIME类型 $imageData = AIGatewayService::file_get_contents($old_path); $base64Data = $imageData['base64Data']; $mimeType = $imageData['mimeType']; $formattedPrompt = "1. 输出语言:所有内容必须为纯简体中文,禁止出现任何英文、拼音、数字、注释、解释性文字、引导语、示例、标点外的特殊符号; 2. 第一步(提取原图产品): - 用1句完整中文描述原图产品,字数控制在50字以内; - 必须包含「主体细节、产品名称、商标、类型、颜色、风格」核心; 3. 第二步(生成新图提示词): - 仅替换【模板提示词】中「产品主体」相关内容为第一步的产品描述; - 严格保留模板中「设计风格、光影、背景、比例、排版」等非产品相关信息; - 完全排除模板中的标题类信息,仅保留提示词核心内容; - 替换后必须完全保留原图产品的核心特征,禁止修改模板非产品核心信息; 4. 输出格式:不允许添加任何解释、引导、说明、示例、备注,仅返回「产品描述 + 替换后提示词」,直接输出纯文本; 【模板提示词】{$prompt}"; $result = $aiGateway->callGptApi($model, $formattedPrompt, $mimeType, $base64Data); // Gemini模型响应格式处理 $imgToTxtContent = $result['candidates'][0]['content']['parts'][0]['text']; // // 图生文成功后,调用文生文功能(gemini-2.0-flash) $txtToTxtPrompt = "转换成中文格式,去掉其他特殊符号,不允许添加任何解释、引导、说明、示例等文字:\n\n{$imgToTxtContent}"; $txtToTxtResult = $aiGateway->txtGptApi($txtToTxtPrompt, 'gemini-2.0-flash'); $finalContent = $txtToTxtResult['candidates'][0]['content']['parts'][0]['text']; // 处理调试输出内容 $debugContent = json_encode($finalContent, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); // 去掉特殊符号(只保留字母、数字、中文、常用标点和换行) $debugContent = preg_replace('/[^\p{Han}\w\s\n\r\.,,。!!??\-\::;;]/u', '', $debugContent); // 去掉第一个冒号前的文字 $debugContent = preg_replace('/^[^:]+:/', '', $debugContent); return json([ 'code' => 0, 'msg' => '处理成功', 'data' => [ 'content' => $debugContent, 'english_content' => $imgToTxtContent ] ]); } /** * 文生图接口 */ public function txt_to_img() { $params = $this->request->param(); $prompt = trim($params['prompt']); $model = trim($params['model']); $status_val = trim($params['status_val']); $size = trim($params['size']); // 获取产品信息 $product = Db::name('product')->where('id', 1)->find(); if (empty($product)) { return json([ 'code' => 1, 'msg' => '产品不存在', 'data' => null ]); } $product_code = $product['product_code']; $product_code_prefix = substr($product_code, 0, 9); // 前九位 // 构建URL路径(使用正斜杠) $url_path = '/uploads/merchant/' . $product_code_prefix . '/' . $product_code . '/newimg/'; // 构建物理路径(使用正斜杠确保统一格式) $save_path = ROOT_PATH . 'public' . '/' . 'uploads' . '/' . 'merchant' . '/' . $product_code_prefix . '/' . $product_code . '/' . 'newimg' . '/'; // 移除ROOT_PATH中可能存在的反斜杠,确保统一使用正斜杠 $save_path = str_replace('\\', '/', $save_path); // 自动创建文件夹(如果不存在) if (!is_dir($save_path)) { mkdir($save_path, 0755, true); } // 调用AI生成图片 $aiGateway = new AIGatewayService(); $res = $aiGateway->callDalleApi($prompt, $model, $size); // 提取base64图片数据 if (isset($res['candidates'][0]['content']['parts'][0]['text'])) { $text_content = $res['candidates'][0]['content']['parts'][0]['text']; // 匹配base64图片数据 preg_match('/data:image\/(png|jpg|jpeg);base64,([^"]+)/', $text_content, $matches); if (empty($matches)) { return json([ 'code' => 1, 'msg' => '未找到图片数据', 'data' => null ]); } $image_type = $matches[1]; $base64_data = $matches[2]; // 解码base64数据 $image_data = base64_decode($base64_data); if ($image_data === false) { return json([ 'code' => 1, 'msg' => '图片解码失败', 'data' => null ]); } // 生成唯一文件名(包含扩展名) $file_name = uniqid() . '.' . $image_type; $full_file_path = $save_path . $file_name; // 保存图片到文件系统 if (!file_put_contents($full_file_path, $image_data)) { return json([ 'code' => 1, 'msg' => '图片保存失败', 'data' => null ]); } // 生成数据库存储路径(使用正斜杠格式) $db_img_path = $url_path . $file_name; Db::name('product')->where('id', 1)->update(['product_new_img' => $db_img_path]); return json([ 'code' => 0, 'msg' => '文生图请求成功并保存图片', 'data' => [ 'img' => $db_img_path, 'product_id' => 1 ] ]); } else { return json([ 'code' => 1, 'msg' => 'AI返回格式错误', 'data' => null ]); } } /** * 图生图本地测试 * 原图+参考图+提示词=生成新图 * 已替换为公开测试图(免本地文件)+ 通用提示词 */ public function GET_ImgToImg() { // 【通用简化提示词】:明确图生图核心需求,适配测试场景 $prompt = '参考第二张模板图片的轻奢设计风格、光影布局、纯色背景和产品展示比例,将第一张可乐产品图的主体替换到模板的核心位置,保留模板的所有非产品设计元素,生成1:1高清产品效果图,无水印、无多余文字,画质清晰'; $size = '1:1';// 生成图片比例 $model = 'gemini-3-pro-image-preview'; // 当前使用的模型 $numImages = 1; // 生成图像数量,gemini模型目前只支持生成1张 // 支持多图像生成的模型:dall-e-3, black-forest-labs/FLUX.1-kontext-pro, gpt-image-1 // 这些模型通过设置 'n' 参数来指定生成图像数量 // 检查模型是否支持多图像生成 $supportMultiImages = in_array($model, ['dall-e-3', 'black-forest-labs/FLUX.1-kontext-pro', 'gpt-image-1']); if ($supportMultiImages && $numImages > 1) { // 对于支持多图像生成的模型,可以设置生成数量 $n = $numImages; } /************************** * 替换为:公开测试图(直接转Base64,无需本地文件) * 图1:产品图(可乐罐,通用测试) * 图2:模版图(轻奢产品展示背景,通用测试) **************************/ // 产品图:公开可乐图URL $productImgUrl = 'https://s41.ax1x.com/2026/02/02/pZ4crcj.jpg'; // 模版图:公开轻奢产品展示背景图URL $templateImgUrl = 'https://s41.ax1x.com/2026/02/02/pZ4cw4S.jpg'; // 封装:URL转纯Base64(去前缀)+ MIME类型,无需本地文件,直接测试 function urlToPureBase64($imgUrl) { $imgContent = file_get_contents($imgUrl); if (!$imgContent) throw new Exception("图片URL读取失败"); // 通过文件扩展名确定MIME类型 $extension = strtolower(pathinfo($imgUrl, PATHINFO_EXTENSION)); $mimeTypes = [ 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp' ]; $mime = $mimeTypes[$extension] ?? 'image/jpeg'; $base64 = base64_encode($imgContent); return ['mime' => $mime, 'base64' => $base64]; } try { // 获取两张测试图的纯Base64和MIME $productImg = urlToPureBase64($productImgUrl); $templateImg = urlToPureBase64($templateImgUrl); // ########## 构造API请求参数(适配gemini-3-pro-image-preview) ########## $data = [ "contents" => [ [ "role" => "user", "parts" => [ ["text" => $prompt], // 产品图 ["inlineData" => [ "mimeType" => $productImg['mime'], "data" => $productImg['base64'] ]], // 模版图 ["inlineData" => [ "mimeType" => $templateImg['mime'], "data" => $templateImg['base64'] ]] ] ] ], "generationConfig" => [ "responseModalities" => ["IMAGE"], "imageConfig" => ["aspectRatio" => $size, "quality" => "HIGH"], "temperature" => 0.6, "topP" => 0.9, "maxOutputTokens" => 2048 ] ]; // ########## 调用API ########## $apiUrl = 'https://chatapi.onechats.ai/v1beta/models/gemini-3-pro-image-preview:generateContent'; $apiKey = 'sk-9aIV9nx7pJxJFMrB8REtNbhjYuNBxCcnEOwiJDHd6UwmN2eJ'; $result = AIGatewayService::callApi($apiUrl, $apiKey, $data); // 处理响应 if (isset($result['candidates'][0]['content']['parts'][0]['inlineData']['data'])) { // 直接从inlineData获取图片数据 $base64_data = $result['candidates'][0]['content']['parts'][0]['inlineData']['data']; $image_type = 'jpg'; // 默认类型 // 解码base64数据 $image_data = base64_decode($base64_data); if ($image_data === false) { return json([ 'code' => 1, 'msg' => '图片解码失败', 'data' => null ]); } // 保存图片 $saveDir = ROOT_PATH . 'public/uploads/img2img/'; if (!is_dir($saveDir)) { mkdir($saveDir, 0755, true); } $fileName = 'img2img-' . time() . '.' . $image_type; $savePath = $saveDir . $fileName; if (!file_put_contents($savePath, $image_data)) { return json([ 'code' => 1, 'msg' => '图片保存失败', 'data' => null ]); } // 生成访问路径 $accessPath = '/uploads/img2img/' . $fileName; return json([ 'code' => 0, 'msg' => '图生图已生成', 'data' => [ 'image' => $accessPath, 'model' => $model, 'support_multi_images' => $supportMultiImages ] ]); } else if (isset($result['candidates'][0]['content']['parts'][0]['text'])) { // 尝试从文本响应中提取图片 $text_content = $result['candidates'][0]['content']['parts'][0]['text']; preg_match('/data:image\/(png|jpg|jpeg);base64,([^"]+)/', $text_content, $matches); if (empty($matches)) { return json([ 'code' => 1, 'msg' => '未找到图片数据', 'data' => null ]); } $image_type = $matches[1]; $base64_data = $matches[2]; // 解码base64数据 $image_data = base64_decode($base64_data); if ($image_data === false) { return json([ 'code' => 1, 'msg' => '图片解码失败', 'data' => null ]); } // 保存图片 $saveDir = ROOT_PATH . 'public/uploads/img2img/'; if (!is_dir($saveDir)) { mkdir($saveDir, 0755, true); } $fileName = 'img2img-' . time() . '.' . $image_type; $savePath = $saveDir . $fileName; if (!file_put_contents($savePath, $image_data)) { return json([ 'code' => 1, 'msg' => '图片保存失败', 'data' => null ]); } // 生成访问路径 $accessPath = '/uploads/img2img/' . $fileName; return json([ 'code' => 0, 'msg' => '图生图已生成', 'data' => [ 'image' => $accessPath, 'model' => $model, 'support_multi_images' => $supportMultiImages ] ]); } else { return json([ 'code' => 1, 'msg' => 'API返回格式错误', 'data' => null ]); } } catch (Exception $e) { return json([ 'code' => 1, 'msg' => 'API调用失败:' . $e->getMessage(), 'data' => null ]); } } /** * 图生图本地测试 */ public function imgtowimg() { $prompt = $this->request->param('prompt', ''); $imgRelPath = 'uploads/operate/ai/Preview/arr/0835006071623.png'; $imgPath = ROOT_PATH . 'public/' . $imgRelPath; if (!file_exists($imgPath)) { return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]); } $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 ]] ] ] ]; $apiUrl = "http://20.0.17.188:45001/sdapi/v1/txt2img"; $headers = ['Content-Type: application/json']; $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 json(['code' => 1, 'msg' => '请求失败:' . $error]); } $data = json_decode($response, true); if (!isset($data['images'][0])) { return json(['code' => 1, 'msg' => '接口未返回图像数据']); } $resultImg = base64_decode($data['images'][0]); $saveDir = ROOT_PATH . 'public/uploads/img2img/'; if (!is_dir($saveDir)) { mkdir($saveDir, 0755, true); } $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME); $fileName = $originalBaseName . '-' . time() . '-1024x1248.png'; $savePath = $saveDir . $fileName; file_put_contents($savePath, $resultImg); return json([ 'code' => 0, 'msg' => '图像生成成功', 'data' => [ 'origin_url' => '/uploads/img2img/' . $fileName ] ]); } /** * 后期图像处理-单张图片高清放大处理 */ public function extra_image() { // 配置参数 $config = [ 'input_dir' => 'uploads/img2img/', 'output_dir' => 'uploads/extra_image/', 'api_url' => 'http://20.0.17.188:45001/sdapi/v1/extra-single-image', 'timeout' => 120, // 增加超时时间,高清处理可能耗时较长 'upscale_params' => [ '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, ] ]; // 输入文件处理 $imgRelPath = '0835006071623-1757406184-1024x1248.png'; $imgPath = ROOT_PATH . 'public/' . $config['input_dir'] . $imgRelPath; if (!file_exists($imgPath)) { return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]); } // 读取并编码图片 try { $imgData = file_get_contents($imgPath); if ($imgData === false) { throw new Exception('无法读取图片文件'); } $base64Img = base64_encode($imgData); } catch (Exception $e) { return json(['code' => 1, 'msg' => '图片处理失败:' . $e->getMessage()]); } // 准备API请求数据 $postData = array_merge($config['upscale_params'], ['image' => $base64Img]); $jsonData = json_encode($postData); if ($jsonData === false) { return json(['code' => 1, 'msg' => 'JSON编码失败']); } // 调用API进行高清放大 $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $config['api_url'], CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => $jsonData, CURLOPT_TIMEOUT => $config['timeout'], CURLOPT_CONNECTTIMEOUT => 30, ]); $response = curl_exec($ch); $error = curl_error($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($error) { return json(['code' => 1, 'msg' => 'API请求失败:' . $error]); } if ($httpCode !== 200) { return json(['code' => 1, 'msg' => 'API返回错误状态码:' . $httpCode]); } $data = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { return json(['code' => 1, 'msg' => 'API返回数据解析失败']); } if (!isset($data['image']) || empty($data['image'])) { return json(['code' => 1, 'msg' => '接口未返回有效的图像数据']); } // 保存处理后的图片 try { $resultImg = base64_decode($data['image']); if ($resultImg === false) { throw new Exception('Base64解码失败'); } $saveDir = ROOT_PATH . 'public/' . $config['output_dir']; if (!is_dir($saveDir) && !mkdir($saveDir, 0755, true)) { throw new Exception('无法创建输出目录'); } $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME); $fileName = $originalBaseName . '-hd.png'; // 使用-hd后缀更明确 $savePath = $saveDir . $fileName; if (file_put_contents($savePath, $resultImg) === false) { throw new Exception('无法保存处理后的图片'); } // 返回成功响应 return json([ 'code' => 0, 'msg' => '图像高清放大处理成功', 'data' => [ 'url' => '/' . $config['output_dir'] . $fileName, 'original_size' => filesize($imgPath), 'processed_size' => filesize($savePath), 'resolution' => getimagesize($savePath), // 返回新图片的分辨率 ] ]); } catch (Exception $e) { return json(['code' => 1, 'msg' => '保存结果失败:' . $e->getMessage()]); } } /** * 文本生成图片并保存第一张结果 * @param array $params 请求参数 * @return array 返回结果 */ public function txttowimg() { // API配置 $config = [ 'api_url' => 'https://chatapi.onechats.ai/mj/submit/imagine', 'fetch_url' => 'https://chatapi.onechats.ai/mj/task/', 'api_key' => 'sk-iURfrAgzAjhZ4PpPLwzmWIAhM7zKfrkwDvyxk4RVBQ4ouJNK', 'default_prompt' => '一个猫', 'wait_time' => 3 // 等待生成完成的秒数 ]; try { // 1. 准备请求数据 $prompt = $config['default_prompt']; $postData = [ 'botType' => 'MID_JOURNEY', 'prompt' => $prompt, 'base64Array' => [], 'accountFilter' => [ 'channelId' => "", 'instanceId' => "", 'modes' => [], 'remark' => "", 'remix' => true, 'remixAutoConsidered' => true ], 'notifyHook' => "", 'state' => "" ]; // 2. 提交生成请求 $generateResponse = $this->sendApiRequest($config['api_url'], $postData, $config['api_key']); $generateData = json_decode($generateResponse, true); if (empty($generateData['result'])) { throw new Exception('生成失败: '.($generateData['message'] ?? '未知错误')); } $taskId = $generateData['result']; // 3. 等待图片生成完成 sleep($config['wait_time']); // 4. 获取生成结果 $fetchUrl = $config['fetch_url'].$taskId.'/fetch'; $fetchResponse = $this->sendApiRequest($fetchUrl, [], $config['api_key'], 'GET'); $fetchData = json_decode($fetchResponse, true); if (empty($fetchData['imageUrl'])) { throw new Exception('获取图片失败: '.($fetchData['message'] ?? '未知错误')); } // 5. 处理返回的图片数组(取第一张) $imageUrls = is_array($fetchData['imageUrl']) ? $fetchData['imageUrl'] : [$fetchData['imageUrl']]; $firstImageUrl = $imageUrls[0]; // 6. 保存图片到本地 $savePath = $this->saveImage($firstImageUrl); // 7. 返回结果 return [ 'code' => 200, 'msg' => '图片生成并保存成功', 'data' => [ 'local_path' => $savePath, 'web_url' => request()->domain().$savePath, 'task_id' => $taskId ] ]; } catch (Exception $e) { // 错误处理 return [ 'code' => 500, 'msg' => '处理失败: '.$e->getMessage(), 'data' => null ]; } } /** * 发送API请求 * @param string $url 请求地址 * @param array $data 请求数据 * @param string $apiKey API密钥 * @param string $method 请求方法 * @return string 响应内容 * @throws Exception */ private function sendApiRequest($url, $data, $apiKey, $method = 'POST') { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer '.$apiKey, 'Accept: application/json', 'Content-Type: application/json' ], CURLOPT_POSTFIELDS => $method === 'POST' ? json_encode($data) : null, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_TIMEOUT => 60, CURLOPT_FAILONERROR => true ]); $response = curl_exec($ch); $error = curl_error($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($error) { throw new Exception('API请求失败: '.$error); } if ($httpCode < 200 || $httpCode >= 300) { throw new Exception('API返回错误状态码: '.$httpCode); } return $response; } /** * 保存图片到本地 * @param string $imageUrl 图片URL * @return string 本地保存路径 * @throws Exception */ private function saveImage($imageUrl) { // 1. 创建存储目录 $saveDir = ROOT_PATH.'public'.DS.'uploads'.DS.'midjourney'.DS.date('Ymd'); if (!is_dir($saveDir)) { mkdir($saveDir, 0755, true); } // 2. 生成唯一文件名 $filename = uniqid().'.png'; $localPath = DS.'uploads'.DS.'midjourney'.DS.date('Ymd').DS.$filename; $fullPath = $saveDir.DS.$filename; // 3. 下载图片 $ch = curl_init($imageUrl); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_CONNECTTIMEOUT => 15 ]); $imageData = curl_exec($ch); $error = curl_error($ch); curl_close($ch); if (!$imageData) { throw new Exception('图片下载失败: '.$error); } // 4. 验证图片类型 $imageInfo = getimagesizefromstring($imageData); if (!in_array($imageInfo['mime'] ?? '', ['image/png', 'image/jpeg'])) { throw new Exception('下载内容不是有效图片'); } // 5. 保存文件 if (!file_put_contents($fullPath, $imageData)) { throw new Exception('图片保存失败'); } return $localPath; } }