|
|
@@ -8,6 +8,7 @@ use App\Libs\Utils;
|
|
|
use GuzzleHttp\Client;
|
|
|
use DateTime;
|
|
|
use DateTimeZone;
|
|
|
+use App\Services\AIGeneration\AIImageGenerationService;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
use Illuminate\Support\Facades\Redis;
|
|
|
@@ -23,8 +24,10 @@ class DeepSeekService
|
|
|
private $api_key;
|
|
|
private $sys_message;
|
|
|
private $headers;
|
|
|
+ protected $aiImageGenerationService;
|
|
|
|
|
|
- public function __construct() {
|
|
|
+ public function __construct(AIImageGenerationService $aiImageGenerationService) {
|
|
|
+ $this->aiImageGenerationService = $aiImageGenerationService;
|
|
|
$this->url = 'https://api.deepseek.com/chat/completions';
|
|
|
$this->api_key = env('DEEPSEEK_API_KEY');
|
|
|
$this->headers = [
|
|
|
@@ -2633,6 +2636,7 @@ class DeepSeekService
|
|
|
|
|
|
$origin_prompt = trim((string)getProp($data, 'prompt', '确认分镜大纲'));
|
|
|
$is_regenerate_version = !in_array($origin_prompt, ['确认分镜大纲', '继续策划下一集'], true);
|
|
|
+ $is_global_generate_pics = $origin_prompt == '确认分镜大纲' ? 1 : 0;
|
|
|
$prompt = $origin_prompt;
|
|
|
|
|
|
// 获取章节内容
|
|
|
@@ -2810,13 +2814,14 @@ class DeepSeekService
|
|
|
$episode_arr = handleEpisodeContent($fullContent);
|
|
|
if (empty($episode_arr['acts'])) {
|
|
|
dLog('deepseek')->info('未生成分镜剧本相关的内容');
|
|
|
- return [
|
|
|
+ yield [
|
|
|
'type' => 'done',
|
|
|
'episode' => [],
|
|
|
'answer' => $fullContent,
|
|
|
'reasoning' => $fullReasoningContent,
|
|
|
'usage' => $usage
|
|
|
];
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
$existing_roles = $base_episode ? json_decode((string)getProp($base_episode, 'roles'), true) : [];
|
|
|
@@ -2981,7 +2986,20 @@ class DeepSeekService
|
|
|
}
|
|
|
|
|
|
DB::commit();
|
|
|
- // dd('end');
|
|
|
+
|
|
|
+ if ($is_global_generate_pics) {
|
|
|
+ // 批量生成全局角色图片
|
|
|
+ $this->batchSetGlobalRoleImg([
|
|
|
+ 'anime_id' => $anime_id,
|
|
|
+ 'episode_number' => $episode_number
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 批量生成全局场景图片
|
|
|
+ $this->batchSetGlobalSceneImg([
|
|
|
+ 'anime_id' => $anime_id,
|
|
|
+ 'episode_number' => $episode_number
|
|
|
+ ]);
|
|
|
+ }
|
|
|
|
|
|
$episode['episode_id'] = $episode_id;
|
|
|
$episode['roles'] = $merged_roles;
|
|
|
@@ -3066,7 +3084,21 @@ class DeepSeekService
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- $merged[] = $this->resetEpisodeResourceItem($item, $nameKey);
|
|
|
+ // 重置资源项,并尝试从现有项中继承URL
|
|
|
+ $resetItem = $this->resetEpisodeResourceItem($item, $nameKey);
|
|
|
+
|
|
|
+ // 如果现有项存在且有URL,而生成项没有URL,则继承现有URL
|
|
|
+ if (isset($existingMap[$itemKey])) {
|
|
|
+ $existingUrl = getProp($existingMap[$itemKey], 'url');
|
|
|
+ $generatedUrl = getProp($item, 'url');
|
|
|
+
|
|
|
+ // 条件:1.现有URL存在 2.生成URL不存在 3.名称和描述一致(已通过itemKey和上面的逻辑保证)
|
|
|
+ if (!empty($existingUrl) && empty($generatedUrl)) {
|
|
|
+ $resetItem['url'] = $existingUrl;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $merged[] = $resetItem;
|
|
|
}
|
|
|
|
|
|
return $merged ?: array_values($existingItems);
|
|
|
@@ -3074,10 +3106,18 @@ class DeepSeekService
|
|
|
|
|
|
private function resetEpisodeResourceItem(array $item, string $nameKey): array
|
|
|
{
|
|
|
- return [
|
|
|
+ $resetItem = [
|
|
|
$nameKey => trim((string)getProp($item, $nameKey)),
|
|
|
'description' => trim((string)getProp($item, 'description')),
|
|
|
];
|
|
|
+
|
|
|
+ // 如果生成项本身有URL,保留它
|
|
|
+ $url = getProp($item, 'url');
|
|
|
+ if (!empty($url)) {
|
|
|
+ $resetItem['url'] = $url;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $resetItem;
|
|
|
}
|
|
|
|
|
|
private function isEpisodeResourceChanged(array $existingItem, array $generatedItem, string $nameKey): bool
|
|
|
@@ -3101,6 +3141,110 @@ class DeepSeekService
|
|
|
return strtolower((string)preg_replace('/\s+/u', '', $value));
|
|
|
}
|
|
|
|
|
|
+ // 生成全局角色图片
|
|
|
+ private function batchSetGlobalRoleImg($data) {
|
|
|
+ $anime_id = getProp($data, 'anime_id');
|
|
|
+ $episode_number = getProp($data, 'episode_number');
|
|
|
+ if (!$anime_id) Utils::throwError('1002:请选择对话');
|
|
|
+ $anime = DB::table('mp_animes')->where('id', $anime_id)->where('is_deleted', 0)->first();
|
|
|
+ $roles = json_decode(getProp($anime, 'roles'), true);
|
|
|
+ $art_style = getProp($anime, 'art_style');
|
|
|
+
|
|
|
+ foreach ($roles as &$role) {
|
|
|
+ $role_name = getProp($role, 'role');
|
|
|
+ $description = getProp($role, 'description');
|
|
|
+ if ($art_style) $description = "美术风格:\n$art_style\n主体描述:$description";
|
|
|
+ // 参考图地址
|
|
|
+ $ref_img_url = getProp($role, 'url');
|
|
|
+
|
|
|
+ // 此处调用AIImageGenerationService的生成图片任务接口: createImageGenerationTask(注意使用默认参数,prompt使用角色描述),并返回任务id
|
|
|
+ try {
|
|
|
+ $params = [
|
|
|
+ 'prompt' => $description,
|
|
|
+ 'ref_img_urls' => !empty($ref_img_url) ? [$ref_img_url] : []
|
|
|
+ ];
|
|
|
+
|
|
|
+ $task = $this->aiImageGenerationService->createImageGenerationTask($params);
|
|
|
+ $task_id = $task->id;
|
|
|
+
|
|
|
+ if (!$task_id) {
|
|
|
+ Utils::throwError("20003: 角色({$role_name})生成图片失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ $role['task_id'] = $task_id;
|
|
|
+ $role['task_status'] = 'processing';
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Utils::throwError("20003:" . $e->getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存角色信息
|
|
|
+ // $boolen = DB::table('mp_animes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->where('is_default', 1)->update([
|
|
|
+ $boolen = DB::table('mp_animes')->where('id', $anime_id)->update([
|
|
|
+ 'roles' => json_encode($roles, 256),
|
|
|
+ 'generate_status' => '执行中',
|
|
|
+ 'generate_at' => date('Y-m-d H:i:s'),
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s')
|
|
|
+ ]);
|
|
|
+ if (!$boolen) {
|
|
|
+ Utils::throwError('20003:保存角色信息失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ return $boolen;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成全局场景图片
|
|
|
+ private function batchSetGlobalSceneImg($data) {
|
|
|
+ $anime_id = getProp($data, 'anime_id');
|
|
|
+ if (!$anime_id) Utils::throwError('1002:请选择对话');
|
|
|
+ $episode_number = getProp($data, 'episode_number');
|
|
|
+ $anime = DB::table('mp_animes')->where('id', $anime_id)->where('is_deleted', 0)->first();
|
|
|
+ $scenes = json_decode(getProp($anime, 'scenes'), true);
|
|
|
+ $art_style = getProp($anime, 'art_style');
|
|
|
+
|
|
|
+ foreach ($scenes as &$scene) {
|
|
|
+ $scene_name = getProp($scene, 'scene');
|
|
|
+ $description = getProp($scene, 'description');
|
|
|
+ if ($art_style) $description = "美术风格:\n$art_style\n\n场景描述:$description";
|
|
|
+ // 参考图地址
|
|
|
+ $ref_img_url = getProp($scene, 'url');
|
|
|
+
|
|
|
+ // 此处调用AIImageGenerationService的生成图片任务接口: createImageGenerationTask(注意使用默认参数,prompt使用角色描述),并返回任务id
|
|
|
+ try {
|
|
|
+ $params = [
|
|
|
+ 'prompt' => $description,
|
|
|
+ 'ref_img_urls' => !empty($ref_img_url) ? [$ref_img_url] : []
|
|
|
+ ];
|
|
|
+
|
|
|
+ $task = $this->aiImageGenerationService->createImageGenerationTask($params);
|
|
|
+ $task_id = $task->id;
|
|
|
+
|
|
|
+ if (!$task_id) {
|
|
|
+ Utils::throwError("20003: 角色({$scene_name})生成图片失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ $scene['task_id'] = $task_id;
|
|
|
+ $scene['task_status'] = 'processing';
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Utils::throwError("20003:" . $e->getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存角色信息
|
|
|
+ // $boolen = DB::table('mp_anime_episodes')->where('anime_id', $anime_id)->where('episode_number', $episode_number)->where('is_default', 1)->update([
|
|
|
+ $boolen = DB::table('mp_animes')->where('id', $anime_id)->update([
|
|
|
+ 'scenes' => json_encode($scenes, 256),
|
|
|
+ 'generate_status' => '执行中',
|
|
|
+ 'generate_at' => date('Y-m-d H:i:s'),
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s')
|
|
|
+ ]);
|
|
|
+ if (!$boolen) {
|
|
|
+ Utils::throwError('20003:保存角色信息失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ return $boolen;
|
|
|
+ }
|
|
|
+
|
|
|
public function chatChangeImg($data) {
|
|
|
$segment = getProp($data, 'segment');
|
|
|
$segment_content = getProp($segment, 'segment_content');
|