|
|
@@ -6161,7 +6161,7 @@ class AnimeService
|
|
|
public static function appendInnerPrompt($prompt, $innerPromptType = 0)
|
|
|
{
|
|
|
$innerPromptMap = [
|
|
|
- 1 => '基于参考图生成主体的标准正面、侧面、后面三视图,主体完整。在最左侧添加主体正视图的特写,主体清晰完整。背景修改成纯白色,整体保持与参考图一致的美术风格和色彩搭配。画面无说明文字',
|
|
|
+ 1 => '基于参考图生成主体的标准正面、侧面、后面三视图,主体完整。在最左侧添加主体正视图的特写,特写大小为标准一寸照,人物不要带表情。右侧是全身的三视图,其中正面、侧面的三视图给人脸打白色码、不显示五官。主体清晰完整。背景修改成纯白色,整体保持与参考图一致的美术风格和色彩搭配。画面无说明文字。',
|
|
|
2 => '基于参考图及提示词内容,生成9张连贯的系列图片:9张图片围绕同一主体展开,保持角色形象、美术风格和色彩搭配完全一致,画面内容依次衔接、情节连贯,整体构成一组完整的系列图。每张图片主体完整清晰,构图合理,画面干净,无文字、无水印、无字幕。',
|
|
|
];
|
|
|
|
|
|
@@ -7928,7 +7928,7 @@ class AnimeService
|
|
|
->where('type', 1)->first();
|
|
|
if (!$product) Utils::throwError('20003:资产不存在');
|
|
|
|
|
|
- $prompt = trim(getProp($data, 'prompt', '基于参考图生成主体的标准正面、侧面、后面三视图,主体完整。在最左侧添加主体正视图的特写,主体清晰完整。背景修改成纯白色,整体保持与参考图一致的美术风格和色彩搭配。画面无说明文字'));
|
|
|
+ $prompt = trim(getProp($data, 'prompt', '基于参考图生成主体的标准正面、侧面、后面三视图,主体完整。在最左侧添加主体正视图的特写,特写大小为标准一寸照,人物不要带表情。右侧是全身的三视图,其中正面、侧面的三视图给人脸打白色码、不显示五官。主体清晰完整。背景修改成纯白色,整体保持与参考图一致的美术风格和色彩搭配。画面无说明文字。'));
|
|
|
$ref_img_url = trim(getProp($data, 'ref_img_url'));
|
|
|
if (!$ref_img_url) {
|
|
|
// 如果没有传入参考图,使用角色表中的图片
|
|
|
@@ -8778,7 +8778,7 @@ class AnimeService
|
|
|
'task_center_id' => $taskCenterId,
|
|
|
];
|
|
|
|
|
|
- // 有需要轮询的任务(已有任务或本次补建):走 SSE 返回结果
|
|
|
+ // 有需要轮询的任务(已有任务或本次补建):走 SSE 轮询
|
|
|
if (!empty($productTaskMap)) {
|
|
|
return $this->pollProductImageTasks(
|
|
|
$script_id,
|
|
|
@@ -8786,20 +8786,13 @@ class AnimeService
|
|
|
$productTaskMap,
|
|
|
$typeFolderIds,
|
|
|
$taskCenterId,
|
|
|
+ ['import_result' => $resultData],
|
|
|
['import_result' => $resultData]
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- // 资产图片已全部生成完成或无法生成:直接返回结果
|
|
|
- if ($taskCenterId) {
|
|
|
- $this->taskCenterService->updateTask($taskCenterId, [
|
|
|
- 'status' => MpTaskCenter::STATUS_SUCCESS,
|
|
|
- 'result' => json_encode($resultData, JSON_UNESCAPED_UNICODE),
|
|
|
- 'error_message' => null,
|
|
|
- ]);
|
|
|
- }
|
|
|
-
|
|
|
- return $resultData;
|
|
|
+ // 没有需要轮询的任务:直接通过 complete 事件返回导入结果
|
|
|
+ return $this->streamImportComplete($script_id, $script_name, $resultData, $typeFolderIds, $taskCenterId);
|
|
|
}
|
|
|
|
|
|
// 事务内创建资产、类型文件夹与剧本映射
|
|
|
@@ -8940,7 +8933,7 @@ class AnimeService
|
|
|
'task_center_id' => $taskCenterId,
|
|
|
];
|
|
|
|
|
|
- // 有待生成的图片任务:返回 SSE 轮询结果(导入统计放在 init 事件中)
|
|
|
+ // 有待生成的图片任务:返回 SSE 轮询结果(导入统计在 init 与 complete 事件中)
|
|
|
if (!empty($productTaskMap)) {
|
|
|
return $this->pollProductImageTasks(
|
|
|
$script_id,
|
|
|
@@ -8948,20 +8941,13 @@ class AnimeService
|
|
|
$productTaskMap,
|
|
|
$typeFolderIds,
|
|
|
$taskCenterId,
|
|
|
+ ['import_result' => $resultData],
|
|
|
['import_result' => $resultData]
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- // 所有资产都已有图片或未开启生成:直接返回结果
|
|
|
- if ($taskCenterId) {
|
|
|
- $this->taskCenterService->updateTask($taskCenterId, [
|
|
|
- 'status' => MpTaskCenter::STATUS_SUCCESS,
|
|
|
- 'result' => json_encode($resultData, JSON_UNESCAPED_UNICODE),
|
|
|
- 'error_message' => null,
|
|
|
- ]);
|
|
|
- }
|
|
|
-
|
|
|
- return $resultData;
|
|
|
+ // 没有需要轮询的任务:直接通过 complete 事件返回导入结果
|
|
|
+ return $this->streamImportComplete($script_id, $script_name, $resultData, $typeFolderIds, $taskCenterId);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -10213,6 +10199,105 @@ class AnimeService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 无需轮询时的 SSE 返回:直接通过 complete 事件返回导入结果
|
|
|
+ *
|
|
|
+ * @param int $script_id
|
|
|
+ * @param string $script_name
|
|
|
+ * @param array $resultData 导入结果(原普通 JSON 的 data 内容)
|
|
|
+ * @param array $type_folder_ids
|
|
|
+ * @param int|null $taskCenterId
|
|
|
+ * @return \Generator
|
|
|
+ */
|
|
|
+ private function streamImportComplete($script_id, $script_name, array $resultData, $type_folder_ids, $taskCenterId = null) {
|
|
|
+ if ($taskCenterId) {
|
|
|
+ $initResponse = [
|
|
|
+ 'type' => 'init',
|
|
|
+ 'script_id' => $script_id,
|
|
|
+ 'script_name' => $script_name,
|
|
|
+ 'timestamp' => date('Y-m-d H:i:s'),
|
|
|
+ 'task_center_id' => (int)$taskCenterId,
|
|
|
+ 'import_result' => $resultData,
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield "data: " . json_encode($initResponse, JSON_UNESCAPED_UNICODE) . "\n\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ $stats = $this->collectScriptProductStats($script_id);
|
|
|
+
|
|
|
+ $response = [
|
|
|
+ 'type' => 'complete',
|
|
|
+ 'script_id' => $script_id,
|
|
|
+ 'script_name' => $script_name,
|
|
|
+ 'stats' => $stats,
|
|
|
+ 'timestamp' => date('Y-m-d H:i:s'),
|
|
|
+ 'import_result' => $resultData,
|
|
|
+ ];
|
|
|
+ if ($taskCenterId) {
|
|
|
+ $response['task_center_id'] = (int)$taskCenterId;
|
|
|
+ }
|
|
|
+
|
|
|
+ yield "data: " . json_encode($response, JSON_UNESCAPED_UNICODE) . "\n\n";
|
|
|
+
|
|
|
+ // 更新任务中心状态和结果
|
|
|
+ $this->finishScriptProductTask($taskCenterId, $response);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 统计剧本资产的图片生成状态(仅按 mp_products 当前状态统计,不驱动任务)
|
|
|
+ *
|
|
|
+ * @param int $script_id
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ private function collectScriptProductStats($script_id) {
|
|
|
+ $stats = [
|
|
|
+ 'character' => ['total' => 0, 'success' => 0, 'completed' => 0],
|
|
|
+ 'scene' => ['total' => 0, 'success' => 0, 'completed' => 0],
|
|
|
+ 'prop' => ['total' => 0, 'success' => 0, 'completed' => 0],
|
|
|
+ ];
|
|
|
+ $typeNames = [1 => 'roles', 2 => 'scenes', 3 => 'props'];
|
|
|
+
|
|
|
+ $productIds = DB::table('mp_script_product_mappings')
|
|
|
+ ->where('script_id', $script_id)
|
|
|
+ ->pluck('product_id')
|
|
|
+ ->unique()
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ if (empty($productIds)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ $products = DB::table('mp_products')
|
|
|
+ ->whereIn('id', $productIds)
|
|
|
+ ->where('is_deleted', 0)
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ foreach ($products as $product) {
|
|
|
+ $typeName = $typeNames[$product->product] ?? 'unknown';
|
|
|
+
|
|
|
+ if (!isset($stats[$typeName])) {
|
|
|
+ $stats[$typeName] = ['total' => 0, 'success' => 0, 'completed' => 0];
|
|
|
+ }
|
|
|
+
|
|
|
+ $stats[$typeName]['total']++;
|
|
|
+
|
|
|
+ if ($product->pic_task_status === '生成成功') {
|
|
|
+ $stats[$typeName]['success']++;
|
|
|
+ $stats[$typeName]['completed']++;
|
|
|
+ } elseif ($product->pic_task_status === '生成失败') {
|
|
|
+ $stats[$typeName]['completed']++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($stats as $typeName => $stat) {
|
|
|
+ if ($stat['total'] === 0) {
|
|
|
+ unset($stats[$typeName]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $stats;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 轮询资产图片生成任务状态(SSE流式返回)
|
|
|
* 只通过查询数据库获取任务状态,不主动调用API
|
|
|
*
|
|
|
@@ -10222,9 +10307,10 @@ class AnimeService
|
|
|
* @param array $type_folder_ids 类型文件夹ID映射
|
|
|
* @param int|null $taskCenterId 任务中心ID
|
|
|
* @param array $initExtra 追加到 init 事件中的额外数据
|
|
|
+ * @param array $completeExtra 追加到 complete、timeout 事件中的额外数据
|
|
|
* @return \Generator
|
|
|
*/
|
|
|
- private function pollProductImageTasks($script_id, $script_name, $productTaskMap, $type_folder_ids, $taskCenterId = null, array $initExtra = []) {
|
|
|
+ private function pollProductImageTasks($script_id, $script_name, $productTaskMap, $type_folder_ids, $taskCenterId = null, array $initExtra = [], array $completeExtra = []) {
|
|
|
$startTime = time();
|
|
|
$timeout = 1800; // 30分钟超时
|
|
|
$pollInterval = 10; // 10秒轮询一次
|
|
|
@@ -10409,6 +10495,9 @@ class AnimeService
|
|
|
if ($taskCenterId) {
|
|
|
$response['task_center_id'] = $taskCenterId;
|
|
|
}
|
|
|
+ if ($eventType === 'complete') {
|
|
|
+ $response = array_merge($response, $completeExtra);
|
|
|
+ }
|
|
|
|
|
|
yield "data: " . json_encode($response, JSON_UNESCAPED_UNICODE) . "\n\n";
|
|
|
|
|
|
@@ -10446,6 +10535,7 @@ class AnimeService
|
|
|
if ($taskCenterId) {
|
|
|
$response['task_center_id'] = $taskCenterId;
|
|
|
}
|
|
|
+ $response = array_merge($response, $completeExtra);
|
|
|
|
|
|
yield "data: " . json_encode($response, JSON_UNESCAPED_UNICODE) . "\n\n";
|
|
|
|