|
|
@@ -34,10 +34,12 @@ class DeepSeekService
|
|
|
$this->sys_message = [
|
|
|
'role' => 'system',
|
|
|
'content' => "你是一个专业的文档分析助手及资深编剧,请根据用户提供的文档内容完成示例格式的内容输出(需通过以下几个板块进行回复: <故事梗概><剧本亮点><人物关系><核心矛盾><主体列表><美术风格><场景列表><分集剧本>,每个板块之间用###分隔;同时板块之间需满足以下要求:\n
|
|
|
+ 强制要求:\n
|
|
|
+ 你只负责与剧本相关的内容,如果用户输入与剧本无关的内容,请告知用户并拒绝继续回答,此为第一优先原则,不满足此原则就忽略其他要求并停止继续输出\n
|
|
|
+ 普通要求:\n
|
|
|
1.<主体列表>中出现的主体必须尽可能统一,只允许同一个角色年龄段相差较大的情况下可重复,比如梁萧-少年、梁萧-青年、梁萧-中年、梁萧-老年这种情况,不得出现梁萧-愤怒、梁萧-隐忍、梁萧-求生等情绪和状态变化的同一角色;并且角色的样貌、形体及穿着描述尽可能详细\n
|
|
|
- 2.<分集剧本>要求包含“分集序号”、“分集标题”以及分镜信息,分镜信息需包含“场景描述”、“运镜”、“出场角色”和“台词内容”这几个部分,其中“出场角色”和“场景描述”必须在前面的板块<主体列表>和<场景列表>中选取,不得自定义。\n
|
|
|
- 3.你只负责与剧本相关的内容,如果用户输入与剧本无关的内容,请告知用户并拒绝继续回答\n
|
|
|
- 示例如下:\n\n
|
|
|
+ 2.<分集剧本>要求包含“分集序号”、“分集标题”以及分镜信息,分镜信息需包含“场景描述”、“运镜”、“出场角色”和“台词内容”这几个部分,其中“出场角色”和“场景描述”必须在前面的板块<主体列表>和<场景列表>中选取,不得自定义。\n\n
|
|
|
+ 示例如下:\n
|
|
|
###剧本名:西昆仑
|
|
|
|
|
|
###故事梗概
|
|
|
@@ -1813,10 +1815,12 @@ class DeepSeekService
|
|
|
[
|
|
|
'role' => 'system',
|
|
|
'content' => "你是一个专业的文档分析助手及资深编剧,请根据用户提供的文档内容及下面提供的示例格式完成剧本大纲(需通过以下几个板块进行回复: <故事梗概><剧本亮点><人物关系><核心矛盾><主体列表><美术风格><场景列表>,每个板块之间用###分隔;同时板块之间需满足以下要求:\n
|
|
|
+ 强制要求:\n
|
|
|
+ 你只负责与剧本相关的内容,如果用户输入与剧本无关的内容,请告知用户并拒绝继续回答,此为第一优先原则,不满足此原则就忽略其他要求并停止继续输出\n
|
|
|
+ 普通要求:\n
|
|
|
1.<主体列表>中出现的主体必须尽可能统一,只允许同一个角色年龄段相差较大的情况下可重复,比如梁萧-少年、梁萧-青年、梁萧-中年、梁萧-老年这种情况,不得出现梁萧-愤怒、梁萧-隐忍、梁萧-求生等情绪和状态变化的同一角色;并且角色的样貌、形体及穿着描述尽可能详细\n
|
|
|
- 2.<场景列表>与主体列表同理,需尽可能的对场景环境进行详细描述\n
|
|
|
- 3.你只负责与剧本相关的内容,如果用户输入与剧本无关的内容,请告知用户并拒绝继续回答\n
|
|
|
- 示例如下:\n\n
|
|
|
+ 2.<场景列表>与主体列表同理,需尽可能的对场景环境进行详细描述\n\n
|
|
|
+ 示例如下:\n
|
|
|
###剧本名:西昆仑
|
|
|
|
|
|
###故事梗概
|
|
|
@@ -2133,10 +2137,14 @@ class DeepSeekService
|
|
|
$system_message = [
|
|
|
'role' => 'system',
|
|
|
'content' => "你是一个专业的文档分析助手及资深编剧,请根据用户提供的文档内容及下面提供的示例格式完成剧本大纲(需通过以下几个板块进行回复: <故事梗概><剧本亮点><人物关系><核心矛盾><主体列表><美术风格><场景列表>,每个板块之间用###分隔;同时板块之间需满足以下要求:\n
|
|
|
+ 强制要求:\n
|
|
|
+ 1.你只负责与剧本相关的内容,如果用户输入与剧本无关的内容,请告知用户并拒绝继续回答,此为第一优先原则,不满足此原则就忽略其他要求并停止继续输出\n
|
|
|
+ 2.如果用户输入修改美术风格、修改画风这类内容,请告知用户不可修改美术风格,并拒绝继续回答\n
|
|
|
+ 3.以上两条要求对修改主体角色或场景不生效,允许用户调整主体角色说明和场景描述等\n
|
|
|
+ 普通要求:\n
|
|
|
1.<主体列表>中出现的主体必须尽可能统一,只允许同一个角色年龄段相差较大的情况下可重复,比如梁萧-少年、梁萧-青年、梁萧-中年、梁萧-老年这种情况,不得出现梁萧-愤怒、梁萧-隐忍、梁萧-求生等情绪和状态变化的同一角色;并且角色的样貌、形体及穿着描述尽可能详细\n
|
|
|
- 2.<场景列表>与主体列表同理,需尽可能的对场景环境进行详细描述\n
|
|
|
- 3.你只负责与剧本相关的内容,如果用户输入与剧本无关的内容,请告知用户并拒绝继续回答\n
|
|
|
- 示例如下:\n\n
|
|
|
+ 2.<场景列表>与主体列表同理,需尽可能的对场景环境进行详细描述\n\n
|
|
|
+ 示例如下:\n
|
|
|
###剧本名:西昆仑
|
|
|
|
|
|
###故事梗概
|
|
|
@@ -2180,11 +2188,15 @@ class DeepSeekService
|
|
|
$system_message = [
|
|
|
'role' => 'system',
|
|
|
'content' => "你是一个专业的文档分析助手及资深编剧,请根据用户提供的文档内容及下面提供的示例格式完成剧本大纲(需通过以下几个板块进行回复: <故事梗概><剧本亮点><人物关系><核心矛盾><主体列表><美术风格><场景列表><分集详细内容>,每个板块之间用###分隔;同时板块之间需满足以下要求:\n
|
|
|
+ 强制要求:\n
|
|
|
+ 1.你只负责与剧本相关的内容,如果用户输入与剧本无关的内容,请告知用户并拒绝继续回答,此为第一优先原则,不满足此原则就忽略其他要求并停止继续输出\n
|
|
|
+ 2.如果用户输入修改美术风格、修改画风这类内容,请告知用户不可修改美术风格,并拒绝继续回答\n
|
|
|
+ 3.以上两条要求对修改主体角色或场景不生效,允许用户调整主体角色说明和场景描述等\n
|
|
|
+ 普通要求:\n
|
|
|
1.<主体列表>中出现的主体必须尽可能统一,只允许同一个角色年龄段相差较大的情况下可重复,比如梁萧-少年、梁萧-青年、梁萧-中年、梁萧-老年这种情况,不得出现梁萧-愤怒、梁萧-隐忍、梁萧-求生等情绪和状态变化的同一角色;并且角色的样貌、形体及穿着描述尽可能详细\n
|
|
|
2.<场景列表>与主体列表同理,需尽可能的对场景环境进行详细描述\n
|
|
|
- 3.<分集详细内容>中每一集的内容需尽可能的详细,保留对话内容(使用原文内容,不得自己生成),一般每集字数尽量不低于500字,每集结尾需以##第N章 作为章节标题\n
|
|
|
- 4.你只负责与剧本相关的内容,如果用户输入与剧本无关的内容,请告知用户并拒绝继续回答\n
|
|
|
- 示例如下:\n\n
|
|
|
+ 3.<分集详细内容>中每一集的内容需尽可能的详细,保留对话内容(使用原文内容,不得自己生成),一般每集字数尽量不低于500字,每集结尾需以##第N章 作为章节标题\n\n
|
|
|
+ 示例如下:\n
|
|
|
###剧本名:西昆仑
|
|
|
|
|
|
###故事梗概
|
|
|
@@ -2395,23 +2407,6 @@ class DeepSeekService
|
|
|
$fullContent = $this->replaceArtStyleSection($fullContent, $mappedArtStyle);
|
|
|
}
|
|
|
|
|
|
- // // 新建动漫
|
|
|
- // $anime_name = getProp($script_arr, 'script_name');
|
|
|
- // if (!$anime_name) {
|
|
|
- // yield [
|
|
|
- // 'type' => 'done',
|
|
|
- // 'answer' => $fullContent,
|
|
|
- // 'reasoning' => $fullReasoningContent,
|
|
|
- // 'usage' => $usage,
|
|
|
- // 'error' => '未能识别到剧本名称,请确认内容是否符合要求'
|
|
|
- // ];
|
|
|
- // }
|
|
|
-
|
|
|
- // // 确认对话名称唯一性
|
|
|
- // if (DB::table('mp_animes')->where('anime_name', $anime_name)->value('id')) {
|
|
|
- // $anime_name = $anime_name . '_' . date('YmdHis');
|
|
|
- // }
|
|
|
-
|
|
|
$table_data = [
|
|
|
'user_id' => $uid,
|
|
|
// 'anime_name' => $anime_name,
|
|
|
@@ -2505,25 +2500,55 @@ class DeepSeekService
|
|
|
public function chat($data) {
|
|
|
$uid = Site::getUid();
|
|
|
$anime_id = getProp($data, 'anime_id');
|
|
|
+ $file = getProp($data, 'file');
|
|
|
+ $content = getProp($data, 'content', '');
|
|
|
+ $bid = getProp($data, 'bid', 0);
|
|
|
$episode_number = getProp($data, 'episode_number', 1);
|
|
|
+ $prompt = trim((string)getProp($data, 'prompt', '确认分镜大纲'));
|
|
|
$anime = DB::table('mp_animes')->where('id', $anime_id)->select('content', 'roles', 'scenes')->first();
|
|
|
if (!$anime) Utils::throwError('20003:该对话不存在!');
|
|
|
- $roles = json_decode(getProp($anime, 'roles'), true);
|
|
|
- $scenes = json_decode(getProp($anime, 'scenes'), true);
|
|
|
+ $current_episode = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->where('is_default', 1)->first();
|
|
|
+ $base_episode = $current_episode ?: DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->orderByDesc('id')->first();
|
|
|
+ $roles = $base_episode ? json_decode((string)getProp($base_episode, 'roles'), true) : json_decode((string)getProp($anime, 'roles'), true);
|
|
|
+ $scenes = $base_episode ? json_decode((string)getProp($base_episode, 'scenes'), true) : json_decode((string)getProp($anime, 'scenes'), true);
|
|
|
+ $roles = is_array($roles) ? $roles : [];
|
|
|
+ $scenes = is_array($scenes) ? $scenes : [];
|
|
|
// 转换主体列表和场景列表的格式
|
|
|
- $roles_content = '';
|
|
|
- foreach ($roles as $role) {
|
|
|
- $roles_content .= getProp($role, 'role').': '.getProp($role, 'description')."\n";
|
|
|
- }
|
|
|
- $scenes_content = '';
|
|
|
- foreach($scenes as $scene) {
|
|
|
- $scenes_content .= getProp($scene, 'scene').': '.getProp($scene, 'description')."\n";
|
|
|
+ $roles_content = $this->buildEpisodeItemContent($roles, 'role');
|
|
|
+ $scenes_content = $this->buildEpisodeItemContent($scenes, 'scene');
|
|
|
+
|
|
|
+ $existing_roles = $base_episode ? json_decode((string)getProp($base_episode, 'roles'), true) : [];
|
|
|
+ $existing_scenes = $base_episode ? json_decode((string)getProp($base_episode, 'scenes'), true) : [];
|
|
|
+ $existing_roles = is_array($existing_roles) ? $existing_roles : [];
|
|
|
+ $existing_scenes = is_array($existing_scenes) ? $existing_scenes : [];
|
|
|
+
|
|
|
+ // 提取文件内容
|
|
|
+ if ($content) {
|
|
|
+ $uploaded_content = filterContent((string)getProp($data, 'content', ''));
|
|
|
+ } elseif ($file) {
|
|
|
+ $uploaded_content = $this->extractFileContent($file);
|
|
|
+ if (!$uploaded_content) {
|
|
|
+ Utils::throwError('20003:无法提取文件内容,请检查文件格式');
|
|
|
+ }
|
|
|
+ } elseif ($bid) {
|
|
|
+ $uploaded_content = $this->getContentByBid($bid);
|
|
|
+ if (!$uploaded_content) {
|
|
|
+ Utils::throwError('20003:无法获取书籍内容,请检查书籍');
|
|
|
+ }
|
|
|
+ }else {
|
|
|
+ $uploaded_content = '';
|
|
|
}
|
|
|
- $prompt = getProp($data, 'prompt', '确认分镜大纲');
|
|
|
+
|
|
|
+ $prompt = trim((string)getProp($data, 'prompt', '确认分镜大纲'));
|
|
|
$demo = "你是一个专业的文档分析助手及资深编剧,请根据用户提供的文档内容完成示例格式的内容输出(需包含以下几个板块: <剧集标题><故事梗概><美术风格><主体列表><场景列表><分镜剧本>,每个板块之间用###分隔;同时板块之间需满足以下要求:\n
|
|
|
- 0.你只负责与剧本相关的内容,如果用户输入与剧本无关的内容,请告知用户并拒绝继续回答\n
|
|
|
+ 强制要求:
|
|
|
+ 1.你只负责与剧本相关的内容,如果用户输入与剧本无关的内容,请告知用户并拒绝继续回答,此为第一优先原则,不满足此原则就忽略其他要求并停止继续输出
|
|
|
+ 2.如果用户输入修改美术风格、修改画风这类内容,请告知用户不可修改美术风格,并拒绝继续回答\n
|
|
|
+ 3.以上两条要求对修改主体角色或场景不生效,允许用户调整主体角色说明和场景描述等\n
|
|
|
+ 普通要求:
|
|
|
1.剧集标题必须与文档内容高度相关,剧集序号是{$episode_number}
|
|
|
2.<主体列表>必须在以下主体中选择多个(根据拆分出的分镜剧本来确认具体几个),不得自定义(需带上冒号后的具体描述):{$roles_content}\n
|
|
|
+ 2.1如果分集内容中出现了<主体列表>中不存在的新主体,则不用在上述主体中选择,而是按照相同的格式新增一个主体显示在主题列表中
|
|
|
3.<场景列表>必须在以下场景中选择多个(根据拆分出的分镜剧本来确认具体几个),不得自定义(需带上冒号后的具体描述):{$scenes_content}\n
|
|
|
4.<分镜剧本>要求包含“分幕标题”以及“分镜信息”: \n
|
|
|
4.1“分幕标题”是根据章节中的场景进行拆分,类似于“第1幕 场景、时间及室内或室外”的信息,其中场景只能在所给出的场景列表中选(不带场景详细描述)。\n
|
|
|
@@ -2533,8 +2558,8 @@ class DeepSeekService
|
|
|
4.5“分镜信息”中的“配音角色”需对应本集中的<主体列表>中的一个(非对话内容则统一固定为“旁白”角色),不得自己生成。\n
|
|
|
4.6“分镜信息”中的“场景”需对应本集中的<场景列表>中的一个(不带详细表述)\n
|
|
|
4.7“分镜信息”中的“出镜角色”需对应本集中的<主体列表>中的一个或多个(不带详细描述),如果没有出镜角色则填无\n
|
|
|
- 4.8“分镜信息”中的“尾帧描述”需根据本镜和下一镜的剧本信息进行过渡生成,用于后续转视频进行衔接上下镜头的画面\n
|
|
|
- 示例格式:\n\n
|
|
|
+ 4.8“分镜信息”中的“尾帧描述”需根据本镜和下一镜的剧本信息进行过渡生成,用于后续转视频进行衔接上下镜头的画面\n\n
|
|
|
+ 示例格式:\n
|
|
|
第1集:隐形的守护者
|
|
|
|
|
|
###故事梗概
|
|
|
@@ -2606,13 +2631,19 @@ class DeepSeekService
|
|
|
尾帧描述:固定镜头。光影在火车票表面微微晃动,强调其陈旧的纸张纹理与关键的行程信息。
|
|
|
\n\n";
|
|
|
|
|
|
+ $origin_prompt = trim((string)getProp($data, 'prompt', '确认分镜大纲'));
|
|
|
+ $is_regenerate_version = !in_array($origin_prompt, ['确认分镜大纲', '继续策划下一集'], true);
|
|
|
+ $prompt = $origin_prompt;
|
|
|
+
|
|
|
// 获取章节内容
|
|
|
$full_content = getProp($anime, 'content');
|
|
|
// 根据章节内容拆分章节
|
|
|
$chapters = $this->splitContent($full_content);
|
|
|
+ $chapter_content = isset($chapters[($episode_number-1)]['content']) ? $chapters[($episode_number-1)]['content'] : '';
|
|
|
+ $messages = [];
|
|
|
|
|
|
if ($prompt == '确认分镜大纲') {
|
|
|
- $content = isset($chapters[($episode_number-1)]['content']) ? $chapters[($episode_number-1)]['content'] : '';
|
|
|
+ $content = $uploaded_content ?: $chapter_content;
|
|
|
if (!$content) {
|
|
|
Utils::throwError('20003:章节内容无法获取,请确认');
|
|
|
}
|
|
|
@@ -2624,7 +2655,7 @@ class DeepSeekService
|
|
|
'content' => $question
|
|
|
];
|
|
|
}else if ($prompt == '继续策划下一集') {
|
|
|
- $content = isset($chapters[($episode_number-1)]['content']) ? $chapters[($episode_number-1)]['content'] : '';
|
|
|
+ $content = $uploaded_content ?: $chapter_content;
|
|
|
if (!$content) {
|
|
|
Utils::throwError('20003:章节内容无法获取,请确认');
|
|
|
}
|
|
|
@@ -2632,10 +2663,7 @@ class DeepSeekService
|
|
|
|
|
|
// 获取上一集最后记录
|
|
|
$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 = $this->getEpisodeChatMessages($anime_id, $prev_sequence);
|
|
|
// 构建消息
|
|
|
$messages[] =
|
|
|
[
|
|
|
@@ -2643,8 +2671,16 @@ class DeepSeekService
|
|
|
'content' => $question
|
|
|
];
|
|
|
}else {
|
|
|
- $content = $full_content;
|
|
|
- $question = $prompt;
|
|
|
+ $content = $uploaded_content ?: trim((string)getProp($base_episode, 'content'));
|
|
|
+ if (!$content) {
|
|
|
+ $content = $chapter_content ?: $full_content;
|
|
|
+ }
|
|
|
+ if (!$content) {
|
|
|
+ Utils::throwError('20003:章节内容无法获取,请确认');
|
|
|
+ }
|
|
|
+ $messages = $this->getEpisodeChatMessages($anime_id, $episode_number);
|
|
|
+ $question = $demo . "\n请基于以上历史对话记录,重新生成第{$episode_number}集完整的新版本剧集,并严格按示例格式输出完整内容。"
|
|
|
+ . "\n\n本次修改要求如下:\n{$origin_prompt}";
|
|
|
$messages[] =
|
|
|
[
|
|
|
'role' => 'user',
|
|
|
@@ -2769,67 +2805,113 @@ class DeepSeekService
|
|
|
'reasoning_length' => strlen($fullReasoningContent)
|
|
|
]);
|
|
|
|
|
|
-// // $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);
|
|
|
+ if (empty($episode_arr['acts'])) {
|
|
|
+ dLog('deepseek')->info('未生成分镜剧本相关的内容');
|
|
|
+ return [
|
|
|
+ 'type' => 'done',
|
|
|
+ 'episode' => [],
|
|
|
+ 'answer' => $fullContent,
|
|
|
+ 'reasoning' => $fullReasoningContent,
|
|
|
+ 'usage' => $usage
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ $existing_roles = $base_episode ? json_decode((string)getProp($base_episode, 'roles'), true) : [];
|
|
|
+ $existing_scenes = $base_episode ? json_decode((string)getProp($base_episode, 'scenes'), true) : [];
|
|
|
+ $existing_roles = is_array($existing_roles) ? $existing_roles : [];
|
|
|
+ $existing_scenes = is_array($existing_scenes) ? $existing_scenes : [];
|
|
|
+ $generated_roles = isset($episode_arr['roles']) && is_array($episode_arr['roles']) ? $episode_arr['roles'] : [];
|
|
|
+ $generated_scenes = isset($episode_arr['scenes']) && is_array($episode_arr['scenes']) ? $episode_arr['scenes'] : [];
|
|
|
+ $merged_roles = $this->mergeEpisodeResourceItems($existing_roles, $generated_roles, 'role');
|
|
|
+ $merged_scenes = $this->mergeEpisodeResourceItems($existing_scenes, $generated_scenes, 'scene');
|
|
|
+ // dLog('deepseek')->info('修改后的数据: ', [$merged_roles, $merged_scenes]);
|
|
|
+ // exit();
|
|
|
+ $now = date('Y-m-d H:i:s');
|
|
|
|
|
|
try {
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
- // 新建动漫剧集
|
|
|
+ $version_count = DB::table('mp_anime_episodes')
|
|
|
+ ->where('anime_id', $anime_id)
|
|
|
+ ->where('episode_number', $episode_number)
|
|
|
+ ->count('id');
|
|
|
+
|
|
|
$episode = [
|
|
|
'anime_id' => $anime_id,
|
|
|
'episode_number' => $episode_number,
|
|
|
- 'title' => $episode_arr['episode_title'],
|
|
|
+ 'title' => getProp($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),
|
|
|
- 'sequence' => 1,
|
|
|
+ 'intro' => getProp($episode_arr, 'intro'),
|
|
|
+ 'art_style' => getProp($episode_arr, 'art_style'),
|
|
|
+ 'roles' => json_encode($merged_roles, 256),
|
|
|
+ 'scenes' => json_encode($merged_scenes, 256),
|
|
|
'generate_status' => '待执行',
|
|
|
- 'generate_at' => date('Y-m-d H:i:s'),
|
|
|
+ 'generate_at' => $now,
|
|
|
'is_default' => 1,
|
|
|
- 'updated_at' => date('Y-m-d H:i:s'),
|
|
|
+ 'updated_at' => $now,
|
|
|
];
|
|
|
|
|
|
- $current_episode = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->where('is_default', 1)->select('id', 'sequence')->first();
|
|
|
- if ($current_episode) {
|
|
|
- $episode['sequence'] = getProp($current_episode, 'sequence');
|
|
|
- $episode_id = getProp($current_episode, 'id');
|
|
|
- $boolen2 = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_id', $episode_id)->update($episode);
|
|
|
- }else {
|
|
|
- $episode['created_at'] = date('Y-m-d H:i:s');
|
|
|
+ if ($is_regenerate_version) {
|
|
|
+ DB::table('mp_anime_episodes')
|
|
|
+ ->where('anime_id', $anime_id)
|
|
|
+ ->where('episode_number', $episode_number)
|
|
|
+ ->update(['is_default' => 0, 'updated_at' => $now]);
|
|
|
+
|
|
|
+ $episode['sequence'] = $version_count + 1;
|
|
|
+ $episode['created_at'] = $now;
|
|
|
$episode_id = DB::table('mp_anime_episodes')->insertGetId($episode);
|
|
|
- $boolen2 = $episode_id ? true : false;
|
|
|
- }
|
|
|
-
|
|
|
- if (!$boolen2) {
|
|
|
- dLog('deepseek')->info('分集内容保存失败', $episode);
|
|
|
- Utils::throwError('20003:分集内容保存失败');
|
|
|
+ if (!$episode_id) {
|
|
|
+ Utils::throwError('20003:创建剧集版本失败');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $target_episode = $current_episode ?: $base_episode;
|
|
|
+ if ($target_episode) {
|
|
|
+ $episode_id = getProp($target_episode, 'id');
|
|
|
+ $episode['sequence'] = getProp($target_episode, 'sequence', 1);
|
|
|
+
|
|
|
+ DB::table('mp_anime_episodes')
|
|
|
+ ->where('anime_id', $anime_id)
|
|
|
+ ->where('episode_number', $episode_number)
|
|
|
+ ->where('id', '<>', $episode_id)
|
|
|
+ ->update(['is_default' => 0, 'updated_at' => $now]);
|
|
|
+
|
|
|
+ $boolen2 = DB::table('mp_anime_episodes')->where('id', $episode_id)->update($episode);
|
|
|
+ if ($boolen2 === false) {
|
|
|
+ Utils::throwError('20003:分集内容保存失败');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ DB::table('mp_anime_episodes')
|
|
|
+ ->where('anime_id', $anime_id)
|
|
|
+ ->where('episode_number', $episode_number)
|
|
|
+ ->update(['is_default' => 0, 'updated_at' => $now]);
|
|
|
+
|
|
|
+ $episode['sequence'] = $version_count > 0 ? ($version_count + 1) : 1;
|
|
|
+ $episode['created_at'] = $now;
|
|
|
+ $episode_id = DB::table('mp_anime_episodes')->insertGetId($episode);
|
|
|
+ if (!$episode_id) {
|
|
|
+ Utils::throwError('20003:分集内容保存失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // 删除历史分集内容
|
|
|
- DB::table('mp_episode_segments')->where('anime_id', $anime_id)->where('episode_id', $episode_id)->delete();
|
|
|
$segments = [];
|
|
|
- foreach($episode_arr['acts'] as $act) {
|
|
|
- foreach($act['segments'] as $segment) {
|
|
|
+ $acts = isset($episode_arr['acts']) && is_array($episode_arr['acts']) ? $episode_arr['acts'] : [];
|
|
|
+ foreach ($acts as $act) {
|
|
|
+ $act_segments = getProp($act, 'segments', []);
|
|
|
+ if (!is_array($act_segments)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($act_segments as $segment) {
|
|
|
$segments[] = [
|
|
|
'anime_id' => $anime_id,
|
|
|
'episode_id' => $episode_id,
|
|
|
'episode_number' => $episode_number,
|
|
|
- 'act_number' => $act['act_number'],
|
|
|
- 'act_title' => $act['act_title'],
|
|
|
+ 'act_number' => getProp($act, 'act_number'),
|
|
|
+ 'act_title' => getProp($act, 'act_title'),
|
|
|
'segment_id' => getProp($segment, 'segment_id'),
|
|
|
'segment_number' => getProp($segment, 'segment_number'),
|
|
|
'segment_content' => getProp($segment, 'segment_content'),
|
|
|
@@ -2841,45 +2923,50 @@ class DeepSeekService
|
|
|
'frame_type' => getProp($segment, 'frame_type'),
|
|
|
'scene' => getProp($segment, 'scene'),
|
|
|
'characters' => getProp($segment, 'characters'),
|
|
|
- 'created_at' => date('Y-m-d H:i:s'),
|
|
|
- 'updated_at' => date('Y-m-d H:i:s')
|
|
|
+ 'created_at' => $now,
|
|
|
+ 'updated_at' => $now
|
|
|
];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- $boolen3 = DB::table('mp_episode_segments')->insert($segments);
|
|
|
- if (!$boolen3) {
|
|
|
- dLog('deepseek')->info('分镜内容保存失败', $episode);
|
|
|
- Utils::throwError('20003:分镜内容保存失败');
|
|
|
+ if ($segments) {
|
|
|
+ $boolen3 = DB::table('mp_episode_segments')->insert($segments);
|
|
|
+ if (!$boolen3) {
|
|
|
+ Utils::throwError('20003:分镜内容保存失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $record_content = $origin_prompt;
|
|
|
+ if ($uploaded_content !== '') {
|
|
|
+ $record_content .= "\n\n###上传内容\n" . $uploaded_content;
|
|
|
}
|
|
|
|
|
|
- // 保存对话记录
|
|
|
$recrods = [
|
|
|
[
|
|
|
- 'uid' => $uid,
|
|
|
- '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')
|
|
|
+ 'uid' => $uid,
|
|
|
+ 'anime_id' => $anime_id,
|
|
|
+ 'role' => 'user',
|
|
|
+ 'content' => $record_content,
|
|
|
+ 'sequence' => $episode_number,
|
|
|
+ 'episode_id' => $episode_id,
|
|
|
+ 'created_at' => $now,
|
|
|
+ 'updated_at' => $now
|
|
|
],
|
|
|
[
|
|
|
- 'uid' => $uid,
|
|
|
- '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')
|
|
|
+ 'uid' => $uid,
|
|
|
+ 'anime_id' => $anime_id,
|
|
|
+ 'role' => 'assistant',
|
|
|
+ 'content' => $fullContent,
|
|
|
+ 'sequence' => $episode_number,
|
|
|
+ 'episode_id' => $episode_id,
|
|
|
+ 'created_at' => $now,
|
|
|
+ 'updated_at' => $now
|
|
|
]
|
|
|
];
|
|
|
- // 保存对话记录
|
|
|
- $boolen3 = DB::table('mp_anime_records')->insert($recrods);
|
|
|
- if (!$boolen3) {
|
|
|
+ $boolen4 = DB::table('mp_anime_records')->insert($recrods);
|
|
|
+ if (!$boolen4) {
|
|
|
Utils::throwError('20003:对话记录保存失败');
|
|
|
}
|
|
|
-
|
|
|
}catch (\Exception $e) {
|
|
|
DB::rollBack();
|
|
|
dLog('deepseek')->info('新建对话错误信息: '.$e->getMessage());
|
|
|
@@ -2890,15 +2977,16 @@ class DeepSeekService
|
|
|
'usage' => $usage,
|
|
|
'error' => $e->getMessage(),
|
|
|
];
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
DB::commit();
|
|
|
// dd('end');
|
|
|
|
|
|
$episode['episode_id'] = $episode_id;
|
|
|
- $episode['roles'] = json_decode($episode['roles'], true);
|
|
|
- $episode['scenes'] = json_decode($episode['scenes'], true);
|
|
|
- $episode['acts'] = json_decode($episode['acts'], true);
|
|
|
+ $episode['roles'] = $merged_roles;
|
|
|
+ $episode['scenes'] = $merged_scenes;
|
|
|
+ $episode['acts'] = isset($episode_arr['acts']) && is_array($episode_arr['acts']) ? $episode_arr['acts'] : [];
|
|
|
yield [
|
|
|
'type' => 'done',
|
|
|
'episode' => $episode,
|
|
|
@@ -2908,6 +2996,111 @@ class DeepSeekService
|
|
|
];
|
|
|
}
|
|
|
|
|
|
+ private function buildEpisodeItemContent(array $items, string $nameKey): string
|
|
|
+ {
|
|
|
+ $content = '';
|
|
|
+ foreach ($items as $item) {
|
|
|
+ $name = trim((string)getProp($item, $nameKey));
|
|
|
+ if ($name === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $description = trim((string)getProp($item, 'description'));
|
|
|
+ $content .= $name . ': ' . $description . "\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ return trim($content);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getEpisodeChatMessages($animeId, $sequence): array
|
|
|
+ {
|
|
|
+ if ((int)$sequence <= 0) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ return DB::table('mp_anime_records')
|
|
|
+ ->where('anime_id', $animeId)
|
|
|
+ ->where('sequence', $sequence)
|
|
|
+ ->select('role', 'content')
|
|
|
+ ->orderBy('created_at')
|
|
|
+ ->orderBy('id')
|
|
|
+ ->get()
|
|
|
+ ->map(function ($value) {
|
|
|
+ return (array)$value;
|
|
|
+ })
|
|
|
+ ->toArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function mergeEpisodeResourceItems(array $existingItems, array $generatedItems, string $nameKey): array
|
|
|
+ {
|
|
|
+ $existingMap = [];
|
|
|
+ foreach ($existingItems as $item) {
|
|
|
+ $itemKey = $this->normalizeEpisodeResourceKey(getProp($item, $nameKey));
|
|
|
+ if ($itemKey === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $existingMap[$itemKey] = $item;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$generatedItems) {
|
|
|
+ return array_values($existingItems);
|
|
|
+ }
|
|
|
+
|
|
|
+ $merged = [];
|
|
|
+ foreach ($generatedItems as $item) {
|
|
|
+ $itemKey = $this->normalizeEpisodeResourceKey(getProp($item, $nameKey));
|
|
|
+ if ($itemKey === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isset($existingMap[$itemKey])) {
|
|
|
+ if (trim((string)getProp($item, 'description')) === '') {
|
|
|
+ $merged[] = $existingMap[$itemKey];
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$this->isEpisodeResourceChanged($existingMap[$itemKey], $item, $nameKey)) {
|
|
|
+ $merged[] = $existingMap[$itemKey];
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $merged[] = $this->resetEpisodeResourceItem($item, $nameKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $merged ?: array_values($existingItems);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function resetEpisodeResourceItem(array $item, string $nameKey): array
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ $nameKey => trim((string)getProp($item, $nameKey)),
|
|
|
+ 'description' => trim((string)getProp($item, 'description')),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private function isEpisodeResourceChanged(array $existingItem, array $generatedItem, string $nameKey): bool
|
|
|
+ {
|
|
|
+ $existingKey = $this->normalizeEpisodeResourceKey(getProp($existingItem, $nameKey));
|
|
|
+ $generatedKey = $this->normalizeEpisodeResourceKey(getProp($generatedItem, $nameKey));
|
|
|
+ if ($existingKey !== $generatedKey) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return trim((string)getProp($existingItem, 'description')) !== trim((string)getProp($generatedItem, 'description'));
|
|
|
+ }
|
|
|
+
|
|
|
+ private function normalizeEpisodeResourceKey($value): string
|
|
|
+ {
|
|
|
+ $value = trim((string)$value);
|
|
|
+ if ($value === '') {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ return strtolower((string)preg_replace('/\s+/u', '', $value));
|
|
|
+ }
|
|
|
+
|
|
|
public function chatChangeImg($data) {
|
|
|
$segment = getProp($data, 'segment');
|
|
|
$segment_content = getProp($segment, 'segment_content');
|