Explorar o código

新增GPT-image2文生图模型

lh hai 2 meses
pai
achega
ef90f7bb3a

+ 1 - 0
app/Consts/BaseConst.php

@@ -83,6 +83,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 MODELS_MAP = [
         'doubao-seed-2-0-pro-260215'        => 'ep-20260422093628-pv5vj',

+ 133 - 0
app/Jobs/ProcessGptImage2GenerationJob.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Models\MpGeneratePicTask;
+use App\Services\AIGeneration\AIImageGenerationService;
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+
+class ProcessGptImage2GenerationJob implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    /**
+     * 任务最大尝试次数
+     *
+     * @var int
+     */
+    public $tries = 3;
+
+    /**
+     * 任务超时时间(秒)
+     *
+     * @var int
+     */
+    public $timeout = 300;
+
+    /**
+     * 任务ID
+     *
+     * @var int
+     */
+    protected $taskId;
+
+    /**
+     * 创建一个新的任务实例
+     *
+     * @param int $taskId
+     * @return void
+     */
+    public function __construct(int $taskId)
+    {
+        $this->taskId = $taskId;
+        $this->onQueue('{GenerateGptImage2Pics}');
+    }
+
+    /**
+     * 执行任务
+     *
+     * @param AIImageGenerationService $service
+     * @return void
+     */
+    public function handle(AIImageGenerationService $service)
+    {
+        try {
+            // 查找任务
+            $task = MpGeneratePicTask::find($this->taskId);
+            
+            if (!$task) {
+                dLog('generate')->error('队列任务:找不到GPT-Image2图片生成任务', ['task_id' => $this->taskId]);
+                logDB('generate', 'error', '队列任务:找不到GPT-Image2图片生成任务', ['task_id' => $this->taskId]);
+                return;
+            }
+
+            // 检查任务状态,只处理pending状态的任务
+            if ($task->status !== MpGeneratePicTask::STATUS_PENDING) {
+                dLog('generate')->info('队列任务:任务状态不是pending,跳过处理', [
+                    'task_id' => $this->taskId,
+                    'status' => $task->status
+                ]);
+                return;
+            }
+
+            dLog('generate')->info('队列任务:开始处理GPT-Image2图片生成任务', ['task_id' => $this->taskId]);
+
+            // 调用服务方法提交任务到GPT-Image2 API
+            $service->submitTaskToGptImage2Api($task);
+
+            dLog('generate')->info('队列任务:GPT-Image2图片生成任务处理完成', ['task_id' => $this->taskId]);
+
+        } catch (\Exception $e) {
+            dLog('generate')->error('队列任务:处理GPT-Image2图片生成任务失败', [
+                'task_id' => $this->taskId,
+                'error' => $e->getMessage()
+            ]);
+            logDB('generate', 'error', '队列任务:处理GPT-Image2图片生成任务失败', [
+                'task_id' => $this->taskId,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+
+            // 重新抛出异常,让队列系统处理重试
+            throw $e;
+        }
+    }
+
+    /**
+     * 任务失败时的处理
+     *
+     * @param \Throwable $exception
+     * @return void
+     */
+    public function failed(\Throwable $exception)
+    {
+        dLog('generate')->error('队列任务:GPT-Image2图片生成任务最终失败', [
+            'task_id' => $this->taskId,
+            'error' => $exception->getMessage()
+        ]);
+        logDB('generate', 'error', '队列任务:GPT-Image2图片生成任务最终失败', [
+            'task_id' => $this->taskId,
+            'error' => $exception->getMessage(),
+            'attempts' => $this->attempts()
+        ]);
+
+        // 更新任务状态为失败
+        try {
+            $task = MpGeneratePicTask::find($this->taskId);
+            if ($task && $task->status === MpGeneratePicTask::STATUS_PENDING) {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => '队列任务处理失败: ' . $exception->getMessage()
+                ]);
+            }
+        } catch (\Exception $e) {
+            dLog('generate')->error('队列任务:更新任务状态失败', [
+                'task_id' => $this->taskId,
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
+}

+ 168 - 2
app/Services/AIGeneration/AIImageGenerationService.php

@@ -216,6 +216,158 @@ class AIImageGenerationService
     }
 
     /**
+     * 提交任务到GPT-Image2 API
+     *
+     * @param MpGeneratePicTask $task
+     * @return void
+     */
+    public function submitTaskToGptImage2Api(MpGeneratePicTask $task): void
+    {
+        try {
+            // 验证环境变量配置
+            $apiKey = env('GPT_IMAGE2_API_KEY');
+                
+            if (empty($apiKey)) {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => 'GPT-Image2 API KEY未配置,请检查环境变量GPT_IMAGE2_API_KEY'
+                ]);
+                return;
+            }
+            
+            // 更新状态为处理中
+            $task->updateStatus(MpGeneratePicTask::STATUS_PROCESSING);
+                
+            // 构建GPT-Image2 API请求参数
+            $apiParams = [
+                'model' => 'gpt-image-2',
+                'prompt' => $task->prompt,
+            ];
+            
+            // 尺寸参数
+            if ($task->width && $task->height) {
+                $apiParams['size'] = $task->width . 'x' . $task->height;
+            }
+            
+            // 参考图片
+            if ($task->ref_img_url) {
+                $imageUrl = is_array($task->ref_img_url) ? $task->ref_img_url[0] : $task->ref_img_url;
+                $apiParams['image_url'] = [
+                    'url' => $imageUrl
+                ];
+            }
+
+            dLog('generate')->info('GPT-Image2 API参数: ', $apiParams);
+            logDB('generate', 'info', 'GPT-Image2任务提交', ['task_id' => $task->task_id, 'params' => $apiParams]);
+            
+            // 调用GPT-Image2 API
+            $response = $this->httpClient->post('https://token.ithinkai.cn/v1/images/generations', [
+                'headers' => [
+                    'Authorization' => 'Bearer ' . $apiKey,
+                    'Content-Type' => 'application/json',
+                ],
+                'json' => $apiParams,
+            ]);
+    
+            $responseData = json_decode($response->getBody()->getContents(), true);
+            dLog('generate')->info('GPT-Image2 API响应: ', $responseData);
+            logDB('generate', 'info', 'GPT-Image2 API响应', ['task_id' => $task->task_id, 'response' => $responseData]);
+                
+            if (isset($responseData['error'])) {
+                // API返回错误
+                logDB('generate', 'error', 'GPT-Image2任务失败', ['task_id' => $task->task_id, 'error' => $responseData['error']['message'] ?? 'API Error']);
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => $responseData['error']['message'] ?? 'API Error',
+                    'result_json' => $responseData
+                ]);
+            } else {
+                // 任务提交成功,直接处理结果
+                $this->processGptImage2ApiResponse($task, $responseData);
+            }
+        } catch (\Exception $e) {
+            // 记录错误
+            dLog('generate')->error('GPT-Image2 API请求失败: ' . $e->getMessage());
+            logDB('generate', 'error', 'GPT-Image2任务异常', ['task_id' => $task->task_id, 'error' => $e->getMessage()]);
+            $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                'error_message' => 'API请求失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 处理GPT-Image2 API响应结果
+     *
+     * @param MpGeneratePicTask $task
+     * @param array $responseData
+     * @return void
+     */
+    private function processGptImage2ApiResponse(MpGeneratePicTask $task, array $responseData): void
+    {
+        try {
+            if (!isset($responseData['data']) || empty($responseData['data'])) {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => '未返回图片数据',
+                    'result_json' => $responseData
+                ]);
+                return;
+            }
+
+            $result_urls = [];
+            foreach ($responseData['data'] as $imageData) {
+                if (isset($imageData['url'])) {
+                    $url = $imageData['url'];
+                    $pic_name = 'ai_generation_gpt_' . 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;
+                }
+            }
+
+            if (empty($result_urls)) {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => '所有图片生成失败',
+                    'result_json' => $responseData
+                ]);
+                return;
+            }
+
+            // 更新任务状态为成功
+            $task->updateStatus(MpGeneratePicTask::STATUS_SUCCESS, [
+                'result_url' => $result_urls,
+                'result_json' => $responseData
+            ]);
+
+            // 同步调整分镜图片状态和结果
+            $segment_id = getProp($task, 'alias_segment_id');
+            if ($segment_id) {
+                DB::table('mp_episode_segments')->where('segment_id', $segment_id)->update([
+                    'img_url' => $result_urls[0],
+                    'pic_task_status' => '已完成',
+                ]);
+                
+                // 如果是首帧图ID则更新anime表
+                if (Redis::sismember('anime_first_frame_urls', $segment_id)) {
+                    $anime_id = DB::table('mp_episode_segments')->where('segment_id', $segment_id)->value('anime_id');
+                    DB::table('mp_animes')->where('id', $anime_id)->update(['first_frame_url' => $result_urls[0], 'updated_at' => date('Y-m-d H:i:s')]);
+                    Redis::srem('anime_first_frame_urls', $segment_id);
+                }
+            }
+        } catch (\Exception $e) {
+            $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                'error_message' => '处理响应失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
      * 提交任务到火山图片生成API
      *
      * @param MpGeneratePicTask $task
@@ -553,7 +705,21 @@ class AIImageGenerationService
             \App\Jobs\ProcessVolcImageGenerationJob::dispatch($task->id)->onConnection('redis');
         }
         
