|
|
@@ -3,6 +3,7 @@
|
|
|
namespace App\Services\DeepSeek;
|
|
|
|
|
|
use App\Consts\ErrorConst;
|
|
|
+use App\Consts\BaseConst;
|
|
|
use App\Facade\Site;
|
|
|
use App\Libs\Utils;
|
|
|
use GuzzleHttp\Client;
|
|
|
@@ -26,6 +27,7 @@ class DeepSeekService
|
|
|
private $sys_message;
|
|
|
private $headers;
|
|
|
private $valid_text_models;
|
|
|
+ private $gpt_text_models;
|
|
|
protected $aiImageGenerationService;
|
|
|
|
|
|
public function __construct(AIImageGenerationService $aiImageGenerationService) {
|
|
|
@@ -38,6 +40,8 @@ class DeepSeekService
|
|
|
];
|
|
|
// 火山引擎模型列表
|
|
|
$this->valid_text_models = DB::table('mp_text_models')->where('is_enabled', 1)->pluck('model')->toArray();
|
|
|
+ // GPT文本模型列表
|
|
|
+ $this->gpt_text_models = BaseConst::GPT_TEXT_MODELS;
|
|
|
$this->sys_message = [
|
|
|
'role' => 'system',
|
|
|
'content' => "你是一个专业的文档分析助手及资深编剧,请根据用户提供的文档内容完成示例格式的内容输出(需通过以下几个板块进行回复: <故事梗概><剧本亮点><人物关系><核心矛盾><主体列表><美术风格><场景列表><分集剧本>,每个板块之间必须使用###分隔,#不能多也不能少;同时板块之间需满足以下要求:\n
|
|
|
@@ -247,8 +251,8 @@ class DeepSeekService
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
return $this->deepSeekStreamResponse($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
- // GPT-5.4 模型使用 TokenRouter API
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
+ // GPT 模型使用 TokenRouter API
|
|
|
return $this->gpt54StreamResponse($post_data);
|
|
|
} else if (in_array($model, $this->valid_text_models)) {
|
|
|
// 火山引擎支持的模型使用火山引擎 API
|
|
|
@@ -525,9 +529,10 @@ class DeepSeekService
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek模型
|
|
|
$post_data['response_format'] = ['type' => 'json_object'];
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
- // GPT-5.4模型
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
+ // GPT模型
|
|
|
$post_data['response_format'] = ['type' => 'json_object'];
|
|
|
+ dd($post_data);
|
|
|
} else if (in_array($model, $this->valid_text_models)) {
|
|
|
// 火山引擎模型
|
|
|
$post_data['response_format'] = ['type' => 'json_object'];
|
|
|
@@ -550,11 +555,11 @@ class DeepSeekService
|
|
|
'model' => $model,
|
|
|
'finish_reason' => 'stop'
|
|
|
];
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
- // GPT-5.4 模型使用现有的 gpt54ChatOnly 方法
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
+ // GPT 模型使用现有的 gpt54ChatOnly 方法
|
|
|
$result = $this->gpt54ChatOnly($post_data);
|
|
|
|
|
|
- dLog('deepseek')->info('newGenerateText使用GPT-5.4模型', ['requested_model' => $model]);
|
|
|
+ dLog('deepseek')->info('newGenerateText使用GPT模型', ['requested_model' => $model]);
|
|
|
|
|
|
$aiResult = [
|
|
|
'content' => $result['fullContent'],
|
|
|
@@ -1290,16 +1295,16 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * GPT-5.4 流式输出处理方法
|
|
|
+ * GPT 流式输出处理方法
|
|
|
*
|
|
|
- * @param array $post_data GPT-5.4 API请求参数
|
|
|
+ * @param array $post_data GPT API请求参数
|
|
|
* @return \Generator 返回生成器,用于流式输出
|
|
|
*/
|
|
|
private function gpt54StreamResponse($post_data) {
|
|
|
$apiKey = env('GPT_54_API_KEY');
|
|
|
|
|
|
if (empty($apiKey)) {
|
|
|
- Utils::throwError('20003:GPT-5.4 API Key未配置');
|
|
|
+ Utils::throwError('20003:GPT API Key未配置');
|
|
|
}
|
|
|
|
|
|
$client = new Client(['timeout' => 1200, 'verify' => false]);
|
|
|
@@ -1328,7 +1333,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
$usage = [];
|
|
|
$buffer = '';
|
|
|
|
|
|
- dLog('deepseek')->info('开始读取 GPT-5.4 流式响应');
|
|
|
+ dLog('deepseek')->info('开始读取 GPT 流式响应');
|
|
|
|
|
|
// 逐行读取流式响应
|
|
|
while (!$body->eof()) {
|
|
|
@@ -1483,7 +1488,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
$chatResult = $this->chatOnly($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$chatResult = $this->gpt54ChatOnly($post_data);
|
|
|
} else {
|
|
|
@@ -2484,7 +2489,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
$streamGenerator = $this->deepSeekStreamResponse($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$streamGenerator = $this->gpt54StreamResponse($post_data);
|
|
|
} else {
|
|
|
@@ -2768,7 +2773,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
$streamGenerator = $this->deepSeekStreamResponse($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$streamGenerator = $this->gpt54StreamResponse($post_data);
|
|
|
} else {
|
|
|
@@ -2985,7 +2990,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
$chatResult = $this->chatOnly($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$chatResult = $this->gpt54ChatOnly($post_data);
|
|
|
} else {
|
|
|
@@ -3895,7 +3900,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
$streamGenerator = $this->deepSeekStreamResponse($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$streamGenerator = $this->gpt54StreamResponse($post_data);
|
|
|
} else {
|
|
|
@@ -4741,7 +4746,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
try {
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
$streamGenerator = $this->deepSeekStreamResponse($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$streamGenerator = $this->gpt54StreamResponse($post_data);
|
|
|
} else {
|
|
|
@@ -5603,7 +5608,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
$streamGenerator = $this->deepSeekStreamResponse($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$streamGenerator = $this->gpt54StreamResponse($post_data);
|
|
|
} else {
|
|
|
@@ -6221,7 +6226,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
$streamGenerator = $this->deepSeekStreamResponse($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$streamGenerator = $this->gpt54StreamResponse($post_data);
|
|
|
} else {
|
|
|
@@ -7124,7 +7129,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
try {
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
$streamGenerator = $this->deepSeekStreamResponse($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$streamGenerator = $this->gpt54StreamResponse($post_data);
|
|
|
} else {
|
|
|
@@ -8147,7 +8152,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
$streamGenerator = $this->deepSeekStreamResponse($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$streamGenerator = $this->gpt54StreamResponse($post_data);
|
|
|
} else {
|
|
|
@@ -8377,6 +8382,306 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
];
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 重新生成分段剧本
|
|
|
+ * @param array $data 包含以下字段:
|
|
|
+ * - prompt: string|null 用户修改要求(可选)
|
|
|
+ * - template_id: int|null 提示词模板ID(可选)
|
|
|
+ * - act_id: int|null 单个片段ID(与episode_id二选一)
|
|
|
+ * - episode_id: int|null 剧集ID,用于重新生成整个剧集的所有片段(与act_id二选一)
|
|
|
+ * @return array 返回生成结果
|
|
|
+ */
|
|
|
+ public function regenerateSegmentScript($data) {
|
|
|
+ $uid = Site::getUid();
|
|
|
+ $prompt = trim((string)getProp($data, 'prompt', ''));
|
|
|
+ $template_id = getProp($data, 'template_id', 0);
|
|
|
+ $act_id = getProp($data, 'act_id', 0);
|
|
|
+ $episode_id = getProp($data, 'episode_id', 0);
|
|
|
+
|
|
|
+ // 验证:prompt和template_id至少提供一个
|
|
|
+ if (empty($prompt) && empty($template_id)) {
|
|
|
+ Utils::throwError('20003:请提供修改要求或选择提示词模板');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证:act_id和episode_id二选一
|
|
|
+ if (empty($act_id) && empty($episode_id)) {
|
|
|
+ Utils::throwError('20003:请提供片段ID或剧集ID');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($act_id) && !empty($episode_id)) {
|
|
|
+ Utils::throwError('20003:片段ID和剧集ID只能选择一个');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果提供了template_id,获取模板提示词
|
|
|
+ $template_prompt = '';
|
|
|
+ if ($template_id > 0) {
|
|
|
+ $template = DB::table('mp_prompt_templates')
|
|
|
+ ->where('id', $template_id)
|
|
|
+ ->where('is_deleted', 0)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$template) {
|
|
|
+ Utils::throwError('20003:提示词模板不存在或已删除');
|
|
|
+ }
|
|
|
+
|
|
|
+ $template_prompt = $template->template_prompt ?? '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 合并用户提示词和模板提示词
|
|
|
+ $final_prompt = '';
|
|
|
+ if (!empty($template_prompt)) {
|
|
|
+ $final_prompt = $template_prompt;
|
|
|
+ if (!empty($prompt)) {
|
|
|
+ $final_prompt .= "\n\n补充要求:" . $prompt;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $final_prompt = $prompt;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取需要重新生成的内容参考
|
|
|
+ $reference_content = '';
|
|
|
+ $target_episode_id = 0;
|
|
|
+ $target_anime_id = 0;
|
|
|
+ $target_episode_number = 0;
|
|
|
+
|
|
|
+ if ($act_id > 0) {
|
|
|
+ // 单个片段模式:获取该片段的内容
|
|
|
+ $segment = DB::table('mp_episode_segments')
|
|
|
+ ->where('id', $act_id)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$segment) {
|
|
|
+ Utils::throwError('20003:片段不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ $target_episode_id = $segment->episode_id;
|
|
|
+ $target_anime_id = $segment->anime_id;
|
|
|
+ $target_episode_number = $segment->episode_number;
|
|
|
+ $reference_content = $segment->act_content ?? '';
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // 整个剧集模式:获取所有片段的内容
|
|
|
+ $segments = DB::table('mp_episode_segments')
|
|
|
+ ->where('episode_id', $episode_id)
|
|
|
+ ->orderBy('act_number')
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ if ($segments->isEmpty()) {
|
|
|
+ Utils::throwError('20003:该剧集没有片段数据');
|
|
|
+ }
|
|
|
+
|
|
|
+ $target_episode_id = $episode_id;
|
|
|
+ $target_anime_id = $segments[0]->anime_id;
|
|
|
+ $target_episode_number = $segments[0]->episode_number;
|
|
|
+
|
|
|
+ // 拼接所有片段内容,保留格式用于AI理解
|
|
|
+ $act_contents = [];
|
|
|
+ foreach ($segments as $seg) {
|
|
|
+ $act_number = $seg->act_number;
|
|
|
+ $act_content = $seg->act_content ?? '';
|
|
|
+ if (!empty($act_content)) {
|
|
|
+ $act_contents[] = "##片段{$act_number}\n" . $act_content;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $reference_content = implode("\n\n", $act_contents);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($reference_content)) {
|
|
|
+ Utils::throwError('20003:没有可参考的片段内容');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取剧集的基本信息(intro、art_style、roles、scenes)
|
|
|
+ $episode = DB::table('mp_anime_episodes')
|
|
|
+ ->where('id', $target_episode_id)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$episode) {
|
|
|
+ Utils::throwError('20003:剧集不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ $intro = $episode->intro ?? '';
|
|
|
+ $art_style = $episode->art_style ?? '';
|
|
|
+ $roles = json_decode($episode->roles ?? '[]', true);
|
|
|
+ $scenes = json_decode($episode->scenes ?? '[]', true);
|
|
|
+
|
|
|
+ // 构建提示词中的主体列表和场景列表参考
|
|
|
+ $roles_ref = '';
|
|
|
+ if (!empty($roles) && is_array($roles)) {
|
|
|
+ $roles_list = [];
|
|
|
+ foreach ($roles as $role) {
|
|
|
+ $role_name = getProp($role, 'role', '');
|
|
|
+ $role_desc = getProp($role, 'description', '');
|
|
|
+ $pic_prompt = getProp($role, 'pic_prompt', '');
|
|
|
+ $voice_prompt = getProp($role, 'voice_prompt', '');
|
|
|
+
|
|
|
+ if (!empty($role_name)) {
|
|
|
+ $role_line = $role_name;
|
|
|
+ if (!empty($role_desc)) $role_line .= ":" . $role_desc;
|
|
|
+ if (!empty($pic_prompt)) $role_line .= "{" . $pic_prompt . "}";
|
|
|
+ if (!empty($voice_prompt)) $role_line .= "{{" . $voice_prompt . "}}";
|
|
|
+ $roles_list[] = $role_line;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!empty($roles_list)) {
|
|
|
+ $roles_ref = "\n\n【主体列表参考】\n" . implode("\n", $roles_list);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $scenes_ref = '';
|
|
|
+ if (!empty($scenes) && is_array($scenes)) {
|
|
|
+ $scenes_list = [];
|
|
|
+ foreach ($scenes as $scene) {
|
|
|
+ $scene_name = getProp($scene, 'scene', '');
|
|
|
+ $scene_desc = getProp($scene, 'description', '');
|
|
|
+ $pic_prompt = getProp($scene, 'pic_prompt', '');
|
|
|
+
|
|
|
+ if (!empty($scene_name)) {
|
|
|
+ $scene_line = $scene_name;
|
|
|
+ if (!empty($scene_desc)) $scene_line .= ":" . $scene_desc;
|
|
|
+ if (!empty($pic_prompt)) $scene_line .= "{" . $pic_prompt . "}";
|
|
|
+ $scenes_list[] = $scene_line;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!empty($scenes_list)) {
|
|
|
+ $scenes_ref = "\n\n【场景列表参考】\n" . implode("\n", $scenes_list);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建美术风格参考
|
|
|
+ $art_style_ref = '';
|
|
|
+ if (!empty($art_style)) {
|
|
|
+ $art_style_ref = "\n\n【美术风格参考】\n" . $art_style;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建内容简介参考
|
|
|
+ $intro_ref = '';
|
|
|
+ if (!empty($intro)) {
|
|
|
+ $intro_ref = "\n\n【内容简介参考】\n" . $intro;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建完整的AI提示词
|
|
|
+ $system_prompt = "你是一个专业的剧本编剧和影视导演,擅长根据已有的分段剧本进行优化和调整。你需要严格按照用户的要求重新生成分段剧本内容,保持原有的格式和结构。";
|
|
|
+
|
|
|
+ $user_prompt = "请根据以下要求重新生成分段剧本:\n\n【用户要求】\n{$final_prompt}";
|
|
|
+ $user_prompt .= "\n\n【原分段剧本内容】\n{$reference_content}";
|
|
|
+ $user_prompt .= $intro_ref;
|
|
|
+ $user_prompt .= $art_style_ref;
|
|
|
+ $user_prompt .= $roles_ref;
|
|
|
+ $user_prompt .= $scenes_ref;
|
|
|
+
|
|
|
+ $user_prompt .= "\n\n【格式要求】\n";
|
|
|
+ $user_prompt .= "1. 必须保持原有的分段格式(##片段X格式)\n";
|
|
|
+ $user_prompt .= "2. 每个片段包含:时长、多个分镜\n";
|
|
|
+ $user_prompt .= "3. 每个分镜包含:分镜序号、场景、画面、运镜、配音台词、背景音效\n";
|
|
|
+ $user_prompt .= "4. 场景必须从【场景列表参考】中选择(如果提供了场景列表)\n";
|
|
|
+ $user_prompt .= "5. 出场角色必须从【主体列表参考】中选择(如果提供了主体列表)\n";
|
|
|
+ $user_prompt .= "6. 保持美术风格的一致性(如果提供了美术风格参考)\n";
|
|
|
+ $user_prompt .= "7. 只需输出分段剧本部分,不要输出其他内容(不要包含###分段剧本标题)\n";
|
|
|
+
|
|
|
+ // 调用AI生成新的分段剧本
|
|
|
+ try {
|
|
|
+ $ai_result = $this->newGenerateText([
|
|
|
+ 'model' => 'deepseek-v4-pro',
|
|
|
+ 'system_prompt' => $system_prompt,
|
|
|
+ 'prompt' => $user_prompt,
|
|
|
+ 'temperature' => 0.7,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $generated_content = $ai_result['content'] ?? '';
|
|
|
+
|
|
|
+ if (empty($generated_content)) {
|
|
|
+ Utils::throwError('20003:AI生成内容为空,请重试');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析生成的内容
|
|
|
+ $parsed_segments = $this->parseSegmentContent($generated_content);
|
|
|
+
|
|
|
+ if (empty($parsed_segments)) {
|
|
|
+ Utils::throwError('20003:无法解析生成的分段剧本,请检查格式');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存到数据库
|
|
|
+ $now = date('Y-m-d H:i:s');
|
|
|
+
|
|
|
+ if ($act_id > 0) {
|
|
|
+ // 单个片段模式:更新单条记录
|
|
|
+ if (count($parsed_segments) > 0) {
|
|
|
+ $new_segment = $parsed_segments[0];
|
|
|
+
|
|
|
+ $update_data = [
|
|
|
+ 'act_content' => $generated_content,
|
|
|
+ 'act_title' => getProp($new_segment, 'act_title', ''),
|
|
|
+ 'act_duration' => getProp($new_segment, 'act_duration', 0),
|
|
|
+ 'updated_at' => $now,
|
|
|
+ ];
|
|
|
+
|
|
|
+ DB::table('mp_episode_segments')
|
|
|
+ ->where('id', $act_id)
|
|
|
+ ->update($update_data);
|
|
|
+
|
|
|
+ dLog('deepseek')->info('单个片段重新生成成功', ['act_id' => $act_id]);
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // 整个剧集模式:删除原有记录并批量保存新记录
|
|
|
+ DB::beginTransaction();
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 删除原有的所有片段记录
|
|
|
+ DB::table('mp_episode_segments')
|
|
|
+ ->where('episode_id', $target_episode_id)
|
|
|
+ ->delete();
|
|
|
+
|
|
|
+ // 批量插入新的片段记录
|
|
|
+ $segments_to_insert = [];
|
|
|
+ foreach ($parsed_segments as $index => $segment) {
|
|
|
+ $act_number = $index + 1;
|
|
|
+
|
|
|
+ $segments_to_insert[] = [
|
|
|
+ 'anime_id' => $target_anime_id,
|
|
|
+ 'episode_id' => $target_episode_id,
|
|
|
+ 'episode_number' => $target_episode_number,
|
|
|
+ 'act_number' => $act_number,
|
|
|
+ 'act_title' => getProp($segment, 'act_title', ''),
|
|
|
+ 'act_duration' => getProp($segment, 'act_duration', 0),
|
|
|
+ 'act_content' => getProp($segment, 'act_content', ''),
|
|
|
+ 'created_at' => $now,
|
|
|
+ 'updated_at' => $now,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($segments_to_insert)) {
|
|
|
+ DB::table('mp_episode_segments')->insert($segments_to_insert);
|
|
|
+ }
|
|
|
+
|
|
|
+ DB::commit();
|
|
|
+
|
|
|
+ dLog('deepseek')->info('整个剧集片段重新生成成功', [
|
|
|
+ 'episode_id' => $target_episode_id,
|
|
|
+ 'segments_count' => count($segments_to_insert)
|
|
|
+ ]);
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ DB::rollBack();
|
|
|
+ dLog('deepseek')->error('保存重新生成的片段失败: ' . $e->getMessage());
|
|
|
+ Utils::throwError('20003:保存失败,请重试');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'success' => true,
|
|
|
+ 'message' => $act_id > 0 ? '单个片段重新生成成功' : '整个剧集片段重新生成成功',
|
|
|
+ 'generated_content' => $generated_content,
|
|
|
+ 'parsed_segments' => $parsed_segments,
|
|
|
+ 'usage' => $ai_result['usage'] ?? [],
|
|
|
+ ];
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ dLog('deepseek')->error('重新生成分段剧本失败: ' . $e->getMessage());
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private function buildSingleEpisodeFromScript(array $script_arr, string $fullContent, string $animeName = ''): array
|
|
|
{
|
|
|
$episode_arr = [
|
|
|
@@ -9872,7 +10177,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
$chatResult = $this->chatOnly($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$chatResult = $this->gpt54ChatOnly($post_data);
|
|
|
} else {
|
|
|
@@ -10368,7 +10673,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
if (in_array($model, ['deepseek-reasoner', 'deepseek-chat', 'deepseek-v4-flash', 'deepseek-v4-pro'])) {
|
|
|
// DeepSeek 官方模型使用 DeepSeek API
|
|
|
$chatResult = $this->chatOnly($post_data);
|
|
|
- } else if ($model === 'gpt-5.4') {
|
|
|
+ } else if (in_array($model, $this->gpt_text_models)) {
|
|
|
// GPT-5.4 模型使用 TokenRouter API
|
|
|
$chatResult = $this->gpt54ChatOnly($post_data);
|
|
|
} else {
|