Parcourir la source

优化剧集结构

lh il y a 4 mois
Parent
commit
3376f48f4c

+ 8 - 1
app/Http/Controllers/Anime/AnimeController.php

@@ -64,13 +64,20 @@ class AnimeController extends BaseController
         return $this->success(['success'=>$result ? 1 : 0]);
     }
 
-    // 动漫详情
+    // 动漫大纲
     public function animeDetail(Request $request) {
         $data = $request->all();
 
         $result = $this->AnimeService->animeDetail($data);
         return $this->success($result);
+    }
+
+    // 动漫剧集
+    public function episodeInfo(Request $request) {
+        $data = $request->all();
 
+        $result = $this->AnimeService->episodeInfo($data);
+        return $this->success($result);
     }
 
     // public function scriptList(Request $request) {

+ 10 - 8
app/Libs/Helpers.php

@@ -2546,22 +2546,22 @@ function handleEpisodeContent($originalContent) {
     ];
     
     // 提取剧集标题
-    if (preg_match('/^([^#\n]+?)(?=\n|###)/m', trim($originalContent), $titleMatch)) {
+    if (preg_match('/^([^#\n]+?)(?=\n|###)/m', trim($originalContent, '#'), $titleMatch)) {
         $result['episode_title'] = trim($titleMatch[1]);
     }
     
     // 提取故事梗概 - 使用更精确的匹配
-    if (preg_match('/###故事梗概\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $summaryMatch)) {
+    if (preg_match('/###[  ]+故事梗概\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $summaryMatch)) {
         $result['intro'] = trim($summaryMatch[1]);
     }
     
     // 提取美术风格
-    if (preg_match('/###美术风格\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $styleMatch)) {
+    if (preg_match('/###[  ]+美术风格\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $styleMatch)) {
         $result['art_style'] = trim($styleMatch[1]);
     }
     
     // 提取主体列表
-    if (preg_match('/###主体列表\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $charactersMatch)) {
+    if (preg_match('/###[  ]+主体列表\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $charactersMatch)) {
         $charactersText = trim($charactersMatch[1]);
         $characterLines = explode("\n", $charactersText);
         
@@ -2569,7 +2569,8 @@ function handleEpisodeContent($originalContent) {
             $line = trim($line);
             if (empty($line)) continue;
             
-            if (preg_match('/^([^:]+):(.+)$/', $line, $charMatch)) {
+            // 兼容中文冒号:和英文冒号:
+            if (preg_match('/^([^::]+)[::](.+)$/u', $line, $charMatch)) {
                 $result['roles'][] = [
                     'role' => trim($charMatch[1]),
                     'description' => trim($charMatch[2])
@@ -2579,7 +2580,7 @@ function handleEpisodeContent($originalContent) {
     }
     
     // 提取场景列表
-    if (preg_match('/###场景列表\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $scenesMatch)) {
+    if (preg_match('/###[  ]+场景列表\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $scenesMatch)) {
         $scenesText = trim($scenesMatch[1]);
         $sceneLines = explode("\n", $scenesText);
         
@@ -2587,7 +2588,8 @@ function handleEpisodeContent($originalContent) {
             $line = trim($line);
             if (empty($line)) continue;
             
-            if (preg_match('/^([^:]+):(.+)$/', $line, $sceneMatch)) {
+            // 兼容中文冒号:和英文冒号:
+            if (preg_match('/^([^::]+)[::](.+)$/u', $line, $sceneMatch)) {
                 $result['scenes'][] = [
                     'scene' => trim($sceneMatch[1]),
                     'description' => trim($sceneMatch[2])
@@ -2597,7 +2599,7 @@ function handleEpisodeContent($originalContent) {
     }
     
     // 提取分镜剧本
-    if (preg_match('/###分镜剧本\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $storyboardMatch)) {
+    if (preg_match('/###[  ]+分镜剧本\s*\n(.*?)(?=\n\s*###[^#]|\z)/s', $originalContent, $storyboardMatch)) {
         $storyboardText = trim($storyboardMatch[1]);
         
         // 按幕分割 - 修复第1幕识别和乱码问题

+ 50 - 30
app/Services/Anime/AnimeService.php

@@ -6,6 +6,7 @@ use App\Consts\ErrorConst;
 use App\Facade\Site;
 use App\Libs\Utils;
 use App\Services\AIGeneration\AIImageGenerationService;
+use Dflydev\DotAccessData\Util;
 use GuzzleHttp\Client;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
@@ -36,6 +37,7 @@ class AnimeService
 
     public function batchSetRoleImg($data) {
         $anime_id = getProp($data, 'anime_id');
+        $episode_number = getProp($data, 'episode_number');
         if (!$anime_id) Utils::throwError('1002:请选择对话');
         $roles = getProp($data, 'roles');
         
@@ -67,7 +69,7 @@ class AnimeService
         }
         
         // 保存角色信息
-        $boolen = DB::table('mp_animes')->where('id', $anime_id)->update([
+        $boolen = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->update([
             'roles'             => json_encode($roles, 256),
             'generate_status'   => '执行中',
             'updated_at'        => date('Y-m-d H:i:s')
@@ -83,6 +85,7 @@ class AnimeService
     public function batchSetSceneImg($data) {
         $anime_id = getProp($data, 'anime_id');
         if (!$anime_id) Utils::throwError('1002:请选择对话');
+        $episode_number = getProp($data, 'episode_number');
         $scenes = getProp($data, 'scenes');
         
         foreach ($scenes as &$scene) {
@@ -113,7 +116,7 @@ class AnimeService
         }
         
         // 保存角色信息
-        $boolen = DB::table('mp_animes')->where('id', $anime_id)->update([
+        $boolen = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->update([
             'scenes'             => json_encode($scenes, 256),
             'generate_status'   => '执行中',
             'updated_at'        => date('Y-m-d H:i:s')
@@ -214,34 +217,51 @@ class AnimeService
         $anime['roles'] = json_decode($anime['roles']);
         $anime['scenes'] = json_decode($anime['scenes']);
 
-        // 获取分集信息
-        $episodes = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->orderBy('episode_number')
-        ->select('id', 'episode_number', 'title', 'content', 'segment_number', 'segment_content')
-        ->orderBy('episode_number')->orderBy('segment_number')->get()->map(function ($value) {
-            return (array)$value;
-        })->toArray();
-        $return_episodes = [];
-        foreach($episodes as $episode) {
-            $episode_number = getProp($episode, 'episode_number');
-            if (isset($return_episodes[$episode_number])) {
-                $return_episodes[$episode_number]['segments'][] = [
-                        'segment_number'    => getProp($episode, 'segment_number'),
-                        'segment_content'   => getProp($episode, 'segment_content')
-                    ];
-            }else {
-                $return_episodes[$episode_number] = [
-                    'episode_number'    => $episode_number, 
-                    'title'             => getProp($episode, 'title'),
-                    'segments'          => [
-                        [
-                            'segment_number'    => getProp($episode, 'segment_number'),
-                            'segment_content'   => getProp($episode, 'segment_content')
-                        ]
-                    ]
-                ];
-            }
-        }
-        $anime['episodes'] = array_values($return_episodes);
+        // // 获取分集信息
+        // $episodes = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->orderBy('episode_number')
+        // ->select('id', 'episode_number', 'title', 'content', 'segment_number', 'segment_content')
+        // ->orderBy('episode_number')->orderBy('segment_number')->get()->map(function ($value) {
+        //     return (array)$value;
+        // })->toArray();
+        // $return_episodes = [];
+        // foreach($episodes as $episode) {
+        //     $episode_number = getProp($episode, 'episode_number');
+        //     if (isset($return_episodes[$episode_number])) {
+        //         $return_episodes[$episode_number]['segments'][] = [
+        //                 'segment_number'    => getProp($episode, 'segment_number'),
+        //                 'segment_content'   => getProp($episode, 'segment_content')
+        //             ];
+        //     }else {
+        //         $return_episodes[$episode_number] = [
+        //             'episode_number'    => $episode_number, 
+        //             'title'             => getProp($episode, 'title'),
+        //             'segments'          => [
+        //                 [
+        //                     'segment_number'    => getProp($episode, 'segment_number'),
+        //                     'segment_content'   => getProp($episode, 'segment_content')
+        //                 ]
+        //             ]
+        //         ];
+        //     }
+        // }
+        // $anime['episodes'] = array_values($return_episodes);
         return $anime;
     }
+
+    public function episodeInfo($data) {
+        $anime_id = getProp($data, 'anime_id');
+        $episode_number = getProp($data, 'episode_number');
+        if (!$anime_id || !$episode_number) {
+            Utils::throwError('1002:请选择剧集');
+        }
+
+        $episode = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->select('anime_id', 'episode_number', 'title', 'intro', 'art_style', 'roles', 'scenes', 'acts')->first();
+        if (!$episode) Utils::throwError('20003: 该剧集不存在!');
+        $episode = (array)$episode;
+        $episode['roles'] = json_decode($episode['roles'], true);
+        $episode['scenes'] = json_decode($episode['scenes'], true);
+        $episode['acts'] = json_decode($episode['acts'], true);
+
+        return $episode;
+    }
 }

+ 97 - 61
app/Services/DeepSeek/DeepSeekService.php

@@ -2285,17 +2285,17 @@ EOD;
         // 转换主体列表和场景列表的格式
         $roles_content = '';
         foreach ($roles as $role) {
-            $roles_content .= getProp($role, 'role').': '.getProp($role, 'role_desc')."\n";
+            $roles_content .= getProp($role, 'role').': '.getProp($role, 'description')."\n";
         }
         $scenes_content = '';
         foreach($scenes as $scene) {
-            $scenes_content .= getProp($scene, 'scene').': '.getProp($scene, 'scene_desc')."\n";
+            $scenes_content .= getProp($scene, 'scene').': '.getProp($scene, 'description')."\n";
         }
-        $prompt = getProp($data, 'prompt');
+        $prompt = getProp($data, 'prompt', '确认分镜大纲');
         $demo = "你是一个专业的文档分析助手及资深编剧,请根据用户提供的文档内容完成示例格式的内容输出(需包含以下几个板块: <剧集标题><故事梗概><美术风格><主体列表><场景列表><分镜剧本>,每个板块之间用###分隔;同时板块之间需满足以下要求:\n\n
                     1.剧集标题必须与文档内容高度相关,剧集序号是{$episode_number}
-                    2.<主体列表>必须在以下主体中选择多个(根据拆分出的分镜剧本来确认具体几个),不得自定义:{$roles_content}\n\n
-                    3.<场景列表>必须在以下场景中选择多个(根据拆分出的分镜剧本来确认具体几个),不得自定义:{$scenes_content}\n\n
+                    2.<主体列表>必须在以下主体中选择多个(根据拆分出的分镜剧本来确认具体几个),不得自定义(需带上冒号后的具体描述):{$roles_content}\n\n
+                    3.<场景列表>必须在以下场景中选择多个(根据拆分出的分镜剧本来确认具体几个),不得自定义(需带上冒号后的具体描述):{$scenes_content}\n\n
                     4.<分镜剧本>要求包含“分幕标题”以及“分镜信息”: \n\n
                     4.1“分幕标题”是根据章节中的场景进行拆分,类似于“第1幕 场景、时间及室内或室外”的信息,其中场景只能在所给出的场景列表中选(不带场景详细描述)。
                     4.2“分镜信息”需同一场景内的对话内容完成分镜(每一幕分镜尽量不得低于5个),需包含“画面描述”、“构图设计”、“运镜调度”、“配音音色”、“台词内容”和“画面类型”这几个部分,其中“画面描述”如果出现主体,只能选择上述主体列表中的一个,主体的名称也需要保持一致(不带主体具体描述)。
@@ -2372,29 +2372,44 @@ EOD;
                 Utils::throwError('20003:章节内容无法获取,请确认');
             }
             $question = $demo."请根据以上要求分析本章剧情,完成分镜剧本,本章剧情内容如下: \n\n$content";
+            // 构建消息
+            $messages[] = 
+                [
+                    'role'    => 'user',
+                    'content' => $question
+                ];
         }else if ($prompt == '继续策划下一集') {
             $content = isset($chapters[($episode_number-1)]['content']) ? $chapters[($episode_number-1)]['content'] : '';
             if (!$content) {
                 Utils::throwError('20003:章节内容无法获取,请确认');
             }
             $question = $demo."请根据以上要求分析本章剧情,完成分镜剧本(需衔接上一章内容),本章剧情内容如下: \n\n$content";
+
+            // 获取上一集最后记录
+            $prev_sequence = $episode_number - 1;
+            $messages = DB::table('mp_anime_records')->where('anime_id', $anime_id)->where('sequence', $prev_sequence)
+            ->select('role', 'content')->orderBy('created_at', 'desc')->orderBy('id')->get()->map(function($value) {
+                return (array)$value;
+            })->toArray();
+            // 构建消息
+            $messages[] = 
+                [
+                    'role'    => 'user',
+                    'content' => $question
+                ];
         }else {
             $content = '';
             $question = $prompt;
+            $messages[] = 
+                [
+                    'role'    => 'user',
+                    'content' => $question
+                ];
         }
         
         $model = getProp($data, 'model', 'v3');
         $model = $model == 'r1' ? 'deepseek-reasoner' : 'deepseek-chat';
 
-
-        // 构建消息
-        $messages[] = [
-            [
-                'role'    => 'user',
-                'content' => $question
-            ]
-        ];
-
         $post_data = [
             'model'             => $model,
             'messages'          => $messages,
@@ -2509,14 +2524,16 @@ EOD;
             'reasoning_length' => strlen($fullReasoningContent)
         ]);
 
-// $fullContent = '111';
-// $fullReasoningContent = [];
-// $usage = [];
-// $uid = 0;
-// $content = '';
-// $question = '';
-// $episode_number = 1;
-// $anime_id = 13;
+// // $fullContent = '111';
+// // $fullReasoningContent = [];
+// // $usage = [];
+// // $uid = 0;
+// // $content = '';
+// // $question = '';
+// // $episode_number = 1;
+// // $anime_id = 13;
+// $fullContent = DB::table('mp_anime_records')->orderBy('id', 'desc')->value('content');
+        dLog('deepseek')->info('完整内容: '.$fullContent);
         // 处理完整内容并返回最终结果
         $episode_arr = handleEpisodeContent($fullContent);
 
@@ -2524,42 +2541,63 @@ EOD;
             DB::beginTransaction();
 
             // 新建动漫剧集
-            // 删除历史分集内容
-            DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->delete();
+            // // 删除历史分集内容
+            // DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->delete();
             
-            $episodes  = [];
-            $sequence = 1;
-            foreach($episode_arr['acts'] as $act) {
-                foreach($act['segments'] as $segment) {
-                    $episodes[] = [
-                        'anime_id' => $anime_id,
-                        'episode_number' => $episode_number,
-                        'title' => $episode_arr['episode_title'],
-                        // 'content' => $episode['content'],
-                        'content'   => '',
-                        'intro' => $episode_arr['intro'],
-                        'art_style' => $episode_arr['art_style'],
-                        'act_number' => $act['act_number'],
-                        'act_title' => $act['act_title'],
-                        'roles' => json_encode($episode_arr['roles'], 256),
-                        'scenes' => json_encode($episode_arr['scenes'], 256),
-                        'segment_number' => $segment['segment_number'],
-                        'segment_content' => $segment['segment_content'],
-                        'sequence' => $sequence,
-                        'created_at' => date('Y-m-d H:i:s'),
-                        'updated_at' => date('Y-m-d H:i:s')
-                    ];
-                    $sequence++;
-                }
-            }
+            // $episodes  = [];
+            // $sequence = 1;
+            // foreach($episode_arr['acts'] as $act) {
+            //     foreach($act['segments'] as $segment) {
+            //         $episodes[] = [
+            //             'anime_id' => $anime_id,
+            //             'episode_number' => $episode_number,
+            //             'title' => $episode_arr['episode_title'],
+            //             // 'content' => $episode['content'],
+            //             'content'   => '',
+            //             'intro' => $episode_arr['intro'],
+            //             'art_style' => $episode_arr['art_style'],
+            //             'act_number' => $act['act_number'],
+            //             'act_title' => $act['act_title'],
+            //             'roles' => json_encode($episode_arr['roles'], 256),
+            //             'scenes' => json_encode($episode_arr['scenes'], 256),
+            //             'segment_number' => $segment['segment_number'],
+            //             'segment_content' => $segment['segment_content'],
+            //             'sequence' => $sequence,
+            //             'created_at' => date('Y-m-d H:i:s'),
+            //             'updated_at' => date('Y-m-d H:i:s')
+            //         ];
+            //         $sequence++;
+            //     }
+            // }
 
-            $boolen2 = DB::table('mp_anime_episodes')->insert($episodes);
+            $episode = [
+                'anime_id' => $anime_id,
+                'episode_number' => $episode_number,
+                'title' => $episode_arr['episode_title'],
+                'content' => $content,
+                // 'content'   => '',
+                'intro' => $episode_arr['intro'],
+                'art_style' => $episode_arr['art_style'],
+                'roles' => json_encode($episode_arr['roles'], 256),
+                'scenes' => json_encode($episode_arr['scenes'], 256),
+                'acts' => json_encode($episode_arr['acts'], 256),
+                // 'sequence' => 0,
+                'created_at' => date('Y-m-d H:i:s'),
+            ];
+
+            $episode_id = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->value('id');
+            if ($episode_id) {
+                $episode['updated_at'] = date('Y-m-d H:i:s');
+                $boolen2 = DB::table('mp_anime_episodes')->where('id', $episode_id)->update($episode);
+            }else {
+                $boolen2 = DB::table('mp_anime_episodes')->insert($episode);
+            }
+            
             if (!$boolen2) {
-                dLog('deepseek')->info('分集内容保存失败', $episodes);
+                dLog('deepseek')->info('分集内容保存失败', $episode);
                 Utils::throwError('20003:分集内容保存失败');
             }
 
-
             // 保存对话记录
             $recrods = [
                 [
@@ -2567,6 +2605,7 @@ EOD;
                     'anime_id'  => $anime_id,
                     'role'      => 'user',
                     'content'   => $prompt,
+                    'sequence'  => $episode_number,
                     'created_at' => date('Y-m-d H:i:s'),
                     'updated_at' => date('Y-m-d H:i:s')
                 ],
@@ -2575,6 +2614,7 @@ EOD;
                     'anime_id'  => $anime_id,
                     'role'      => 'assistant',
                     'content'   => $fullContent,
+                    'sequence'  => $episode_number,
                     'created_at' => date('Y-m-d H:i:s'),
                     'updated_at' => date('Y-m-d H:i:s')
                 ]
@@ -2597,18 +2637,14 @@ EOD;
             ];
         }
         
+        DB::commit();
 
-        // DB::commit();
-        $table_data['anime_id'] = $anime_id;
-        $table_data['roles'] = json_decode($table_data['roles'], true);
-        $table_data['scenes'] = json_decode($table_data['scenes'], true);
-        $table_data['episodes'] = $episode_arr['episodes'];
-        unset($table_data['created_at']);
-        unset($table_data['updated_at']);
-        unset($table_data['status']);
+        $episode['roles'] = json_decode($episode['roles'], true);
+        $episode['scenes'] = json_decode($episode['scenes'], true);
+        $episode['acts'] = json_decode($episode['acts'], true);
         yield [
             'type' => 'done',
-            'script' => $table_data,
+            'episode' => $episode,
             'answer' => $fullContent,
             'reasoning' => $fullReasoningContent,
             'usage' => $usage

+ 1 - 0
routes/api.php

@@ -130,6 +130,7 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
         Route::post('addChat', [DeepSeekController::class, 'addChat']);
         Route::post('chat', [DeepSeekController::class, 'chat']);
         Route::get('detail', [AnimeController::class, 'animeDetail']);
+        Route::get('episodeInfo', [AnimeController::class, 'episodeInfo']);
         Route::post('edit', [AnimeController::class, 'editAnime']);
         Route::post('batchUploadImg', [ImageGenerationController::class, 'batchUploadImg']);
         Route::post('batchSetRoleImg', [AnimeController::class, 'batchSetRoleImg']);