|
|
@@ -9,12 +9,15 @@ use GuzzleHttp\Exception\GuzzleException;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
use Illuminate\Support\Facades\Redis;
|
|
|
+use App\Consts\BaseConst;
|
|
|
|
|
|
class AIImageGenerationService
|
|
|
{
|
|
|
private $volcEngineService;
|
|
|
private $httpClient;
|
|
|
|
|
|
+ // 模型常量
|
|
|
+ const MODEL_JIMENG_4 = 'jimeng_4.0';
|
|
|
|
|
|
public function __construct(VolcEngineService $volcEngineService)
|
|
|
{
|
|
|
@@ -32,8 +35,11 @@ class AIImageGenerationService
|
|
|
*/
|
|
|
public function createImageGenerationTask(array $params): MpGeneratePicTask
|
|
|
{
|
|
|
- // 检查是否有正在处理的任务
|
|
|
- if ($this->hasProcessingTask()) {
|
|
|
+ // 获取模型类型,默认使用即梦AI 4.0
|
|
|
+ $model = $params['model'] ?? self::MODEL_JIMENG_4;
|
|
|
+
|
|
|
+ // 检查是否有正在处理的任务(仅即梦AI需要排队)
|
|
|
+ if ($model === self::MODEL_JIMENG_4 && $this->hasProcessingTask()) {
|
|
|
// 如果有正在处理的任务,创建排队任务
|
|
|
return $this->createQueuedTask($params);
|
|
|
}
|
|
|
@@ -54,10 +60,13 @@ class AIImageGenerationService
|
|
|
'mask_img_url' => $params['mask_img_url'] ?? null,
|
|
|
'extra_params' => $params['extra_params'] ?? null,
|
|
|
'status' => MpGeneratePicTask::STATUS_PENDING,
|
|
|
+ 'model' => $model,
|
|
|
]);
|
|
|
|
|
|
- // 发送异步请求到即梦AI API创建任务
|
|
|
- $this->submitTaskToApi($task);
|
|
|
+ // 仅即梦AI立即提交任务,火山API在定时任务中提交
|
|
|
+ if ($model === self::MODEL_JIMENG_4) {
|
|
|
+ $this->submitTaskToJimengApi($task);
|
|
|
+ }
|
|
|
|
|
|
return $task;
|
|
|
}
|
|
|
@@ -83,6 +92,9 @@ class AIImageGenerationService
|
|
|
{
|
|
|
// 生成唯一的任务ID
|
|
|
$taskId = 'img_gen_' . time() . '_' . uniqid();
|
|
|
+
|
|
|
+ // 获取模型类型
|
|
|
+ $model = $params['model'] ?? self::MODEL_JIMENG_4;
|
|
|
|
|
|
// 创建排队状态的任务记录
|
|
|
return MpGeneratePicTask::create([
|
|
|
@@ -96,7 +108,8 @@ class AIImageGenerationService
|
|
|
'ref_img_url' => $params['ref_img_urls'] ?? null,
|
|
|
'mask_img_url' => $params['mask_img_url'] ?? null,
|
|
|
'extra_params' => $params['extra_params'] ?? null,
|
|
|
- 'status' => MpGeneratePicTask::STATUS_PENDING, // 保持PENDING状态,等待处理
|
|
|
+ 'status' => MpGeneratePicTask::STATUS_PENDING,
|
|
|
+ 'model' => $model,
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
@@ -106,7 +119,7 @@ class AIImageGenerationService
|
|
|
* @param MpGeneratePicTask $task
|
|
|
* @return void
|
|
|
*/
|
|
|
- private function submitTaskToApi(MpGeneratePicTask $task): void
|
|
|
+ private function submitTaskToJimengApi(MpGeneratePicTask $task): void
|
|
|
{
|
|
|
try {
|
|
|
// 验证环境变量配置
|
|
|
@@ -149,7 +162,7 @@ class AIImageGenerationService
|
|
|
$apiParams['size'] = (int)$area;
|
|
|
}
|
|
|
|
|
|
- dLog('generate')->info('文生图参数: ', $apiParams);
|
|
|
+ dLog('generate')->info('即梦AI文生图参数: ', $apiParams);
|
|
|
|
|
|
//调用即梦AI 4.0 API提交任务
|
|
|
$response = $this->volcEngineService->request(
|
|
|
@@ -195,6 +208,171 @@ class AIImageGenerationService
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 提交任务到火山图片生成API
|
|
|
+ *
|
|
|
+ * @param MpGeneratePicTask $task
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ private function submitTaskToVolcApi(MpGeneratePicTask $task): void
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ // 验证环境变量配置
|
|
|
+ $apiKey = env('VOLC_AI_API_KEY');
|
|
|
+
|
|
|
+ if (empty($apiKey)) {
|
|
|
+ $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
|
|
|
+ 'error_message' => '火山引擎API KEY未配置,请检查环境变量VOLC_AI_API_KEY'
|
|
|
+ ]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新状态为处理中
|
|
|
+ $task->updateStatus(MpGeneratePicTask::STATUS_PROCESSING);
|
|
|
+
|
|
|
+ // 构建火山图片生成API请求参数
|
|
|
+ $apiParams = [
|
|
|
+ 'model' => $task->model,
|
|
|
+ 'prompt' => $task->prompt,
|
|
|
+ 'watermark' => false,
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 尺寸参数
|
|
|
+ if ($task->width && $task->height) {
|
|
|
+ $apiParams['size'] = $task->width . 'x' . $task->height;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 参考图片
|
|
|
+ if ($task->ref_img_url) {
|
|
|
+ if (is_array($task->ref_img_url)) {
|
|
|
+ // $apiParams['image'] = count($task->ref_img_url) === 1 ? $task->ref_img_url[0] : $task->ref_img_url;
|
|
|
+ $apiParams['image'] = $task->ref_img_url;
|
|
|
+ } else {
|
|
|
+ $apiParams['image'] = $task->ref_img_url;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成多图或者单图
|
|
|
+ if ((int)$task->image_num === 1) {
|
|
|
+ $apiParams['sequential_image_generation'] = 'disabled';
|
|
|
+ }else if ($task->image_num > 1) {
|
|
|
+ $apiParams['sequential_image_generation'] = 'auto';
|
|
|
+ $apiParams['sequential_image_generation_options']['max_images'] = $task->image_num;
|
|
|
+ }
|
|
|
+
|
|
|
+ dLog('generate')->info('火山图片生成API参数: ', $apiParams);
|
|
|
+
|
|
|
+ // 调用火山图片生成API
|
|
|
+ $response = $this->httpClient->post('https://ark.cn-beijing.volces.com/api/v3/images/generations', [
|
|
|
+ 'headers' => [
|
|
|
+ 'Authorization' => 'Bearer ' . $apiKey,
|
|
|
+ 'Content-Type' => 'application/json',
|
|
|
+ ],
|
|
|
+ 'json' => $apiParams,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $responseData = json_decode($response->getBody()->getContents(), true);
|
|
|
+ dLog('generate')->info('火山图片生成API响应: ', $responseData);
|
|
|
+
|
|
|
+ if (isset($responseData['error'])) {
|
|
|
+ // API返回错误
|
|
|
+ $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
|
|
|
+ 'error_message' => $responseData['error']['message'] ?? 'API Error',
|
|
|
+ 'result_json' => $responseData
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ // 任务提交成功,直接处理结果
|
|
|
+ $this->processVolcApiResponse($task, $responseData);
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ // 记录错误
|
|
|
+ dLog('generate')->error('火山API请求失败: ' . $e->getMessage());
|
|
|
+ $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
|
|
|
+ 'error_message' => 'API请求失败: ' . $e->getMessage()
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理火山API响应结果
|
|
|
+ *
|
|
|
+ * @param MpGeneratePicTask $task
|
|
|
+ * @param array $responseData
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ private function processVolcApiResponse(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['error'])) {
|
|
|
+ // 单张图片生成失败
|
|
|
+ dLog('generate')->warning('单张图片生成失败: ', $imageData['error']);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isset($imageData['url'])) {
|
|
|
+ $url = $imageData['url'];
|
|
|
+ $pic_name = 'ai_generation_' . 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()
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public function getTaskList($data)
|
|
|
{
|
|
|
$task_id = getProp($data, 'task_id');
|
|
|
@@ -219,6 +397,28 @@ class AIImageGenerationService
|
|
|
*/
|
|
|
public function queryTaskStatus(MpGeneratePicTask $task): array
|
|
|
{
|
|
|
+ // 根据模型类型调用不同的查询方法
|
|
|
+ if ($task->model === self::MODEL_JIMENG_4) {
|
|
|
+ return $this->queryJimengTaskStatus($task);
|
|
|
+ } else {
|
|
|
+ // 火山API是同步返回结果,不需要查询
|
|
|
+ return [
|
|
|
+ 'status' => $task->status === MpGeneratePicTask::STATUS_SUCCESS ? 'success' : 'failed',
|
|
|
+ 'error_message' => $task->error_message,
|
|
|
+ 'result_url' => $task->result_url,
|
|
|
+ 'result_json' => $task->result_json ?? []
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询即梦AI任务状态
|
|
|
+ *
|
|
|
+ * @param MpGeneratePicTask $task
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ private function queryJimengTaskStatus(MpGeneratePicTask $task): array
|
|
|
+ {
|
|
|
try {
|
|
|
// 获取API任务ID
|
|
|
$apiTaskId = $task->task_id ?? null;
|
|
|
@@ -325,8 +525,22 @@ class AIImageGenerationService
|
|
|
public function updatePendingTasks(): void
|
|
|
{
|
|
|
dLog('generate')->info('更新图片状态ing');
|
|
|
- // 获取所有处理中的任务
|
|
|
+
|
|
|
+ // 1. 处理火山API的pending任务(批量提交)
|
|
|
+ $volcPendingTasks = MpGeneratePicTask::where('status', MpGeneratePicTask::STATUS_PENDING)
|
|
|
+ ->whereIn('model', BaseConst::VOLC_PIC_MODELS)
|
|
|
+ ->orderBy('created_at', 'asc')
|
|
|
+ ->limit(50) // 每次最多处理50个任务,避免超时
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ foreach ($volcPendingTasks as $task) {
|
|
|
+ dLog('generate')->info('开始处理火山API任务: ' . $task->task_id);
|
|
|
+ $this->submitTaskToVolcApi($task);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 获取所有处理中的任务(仅即梦AI需要查询状态)
|
|
|
$processingTasks = MpGeneratePicTask::where('status', MpGeneratePicTask::STATUS_PROCESSING)
|
|
|
+ ->where('model', self::MODEL_JIMENG_4)
|
|
|
->get();
|
|
|
|
|
|
foreach ($processingTasks as $task) {
|
|
|
@@ -376,7 +590,7 @@ class AIImageGenerationService
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 检查是否有任务完成,如果有则处理下一个排队任务
|
|
|
+ // 3. 检查是否有任务完成,如果有则处理下一个排队任务(仅即梦AI)
|
|
|
$this->processNextQueuedTask();
|
|
|
}
|
|
|
|
|
|
@@ -387,19 +601,20 @@ class AIImageGenerationService
|
|
|
*/
|
|
|
public function processNextQueuedTask(): void
|
|
|
{
|
|
|
- // 检查是否还有正在处理的任务
|
|
|
+ // 检查是否还有正在处理的任务(仅即梦AI)
|
|
|
if ($this->hasProcessingTask()) {
|
|
|
return; // 如果还有处理中的任务,不处理新任务
|
|
|
}
|
|
|
|
|
|
- // 获取最早的待处理任务
|
|
|
+ // 获取最早的待处理任务(仅即梦AI)
|
|
|
$nextTask = MpGeneratePicTask::where('status', MpGeneratePicTask::STATUS_PENDING)
|
|
|
+ ->where('model', self::MODEL_JIMENG_4)
|
|
|
->orderBy('created_at', 'asc')
|
|
|
->first();
|
|
|
|
|
|
if ($nextTask) {
|
|
|
dLog('generate')->info('开始处理排队任务: ' . $nextTask->task_id);
|
|
|
- $this->submitTaskToApi($nextTask);
|
|
|
+ $this->submitTaskToJimengApi($nextTask);
|
|
|
}
|
|
|
}
|
|
|
|