浏览代码

全能模式下的新建对话和调整大纲都增加对道具列表的解析和保存

lh 20 小时之前
父节点
当前提交
dcbcf7c777
共有 5 个文件被更改,包括 281 次插入301 次删除
  1. 0 148
      MODIFICATION_GUIDE.md
  2. 0 90
      addChatForAce_modifications.md
  3. 72 2
      app/Libs/Helpers.php
  4. 31 17
      app/Services/Anime/AnimeService.php
  5. 178 44
      app/Services/DeepSeek/DeepSeekService.php

+ 0 - 148
MODIFICATION_GUIDE.md

@@ -1,148 +0,0 @@
-# addChatForAce 方法完整修改指南
-
-## 已完成的修改
-
-### ✅ 1. 道具提示词构建 (行5966-6004)
-已在 `addChatForAce` 方法中添加了道具列表的生成逻辑:
-- 新增 `$extra_prop_prompt` 变量
-- 在 foreach 循环中添加 product === 3 的道具处理
-- 添加道具强制设定提示词
-
-##未完成的修改
-
-### 📝 2. 修改单剧集 systemPrompt (行6006附近)
-
-需要将板块列表从:
-```
-<故事梗概><剧本亮点><人物关系><核心矛盾><主体列表><美术风格><场景列表><分段剧本>
-```
-
-改为:
-```
-<故事梗概><剧本亮点><人物关系><核心矛盾><主体列表><美术风格><场景列表><道具列表><分段剧本>
-```
-
-并在场景列表相关说明后添加道具列表说明:
-```
-4.<道具列表>与场景列表同理,需尽可能的对道具环境进行详细描述,必须严格注意不要出现任何角色和人物。
-    4.1<道具列表>分以下两部分(强制格式):道具描述和道具提示词,格式必须为:道具名:道具描述{道具提示词}
-    4.2<道具列表>的道具描述是分析在整部剧中出现过的道具,道具描述尽量简洁贴合原文。
-    4.3<道具列表>的道具提示词用于视觉呈现的道具描述,该道具需包含材质特征、颜色、尺寸比例、细节装饰、视觉风格等,描述需具有镜头感与画面表现力,适合直接用于AI图像生成模型,避免抽象词汇,强调具体可视化细节与整体视觉一致性,同时保证风格统一且符合剧情设定。该提示词在道具描述后用{}框起来。
-    {$extra_prop_prompt}
-```
-
-### 📝 3. 在示例格式中添加道具列表 (行6100附近)
-
-在场景列表示例后(###场景列表之后,###分段剧本之前)添加:
-```
-###道具列表
-神秘药瓶:一个古朴的玉质小瓶,装有救命的阴阳球。{特写镜头,古朴的玉质小瓶,瓶身呈半透明的青白色玉质,高约8厘米,直径3厘米,瓶口用红绳系着精致的翠绿色玉塞。瓶身雕刻有精细的云纹图案,在灯光下泛着温润的光泽。无人物。}
-```
-
-### 📝 4. 更新 table_data 保存逻辑 (行6300附近)
-
-在以下代码中:
-```php
-$table_data = [
-    'user_id' => $uid,
-    'anime_name' => $anime_name,
-    ...
-    'scenes' => isset($script_arr['scenes']) && is_array($script_arr['scenes']) ? json_encode($script_arr['scenes'], 256) : '[]',
-    'status' => '解析完成',
-```
-
-添加 props 字段:
-```php
-    'scenes' => isset($script_arr['scenes']) && is_array($script_arr['scenes']) ? json_encode($script_arr['scenes'], 256) : '[]',
-    'props' => isset($script_arr['props']) && is_array($script_arr['props']) ? json_encode($script_arr['props'], 256) : '[]',
-    'status' => '解析完成',
-```
-
-### 📝 5. 更新 saveEpisodeVersionData 调用 (行6400附近)
-
-找到:
-```php
-$saveResult = $this->saveEpisodeVersionData(
-    $anime_id,
-    1,
-    $episode_arr,
-    is_array(getProp($script_arr, 'roles')) ? getProp($script_arr, 'roles') : [],
-    is_array(getProp($script_arr, 'scenes')) ? getProp($script_arr, 'scenes') : [],
-```
-
-改为:
-```php
-$saveResult = $this->saveEpisodeVersionData(
-    $anime_id,
-    1,
-    $episode_arr,
-    is_array(getProp($script_arr, 'roles')) ? getProp($script_arr, 'roles') : [],
-    is_array(getProp($script_arr, 'scenes')) ? getProp($script_arr, 'scenes') : [],
-    is_array(getProp($script_arr, 'props')) ? getProp($script_arr, 'props') : [],
-```
-
-### 📝 6. 更新返回数据 (行6500附近)
-
-找到:
-```php
-$table_data['roles'] = json_decode($table_data['roles'], true);
-$table_data['scenes'] = json_decode($table_data['scenes'], true);
-```
-
-改为:
-```php
-$table_data['roles'] = json_decode($table_data['roles'], true);
-$table_data['scenes'] = json_decode($table_data['scenes'], true);
-$table_data['props'] = json_decode($table_data['props'], true);
-```
-
-### 📝 7. 更新 mp_animes 表同步逻辑 (行6420附近)
-
-找到:
-```php
-$boolen5 = DB::table('mp_animes')->where('id', $anime_id)->update([
-    'roles' => json_encode($saveResult['merged_roles'], 256),
-    'scenes' => json_encode($saveResult['merged_scenes'], 256),
-    'updated_at' => $now
-]);
-```
-
-改为:
-```php
-$boolen5 = DB::table('mp_animes')->where('id', $anime_id)->update([
-    'roles' => json_encode($saveResult['merged_roles'], 256),
-    'scenes' => json_encode($saveResult['merged_scenes'], 256),
-    'props' => json_encode($saveResult['merged_props'], 256),
-    'updated_at' => $now
-]);
-```
-
-并更新后续代码:
-```php
-$table_data['roles'] = json_encode($saveResult['merged_roles'], 256);
-$table_data['scenes'] = json_encode($saveResult['merged_scenes'], 256);
-$table_data['props'] = json_encode($saveResult['merged_props'], 256);
-```
-
-## 参考 chatForAce 方法
-
-chatForAce 方法中已经有完整的道具列表处理逻辑,可以参考:
-- 行7490附近的 chatForAce 方法
-- 包含道具的强制提示词设定
-- 道具列表的格式要求
-- 分镜中道具的使用说明
-
-## 测试要点
-
-完成所有修改后,需要测试:
-1. 单剧集模式是否能正确生成道具列表
-2. 道具数据是否正确保存到 mp_animes 和 mp_anime_episodes 表
-3. 道具提示词格式是否符合要求
-4. 与 chatForAce 方法的输出结果一致性
-
-## 注意事项
-
-1. 所有修改都只针对 addChatForAce 方法(行5851开始)
-2. 不要修改 addChat 方法(行6762附近)
-3. 确保 systemPrompt 的格式与 chatForAce 完全一致
-4. 道具提示词的编号应为 4.x,不要与场景的 3.x 冲突

文件差异内容过多而无法显示
+ 0 - 90
addChatForAce_modifications.md


+ 72 - 2
app/Libs/Helpers.php

@@ -3086,6 +3086,11 @@ function handleScriptContentForAce($originalContent) {
     preg_match('/###\s*场景列表\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $scenesMatch);
     preg_match('/###\s*场景列表\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $scenesMatch);
     $scenesText = isset($scenesMatch[1]) ? trim($scenesMatch[1]) : '';
     $scenesText = isset($scenesMatch[1]) ? trim($scenesMatch[1]) : '';
     $parts['scenes'] = parseScenesFromText($scenesText);
     $parts['scenes'] = parseScenesFromText($scenesText);
+    
+    // 提取道具列表(支持多个空格)
+    preg_match('/###\s*道具列表\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $propsMatch);
+    $propsText = isset($propsMatch[1]) ? trim($propsMatch[1]) : '';
+    $parts['props'] = parsePropsFromText($propsText);
 
 
     // 提取分集详细内容(多剧集模式,支持多个空格)
     // 提取分集详细内容(多剧集模式,支持多个空格)
     preg_match('/###\s*分集详细内容\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $contentMatch);
     preg_match('/###\s*分集详细内容\s*\n(.*?)(?=\n\s*###|$)/s', $originalContent, $contentMatch);
@@ -3142,6 +3147,11 @@ function handleScriptContentForAce($originalContent) {
             $episodeSections[] = "###场景列表\n" . $scenesText;
             $episodeSections[] = "###场景列表\n" . $scenesText;
         }
         }
         
         
+        // 添加道具列表(如果有)
+        if (!empty($propsText)) {
+            $episodeSections[] = "###道具列表\n" . $propsText;
+        }
+        
         // 添加分镜剧本内容(兼容"分镜剧本"和"分段剧本")
         // 添加分镜剧本内容(兼容"分镜剧本"和"分段剧本")
         if (strpos($singleStoryboard, '###分镜剧本') === false && strpos($singleStoryboard, '###分段剧本') === false) {
         if (strpos($singleStoryboard, '###分镜剧本') === false && strpos($singleStoryboard, '###分段剧本') === false) {
             $episodeSections[] = "###分镜剧本\n" . $singleStoryboard;
             $episodeSections[] = "###分镜剧本\n" . $singleStoryboard;
@@ -3160,11 +3170,13 @@ function handleScriptContentForAce($originalContent) {
             $parts['acts'] = getProp($episode_arr, 'acts', []);
             $parts['acts'] = getProp($episode_arr, 'acts', []);
         }
         }
 
 
-        // 更新roles和scenes
+        // 更新roles、scenes和props
         $episode_roles = getProp($episode_arr, 'roles');
         $episode_roles = getProp($episode_arr, 'roles');
         $episode_scenes = getProp($episode_arr, 'scenes');
         $episode_scenes = getProp($episode_arr, 'scenes');
+        $episode_props = getProp($episode_arr, 'props');
         if ($episode_roles) $parts['roles'] = $episode_roles;
         if ($episode_roles) $parts['roles'] = $episode_roles;
         if ($episode_scenes) $parts['scenes'] = $episode_scenes;
         if ($episode_scenes) $parts['scenes'] = $episode_scenes;
+        if ($episode_props) $parts['props'] = $episode_props;
     }
     }
 
 
     // 如果上面的处理没有得到 acts,尝试直接用原始内容调用 handleEpisodeContentForAce
     // 如果上面的处理没有得到 acts,尝试直接用原始内容调用 handleEpisodeContentForAce
@@ -3180,11 +3192,13 @@ function handleScriptContentForAce($originalContent) {
                 $parts['art_style'] = getProp($fallbackEpisodeArr, 'art_style', '');
                 $parts['art_style'] = getProp($fallbackEpisodeArr, 'art_style', '');
             }
             }
         }
         }
-        // 更新roles和scenes
+        // 更新roles、scenes和props
         $episode_roles = getProp($fallbackEpisodeArr, 'roles');
         $episode_roles = getProp($fallbackEpisodeArr, 'roles');
         $episode_scenes = getProp($fallbackEpisodeArr, 'scenes');
         $episode_scenes = getProp($fallbackEpisodeArr, 'scenes');
+        $episode_props = getProp($fallbackEpisodeArr, 'props');
         if ($episode_roles) $parts['roles'] = $episode_roles;
         if ($episode_roles) $parts['roles'] = $episode_roles;
         if ($episode_scenes) $parts['scenes'] = $episode_scenes;
         if ($episode_scenes) $parts['scenes'] = $episode_scenes;
+        if ($episode_props) $parts['props'] = $episode_props;
     }
     }
 
 
     // 多剧集格式:继续兼容旧的分集剧本结构
     // 多剧集格式:继续兼容旧的分集剧本结构