-        // 2. 获取所有处理中的任务(仅即梦AI需要查询状态)
+        // 2. 处理GPT-Image2的pending任务(批量提交到队列)
+        $gptImage2PendingTasks = MpGeneratePicTask::where('status', MpGeneratePicTask::STATUS_PENDING)
+            ->whereIn('model', BaseConst::GPT_IMAGE2_MODELS)
+            ->orderBy('created_at', 'asc')
+            ->limit(50) // 每次最多处理50个任务,避免超时
+            ->get();
+
+        foreach ($gptImage2PendingTasks as $task) {
+            dLog('generate')->info('开始分发GPT-Image2任务到队列: ' . $task->task_id);
+            
+            // 分发到队列
+            \App\Jobs\ProcessGptImage2GenerationJob::dispatch($task->id)->onConnection('redis');
+        }
+        
+        // 3. 获取所有处理中的任务(仅即梦AI需要查询状态)
         $processingTasks = MpGeneratePicTask::where('status', MpGeneratePicTask::STATUS_PROCESSING)
             ->where('model', self::MODEL_JIMENG_4)
             ->get();
@@ -608,7 +774,7 @@ class AIImageGenerationService
             }
         }
 
-        // 3. 检查是否有任务完成,如果有则处理下一个排队任务(仅即梦AI)
+        // 4. 检查是否有任务完成,如果有则处理下一个排队任务(仅即梦AI)
         $this->processNextQueuedTask();
     }