Преглед изворни кода

新增推送资产的功能

lh пре 1 дан
родитељ
комит
125425b0a6

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

@@ -2289,6 +2289,13 @@ class AnimeController extends BaseController
         return $this->success($result);
     }
 
+    // 推送资产或文件夹到目标位置
+    public function pushProductOrFolder(Request $request) {
+        $data = $request->all();
+        $result = $this->AnimeService->pushProductOrFolder($data);
+        return $this->success($result);
+    }
+
     /**
      * 保存剧本资产关联关系
      * @param Request $request

+ 22 - 0
app/Http/Controllers/DeepSeek/DeepSeekController.php

@@ -1112,4 +1112,26 @@ class DeepSeekController extends BaseController
         $result = $this->deepseekService->regenerateSegmentScript($data);
         return $this->success($result);
     }
+
+    /**
+     * 批量生成多个分集
+     * @param Request $request
+     * @return mixed
+     */
+    public function batchGenerateEpisodes(Request $request) {
+        $data = $request->all();
+        $result = $this->deepseekService->batchGenerateEpisodes($data);
+        return $this->success($result);
+    }
+
+    /**
+     * 获取批量生成任务状态
+     * @param Request $request
+     * @return mixed
+     */
+    public function getBatchGenerationTaskStatus(Request $request) {
+        $data = $request->all();
+        $result = $this->deepseekService->getBatchGenerationTaskStatus($data);
+        return $this->success($result);
+    }
 }

+ 196 - 0
app/Jobs/ProcessBatchEpisodeGenerationJob.php