@@ -4378,6 +4392,62 @@ function parseScenesFromText(string $scenesText): array
     return $scenes;
     return $scenes;
 }
 }
 
 
+/**
+ * 将道具列表文本拆分为道具数组
+ * 
+ * @param string $propsText 道具列表文本内容
+ * @return array 道具数组
+ */
+function parsePropsFromText(string $propsText): array
+{
+    if (empty($propsText)) {
+        return [];
+    }
+    
+    $props = [];
+    
+    // 按行分割文本
+    $lines = explode("\n", $propsText);
+    
+    foreach ($lines as $line) {
+        $line = trim($line);
+        if (empty($line)) {
+            continue;
+        }
+        
+        $line = str_replace(':', ':', $line);
+        if (strstr($line, ':')) {
+            $line_arr = explode(':', $line, 2);
+            if (count($line_arr) == 2) {
+                $prop = trim($line_arr[0]);
+                $description = trim($line_arr[1]);
+                $picPrompt = null;
+                
+                // 检查描述末尾是否有{道具图片提示词}格式
+                if (preg_match('/^(.*?)\{([^}]+)\}\s*$/u', $description, $promptMatch)) {
+                    // 匹配格式:道具描述{道具图片提示词}
+                    $description = trim($promptMatch[1]);
+                    $picPrompt = trim($promptMatch[2]);
+                }
+                
+                $propData = [
+                    'prop' => $prop,
+                    'description' => $description,
+                ];
+                
+                // 如果有道具图片提示词,添加到数组中
+                if ($picPrompt) {
+                    $propData['pic_prompt'] = $picPrompt;
+                }
+                
+                $props[] = $propData;
+            }
+        }
+    }
+    
+    return $props;
+}
+
 
 
 /**
 /**
  * 记录日志到数据库
  * 记录日志到数据库

+ 31 - 17
app/Services/Anime/AnimeService.php

@@ -7007,6 +7007,7 @@ class AnimeService
         $role = Site::getRole();
         $role = Site::getRole();
         $product_id = getProp($data, 'product_id');
         $product_id = getProp($data, 'product_id');
         $target_parent_id = getProp($data, 'target_parent_id', 0);
         $target_parent_id = getProp($data, 'target_parent_id', 0);
+        $target_product = getProp($data, 'product'); // 目标 product 类型,用于跨类型推送
         
         
         if (!$product_id) {
         if (!$product_id) {
             Utils::throwError('20003:请选择要推送的项目');
             Utils::throwError('20003:请选择要推送的项目');
@@ -7031,6 +7032,7 @@ class AnimeService
         $target_level = 0;
         $target_level = 0;
         $target_uid = $uid; // 默认推送到个人库
         $target_uid = $uid; // 默认推送到个人库
         $target_is_public = 0;
         $target_is_public = 0;
+        $final_product_type = $sourceItem->product; // 默认使用源的 product 类型
         
         
         if ($target_parent_id > 0) {
         if ($target_parent_id > 0) {
             // 查询目标文件夹(只通过cpid查询,不限制user_id)
             // 查询目标文件夹(只通过cpid查询,不限制user_id)
@@ -7045,24 +7047,30 @@ class AnimeService
                 Utils::throwError('20003:目标文件夹不存在');
                 Utils::throwError('20003:目标文件夹不存在');
             }
             }
             
             
-            // 检查 product 类型是否一致
-            if ($targetParent->product != $sourceItem->product) {
-                Utils::throwError('20003:不能推送到不同类型的文件夹下');
-            }
-            
             // 通过user_id判断目标是公共库还是个人库
             // 通过user_id判断目标是公共库还是个人库
             $target_level = $targetParent->level;
             $target_level = $targetParent->level;
             $target_uid = $targetParent->user_id;
             $target_uid = $targetParent->user_id;
             $target_is_public = ($target_uid == 0) ? 1 : 0;
             $target_is_public = ($target_uid == 0) ? 1 : 0;
+            
+            // 使用目标文件夹的 product 类型
+            $final_product_type = $targetParent->product;
         }
         }
         // 如果 target_parent_id = 0(推送到根目录),需要通过参数判断推送到个人库还是公共库
         // 如果 target_parent_id = 0(推送到根目录),需要通过参数判断推送到个人库还是公共库
         else {
         else {
             $push_to_public = getProp($data, 'push_to_public');
             $push_to_public = getProp($data, 'push_to_public');
-            if ($push_to_public === '') Utils::throwError('20003:请选择是否推送到公共库');
+            if ($push_to_public === '') {
+                Utils::throwError('20003:请选择是否推送到公共库');
+            }
             if ($push_to_public) {
             if ($push_to_public) {
                 $target_uid = 0;
                 $target_uid = 0;
                 $target_is_public = 1;
                 $target_is_public = 1;
             }
             }
+            
+            // 推送到根目录时,必须指定 product 类型
+            if ($target_product === null || $target_product === '') {
+                Utils::throwError('20003:推送到根目录时必须指定目标类型(product)');
+            }
+            $final_product_type = $target_product;
         }
         }
         
         
         // 如果目标是公共库,需要admin权限
         // 如果目标是公共库,需要admin权限
@@ -7076,17 +7084,17 @@ class AnimeService
         try {
         try {
             DB::beginTransaction();
             DB::beginTransaction();
             
             
-            // 如果是资产,直接复制
+            // 如果是资产,直接复制(支持跨类型推送,更新 product 字段)
             if ($sourceItem->type == 1) {
             if ($sourceItem->type == 1) {
-                $newId = $this->copyProduct($sourceItem, $target_parent_id, $target_level + 1, $cpid, $target_uid);
+                $newId = $this->copyProduct($sourceItem, $target_parent_id, $target_level + 1, $cpid, $target_uid, $final_product_type);
                 DB::commit();
                 DB::commit();
                 return [
                 return [
                     'product_id' => $newId,
                     'product_id' => $newId,
                 ];
                 ];
             }
             }
             
             
-            // 如果是文件夹,递归复制
-            $newFolderId = $this->copyFolderRecursive($sourceItem, $target_parent_id, $target_level + 1, $cpid, $target_uid, $source_uid);
+            // 如果是文件夹,递归复制(支持跨类型推送)
+            $newFolderId = $this->copyFolderRecursive($sourceItem, $target_parent_id, $target_level + 1, $cpid, $target_uid, $source_uid, $final_product_type);
             
             
             DB::commit();
             DB::commit();
             return [
             return [
@@ -7156,7 +7164,10 @@ class AnimeService
      * @param int $targetUid 目标用户ID
      * @param int $targetUid 目标用户ID
      * @return int 返回新资产ID
      * @return int 返回新资产ID
      */
      */
