Просмотр исходного кода

1.新增文生文模型列表2.新增图生视频功能3.新增视频任务回调接口

lh 3 месяцев назад
Родитель
Сommit
dfe1aad984

+ 7 - 0
app/Http/Controllers/AIGeneration/VideoGenerationController.php

@@ -461,6 +461,13 @@ class VideoGenerationController extends BaseController
         return $this->success(['task_id' => $task->id, 'status' => $task->status]);
     }
 
+    public function seedanceCallback(Request $request) {
+        $data = $request->all();
+        $result = $this->aiVideoGenerationService->seedanceCallback($data);
+
+        return $this->success(['success'=> $result ? 1 : 0]);
+    }
+
     /**
      * 查询 Seedance 1.5 Pro 任务状态
      *

+ 182 - 1
app/Http/Controllers/Anime/AnimeController.php

@@ -8,6 +8,7 @@ use App\Consts\ErrorConst;
 use App\Exceptions\ApiException;
 use App\Libs\ApiResponse;
 use App\Libs\Utils;
+use App\Models\MpGenerateVideoTask;
 use App\Services\AIGeneration\AIImageGenerationService;
 use App\Services\AIGeneration\AIVideoGenerationService;
 use App\Services\Anime\AnimeService;
@@ -34,6 +35,20 @@ class AnimeController extends BaseController
         $this->AIVideoGenerationService = $AIVideoGenerationService;
     }
 
+    public function textModel() {
+        $models = [
+            [
+                'model'     => 'v3',
+                'name'      => 'DeepSeek 推理',
+            ],
+            [
+                'model'     => 'r1',
+                'name'      => 'DeepSeek 思考'
+            ],
+        ];
+        return $this->success($models);
+    }
+
 
     // 批量生成主体图片
     public function batchSetRoleImg(Request $request) {
@@ -191,8 +206,174 @@ class AnimeController extends BaseController
 
     public function createSegmentVideoTask(Request $request) {
         $data = $request->all();
+        
+        // 验证参数
+        $validator = Validator::make($data, [
+            'segment_id' => 'required|string',
+            'tail_frame' => 'nullable|string|max:500',
+        ], [
+            'segment_id.required' => '分镜ID不能为空',
+            'tail_frame.max' => '尾帧描述不能超过500个字符',
+        ]);
+        
+        if ($validator->fails()) {
+            Utils::throwError('1002:' . $validator->errors()->first());
+        }
+        
+        // 创建视频生成任务
         $result = $this->AnimeService->createSegmentVideoTask($data);
-        return $this->success($result);
+        $taskId = $result['task_id'];
+        
+        // 设置 SSE 响应头
+        return response()->stream(function () use ($taskId, $result) {
+            // 设置 SSE 响应头
+            echo "data: " . json_encode([
+                'type' => 'task_created',
+                'data' => $result
+            ]) . "\n\n";
+            ob_flush();
+            flush();
+            
+            $startTime = time();
+            $maxDuration = 300; // 5分钟超时
+            $checkInterval = 5; // 每5秒检查一次
+            
+            while (time() - $startTime < $maxDuration) {
+                try {
+                    // 查询任务状态
+                    $task = \App\Models\MpGenerateVideoTask::find($taskId);
+                    if (!$task) {
+                        echo "data: " . json_encode([
+                            'type' => 'error',
+                            'message' => '任务不存在'
+                        ]) . "\n\n";
+                        ob_flush();
+                        flush();
+                        break;
+                    }
+                    
+                    // 如果任务还在处理中,查询最新状态
+                    if ($task->status === 'processing') {
+                        $statusResult = $this->AIVideoGenerationService->querySeedanceTaskStatus($task);
+                        if (isset($statusResult['status'])) {
+                            // 更新任务状态
+                            $task->update([
+                                'status' => $statusResult['status'],
+                                'result_url' => $statusResult['result_url'] ?? null,
+                                'last_frame_url' => $statusResult['last_frame_url'] ?? '',
+                                'error_message' => $statusResult['error_message'] ?? null,
+                                'completed_at' => in_array($statusResult['status'], [
+                                    'success',
+                                    'failed'
+                                ]) ? now() : null
+                            ]);
+                            
+                            // 如果任务成功,更新分镜表
+                            if ($statusResult['status'] === 'success' && isset($statusResult['result_url'])) {
+                                DB::table('mp_episode_segments')
+                                    ->where('video_task_id', $taskId)
+                                    ->update([
+                                        'video_url' => $statusResult['result_url'],
+                                        'video_task_status' => '已完成',
+                                        'last_frame_url' => $statusResult['last_frame_url'] ?? '',
+                                        'updated_at' => date('Y-m-d H:i:s')
+                                    ]);
+                            } elseif ($statusResult['status'] === 'failed') {
+                                DB::table('mp_episode_segments')
+                                    ->where('video_task_id', $taskId)
+                                    ->update([
+                                        'video_task_status' => '失败',
+                                        'updated_at' => date('Y-m-d H:i:s')
+                                    ]);
+                            }
+                        }
+                    }
+                    
+                    // 发送当前状态
+                    echo "data: " . json_encode([
+                        'type' => 'status_update',
+                        'data' => [
+                            'task_id' => $task->id,
+                            'status' => $task->status,
+                            'result_url' => $task->result_url,
+                            'error_message' => $task->error_message,
+                            'progress' => $this->getTaskProgress($task->status),
+                            'elapsed_time' => time() - $startTime
+                        ]
+                    ]) . "\n\n";
+                    ob_flush();
+                    flush();
+                    
+                    // 如果任务完成(成功或失败),结束连接
+                    if (in_array($task->status, [
+                        'success',
+                        'failed'
+                    ])) {
+                        echo "data: " . json_encode([
+                            'type' => 'completed',
+                            'data' => [
+                                'task_id' => $task->id,
+                                'status' => $task->status,
+                                'result_url' => $task->result_url,
+                                'video_url' => $task->result_url, // 兼容字段
+                                'error_message' => $task->error_message
+                            ]
+                        ]) . "\n\n";
+                        ob_flush();
+                        flush();
+                        break;
+                    }
+                    
+                    sleep($checkInterval);
+                    
+                } catch (\Exception $e) {
+                    echo "data: " . json_encode([
+                        'type' => 'error',
+                        'message' => '查询任务状态失败: ' . $e->getMessage()
+                    ]) . "\n\n";
+                    ob_flush();
+                    flush();
+                    sleep($checkInterval);
+                }
+            }
+            
+            // 超时处理
+            if (time() - $startTime >= $maxDuration) {
+                echo "data: " . json_encode([
+                    'type' => 'timeout',
+                    'message' => '任务执行超时,请稍后查询任务状态',
+                    'data' => [
+                        'task_id' => $taskId
+                    ]
+                ]) . "\n\n";
+                ob_flush();
+                flush();
+            }
+            
+        }, 200, [
+            'Content-Type' => 'text/event-stream',
+            'Cache-Control' => 'no-cache',
+            'Connection' => 'keep-alive',
+            'X-Accel-Buffering' => 'no', // 禁用 Nginx 缓冲
+        ]);
+    }
+    
+    /**
+     * 获取任务进度百分比
+     */
+    private function getTaskProgress($status) {
+        switch ($status) {
+            case \App\Models\MpGenerateVideoTask::STATUS_PENDING:
+                return 10;
+            case \App\Models\MpGenerateVideoTask::STATUS_PROCESSING:
+                return 50;
+            case \App\Models\MpGenerateVideoTask::STATUS_SUCCESS:
+                return 100;
+            case \App\Models\MpGenerateVideoTask::STATUS_FAILED:
+                return 0;
+            default:
+                return 0;
+        }
     }
 
     // public function scriptList(Request $request) {

