Pārlūkot izejas kodu

优化全局主体和场景图片生成代码,并新增继承规则

lh 3 mēneši atpakaļ
vecāks
revīzija
3ceb7bd897

+ 100 - 8
app/Console/Anime/AnimeCheckImgUrlCommand.php

@@ -44,19 +44,21 @@ class AnimeCheckImgUrlCommand extends Command
             $scenes = json_decode(getProp($item, 'scenes'), true);
             $role_count = count($roles);
             $scene_count = count($scenes);
-            $role_success_count = 0;
-            $scene_success_count = 0;
 
             // 处理角色图片状态
-            $role_success_count = $this->checkRoles($id, $roles);
+            $role_result = $this->checkRoles($id, $roles);
 
             // 处理场景图片状态
-            $scene_success_count = $this->checkScenes($id, $scenes);
+            $scene_result = $this->checkScenes($id, $scenes);
+            $scene_result['success_count'] = 6;
 
-            if ($role_success_count == $role_count && $scene_success_count == $scene_count) {
+            if ($role_result['success_count'] == $role_count && $scene_result['success_count'] == $scene_count) {
                 // 更新状态为已完成
-                DB::table('mp_animes')->where('id', $id)->update(['generate_status' => '已完成', 'updated_at'=>date('Y-m-d H:i:s')]);
+                // DB::table('mp_animes')->where('id', $id)->update(['generate_status' => '已完成', 'updated_at'=>date('Y-m-d H:i:s')]);
                 dLog('generate')->info('动漫对话图片已全部生成完毕');
+
+                // 同步更新该动漫对话剧集的所有角色或场景
+                $this->syncRolesAndScenes($id, $role_result['roles'], $scene_result['scenes']);
             }
         }
 
@@ -91,7 +93,10 @@ class AnimeCheckImgUrlCommand extends Command
             DB::table('mp_animes')->where('id', $id)->update(['roles' => json_encode($roles, 256)]);
         }
 
-        return $role_success_count;
+        return [
+            'roles'         => $roles,
+            'success_count' => $role_success_count,
+        ];
     }
 
     private function checkScenes($id, $scenes) {
@@ -119,7 +124,94 @@ class AnimeCheckImgUrlCommand extends Command
         if ($scene_success_count > 0) {
             DB::table('mp_animes')->where('id', $id)->update(['scenes' => json_encode($scenes, 256)]);
         }
+        return [
+            'scenes'        => $scenes,
+            'success_count' => $scene_success_count,
+        ];
+    }
+
+    private function syncRolesAndScenes($id, $roles, $scenes) {
+        // 获取该动漫对话对应的所有剧集
+        $episodes = DB::table('mp_anime_episodes')->where('anime_id', $id)->get();
+        
+        if ($episodes->isEmpty()) {
+            dLog('generate')->info("动漫对话ID:{$id} 没有关联的剧集");
+            return;
+        }
+
+        foreach ($episodes as $episode) {
+            $episode_id = getProp($episode, 'id');
+            $episode_roles = json_decode(getProp($episode, 'roles'), true) ?: [];
+            $episode_scenes = json_decode(getProp($episode, 'scenes'), true) ?: [];
+            
+            $roles_updated = false;
+            $scenes_updated = false;
+
+            // 更新剧集中的角色URL
+            foreach ($episode_roles as &$episode_role) {
+                $episode_role_name = getProp($episode_role, 'role');
+                $episode_description = getProp($episode_role, 'description');
+                $episode_url = getProp($episode_role, 'url');
+
+                // 遍历方法中的角色数据
+                foreach ($roles as $role) {
+                    $role_name = getProp($role, 'role');
+                    $role_description = getProp($role, 'description');
+                    $role_url = getProp($role, 'url');
+
+                    // 满足三个条件:1.角色名相同 2.描述相同 3.剧集中URL为空且方法中URL不为空
+                    if ($episode_role_name == $role_name 
+                        && $episode_description == $role_description 
+                        && empty($episode_url) 
+                        && !empty($role_url)) {
+                        $episode_role['url'] = $role_url;
+                        $roles_updated = true;
+                        dLog('generate')->info("剧集ID:{$episode_id} 角色:{$role_name} URL已更新");
+                        break;
+                    }
+                }
+            }
 
-        return $scene_success_count;
+            // 更新剧集中的场景URL
+            foreach ($episode_scenes as &$episode_scene) {
+                $episode_scene_name = getProp($episode_scene, 'scene');
+                $episode_description = getProp($episode_scene, 'description');
+                $episode_url = getProp($episode_scene, 'url');
+
+                // 遍历方法中的场景数据
+                foreach ($scenes as $scene) {
+                    $scene_name = getProp($scene, 'scene');
+                    $scene_description = getProp($scene, 'description');
+                    $scene_url = getProp($scene, 'url');
+
+                    // 满足三个条件:1.场景名相同 2.描述相同 3.剧集中URL为空且方法中URL不为空
+                    if ($episode_scene_name == $scene_name 
+                        && $episode_description == $scene_description 
+                        && empty($episode_url) 
+                        && !empty($scene_url)) {
+                        $episode_scene['url'] = $scene_url;
+                        $scenes_updated = true;
+                        dLog('generate')->info("剧集ID:{$episode_id} 场景:{$scene_name} URL已更新");
+                        break;
+                    }
+                }
+            }
+
+            // 如果有更新,则保存到数据库
+            if ($roles_updated || $scenes_updated) {
+                $update_data = ['updated_at' => date('Y-m-d H:i:s')];
+                
+                if ($roles_updated) {
+                    $update_data['roles'] = json_encode($episode_roles, 256);
+                }
+                
+                if ($scenes_updated) {
+                    $update_data['scenes'] = json_encode($episode_scenes, 256);
+                }
+
+                DB::table('mp_anime_episodes')->where('id', $episode_id)->update($update_data);
+                dLog('generate')->info("剧集ID:{$episode_id} 数据已同步更新");
+            }
+        }
     }
 }