@@ -0,0 +1,196 @@
+<?php
+
+namespace App\Jobs;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Support\Facades\DB;
+use App\Services\DeepSeek\DeepSeekService;
+use App\Facade\Site;
+
+/**
+ * 批量生成分集任务队列
+ */
+class ProcessBatchEpisodeGenerationJob implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    protected $taskId;
+    protected $animeId;
+    protected $startEpisodeNumber;
+    protected $totalEpisodes;
+    protected $requestData;
+    protected $uid;
+    protected $cpid;
+
+    /**
+     * 任务最大尝试次数
+     */
+    public $tries = 3;
+
+    /**
+     * 任务超时时间(秒)
+     */
+    public $timeout = 1800;
+
+    /**
+     * Create a new job instance.
+     *
+     * @param int $taskId 批量任务ID
+     * @param int $animeId 动漫ID
+     * @param int $startEpisodeNumber 起始集数
+     * @param int $totalEpisodes 总共生成集数
+     * @param array $requestData 请求数据
+     * @param int $uid 用户ID
+     * @param int $cpid 公司ID
+     */
+    public function __construct($taskId, $animeId, $startEpisodeNumber, $totalEpisodes, $requestData, $uid, $cpid)
+    {
+        $this->taskId = $taskId;
+        $this->animeId = $animeId;
+        $this->startEpisodeNumber = $startEpisodeNumber;
+        $this->totalEpisodes = $totalEpisodes;
+        $this->requestData = $requestData;
+        $this->uid = $uid;
+        $this->cpid = $cpid;
+    }
+
+    /**
+     * Execute the job.
+     *
+     * @return void
+     */
+    public function handle()
+    {
+        try {
+            // 设置当前用户上下文
+            Site::setUid($this->uid);
+            Site::setCpid($this->cpid);
+
+            $deepSeekService = app(DeepSeekService::class);
+
+            // 逐集生成
+            for ($i = 0; $i < $this->totalEpisodes; $i++) {
+                $currentEpisodeNumber = $this->startEpisodeNumber + $i;
+
+                // 更新当前任务进度
+                $this->updateTaskProgress($currentEpisodeNumber, 'processing');
+
+                try {
+                    // 准备当前集的数据
+                    $episodeData = $this->requestData;
+                    $episodeData['episode_number'] = $currentEpisodeNumber;
+                    $episodeData['prompt'] = '确认分镜大纲';
+
+                    // 调用非流式生成方法
+                    $result = $deepSeekService->chatForAceNonStream($episodeData);
+
+                    // 标记当前集完成
+                    $this->updateTaskProgress($currentEpisodeNumber, 'completed', $result);
+
+                } catch (\Exception $e) {
+                    // 标记当前集失败
+                    $this->updateTaskProgress($currentEpisodeNumber, 'failed', null, $e->getMessage());
+                    
+                    // 记录错误日志
+                    logDB('batch_episode_generation', 'error', "第{$currentEpisodeNumber}集生成失败", [
+                        'task_id' => $this->taskId,
+                        'anime_id' => $this->animeId,
+                        'episode_number' => $currentEpisodeNumber,
+                        'error' => $e->getMessage(),
+                        'trace' => $e->getTraceAsString()
+                    ]);
+
+                    // 继续生成下一集(可选:根据需求决定是否中断整个任务)
+                    // throw $e; // 如果要中断整个任务,取消注释这行
+                }
+            }
+
+            // 标记整个任务完成
+            DB::table('mp_batch_episode_generation_tasks')
+                ->where('id', $this->taskId)
+                ->update([
+                    'status' => 'completed',
+                    'completed_at' => now(),
+                    'updated_at' => now()
+                ]);
+
+        } catch (\Exception $e) {
+            // 标记整个任务失败
+            DB::table('mp_batch_episode_generation_tasks')
+                ->where('id', $this->taskId)
+                ->update([
+                    'status' => 'failed',
+                    'error_message' => $e->getMessage(),
+                    'updated_at' => now()
+                ]);
+
+            // 记录错误日志
+            logDB('batch_episode_generation', 'error', '批量生成任务失败', [
+                'task_id' => $this->taskId,
+                'anime_id' => $this->animeId,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 更新任务进度
+     */
+    private function updateTaskProgress($episodeNumber, $status, $result = null, $errorMessage = null)
+    {
+        $updateData = [
+            'current_episode' => $episodeNumber,
+            'updated_at' => now()
+        ];
+
+        if ($status === 'completed') {
+            $updateData['completed_episodes'] = DB::raw('completed_episodes + 1');
+        }
+
+        if ($errorMessage) {
+            $updateData['error_message'] = $errorMessage;
+        }
+
+        DB::table('mp_batch_episode_generation_tasks')
+            ->where('id', $this->taskId)
+            ->update($updateData);
+
+        // 保存每集的详细状态
+        DB::table('mp_batch_episode_generation_details')->insert([
+            'task_id' => $this->taskId,
+            'episode_number' => $episodeNumber,
+            'status' => $status,
+            'result_data' => $result ? json_encode($result, JSON_UNESCAPED_UNICODE) : null,
+            'error_message' => $errorMessage,
+            'created_at' => now(),
+            'updated_at' => now()
+        ]);
+    }
+
+    /**
+     * 任务失败处理
+     */
+    public function failed(\Throwable $exception)
+    {
+        DB::table('mp_batch_episode_generation_tasks')
+            ->where('id', $this->taskId)
+            ->update([
+                'status' => 'failed',
+                'error_message' => $exception->getMessage(),
+                'updated_at' => now()
+            ]);
+
+        logDB('batch_episode_generation', 'error', '批量生成任务彻底失败', [
+            'task_id' => $this->taskId,
+            'anime_id' => $this->animeId,
+            'error' => $exception->getMessage()
+        ]);
+    }
+}

+ 173 - 0
app/Services/Anime/AnimeService.php

@@ -6711,6 +6711,179 @@ class AnimeService
     }
 
     /**
+     * 推送(复制)资产或文件夹到目标位置
+     * @param array $data 包含 product_id(要推送的资产或文件夹ID)、target_parent_id(目标父文件夹ID,0表示根目录)、is_public(目标是否为公共库)
+     * @return array 返回推送结果
+     */
+    public function pushProductOrFolder($data) {
+        $uid = Site::getUid();
+        $cpid = Site::getCpid();
+        $product_id = getProp($data, 'product_id');
+        $target_parent_id = getProp($data, 'target_parent_id', 0);
+        $source_is_public = getProp($data, 'source_is_public', 0); // 源是否为公共库
+        $target_is_public = getProp($data, 'target_is_public', 0); // 目标是否为公共库
+        
+        if (!$product_id) {
+            Utils::throwError('20003:请选择要推送的项目');
+        }
+        
+        // 根据source_is_public确定源的user_id
+        $source_uid = $source_is_public ? 0 : $uid;
+        // 根据target_is_public确定目标的user_id
+        $target_uid = $target_is_public ? 0 : $uid;
+        
+        // 获取要推送的项目
+        $sourceItem = DB::table('mp_products')
+            ->where('id', $product_id)
+            ->where('cpid', $cpid)
+            ->where('user_id', $source_uid)
+            ->where('is_deleted', 0)
+            ->first();
+        
+        if (!$sourceItem) {
+            Utils::throwError('20003:要推送的项目不存在');
+        }
+        
+        // 验证目标文件夹
+        $target_level = 0;
+        if ($target_parent_id > 0) {
+            $targetParent = DB::table('mp_products')
+                ->where('id', $target_parent_id)
+                ->where('cpid', $cpid)
+                ->where('user_id', $target_uid)
+                ->where('type', 2)
+                ->where('is_deleted', 0)
+                ->first();
+            
+            if (!$targetParent) {
+                Utils::throwError('20003:目标文件夹不存在');
+            }
+            
+            // 检查 product 类型是否一致
+            if ($targetParent->product != $sourceItem->product) {
+                Utils::throwError('20003:不能推送到不同类型的文件夹下');
+            }
+            
+            $target_level = $targetParent->level;
+        }
+        
+        try {
+            DB::beginTransaction();
+            
+            // 如果是资产,直接复制
+            if ($sourceItem->type == 1) {
+                $newId = $this->copyProduct($sourceItem, $target_parent_id, $target_level + 1, $cpid, $target_uid);
+                DB::commit();
+                return [
+                    'success' => 1,
+                    'message' => '推送成功',
+                    'new_id' => $newId
+                ];
+            }
+            
+            // 如果是文件夹,递归复制
+            $newFolderId = $this->copyFolderRecursive($sourceItem, $target_parent_id, $target_level + 1, $cpid, $target_uid, $source_uid);
+            
+            DB::commit();
+            return [
+                'success' => 1,
+                'message' => '推送成功',
+                'new_folder_id' => $newFolderId
+            ];
+            
+        } catch (\Exception $e) {
+            DB::rollBack();
+            dLog('anime')->error('推送失败', [
+                'error' => $e->getMessage(),
+                'product_id' => $product_id
+            ]);
+            Utils::throwError('20003:' . $e->getMessage());
+        }
+    }
+    
+    /**
+     * 复制单个资产
+     * @param object $sourceProduct 源资产对象
+     * @param int $targetParentId 目标父文件夹ID
+     * @param int $targetLevel 目标层级
+     * @param int $cpid 公司ID
+     * @param int $targetUid 目标用户ID
+     * @return int 返回新资产ID
+     */
+    private function copyProduct($sourceProduct, $targetParentId, $targetLevel, $cpid, $targetUid) {
+        $newProductData = [
+            'user_id' => $targetUid,
+            'cpid' => $cpid,
+            'type' => 1,
+            'parent_id' => $targetParentId,
+            'level' => $targetLevel,
+            'product_name' => $sourceProduct->product_name,
+            'url' => $sourceProduct->url,
+            'pic_prompt' => $sourceProduct->pic_prompt ?? '',
+            'three_view_image_url' => $sourceProduct->three_view_image_url ?? '',
+            'product' => $sourceProduct->product,
+            'versions' => $sourceProduct->versions ?? '',
+            'sort_order' => time(),
+            'is_deleted' => 0,
+            'created_at' => date('Y-m-d H:i:s'),
+            'updated_at' => date('Y-m-d H:i:s')
+        ];
+        
+        return DB::table('mp_products')->insertGetId($newProductData);
+    }
+    
+    /**
+     * 递归复制文件夹及其子项
+     * @param object $sourceFolder 源文件夹对象
+     * @param int $targetParentId 目标父文件夹ID
+     * @param int $targetLevel 目标层级
+     * @param int $cpid 公司ID
+     * @param int $targetUid 目标用户ID
+     * @param int $sourceUid 源用户ID
+     * @return int 返回新文件夹ID
+     */
+    private function copyFolderRecursive($sourceFolder, $targetParentId, $targetLevel, $cpid, $targetUid, $sourceUid) {
+        // 创建新文件夹
+        $newFolderData = [
+            'user_id' => $targetUid,
+            'cpid' => $cpid,
+            'type' => 2,
+            'parent_id' => $targetParentId,
+            'level' => $targetLevel,
+            'product_name' => $sourceFolder->product_name,
+            'product' => $sourceFolder->product,
+            'sort_order' => time(),
+            'is_deleted' => 0,
+            'created_at' => date('Y-m-d H:i:s'),
+            'updated_at' => date('Y-m-d H:i:s')
+        ];
+        
+        $newFolderId = DB::table('mp_products')->insertGetId($newFolderData);
+        
+        // 获取源文件夹下的所有子项
+        $children = DB::table('mp_products')
+            ->where('parent_id', $sourceFolder->id)
+            ->where('cpid', $cpid)
+            ->where('user_id', $sourceUid)
+            ->where('is_deleted', 0)
+            ->orderByRaw('type DESC, sort_order DESC, created_at DESC')
+            ->get();
+        
+        // 递归复制子项
+        foreach ($children as $child) {
+            if ($child->type == 1) {
+                // 资产
+                $this->copyProduct($child, $newFolderId, $targetLevel + 1, $cpid, $targetUid);
+            } else {
+                // 文件夹
+                $this->copyFolderRecursive($child, $newFolderId, $targetLevel + 1, $cpid, $targetUid, $sourceUid);
+            }
+        }
+        
+        return $newFolderId;
+    }
+
+    /**
      * 保存剧本资产关联关系
      * @param array $data 请求参数
      * @return bool

+ 225 - 0
app/Services/DeepSeek/DeepSeekService.php

@@ -8652,6 +8652,231 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
     }
 
     /**
+     * chatForAce的非流式版本 - 用于队列任务
+     * 与chatForAce逻辑完全一致,但不使用流式输出
+     * 
+     * @param array $data 请求数据
+     * @return array 生成结果
+     */
+    public function chatForAceNonStream($data) {
+        $allChunks = [];
+        $finalResult = null;
+        
+        // 调用流式方法并收集所有数据
+        foreach ($this->chatForAce($data) as $chunk) {
+            $allChunks[] = $chunk;
+            
+            // 保存最终结果
+            if (isset($chunk['type']) && $chunk['type'] === 'done') {
+                $finalResult = $chunk;
+            }
+        }
+        
+        // 返回最终结果
+        if ($finalResult) {
+            return $finalResult;
+        }
+        
+        // 如果没有done类型的数据,返回最后一个chunk
+        return end($allChunks) ?: ['error' => '生成失败'];
+    }
+
+    /**
+     * 批量生成多个分集
+     * 
+     * @param array $data 请求数据
+     * @return array 任务信息
+     */
+    public function batchGenerateEpisodes($data) {
+        $uid = Site::getUid();
+        $cpid = Site::getCpid();
+        $anime_id = getProp($data, 'anime_id');
+        $generate_episode_number = (int)getProp($data, 'generate_episode_number', 1);
+        
+        if (!$anime_id) {
+            Utils::throwError('20003:anime_id参数缺失');
+        }
+        
+        if ($generate_episode_number < 1) {
+            Utils::throwError('20003:生成集数必须大于0');
+        }
+        
+        // 获取动漫信息
+        $anime = DB::table('mp_animes')->where('id', $anime_id)->first();
+        if (!$anime) {
+            Utils::throwError('20003:该剧集不存在');
+        }
+        
+        // 检查是否是多剧集模式
+        if ((int)getProp($anime, 'is_multi') !== 1) {
+            Utils::throwError('20003:单剧集不支持批量生成');
+        }
+        
+        // 获取当前已生成的最大集数
+        $currentMaxEpisode = DB::table('mp_anime_episodes')
+            ->where('anime_id', $anime_id)
+            ->where('is_default', 1)
+            ->max('episode_number');
+        
+        $currentMaxEpisode = $currentMaxEpisode ?: 0;
+        
+        // 确定起始集数
+        $startEpisodeNumber = $currentMaxEpisode + 1;
+        
+        // 如果用户指定了起始集数(通过episode_number参数)
+        if (isset($data['episode_number'])) {
+            $userStartEpisode = (int)getProp($data, 'episode_number');
+            if ($userStartEpisode > $currentMaxEpisode) {
+                $startEpisodeNumber = $userStartEpisode;
+            }
+        }
+        
+        // 计算结束集数
+        $endEpisodeNumber = $startEpisodeNumber + $generate_episode_number - 1;
+        
+        // 检查是否超过总集数限制
+        $end_episode_sequence = getProp($anime, 'end_episode_sequence');
+        if ($end_episode_sequence && $endEpisodeNumber > $end_episode_sequence) {
+            Utils::throwError("20003:总集数只有{$end_episode_sequence}集,无法生成到第{$endEpisodeNumber}集");
+        }
+        
+        // 检查是否已有进行中的任务
+        $existingTask = DB::table('mp_batch_episode_generation_tasks')
+            ->where('anime_id', $anime_id)
+            ->whereIn('status', ['pending', 'processing'])
+            ->first();
+        
+        if ($existingTask) {
+            Utils::throwError('20003:该剧集已有批量生成任务正在进行中,请等待完成后再试');
+        }
+        
+        $now = date('Y-m-d H:i:s');
+        
+        // 准备任务数据
+        $taskData = [
+            'anime_id' => $anime_id,
+            'uid' => $uid,
+            'cpid' => $cpid,
+            'start_episode' => $startEpisodeNumber,
+            'end_episode' => $endEpisodeNumber,
+            'total_episodes' => $generate_episode_number,
+            'current_episode' => 0,
+            'completed_episodes' => 0,
+            'status' => 'pending',
+            'request_data' => json_encode($data, JSON_UNESCAPED_UNICODE),
+            'created_at' => $now,
+            'updated_at' => $now
+        ];
+        
+        // 创建批量生成任务记录
+        $taskId = DB::table('mp_batch_episode_generation_tasks')->insertGetId($taskData);
+        
+        if (!$taskId) {
+            Utils::throwError('20003:创建批量生成任务失败');
+        }
+        
+        // 更新任务状态为processing
+        DB::table('mp_batch_episode_generation_tasks')
+            ->where('id', $taskId)
+            ->update([
+                'status' => 'processing',
+                'updated_at' => $now
+            ]);
+        
+        // 分发到队列
+        \App\Jobs\ProcessBatchEpisodeGenerationJob::dispatch(
+            $taskId,
+            $anime_id,
+            $startEpisodeNumber,
+            $generate_episode_number,
+            $data,
+            $uid,
+            $cpid
+        );
+        
+        return [
+            'task_id' => $taskId,
+            'anime_id' => $anime_id,
+            'start_episode' => $startEpisodeNumber,
+            'end_episode' => $endEpisodeNumber,
+            'total_episodes' => $generate_episode_number,
+            'status' => 'processing',
+            'message' => "已创建批量生成任务,将生成第{$startEpisodeNumber}集到第{$endEpisodeNumber}集,共{$generate_episode_number}集"
+        ];
+    }
+
+    /**
+     * 获取批量生成任务状态
+     * 
+     * @param array $data 请求数据
+     * @return array 任务状态信息
+     */
+    public function getBatchGenerationTaskStatus($data) {
+        $task_id = getProp($data, 'task_id');
+        $anime_id = getProp($data, 'anime_id');
+        
+        if (!$task_id && !$anime_id) {
+            Utils::throwError('20003:task_id或anime_id参数缺失');
+        }
+        
+        $query = DB::table('mp_batch_episode_generation_tasks');
+        
+        if ($task_id) {
+            $query->where('id', $task_id);
+        } else {
+            // 获取该anime最新的任务
+            $query->where('anime_id', $anime_id)
+                  ->orderByDesc('id')
+                  ->limit(1);
+        }
+        
+        $task = $query->first();
+        
+        if (!$task) {
+            Utils::throwError('20003:任务不存在');
+        }
+        
+        // 获取任务详情
+        $details = DB::table('mp_batch_episode_generation_details')
+            ->where('task_id', $task->id)
+            ->orderBy('episode_number')
+            ->get()
+            ->map(function($item) {
+                return [
+                    'episode_number' => $item->episode_number,
+                    'status' => $item->status,
+                    'error_message' => $item->error_message,
+                    'created_at' => $item->created_at,
+                    'updated_at' => $item->updated_at
+                ];
+            })
+            ->toArray();
+        
+        // 计算进度百分比
+        $progress = 0;
+        if ($task->total_episodes > 0) {
+            $progress = round(($task->completed_episodes / $task->total_episodes) * 100, 2);
+        }
+        
+        return [
+            'task_id' => $task->id,
+            'anime_id' => $task->anime_id,
+            'start_episode' => $task->start_episode,
+            'end_episode' => $task->end_episode,
+            'total_episodes' => $task->total_episodes,
+            'current_episode' => $task->current_episode,
+            'completed_episodes' => $task->completed_episodes,
+            'progress' => $progress,
+            'status' => $task->status,
+            'error_message' => $task->error_message,
+            'details' => $details,
+            'created_at' => $task->created_at,
+            'updated_at' => $task->updated_at,
+            'completed_at' => $task->completed_at
+        ];
+    }
+
+    /**
      * 智能匹配并替换主体和场景名称
      * 如果生成的主体或场景名称是强制设定名称的子串,则自动替换为完整名称
      * 

+ 63 - 0
database/migrations/2026_01_29_000001_create_batch_episode_generation_tables.php

@@ -0,0 +1,63 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateBatchEpisodeGenerationTables extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        // 批量生成分集任务主表
+        Schema::create('mp_batch_episode_generation_tasks', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('anime_id')->comment('动漫ID');
+            $table->unsignedBigInteger('uid')->comment('用户ID');
+            $table->unsignedBigInteger('cpid')->comment('公司ID');
+            $table->integer('start_episode')->comment('起始集数');
+            $table->integer('end_episode')->comment('结束集数');
+            $table->integer('total_episodes')->comment('总集数');
+            $table->integer('current_episode')->default(0)->comment('当前处理的集数');
+            $table->integer('completed_episodes')->default(0)->comment('已完成集数');
+            $table->string('status', 20)->default('pending')->comment('任务状态:pending-待处理, processing-处理中, completed-已完成, failed-失败');
+            $table->text('request_data')->nullable()->comment('请求数据JSON');
+            $table->text('error_message')->nullable()->comment('错误信息');
+            $table->timestamp('completed_at')->nullable()->comment('完成时间');
+            $table->timestamps();
+            
+            $table->index('anime_id');
+            $table->index('uid');
+            $table->index('status');
+        });
+
+        // 批量生成分集详情表
+        Schema::create('mp_batch_episode_generation_details', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('task_id')->comment('任务ID');
+            $table->integer('episode_number')->comment('集数');
+            $table->string('status', 20)->comment('状态:processing-处理中, completed-已完成, failed-失败');
+            $table->text('result_data')->nullable()->comment('结果数据JSON');
+            $table->text('error_message')->nullable()->comment('错误信息');
+            $table->timestamps();
+            
+            $table->index('task_id');
+            $table->index(['task_id', 'episode_number']);
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('mp_batch_episode_generation_details');
+        Schema::dropIfExists('mp_batch_episode_generation_tasks');
+    }
+}

+ 5 - 0
routes/api.php

@@ -198,6 +198,8 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
             Route::post('reGenerateAnimeForAce', [DeepSeekController::class, 'reGenerateAnimeForAce']); // 调整动漫大纲
             Route::post('chatForAce', [DeepSeekController::class, 'chatForAce']);                       // 动漫对话(全能模式)
             Route::post('regenerateSegmentScript', [DeepSeekController::class, 'regenerateSegmentScript']); // 重新生成分段剧本
+            Route::post('batchGenerateEpisodes', [DeepSeekController::class, 'batchGenerateEpisodes']); // 批量生成多个分集
+            Route::get('getBatchGenerationTaskStatus', [DeepSeekController::class, 'getBatchGenerationTaskStatus']); // 获取批量生成任务状态
             Route::get('episodeInfoForAce', [AnimeController::class, 'episodeInfoForAce']);             // 动漫剧集(全能模式)
             Route::get('actChatHistory', [AnimeController::class, 'actChatHistory']);                   // 片段对话历史记录
             Route::get('confirmActs', [AnimeController::class, 'confirmActs']);                         // 确认片段分镜
@@ -231,6 +233,9 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
             Route::get('getFolderPath', [AnimeController::class, 'getFolderPath']); // 获取文件夹路径
             // 生成三视图(基于参考图生成主体的标准正面、侧面、后面三视图,主体完整。在最左侧添加主体正视图的特写,主体清晰完整。背景修改成纯白色,整体保持与参考图一致的美术风格和色彩搭配。画面无说明文字)
             Route::post('generateThreeView', [AnimeController::class, 'generateThreeView']);
+            // 推送资产或文件夹到目标位置
+            Route::post('pushProductOrFolder', [AnimeController::class, 'pushProductOrFolder']);
+
             
             // 剧本资产关联管理
             Route::post('saveScriptProducts', [AnimeController::class, 'saveScriptProducts']);     // 保存剧本资产