소스 검색

调整对话改图逻辑,新增分镜对话历史接口

lh 3 달 전
부모
커밋
b6d1b76443
3개의 변경된 파일129개의 추가작업 그리고 13개의 파일을 삭제
  1. 7 0
      app/Http/Controllers/Anime/AnimeController.php
  2. 121 13
      app/Services/Anime/AnimeService.php
  3. 1 0
      routes/api.php

+ 7 - 0
app/Http/Controllers/Anime/AnimeController.php

@@ -287,6 +287,13 @@ class AnimeController extends BaseController
         return $this->success(['img_url' => $result]);
     }
 
+    public function segmentChatHistory(Request $request) {
+        $data = $request->all();
+
+        $result = $this->AnimeService->segmentChatHistory($data);
+        return $this->success($result);
+    }
+
     // 一键生成分镜
     public function batchSetSegmentPics(Request $request) {
         // 忽略所有超时限制

+ 121 - 13
app/Services/Anime/AnimeService.php

@@ -118,7 +118,7 @@ class AnimeService
         if (!$anime_id) Utils::throwError('20003:请选择对话!');
 
         $query = DB::table('mp_anime_records as ar')->leftJoin('mp_anime_episodes as ae', 'ar.episode_id', '=', 'ae.id')->where('ar.anime_id', $anime_id)
-        ->where('ar.sequence', $sequence)->select('ar.role', 'ar.content', 'ar.created_at', 'ar.episode_id', 'ae.title as episode_title', 'ae.created_at as episode_created_at');
+        ->where('ar.sequence', $sequence)->where('segment_id', 0)->select('ar.role', 'ar.content', 'ar.created_at', 'ar.episode_id', 'ae.title as episode_title', 'ae.created_at as episode_created_at');
         
         return $query->orderBy('ar.id')->get()->map(function ($value) {
             $value = (array)$value;
@@ -741,6 +741,7 @@ class AnimeService
 
     public function chatChangeImg($data) {
         $segment_id = getProp($data, 'segment_id');
+        $type = getProp($data, 'type', 1);      // 类型: 1.对话改图 2.图片生成
         $prompt = getProp($data, 'prompt');
         if (!$prompt || !$segment_id) {
             Utils::throwError('1002:请输入提示词,并且选择分镜');
@@ -786,7 +787,7 @@ class AnimeService
 
         try {
             $params = [
-                'alias_segment_id' => $segment_id,
+                'alias_segment_id' => $type == 2 ? $segment_id : 0,
                 'prompt' => $prompt,
                 'ref_img_urls' => !empty($ref_img_url) ? [$ref_img_url] : [],
                 'width' => $width,
@@ -812,9 +813,79 @@ class AnimeService
         $img_url = $this->loopGetPicTaskResult($task_id);
 
         if (!$img_url) Utils::throwError('20003:图片生成中,请稍后刷新');
+
+        // 图片生成后新增对话记录
+        try {
+            $uid = Site::getUid();
+            $now = date('Y-m-d H:i:s');
+            
+            // 获取segment的anime_id和episode_number
+            $segmentInfo = DB::table('mp_episode_segments as es')
+                ->join('mp_anime_episodes as ae', 'es.episode_id', '=', 'ae.id')
+                ->where('es.segment_id', $segment_id)
+                ->select('ae.anime_id', 'ae.episode_number')
+                ->first();
+            
+            if ($segmentInfo) {
+                $anime_id = getProp($segmentInfo, 'anime_id');
+                $episode_number = getProp($segmentInfo, 'episode_number');
+                
+                // 使用AI反推图片提示词
+                $pic_prompt = $this->generateImagePromptFromUrl($img_url);
+                
+                // 保存对话记录
+                $records = [
+                    [
+                        'uid'       => $uid,
+                        'anime_id'  => $anime_id,
+                        'sequence'  => $episode_number,
+                        'role'      => 'user',
+                        'content'   => $prompt,
+                        'segment_id' => $segment_id,
+                        'img_url'   => '',
+                        'created_at' => $now,
+                        'updated_at' => $now
+                    ],
+                    [
+                        'uid'       => $uid,
+                        'anime_id'  => $anime_id,
+                        'sequence'  => $episode_number,
+                        'role'      => 'assistant',
+                        'content'   => $pic_prompt,
+                        'segment_id' => $segment_id,
+                        'img_url'    => $img_url,
+                        'created_at' => $now,
+                        'updated_at' => $now
+                    ]
+                ];
+                
+                DB::table('mp_anime_records')->insert($records);
+            }
+        } catch (\Exception $e) {
+            // 记录日志但不影响主流程
+            dLog('anime')->error('保存对话记录失败: ' . $e->getMessage(), [
+                'segment_id' => $segment_id,
+                'img_url' => $img_url
+            ]);
+        }
+
         return $img_url;
     }
 
+    public function segmentChatHistory($data) {
+        $segment_id = getProp($data, 'segment_id');
+
+        if (!$segment_id) Utils::throwError('20003:请选择分镜!');
+
+        $query = DB::table('mp_anime_records')->where('segment_id', $segment_id)->select('role', 'content', 'segment_id', 'image_url', 'video_url', 'created_at');
+        
+        return $query->orderBy('ar.id')->get()->map(function ($value) {
+            $value = (array)$value;
+            $value['created_at'] = transDate($value['created_at']);
+            return $value;
+        })->toArray();
+    }
+
     public function changeEpisodeRoles($data) {
         $anime_id = getProp($data, 'anime_id');
         $episode_id = getProp($data, 'episode_id');
@@ -2323,23 +2394,26 @@ class AnimeService
         $model = getProp($task, 'model');
         $isVolcModel = in_array($model, \App\Consts\BaseConst::VOLC_PIC_MODELS);
 
-        // 火山API是同步返回结果的,直接检查任务状态
-        if ($isVolcModel) {
-            // 火山API任务,直接返回结果(已在创建时处理完成)
-            if ($task->status === 'success' && $task->result_url) {
-                $result_urls = is_array($task->result_url) ? $task->result_url : json_decode($task->result_url, true);
-                return is_array($result_urls) ? $result_urls[0] : $task->result_url;
-            }
-            return '';
-        }
-
-        // 即梦AI需要轮询查询状态
+        // 轮询查询任务状态(火山API和即梦AI都需要轮询)
         $start_count = 0;
         $img_url = '';
         while ($start_count <= 20) {    // 循环20次请求,每3s一次
             sleep(3);
 
             $task = MpGeneratePicTask::where('id', $task_id)->first();
+            
+            // 如果任务已经完成,直接返回结果
+            if ($task->status === 'success' && $task->result_url) {
+                $result_urls = is_array($task->result_url) ? $task->result_url : json_decode($task->result_url, true);
+                return is_array($result_urls) ? $result_urls[0] : $task->result_url;
+            }
+            
+            // 如果任务已失败,返回空
+            if ($task->status === 'failed') {
+                return '';
+            }
+            
+            // 查询任务状态
             $statusInfo = $this->aiImageGenerationService->queryTaskStatus($task);
 
             if ($statusInfo['status'] === 'success') {
@@ -3141,4 +3215,38 @@ class AnimeService
             return $segment_content;
         }
     }
+
+    /**
+     * 根据图片URL使用AI反推图片提示词
+     * 
+     * @param string $img_url 图片URL
+     * @return string 反推的提示词
+     */
+    private function generateImagePromptFromUrl($img_url) {
+        try {
+            // 获取默认模型
+            $model = 'doubao-seed-2-0-mini-260215';
+            
+            // 构建请求参数
+            $params = [
+                'model' => $model,
+                'prompt' => '请详细描述这张图片的内容,包括场景、人物、构图、色彩等元素,用于生成图片的提示词。',
+                'image_urls' => [$img_url],
+                'stream' => false,
+                'thinking' => ['type' => 'disabled']  // 不使用思考模式
+            ];
+            
+            // 调用DeepSeek服务的volcEngineChatCompletion方法(非流式)
+            $result = $this->DeepSeekService->volcEngineChatCompletion($params);
+            
+            // 返回生成的内容
+            return getProp($result, 'fullContent', '图片描述生成失败');
+            
+        } catch (\Exception $e) {
+            dLog('anime')->error('AI反推图片提示词失败: ' . $e->getMessage(), [
+                'img_url' => $img_url
+            ]);
+            return '图片描述生成失败: ' . $e->getMessage();
+        }
+    }
 }

+ 1 - 0
routes/api.php

@@ -159,6 +159,7 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
         Route::post('moveSegment', [AnimeController::class, 'moveSegment']);                    // 移动分镜
         Route::post('delSegment', [AnimeController::class, 'delSegment']);                      // 删除分镜
         Route::post('segmentHistory', [AnimeController::class, 'segmentHistory']);              // 分镜历史图片/视频
+        Route::get('segmentChatHistory', [AnimeController::class, 'segmentChatHistory']);           // 分镜对话历史记录
         Route::post('createSegmentVideoTask', [AnimeController::class, 'createSegmentVideoTask']);  // 分镜图片转视频
         Route::post('batchSetSegmentVideos', [AnimeController::class, 'batchSetSegmentVideos']);    // 一键转视频
         Route::post('applyAudioData', [AnimeController::class, 'applyAudioData']);                  // 修改分镜音频参数