Просмотр исходного кода

新增NanoBanana的两种模型

lh 3 месяцев назад
Родитель
Сommit
e3b2274515

+ 14 - 4
app/Console/Commands/CheckImageGenerationTasksCommand.php

@@ -36,12 +36,17 @@ class CheckImageGenerationTasksCommand extends Command
         // 执行50s
         $time_start = time();
         try {
-            // 统计需要处理的任务数量(即梦AI的processing + 火山API和GPT的pending和processing)
+            // 统计需要处理的任务数量(即梦AI和NanoBanana的processing + 火山API和GPT的pending和processing)
             $jimengProcessingCount = DB::table('mp_generate_pic_tasks')
                 ->where('status', 'processing')
                 ->where('model', 'jimeng_4.0')
                 ->count('id');
             
+            $nanoBananaProcessingCount = DB::table('mp_generate_pic_tasks')
+                ->where('status', 'processing')
+                ->whereIn('model', BaseConst::NANO_BANANA_MODELS)
+                ->count('id');
+            
             $volcPendingCount = DB::table('mp_generate_pic_tasks')
                 ->where('status', 'pending')
                 ->whereIn('model', BaseConst::VOLC_PIC_MODELS)
@@ -52,9 +57,9 @@ class CheckImageGenerationTasksCommand extends Command
                 ->whereIn('model', BaseConst::GPT_IMAGE2_MODELS)
                 ->count('id');
             
-            $totalCount = $jimengProcessingCount + $volcPendingCount + $gptPendingCount;
+            $totalCount = $jimengProcessingCount + $nanoBananaProcessingCount + $volcPendingCount + $gptPendingCount;
             
-            dLog('command')->info("待处理任务统计 - 即梦AI处理中: {$jimengProcessingCount}, 火山API待处理: {$volcPendingCount}, GPT待处理: {$gptPendingCount}");
+            dLog('command')->info("待处理任务统计 - 即梦AI处理中: {$jimengProcessingCount}, NanoBanana处理中: {$nanoBananaProcessingCount}, 火山API待处理: {$volcPendingCount}, GPT待处理: {$gptPendingCount}");
             
             while ($totalCount > 0) {
                 $time_diff = time() - $time_start;
@@ -74,6 +79,11 @@ class CheckImageGenerationTasksCommand extends Command
                     ->where('model', 'jimeng_4.0')
                     ->count('id');
                 
+                $nanoBananaProcessingCount = DB::table('mp_generate_pic_tasks')
+                    ->where('status', 'processing')
+                    ->whereIn('model', BaseConst::NANO_BANANA_MODELS)
+                    ->count('id');
+                
                 $volcPendingCount = DB::table('mp_generate_pic_tasks')
                     ->where('status', 'pending')
                     ->whereIn('model', BaseConst::VOLC_PIC_MODELS)
@@ -84,7 +94,7 @@ class CheckImageGenerationTasksCommand extends Command
                     ->whereIn('model', BaseConst::GPT_IMAGE2_MODELS)
                     ->count('id');
                 
-                $totalCount = $jimengProcessingCount + $volcPendingCount + $gptPendingCount;
+                $totalCount = $jimengProcessingCount + $nanoBananaProcessingCount + $volcPendingCount + $gptPendingCount;
             }
             
             dLog('command')->info('任务状态检查完成');

+ 1 - 0
app/Consts/BaseConst.php

@@ -84,6 +84,7 @@ class BaseConst
 
     const VOLC_PIC_MODELS = ['doubao-seedream-5-0-260128', 'doubao-seedream-5-0-lite-260128', 'doubao-seedream-4-5-251128'];
     const GPT_IMAGE2_MODELS = ['gpt-image-2'];
+    const NANO_BANANA_MODELS = ['NanoBanana2', 'NanoBananaPro'];
     // 火山写作奖励计划接入点(使用该接入点可在第二天根据前一天使用量获取免费额度)
     const MODELS_MAP = [
         'doubao-seed-2-0-pro-260215'        => 'ep-20260422093628-pv5vj',

+ 4 - 0
app/Http/Controllers/Anime/AnimeController.php

@@ -1203,6 +1203,10 @@ class AnimeController extends BaseController
 
     // 文生图(通用)
     public function generateImg(Request $request) {
+        // 忽略所有超时限制
+        set_time_limit(0);
+        ini_set('max_execution_time', '0');
+
         $data = $request->all();
 
         $result = $this->AnimeService->generateImg($data);

+ 231 - 3
app/Services/AIGeneration/AIImageGenerationService.php

@@ -18,6 +18,8 @@ class AIImageGenerationService
 
     // 模型常量
     const MODEL_JIMENG_4 = 'jimeng_4.0';
+    const MODEL_NANO_BANANA_2 = 'NanoBanana2';
+    const MODEL_NANO_BANANA_PRO = 'NanoBananaPro';
 
     public function __construct(VolcEngineService $volcEngineService)
     {
@@ -66,9 +68,11 @@ class AIImageGenerationService
             'model' => $model,
         ]);
 
-        // 即梦AI立即提交任务,火山API在定时任务中提交
+        // 即梦AI和NanoBanana系列立即提交任务,火山API在定时任务中提交
         if ($model === self::MODEL_JIMENG_4) {
             $this->submitTaskToJimengApi($task);
+        } elseif (in_array($model, BaseConst::NANO_BANANA_MODELS)) {
+            $this->submitTaskToNanoBananaApi($task);
         }
 
         return $task;
@@ -563,6 +567,8 @@ class AIImageGenerationService
         // 根据模型类型调用不同的查询方法
         if ($task->model === self::MODEL_JIMENG_4) {
             return $this->queryJimengTaskStatus($task);
+        } elseif (in_array($task->model, BaseConst::NANO_BANANA_MODELS)) {
+            return $this->queryNanoBananaTaskStatus($task);
         } else {
             // 火山API是同步返回结果,不需要查询
             return [
@@ -683,6 +689,228 @@ class AIImageGenerationService
     }
 
     /**
+     * 提交任务到NanoBanana API
+     *
+     * @param MpGeneratePicTask $task
+     * @return void
+     */
+    private function submitTaskToNanoBananaApi(MpGeneratePicTask $task): void
+    {
+        try {
+            // 验证环境变量配置
+            $apiKey = env('NANO_BANANA_API_KEY');
+                
+            if (empty($apiKey)) {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => 'NanoBanana API KEY未配置,请检查环境变量NANO_BANANA_API_KEY'
+                ]);
+                return;
+            }
+            
+            // 根据模型选择API地址
+            switch ($task->model) {
+                case self::MODEL_NANO_BANANA_2:
+                    $apiUrl = 'https://api.wuyinkeji.com/api/async/image_nanoBanana2';
+                    break;
+                case self::MODEL_NANO_BANANA_PRO:
+                    $apiUrl = 'https://api.wuyinkeji.com/api/async/image_nanoBanana_pro';
+                    break;
+                default:
+                    $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                        'error_message' => '不支持的NanoBanana模型类型: ' . $task->model
+                    ]);
+                    logDB('generate', 'error', 'NanoBanana模型类型错误', ['task_id' => $task->task_id, 'model' => $task->model]);
+                    return;
+            }
+            
+            // 构建API请求参数
+            $apiParams = [
+                'key' => $apiKey,
+                'prompt' => $task->prompt,
+            ];
+            
+            // 尺寸参数(根据宽高判断)
+            $area = $task->width * $task->height;
+            if ($area >= 1024*1024 && $area < 2048*2048) {
+                $apiParams['size'] = '1k';
+            } elseif ($area >= 2048*2048) {
+                $apiParams['size'] = '2k';
+            } else {
+                $apiParams['size'] = '1k'; // 默认1k
+            }
+            
+            // 画面比例
+            if ($task->width && $task->height) {
+                $ratio = $task->width . ':' . $task->height;
+                // 简化比例
+                $gcd = $this->gcd($task->width, $task->height);
+                $ratio = ($task->width / $gcd) . ':' . ($task->height / $gcd);
+                $apiParams['aspectRatio'] = $ratio;
+            }
+            
+            // 参考图片
+            if ($task->ref_img_url) {
+                $apiParams['urls'] = is_array($task->ref_img_url) ? $task->ref_img_url : [$task->ref_img_url];
+            }
+
+            dLog('generate')->info('NanoBanana API参数: ', $apiParams);
+            logDB('generate', 'info', 'NanoBanana任务提交', ['task_id' => $task->task_id, 'model' => $task->model, 'params' => $apiParams]);
+            
+            // 调用NanoBanana API
+            $response = $this->httpClient->post($apiUrl, [
+                'query' => ['key' => $apiKey],
+                'json' => $apiParams,
+            ]);
+    
+            $responseData = json_decode($response->getBody()->getContents(), true);
+            dLog('generate')->info('NanoBanana API响应: ', $responseData);
+            logDB('generate', 'info', 'NanoBanana API响应', ['task_id' => $task->task_id, 'response' => $responseData]);
+                
+            if ($responseData['code'] !== 200) {
+                // API返回错误
+                logDB('generate', 'error', 'NanoBanana任务提交失败', ['task_id' => $task->task_id, 'error' => $responseData['msg'] ?? 'API Error']);
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => $responseData['msg'] ?? 'API Error',
+                    'extra_params' => $responseData
+                ]);
+            } else {
+                // 任务提交成功,更新状态为处理中
+                $updateData = [];
+                // 存储API返回的任务ID
+                if (isset($responseData['data']['id'])) {
+                    $updateData['task_id'] = $responseData['data']['id'];
+                    $updateData['extra_params'] = $responseData;
+                }
+    
+                $task->updateStatus(MpGeneratePicTask::STATUS_PROCESSING, $updateData);
+            }
+        } catch (\Exception $e) {
+            // 记录错误
+            logDB('generate', 'error', 'NanoBanana任务提交异常', ['task_id' => $task->task_id, 'error' => $e->getMessage()]);
+            $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                'error_message' => 'API请求失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 查询NanoBanana任务状态
+     *
+     * @param MpGeneratePicTask $task
+     * @return array
+     */
+    private function queryNanoBananaTaskStatus(MpGeneratePicTask $task): array
+    {
+        try {
+            // 获取API任务ID
+            $apiTaskId = $task->task_id ?? null;
+            $apiKey = env('NANO_BANANA_API_KEY');
+                
+            if (!$apiTaskId) {
+                return [
+                    'status' => 'failed',
+                    'error_message' => 'API任务ID不存在',
+                    'result_url' => null
+                ];
+            }
+            
+            if (empty($apiKey)) {
+                return [
+                    'status' => 'failed',
+                    'error_message' => 'NanoBanana API KEY未配置',
+                    'result_url' => null
+                ];
+            }
+    
+            // 调用统一查询接口
+            $response = $this->httpClient->get('https://api.wuyinkeji.com/api/async/detail', [
+                'query' => [
+                    'key' => $apiKey,
+                    'id' => $apiTaskId
+                ]
+            ]);
+    
+            $responseData = json_decode($response->getBody()->getContents(), true);
+            dLog('generate')->info('NanoBanana任务状态查询响应: ', $responseData);
+            logDB('generate', 'info', 'NanoBanana任务状态查询', ['task_id' => $apiTaskId, 'response' => $responseData]);
+    
+            if ($responseData['code'] !== 200) {
+                // API返回错误
+                logDB('generate', 'error', 'NanoBanana任务状态查询失败', ['task_id' => $apiTaskId, 'error' => $responseData['msg'] ?? 'API Error']);
+                return [
+                    'status' => 'failed',
+                    'error_message' => $responseData['msg'] ?? 'API Error',
+                    'result_url' => null,
+                    'result_json' => $responseData
+                ];
+            }
+    
+            // 解析API响应
+            $data = $responseData['data'] ?? [];
+            $taskStatus = $data['status'] ?? 0;
+    
+            $returnData = [
+                'status' => 'processing',
+                'error_message' => null,
+                'result_url' => null,
+                'result_json' => $responseData
+            ];
+    
+            // status: 2表示成功
+            if ($taskStatus === 2 && isset($data['result']) && !empty($data['result'])) {
+                $result_urls = [];
+                foreach ($data['result'] as $url) {
+                    $pic_name = 'ai_generation_nanobanana_' . time() . '_' . uniqid();
+                    $pic_ext = getImgExtFromUrl($url);
+                    $pic_name = $pic_name . $pic_ext;
+                    
+                    if ($pic_ext === '.png') {
+                        $comporessed_data = compressRemoteImageUrlToSize($url);
+                        $url = uploadStreamByTos('image', $comporessed_data, $pic_name);
+                    } else {
+                        // 将图片另存到tos
+                        $url = uploadStreamByTos('image', file_get_contents($url), $pic_name);
+                    }
+                    
+                    $result_urls[] = $url;
+                }
+                $returnData['result_url'] = $result_urls;
+                $returnData['status'] = 'success';
+            } elseif ($taskStatus === 3) {
+                // status: 3表示失败
+                $returnData['status'] = 'failed';
+                $returnData['error_message'] = $data['message'] ?? '任务执行失败';
+            }
+            // status: 0或1表示处理中,保持processing状态
+    
+            return $returnData;
+        } catch (\Exception $e) {
+            return [
+                'status' => 'failed',
+                'error_message' => $e->getMessage(),
+                'result_url' => null
+            ];
+        }
+    }
+
+    /**
+     * 计算最大公约数(用于简化比例)
+     *
+     * @param int $a
+     * @param int $b
+     * @return int
+     */
+    private function gcd(int $a, int $b): int
+    {
+        while ($b != 0) {
+            $temp = $b;
+            $b = $a % $b;
+            $a = $temp;
+        }
+        return $a;
+    }
+
+    /**
      * 更新所有待处理任务的状态
      *
      * @return void
@@ -719,9 +947,9 @@ class AIImageGenerationService
             \App\Jobs\ProcessGptImage2GenerationJob::dispatch($task->id)->onConnection('redis');
         }
         
-        // 3. 获取所有处理中的任务(即梦AI需要查询状态)
+        // 3. 获取所有处理中的任务(即梦AI和NanoBanana系列需要查询状态)
         $processingTasks = MpGeneratePicTask::where('status', MpGeneratePicTask::STATUS_PROCESSING)
-            ->where('model', self::MODEL_JIMENG_4)
+            ->whereIn('model', array_merge([self::MODEL_JIMENG_4], BaseConst::NANO_BANANA_MODELS))
             ->get();
 
         foreach ($processingTasks as $task) {

+ 15 - 5
app/Services/Anime/AnimeService.php

@@ -3784,7 +3784,15 @@ class AnimeService
     public function generateImg($data) {
         $prompt = getProp($data, 'prompt');
         $episode_id = getProp($data, 'episode_id');
-        
+        $ref_img_urls = getProp($data, 'ref_img_urls');
+        if (!is_array($ref_img_urls) && !empty($ref_img_urls)) {
+            if (is_json($ref_img_urls)) {
+                $ref_img_urls = json_decode($ref_img_urls, true);
+            }else {
+                $ref_img_urls = explode(',', $ref_img_urls);
+            }
+        }
+
         // 处理图片模型
         $model = getProp($data, 'model');
         if (!$model) {
@@ -3800,7 +3808,8 @@ class AnimeService
         }
         // 验证模型是否在可用模型表中
         if (!DB::table('mp_image_models')->where('model', $model)->where('is_enabled', 1)->exists()) {
-            $model = 'doubao-seedream-5-0-lite-260128';
+            // $model = 'doubao-seedream-5-0-lite-260128';
+            Utils::throwError('20003:该模型已不可用,请更换!');
         }
         // 保存到episode表
         if ($episode_id) {
@@ -3818,10 +3827,11 @@ class AnimeService
             $params = [
                 'prompt' => $prompt,
                 'model' => $model,
-                'ref_img_urls' => [],
+                'ref_img_urls' => '',
                 'width' => 1600,
                 'height' => 2848
             ];
+            if ($ref_img_urls) $params['ref_img_urls'] = is_array($ref_img_urls) ? $ref_img_urls : [$ref_img_urls];
             
             $task = $this->aiImageGenerationService->createImageGenerationTask($params);
             $task_id = $task->id;
@@ -3836,9 +3846,9 @@ class AnimeService
             $isGptModel = in_array($model, \App\Consts\BaseConst::GPT_IMAGE2_MODELS);
             $isSyncModel = $isVolcModel || $isGptModel;
 
-            // 即梦AI需要轮询获取结果(3分钟超时,每3秒查询一次)
+            // 即梦AI需要轮询获取结果(5分钟超时,每3秒查询一次)
             $start_time = time();
-            $timeout = 180; // 3分钟
+            $timeout = 300; // 5分钟
             $img_url = '';
             
             while (time() - $start_time < $timeout) {