Explorar el Código

优化重新生成分镜剧本接口

lh hace 3 semanas
padre
commit
ac1662e4ec
Se han modificado 1 ficheros con 79 adiciones y 15 borrados
  1. 79 15
      app/Services/DeepSeek/DeepSeekService.php

+ 79 - 15
app/Services/DeepSeek/DeepSeekService.php

@@ -12268,6 +12268,8 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
         $act_id = getProp($data, 'act_id', 0);
         $episode_id = getProp($data, 'episode_id', 0);
         $check_mode = (int)getProp($data, 'check_mode', 0); // 1=预览确认模式(仅 act_id 模式生效,不落库,返回 editAct 入参)
+        // 单片段模式:act_id 可以是数据库真实ID(数字)或预览生成的虚拟ID(字符串),非空即单片段模式
+        $isActIdMode = !empty($act_id);
         
         // 验证:prompt和template_id至少提供一个
         if (empty($prompt) && empty($template_id)) {
@@ -12322,11 +12324,27 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
         $target_anime_id = 0;
         $target_episode_number = 0;
         
-        if ($act_id > 0) {
+        if ($isActIdMode) {
             // 单个片段模式:获取该片段的内容
             $segment = DB::table('mp_episode_segments')
                 ->where('id', $act_id)
                 ->first();
+
+            // 数据库不存在时,尝试从预览虚拟 act_id 保存的 redis 数据中恢复
+            if (!$segment) {
+                $previewData = Redis::get($this->buildActPreviewKey($act_id));
+                if ($previewData) {
+                    $previewData = json_decode($previewData, true);
+                    $segment = (object)[
+                        'anime_id'       => getProp($previewData, 'anime_id'),
+                        'episode_id'     => getProp($previewData, 'episode_id'),
+                        'episode_number' => getProp($previewData, 'episode_number'),
+                        'act_number'     => getProp($previewData, 'act_number'),
+                        'act_duration'   => getProp($previewData, 'act_duration'),
+                        'act_content'    => getProp($previewData, 'act_content'),
+                    ];
+                }
+            }
             
             if (!$segment) {
                 Utils::throwError('20003:片段不存在');
@@ -12481,7 +12499,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
 
         // 输出格式铁律(用户消息侧再次强制,避免模型自由发挥)
         $user_prompt .= "\n\n【输出格式铁律-必须严格遵守】\n";
-        if ($act_id > 0) {
+        if ($isActIdMode) {
             $user_prompt .= "- 请直接以“##片段1”开头,紧跟“时长:X s”行,然后输出本片段的完整分镜内容(分镜序号、出场角色、场景、出场道具、画面、运镜、配音台词、背景音效),分镜从1开始连续编号。\n";
             $user_prompt .= "- 只允许根据【原分段剧本内容】生成一个片段(即“##片段1”),禁止生成多个片段。\n";
         } else {
@@ -12489,9 +12507,15 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
         }
         $user_prompt .= "- 缺少“##片段N”标题或“时长:”行即为格式错误;输出前必须检查格式,不得输出其他任何内容。\n";
         $user_prompt .= "- 片段时长必须在4秒到15秒之间(4s≤时长≤15s),超过15秒必须拆分为多个片段,不得输出低于4秒或超过15秒的片段。\n";
+
+        // 整集模式:强制生成与原来一致的片段数量
+        if ($episode_id > 0 && isset($segments)) {
+            $originSegmentCount = count($segments);
+            $user_prompt .= "- 本集原有{$originSegmentCount}个片段,必须严格生成{$originSegmentCount}个片段(##片段1 到 ##片段{$originSegmentCount}),不得多不得少。\n";
+        }
         
         $system_prompt = "\n\n【输出格式铁律-最高优先级】\n";
-        if ($act_id > 0) {
+        if ($isActIdMode) {
             $system_prompt .= "- 直接以“##片段1”开头,紧跟“时长:X s”行,然后输出本片段分镜内容\n";
             $system_prompt .= "- 只允许根据【原分段剧本内容】生成一个片段(即“##片段1”),禁止生成多个片段。\n";
         } else {
@@ -12673,7 +12697,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                         }
                     }
                     // 内容未带时长时,单片段模式沿用原片段的时长
-                    if ($fallbackDuration <= 0 && $act_id > 0 && !empty($segment->act_duration)) {
+                    if ($fallbackDuration <= 0 && $isActIdMode && !empty($segment->act_duration)) {
                         $fallbackDuration = (float)$segment->act_duration;
                     }
                     // 时长范围约束:4~15秒,超限兜底修正(0=未知按最低4秒)
@@ -12693,7 +12717,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             }
 
             // act_id 单片段模式:只允许一个片段,多余片段丢弃,仅保留第一个
-            if ($act_id > 0 && count($parsed_segments) > 1) {
+            if ($isActIdMode && count($parsed_segments) > 1) {
                 dLog('deepseek')->info('regenerateSegmentScript act_id 模式仅保留第一个片段', [
                     'act_id' => $act_id,
                     'original_count' => count($parsed_segments),
@@ -12821,7 +12845,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             $now = date('Y-m-d H:i:s');
             $saved_acts = []; // 用于记录保存后的片段信息
             
-            if ($act_id > 0) {
+            if ($isActIdMode) {
                 // 单个片段模式:更新单条记录
                 if (count($parsed_segments) > 0) {
                     $new_segment = $parsed_segments[0];
@@ -12830,12 +12854,17 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                     if ($check_mode === 1) {
                         return [
                             'check_mode'   => 1,
-                            'act_id'       => (int)$act_id,
+                            'act_id'       => $act_id,
                             'act_title'    => getProp($new_segment, 'act_title', ''),
                             'act_duration' => getProp($new_segment, 'act_duration', 0),
                             'act_content'  => getProp($new_segment, 'act_show_content', ''),
                         ];
                     }
+
+                    // 虚拟 act_id 仅用于预览/参考,不允许直接保存到数据库
+                    if (!is_numeric($act_id)) {
+                        Utils::throwError('20003:该片段不可直接保存');
+                    }
                     
                     $update_data = [
                         'act_content' => getProp($new_segment, 'act_content', ''),
@@ -12882,6 +12911,37 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                         'source'     => 'regenerateSegmentScript',
                     ]);
 
+                    // 为每个片段生成虚拟 act_id 并写入 redis(1天有效),供后续按虚拟 act_id 恢复片段数据
+                    $previewActs = [];
+                    foreach ($parsed_segments as $seg) {
+                        $virtualActId = 'preview_' . time() . '_' . uniqid();
+                        Redis::setex($this->buildActPreviewKey($virtualActId), 86400, json_encode([
+                            'anime_id'        => $target_anime_id,
+                            'episode_id'      => $target_episode_id,
+                            'episode_number'  => $target_episode_number,
+                            'act_number'      => $seg['act_number'],
+                            'act_title'       => $seg['act_title'],
+                            'act_duration'    => $seg['act_duration'],
+                            'act_content'     => $seg['act_content'],
+                            'act_show_content'=> $seg['act_show_content'],
+                        ], JSON_UNESCAPED_UNICODE));
+
+                        $previewActs[] = [
+                            'act_id'                 => $virtualActId,
+                            'act_number'             => $seg['act_number'],
+                            'act_title'              => $seg['act_title'],
+                            'act_duration'           => $seg['act_duration'],
+                            'act_content'            => $seg['act_content'],
+                            'act_show_content'       => $seg['act_show_content'],
+                            'video_url'              => '',
+                            'video_duration'         => 0,
+                            'video_time_point_start' => 0,
+                            'video_time_point_end'   => 0,
+                            'last_frame_url'         => '',
+                            'current_type'           => 0,
+                        ];
+                    }
+
                     return [
                         'check_mode'      => 1,
                         'anime_id'        => $target_anime_id,
@@ -12889,14 +12949,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                         'title'           => getProp($episode, 'title', ''),
                         'episode_number'  => $target_episode_number,
                         'is_generated'    => getProp($episode, 'is_generated', 0),
-                        'acts'            => array_map(function ($seg) {
-                            return [
-                                'act_number'             => $seg['act_number'],
-                                'act_duration'           => $seg['act_duration'],
-                                'act_content'            => $seg['act_content'],
-                                'act_show_content'       => $seg['act_show_content'],
-                            ];
-                        }, $parsed_segments),
+                        'acts'            => $previewActs,
                     ];
                 }
 
@@ -13138,6 +13191,17 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
     }
 
     /**
+     * 预览虚拟 act_id 在 redis 中的键
+     *
+     * @param mixed $actId
+     * @return string
+     */
+    private function buildActPreviewKey($actId): string
+    {
+        return 'anime_act_preview:' . $actId;
+    }
+
+    /**
      * 清理分镜内容:去除时长、旁白音色等非分镜行,只保留完整分镜内容(分镜标记统一为【分镜N】/【镜头N】)
      *
      * @param string $actContent