+ 161 - 0
app/Services/AIGeneration/AIVideoGenerationService.php

@@ -7,6 +7,7 @@ use App\Models\MpGenerateVideoTask;
 use App\Models\AIElement;
 use App\Services\VolcEngineService;
 use GuzzleHttp\Client;
+use Illuminate\Support\Facades\DB;
 
 class AIVideoGenerationService
 {
@@ -644,6 +645,166 @@ class AIVideoGenerationService
         }
     }
 
+    public function seedanceCallback($data) {
+        try {
+            dLog('generate')->info('Seedance 回调数据: ', $data);
+            
+            // 从回调数据中获取任务ID
+            $taskId = $data['id'] ?? null;
+            if (!$taskId) {
+                dLog('generate')->error('Seedance 回调缺少任务ID');
+                return ['success' => false, 'message' => '缺少任务ID'];
+            }
+            
+            // 查找对应的任务记录
+            $task = MpGenerateVideoTask::where('task_id', $taskId)->first();
+            if (!$task) {
+                dLog('generate')->error('Seedance 回调找不到任务: ' . $taskId);
+                return ['success' => false, 'message' => '任务不存在'];
+            }
+            
+            // 解析任务状态
+            $apiStatus = $data['status'] ?? 'unknown';
+            $taskStatus = $this->mapApiStatusToTaskStatus($apiStatus);
+            
+            // 准备更新数据
+            $updateData = [
+                'result_json' => $data,
+                'updated_at' => now()
+            ];
+            
+            if ($taskStatus === MpGenerateVideoTask::STATUS_SUCCESS) {
+                $updateData['status'] = MpGenerateVideoTask::STATUS_SUCCESS;
+                $updateData['completed_at'] = now();
+                
+                // 处理视频结果
+                if (isset($data['content']['video_url'])) {
+                    $video_url = $data['content']['video_url'];
+                    try {
+                        // 下载并保存视频
+                        $video_name = 'ai_generation_' . time() . '_' . uniqid();
+                        $video_ext = getVideoExtFromUrl($video_url);
+                        $video_name = $video_name . $video_ext;
+                        $url = uploadStreamByTos('video', file_get_contents($video_url), $video_name);
+                        $updateData['result_url'] = $url;
+                        
+                        dLog('generate')->info('Seedance 视频保存成功: ' . $url);
+                    } catch (\Exception $e) {
+                        dLog('generate')->error('Seedance 视频保存失败: ' . $e->getMessage());
+                        // 如果保存失败,直接使用原始URL
+                        $updateData['result_url'] = $video_url;
+                    }
+                }
+                
+                // 处理尾帧图片(如果启用了 return_last_frame)
+                if (isset($data['content']['last_frame_url']) && !empty($data['content']['last_frame_url'])) {
+                    $pic_url = $data['content']['last_frame_url'];
+                    try {
+                        $pic_name = 'ai_generation_' . time() . '_' . uniqid();
+                        $pic_ext = getImgExtFromUrl($pic_url);
+                        $pic_name = $pic_name . $pic_ext;
+                        
+                        if ($pic_ext === '.png') {
+                            $compressed_data = compressRemoteImageUrlToSize($pic_url);
+                            $url = uploadStreamByTos('image', $compressed_data, $pic_name);
+                        } else {
+                            $url = uploadStreamByTos('image', file_get_contents($pic_url), $pic_name);
+                        }
+                        $updateData['last_frame_url'] = $url;
+                        
+                        dLog('generate')->info('Seedance 尾帧图片保存成功: ' . $url);
+                    } catch (\Exception $e) {
+                        dLog('generate')->error('Seedance 尾帧图片保存失败: ' . $e->getMessage());
+                        // 如果保存失败,直接使用原始URL
+                        $updateData['last_frame_url'] = $pic_url;
+                    }
+                }
+                
+            } elseif ($taskStatus === MpGenerateVideoTask::STATUS_FAILED) {
+                $updateData['status'] = MpGenerateVideoTask::STATUS_FAILED;
+                $updateData['completed_at'] = now();
+                $updateData['error_message'] = $data['error_message'] ?? $data['failure_reason'] ?? '任务执行失败';
+                
+            } else {
+                // 处理中状态
+                $updateData['status'] = $taskStatus;
+                if ($taskStatus === MpGenerateVideoTask::STATUS_PROCESSING && !$task->started_at) {
+                    $updateData['started_at'] = now();
+                }
+            }
+            
+            // 更新任务记录
+            $task->update($updateData);
+            
+            // 更新分镜表的视频任务状态
+            $this->updateSegmentVideoStatus($task, $taskStatus, $updateData);
+            
+            dLog('generate')->info('Seedance 回调处理完成: ' . $taskId . ', 状态: ' . $taskStatus);
+            
+            return [
+                'success' => true,
+                'message' => '回调处理成功',
+                'task_id' => $taskId,
+                'status' => $taskStatus
+            ];
+            
+        } catch (\Exception $e) {
+            dLog('generate')->error('Seedance 回调处理异常: ' . $e->getMessage());
+            return [
+                'success' => false,
+                'message' => '回调处理失败: ' . $e->getMessage()
+            ];
+        }
+    }
+
+    /**
+     * 更新分镜表的视频任务状态
+     * 
+     * @param MpGenerateVideoTask $task
+     * @param string $taskStatus
+     * @param array $updateData
+     */
+    private function updateSegmentVideoStatus(MpGenerateVideoTask $task, string $taskStatus, array $updateData) {
+        try {
+            // 查找关联的分镜记录
+            $segment = DB::table('mp_episode_segments')
+                ->where('video_task_id', $task->id)
+                ->first();
+                
+            if (!$segment) {
+                dLog('generate')->info('未找到关联的分镜记录: ' . $task->id);
+                return;
+            }
+            
+            $segmentUpdateData = ['updated_at' => date('Y-m-d H:i:s')];
+
+            if (isset($updateData['last_frame_url']) && $updateData['last_frame_url']) {
+                $segmentUpdateData['last_frame_url'] = $updateData['last_frame_url'];
+            }
+            
+            if ($taskStatus === MpGenerateVideoTask::STATUS_SUCCESS) {
+                $segmentUpdateData['video_task_status'] = '已完成';
+                if (isset($updateData['result_url'])) {
+                    $segmentUpdateData['video_url'] = $updateData['result_url'];
+                }
+            } elseif ($taskStatus === MpGenerateVideoTask::STATUS_FAILED) {
+                $segmentUpdateData['video_task_status'] = '失败';
+            } elseif ($taskStatus === MpGenerateVideoTask::STATUS_PROCESSING) {
+                $segmentUpdateData['video_task_status'] = '生成中';
+            }
+            
+            // 更新分镜表
+            DB::table('mp_episode_segments')
+                ->where('video_task_id', $task->id)
+                ->update($segmentUpdateData);
+                
+            dLog('generate')->info('分镜视频状态更新成功: ' . $segment->segment_id . ', 状态: ' . ($segmentUpdateData['video_task_status'] ?? '未知'));
+            
+        } catch (\Exception $e) {
+            dLog('generate')->error('更新分镜视频状态失败: ' . $e->getMessage());
+        }
+    }
+
     /**
      * 生成可灵AI的JWT Token
      *

+ 239 - 1
app/Services/Anime/AnimeService.php

@@ -7,6 +7,7 @@ use App\Facade\Site;
 use App\Libs\Utils;
 use App\Models\MpGeneratePicTask;
 use App\Services\AIGeneration\AIImageGenerationService;
+use App\Services\AIGeneration\AIVideoGenerationService;
 use Dflydev\DotAccessData\Util;
 use GuzzleHttp\Client;
 use Illuminate\Support\Facades\DB;
@@ -18,12 +19,17 @@ use OSS\OssClient;
 class AnimeService
 {
     protected $aiImageGenerationService;
+    protected $aiVideoGenerationService;
     private $url;
     private $api_key;
     private $headers;
 
-    public function __construct(AIImageGenerationService $aiImageGenerationService) {
+    public function __construct(
+        AIImageGenerationService $aiImageGenerationService,
+        AIVideoGenerationService $aiVideoGenerationService
+    ) {
         $this->aiImageGenerationService = $aiImageGenerationService;
+        $this->aiVideoGenerationService = $aiVideoGenerationService;
         $this->url = 'https://api.deepseek.com/chat/completions';
         $this->api_key = env('DEEPSEEK_API_KEY');
         $this->headers = [
@@ -1390,6 +1396,238 @@ class AnimeService
     }
 
     public function createSegmentVideoTask($data) {
+        $segment_id = getProp($data, 'segment_id');
+        $tail_frame = getProp($data, 'tail_frame');
+        
+        if (!$segment_id) {
+            Utils::throwError('1002:分镜ID不能为空');
+        }
+        
+        // 获取分镜信息
+        $segment = DB::table('mp_episode_segments')->where('segment_id', $segment_id)->first();
+        if (!$segment) {
+            Utils::throwError('20003:分镜不存在');
+        }
+        
+        $segment = (array)$segment;
+        
+        // 获取分镜内容
+        $segmentContent = getProp($segment, 'segment_content', '');
+        
+        // 如果没有传入尾帧描述,使用分镜表中的尾帧描述
+        if (!$tail_frame) {
+            $tail_frame = getProp($segment, 'tail_frame', '');
+        }
+        
+        // 构建完整的提示词
+        $fullPrompt = $segmentContent;
+        if ($tail_frame) {
+            $fullPrompt .= "\n尾帧描述:$tail_frame";
+        }
+        
+        // 智能选择视频时长
+        $videoDuration = $this->calculateOptimalVideoDuration($segmentContent, $tail_frame);
+        
+        // 构建视频生成参数
+        $videoParams = [
+            'model' => 'doubao-seedance-1-5-pro-251215',
+            'prompt' => $fullPrompt,
+            'video_duration' => $videoDuration,
+            'video_resolution' => '720P',
+            'seed' => -1,
+            'ratio' => '16:9',
+            'generate_audio' => false,
+            'draft' => false,
+            'watermark' => false,
+            'camera_fixed' => false,
+            'callback_url' => 'http://mpaudio.yqsd.cn'
+        ];
+        
+        // 如果分镜有图片,作为首帧
+        if (getProp($segment, 'img_url')) {
+            $videoParams['first_frame_url'] = getProp($segment, 'img_url');
+        }
+        
+        // 构建content数组
+        $videoParams['content'] = [
+            [
+                'type' => 'text',
+                'text' => $videoParams['prompt'],
+            ]
+        ];
+        
+        // 如果有首帧图片,添加到content中
+        if (isset($videoParams['first_frame_url'])) {
+            $videoParams['content'][] = [
+                'type' => 'image_url',
+                'image_url' => [
+                    'url' => $videoParams['first_frame_url'],
+                ],
+                'role' => 'first_frame',
+            ];
+        }
+
+        // dd($videoParams);
+        
+        // 创建视频生成任务
+        $task = $this->aiVideoGenerationService->createSeedanceTask($videoParams);
+        
+        // 更新分镜表的视频任务信息
+        DB::table('mp_episode_segments')->where('segment_id', $segment_id)->update([
+            'video_task_id' => $task->id,
+            'video_task_status' => '生成中',
+            'updated_at' => date('Y-m-d H:i:s')
+        ]);
+        
+        return [
+            'task_id' => $task->id,
+            'status' => $task->status,
+            'segment_id' => $segment_id,
+            'video_duration' => $videoDuration
+        ];
+    }
+
+    /**
+     * 根据提示词内容智能计算最优视频时长
+     * 
+     * @param string $segmentContent 分镜内容
+     * @param string $tailFrame 尾帧描述
+     * @return int 视频时长(2-12秒)
+     */
+    private function calculateOptimalVideoDuration($segmentContent, $tailFrame = '') {
+        // 合并所有文本内容
+        $fullText = trim($segmentContent . ' ' . $tailFrame);
+        
+        // 如果没有内容,返回默认时长
+        if (empty($fullText)) {
+            return 5;
+        }
+        
+        // 计算文本长度(中文字符按2个字符计算)
+        $textLength = mb_strlen($fullText, 'UTF-8');
+        $chineseCharCount = preg_match_all('/[\x{4e00}-\x{9fff}]/u', $fullText);
+        $adjustedLength = $textLength + $chineseCharCount; // 中文字符权重更高
+        
+        // 分析内容复杂度
+        $complexityScore = $this->analyzeContentComplexity($fullText);
+        
+        // 基础时长计算(根据文本长度)
+        $baseDuration = 3; // 默认2秒
+        
+        // if ($adjustedLength <= 20) {
+        //     $baseDuration = 3;
+        // } elseif ($adjustedLength <= 40) {
+        //     $baseDuration = 4;
+        // } elseif ($adjustedLength <= 60) {
+        //     $baseDuration = 5;
+        // } elseif ($adjustedLength <= 80) {
+        //     $baseDuration = 6;
+        // } elseif ($adjustedLength <= 100) {
+        //     $baseDuration = 7;
+        // } elseif ($adjustedLength <= 120) {
+        //     $baseDuration = 8;
+        // } else {
+        //     $baseDuration = 9;
+        // }
+        
+        // 根据内容复杂度调整时长
+        $finalDuration = $baseDuration + $complexityScore;
+        
+        // 确保时长在2-12秒范围内
+        return max(2, min(12, $finalDuration));
+    }
+
+    /**
+     * 分析内容复杂度,返回时长调整值
+     * 
+     * @param string $text 文本内容
+     * @return int 调整值(-2到+3)
+     */
+    private function analyzeContentComplexity($text) {
+        $score = 0;
+        
+        // 动作关键词(需要更长时间展现)
+        $actionKeywords = [
+            '跑步', '奔跑', '飞行', '跳跃', '战斗', '打斗', '追逐', '逃跑', '舞蹈', '表演',
+            '变化', '变身', '爆炸', '崩塌', '建造', '制作', '烹饪', '绘画', '写字',
+            '移动', '旋转', '翻滚', '攀爬', '游泳', '潜水', '滑行', '飞翔'
+        ];
+        
+        // 静态关键词(可以用较短时间)
+        $staticKeywords = [
+            '站立', '坐着', '躺着', '静止', '凝视', '思考', '沉思', '观察', '等待',
+            '睡觉', '休息', '静坐', '冥想', '阅读', '听音乐'
+        ];
+        
+        // 复杂场景关键词(需要更长时间)
+        $complexSceneKeywords = [
+            '人群', '聚会', '会议', '战争', '庆典', '仪式', '表演', '比赛', '竞技',
+            '多人', '群体', '团队', '合作', '互动', '对话', '争论', '讨论'
+        ];
+        
+        // 情感关键词(需要适中时间表现)
+        $emotionKeywords = [
+            '哭泣', '笑容', '愤怒', '惊讶', '恐惧', '悲伤', '喜悦', '兴奋', '紧张',
+            '感动', '震惊', '困惑', '失望', '希望', '绝望', '温柔', '严肃'
+        ];
+        
+        // 检查动作关键词
+        foreach ($actionKeywords as $keyword) {
+            if (strpos($text, $keyword) !== false) {
+                $score += 1;
+                break; // 避免重复加分
+            }
+        }
+        
+        // 检查静态关键词
+        foreach ($staticKeywords as $keyword) {
+            if (strpos($text, $keyword) !== false) {
+                $score -= 1;
+                break;
+            }
+        }
+        
+        // 检查复杂场景关键词
+        foreach ($complexSceneKeywords as $keyword) {
+            if (strpos($text, $keyword) !== false) {
+                $score += 1;
+                break;
+            }
+        }
+        
+        // 检查情感关键词
+        foreach ($emotionKeywords as $keyword) {
+            if (strpos($text, $keyword) !== false) {
+                $score += 1;
+                break;
+            }
+        }
+        
+        // 检查时间相关词汇
+        if (preg_match('/缓慢|慢慢|渐渐|逐渐|慢动作/', $text)) {
+            $score += 1;
+        }
+        
+        if (preg_match('/快速|迅速|急速|瞬间|立刻|马上/', $text)) {
+            $score -= 1;
+        }
+        
+        // 检查转场词汇
+        if (preg_match('/然后|接着|随后|紧接着|同时|与此同时/', $text)) {
+            $score += 1;
+        }
+        
+        // 检查环境描述(复杂环境需要更多时间)
+        if (preg_match('/风景|景色|环境|背景|氛围|光线|阴影|色彩|细节/', $text)) {
+            $score += 1;
+        }
+        
+        // 检查对话内容(对话需要更多时间)
+        if (preg_match('/说|讲|告诉|回答|询问|问|对话|交谈/', $text)) {
+            $score += 1;
+        }
         
+        // 限制调整范围
+        return max(-1, min(4, $score));
     }
 }

+ 4 - 0
routes/api.php

@@ -127,6 +127,7 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
 
     // 动漫管理
     Route::group(['prefix' => 'anime'], function () {
+        Route::get('textModel', [AnimeController::class, 'textModel']);
         Route::post('addChat', [DeepSeekController::class, 'addChat']);
         Route::post('reGenerateAnime', [DeepSeekController::class, 'reGenerateAnime']);
         Route::get('chatList', [AnimeController::class, 'chatList']);
@@ -164,4 +165,7 @@ Route::get('sseLink', [DeepSeekController::class, 'sseLink']); // sseLink
 Route::get('testLink', function () {
     return 'Hello, World!';
 });
+
+// seedance回调
+Route::any('video/seedanceCallback', [VideoGenerationController::class, 'seedanceCallback']);
 Route::any('{slug}',[AccountController::class, 'index'])->where('slug', '(.*)?');