+ 0 - 1
app/Libs/Helpers.php

@@ -2573,7 +2573,6 @@ function handleEpisodeContent($originalContent) {
     if (preg_match('/###\s*故事梗概\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $summaryMatch)) {
         $result['intro'] = trim($summaryMatch[1]);
     }
-    dd($result);
     
     // 提取美术风格 - 兼容多种格式
     if (preg_match('/###\s*美术风格\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $styleMatch)) {

+ 149 - 5
app/Services/DeepSeek/DeepSeekService.php

@@ -8,6 +8,7 @@ use App\Libs\Utils;
 use GuzzleHttp\Client;
 use DateTime;
 use DateTimeZone;
+use App\Services\AIGeneration\AIImageGenerationService;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Redis;
@@ -23,8 +24,10 @@ class DeepSeekService
     private $api_key;
     private $sys_message;
     private $headers;
+    protected $aiImageGenerationService;
 
-    public function __construct() {
+    public function __construct(AIImageGenerationService $aiImageGenerationService) {
+        $this->aiImageGenerationService = $aiImageGenerationService;
         $this->url = 'https://api.deepseek.com/chat/completions';
         $this->api_key = env('DEEPSEEK_API_KEY');
         $this->headers = [
@@ -2633,6 +2636,7 @@ class DeepSeekService
 
         $origin_prompt = trim((string)getProp($data, 'prompt', '确认分镜大纲'));
         $is_regenerate_version = !in_array($origin_prompt, ['确认分镜大纲', '继续策划下一集'], true);
+        $is_global_generate_pics = $origin_prompt == '确认分镜大纲' ? 1 : 0;
         $prompt = $origin_prompt;
 
         // 获取章节内容
@@ -2810,13 +2814,14 @@ class DeepSeekService
         $episode_arr = handleEpisodeContent($fullContent);
         if (empty($episode_arr['acts'])) {
             dLog('deepseek')->info('未生成分镜剧本相关的内容');
-            return [
+            yield [
                 'type' => 'done',
                 'episode' => [],
                 'answer' => $fullContent,
                 'reasoning' => $fullReasoningContent,
                 'usage' => $usage
             ];
+            return;
         }
 
         $existing_roles = $base_episode ? json_decode((string)getProp($base_episode, 'roles'), true) : [];
@@ -2981,7 +2986,20 @@ class DeepSeekService
         }
         
         DB::commit();
-        // dd('end');
+
+        if ($is_global_generate_pics) {
+            // 批量生成全局角色图片
+            $this->batchSetGlobalRoleImg([
+                'anime_id' => $anime_id,
+                'episode_number' => $episode_number
+            ]);
+            
+            // 批量生成全局场景图片
+            $this->batchSetGlobalSceneImg([
+                'anime_id' => $anime_id,
+                'episode_number' => $episode_number
+            ]);
+        }
 
         $episode['episode_id'] = $episode_id;
         $episode['roles'] = $merged_roles;
@@ -3066,7 +3084,21 @@ class DeepSeekService
                 }
             }
 
-            $merged[] = $this->resetEpisodeResourceItem($item, $nameKey);
+            // 重置资源项,并尝试从现有项中继承URL
+            $resetItem = $this->resetEpisodeResourceItem($item, $nameKey);
+            
+            // 如果现有项存在且有URL,而生成项没有URL,则继承现有URL
+            if (isset($existingMap[$itemKey])) {
+                $existingUrl = getProp($existingMap[$itemKey], 'url');
+                $generatedUrl = getProp($item, 'url');
+                
+                // 条件:1.现有URL存在 2.生成URL不存在 3.名称和描述一致(已通过itemKey和上面的逻辑保证)
+                if (!empty($existingUrl) && empty($generatedUrl)) {
+                    $resetItem['url'] = $existingUrl;
+                }
+            }
+
+            $merged[] = $resetItem;
         }
 
         return $merged ?: array_values($existingItems);
@@ -3074,10 +3106,18 @@ class DeepSeekService
 
     private function resetEpisodeResourceItem(array $item, string $nameKey): array
     {
-        return [
+        $resetItem = [
             $nameKey => trim((string)getProp($item, $nameKey)),
             'description' => trim((string)getProp($item, 'description')),
         ];
+        
+        // 如果生成项本身有URL,保留它
+        $url = getProp($item, 'url');
+        if (!empty($url)) {
+            $resetItem['url'] = $url;
+        }
+        
+        return $resetItem;
     }
 
     private function isEpisodeResourceChanged(array $existingItem, array $generatedItem, string $nameKey): bool
@@ -3101,6 +3141,110 @@ class DeepSeekService
         return strtolower((string)preg_replace('/\s+/u', '', $value));
     }
 
+    // 生成全局角色图片
+    private function batchSetGlobalRoleImg($data) {
+        $anime_id = getProp($data, 'anime_id');
+        $episode_number = getProp($data, 'episode_number');
+        if (!$anime_id) Utils::throwError('1002:请选择对话');
+        $anime = DB::table('mp_animes')->where('id', $anime_id)->where('is_deleted', 0)->first();
+        $roles = json_decode(getProp($anime, 'roles'), true);
+        $art_style = getProp($anime, 'art_style');
+        
+        foreach ($roles as &$role) {
+            $role_name = getProp($role, 'role');
+            $description = getProp($role, 'description');
+            if ($art_style) $description = "美术风格:\n$art_style\n主体描述:$description";
+            // 参考图地址
+            $ref_img_url = getProp($role, 'url');
+            
+            // 此处调用AIImageGenerationService的生成图片任务接口: createImageGenerationTask(注意使用默认参数,prompt使用角色描述),并返回任务id
+            try {
+                $params = [
+                    'prompt' => $description,
+                    'ref_img_urls' => !empty($ref_img_url) ? [$ref_img_url] : []
+                ];
+                
+                $task = $this->aiImageGenerationService->createImageGenerationTask($params);
+                $task_id = $task->id;
+                
+                if (!$task_id) {
+                    Utils::throwError("20003: 角色({$role_name})生成图片失败");
+                }
+                
+                $role['task_id'] = $task_id;
+                $role['task_status'] = 'processing';
+            } catch (\Exception $e) {
+                Utils::throwError("20003:" . $e->getMessage());
+            }
+        }
+        
+        // 保存角色信息
+        // $boolen = DB::table('mp_animes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->where('is_default', 1)->update([
+        $boolen = DB::table('mp_animes')->where('id', $anime_id)->update([
+            'roles'             => json_encode($roles, 256),
+            'generate_status'   => '执行中',
+            'generate_at'       => date('Y-m-d H:i:s'),
+            'updated_at'        => date('Y-m-d H:i:s')
+        ]);
+        if (!$boolen) {
+            Utils::throwError('20003:保存角色信息失败');
+        }
+
+        return $boolen;
+    }
+
+    // 生成全局场景图片
+    private function batchSetGlobalSceneImg($data) {
+        $anime_id = getProp($data, 'anime_id');
+        if (!$anime_id) Utils::throwError('1002:请选择对话');
+        $episode_number = getProp($data, 'episode_number');
+        $anime = DB::table('mp_animes')->where('id', $anime_id)->where('is_deleted', 0)->first();
+        $scenes = json_decode(getProp($anime, 'scenes'), true);
+        $art_style = getProp($anime, 'art_style');
+        
+        foreach ($scenes as &$scene) {
+            $scene_name = getProp($scene, 'scene');
+            $description = getProp($scene, 'description');
+            if ($art_style) $description = "美术风格:\n$art_style\n\n场景描述:$description";
+            // 参考图地址
+            $ref_img_url = getProp($scene, 'url');
+            
+            // 此处调用AIImageGenerationService的生成图片任务接口: createImageGenerationTask(注意使用默认参数,prompt使用角色描述),并返回任务id
+            try {
+                $params = [
+                    'prompt' => $description,
+                    'ref_img_urls' => !empty($ref_img_url) ? [$ref_img_url] : []
+                ];
+                
+                $task = $this->aiImageGenerationService->createImageGenerationTask($params);
+                $task_id = $task->id;
+                
+                if (!$task_id) {
+                    Utils::throwError("20003: 角色({$scene_name})生成图片失败");
+                }
+                
+                $scene['task_id'] = $task_id;
+                $scene['task_status'] = 'processing';
+            } catch (\Exception $e) {
+                Utils::throwError("20003:" . $e->getMessage());
+            }
+        }
+        
+        // 保存角色信息
+        // $boolen = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->where('is_default', 1)->update([
+        $boolen = DB::table('mp_animes')->where('id', $anime_id)->update([
+            'scenes'             => json_encode($scenes, 256),
+            'generate_status'   => '执行中',
+            'generate_at'       => date('Y-m-d H:i:s'),
+            'updated_at'        => date('Y-m-d H:i:s')
+        ]);
+        if (!$boolen) {
+            Utils::throwError('20003:保存角色信息失败');
+        }
+
+        return $boolen;
+    }
+
     public function chatChangeImg($data) {
         $segment = getProp($data, 'segment');
         $segment_content = getProp($segment, 'segment_content');