-    private function copyProduct($sourceProduct, $targetParentId, $targetLevel, $cpid, $targetUid) {
+    private function copyProduct($sourceProduct, $targetParentId, $targetLevel, $cpid, $targetUid, $productType = null) {
+        // 如果指定了 productType,使用指定的;否则使用源的 product 类型
+        $finalProductType = ($productType !== null) ? $productType : $sourceProduct->product;
+        
         $newProductData = [
         $newProductData = [
             'user_id' => $targetUid,
             'user_id' => $targetUid,
             'cpid' => $cpid,
             'cpid' => $cpid,
@@ -7167,7 +7178,7 @@ class AnimeService
             'url' => $sourceProduct->url,
             'url' => $sourceProduct->url,
             'pic_prompt' => $sourceProduct->pic_prompt ?? '',
             'pic_prompt' => $sourceProduct->pic_prompt ?? '',
             'three_view_image_url' => $sourceProduct->three_view_image_url ?? '',
             'three_view_image_url' => $sourceProduct->three_view_image_url ?? '',
-            'product' => $sourceProduct->product,
+            'product' => $finalProductType,
             'versions' => $sourceProduct->versions ?? '',
             'versions' => $sourceProduct->versions ?? '',
             'sort_order' => time(),
             'sort_order' => time(),
             'is_deleted' => 0,
             'is_deleted' => 0,
@@ -7188,7 +7199,10 @@ class AnimeService
      * @param int $sourceUid 源用户ID
      * @param int $sourceUid 源用户ID
      * @return int 返回新文件夹ID
      * @return int 返回新文件夹ID
      */
      */
-    private function copyFolderRecursive($sourceFolder, $targetParentId, $targetLevel, $cpid, $targetUid, $sourceUid) {
+    private function copyFolderRecursive($sourceFolder, $targetParentId, $targetLevel, $cpid, $targetUid, $sourceUid, $productType = null) {
+        // 如果指定了 productType,使用指定的;否则使用源的 product 类型
+        $finalProductType = ($productType !== null) ? $productType : $sourceFolder->product;
+        
         // 创建新文件夹
         // 创建新文件夹
         $newFolderData = [
         $newFolderData = [
             'user_id' => $targetUid,
             'user_id' => $targetUid,
@@ -7197,7 +7211,7 @@ class AnimeService
             'parent_id' => $targetParentId,
             'parent_id' => $targetParentId,
             'level' => $targetLevel,
             'level' => $targetLevel,
             'product_name' => $sourceFolder->product_name,
             'product_name' => $sourceFolder->product_name,
-            'product' => $sourceFolder->product,
+            'product' => $finalProductType,
             'sort_order' => time(),
             'sort_order' => time(),
             'is_deleted' => 0,
             'is_deleted' => 0,
             'created_at' => date('Y-m-d H:i:s'),
             'created_at' => date('Y-m-d H:i:s'),
@@ -7215,14 +7229,14 @@ class AnimeService
             ->orderByRaw('type DESC, sort_order DESC, created_at DESC')
             ->orderByRaw('type DESC, sort_order DESC, created_at DESC')
             ->get();
             ->get();
         
         
-        // 递归复制子项
+        // 递归复制子项(子项继承目标文件夹的 product 类型)
         foreach ($children as $child) {
         foreach ($children as $child) {
             if ($child->type == 1) {
             if ($child->type == 1) {
                 // 资产
                 // 资产
-                $this->copyProduct($child, $newFolderId, $targetLevel + 1, $cpid, $targetUid);
+                $this->copyProduct($child, $newFolderId, $targetLevel + 1, $cpid, $targetUid, $finalProductType);
             } else {
             } else {
                 // 文件夹
                 // 文件夹
-                $this->copyFolderRecursive($child, $newFolderId, $targetLevel + 1, $cpid, $targetUid, $sourceUid);
+                $this->copyFolderRecursive($child, $newFolderId, $targetLevel + 1, $cpid, $targetUid, $sourceUid, $finalProductType);
             }
             }
         }
         }
         
         

文件差异内容过多而无法显示
+ 178 - 44
app/Services/DeepSeek/DeepSeekService.php