f试ssefahah�能���马��计算机视觉事实上事实上飒飒飒gj����hfkttkgdgkmcuocucocucoc顶顶顶顶顶顶顶顶顶顶顶顶uco朵朵��放2�峨2555555666369🌹 �����ѩ�����һ����dddf2qevf2fv69666 ? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!database/migrations/2024_10_29_133239_is_demo_to_user_fall_table.php 0000644 00000001026 15266620554 0020640 0 ustar 00 bigInteger('is_demo')->default(0)->after('user_id'); }); } public function down(): void { Schema::table('user_fall', function (Blueprint $table) { $table->dropColumn('is_demo'); }); } }; database/migrations/2024_10_21_133239_create_user_fall_table.php 0000644 00000001731 15266620554 0017755 0 ustar 00 id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->string('prompt')->nullable(); $table->string('prompt_image_url')->nullable(); $table->string('status')->nullable(); $table->string('request_id')->nullable(); $table->string('response_url')->nullable(); $table->string('model')->nullable(); $table->string('video_url')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('user_fall'); } }; database/migrations/2024_10_29_133239_change_prompt_column_to_text_in_user_fall_table.php 0000644 00000001010 15266620554 0025147 0 ustar 00 text('prompt')->change(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('user_fall'); } }; extension.json 0000644 00000000213 15266620554 0007462 0 ustar 00 { "name": "AI Avatar Pro", "type": "extension", "version": "2.7.0", "description": "AI Avatar Pro extension for magicai" } System/Services/SoraService.php 0000644 00000005770 15266620554 0012575 0 ustar 00 'model', 'contents' => $param['model']], ['name' => 'prompt', 'contents' => $param['prompt']], ['name' => 'seconds', 'contents' => (string) $param['seconds']], ['name' => 'size', 'contents' => $param['size'] ?? '720x1280'], ]; if (isset($param['image_url']) && $param['image_url']) { $file = $param['image_url']; $mimeType = $file->getMimeType(); $multipart[] = [ 'name' => 'input_reference', 'contents' => fopen($file->getRealPath(), 'rb'), 'filename' => $file->getClientOriginalName(), 'headers' => [ 'Content-Type' => $mimeType, ], ]; } $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . ApiHelper::setOpenAIKey(), ])->asMultipart() ->post(self::SORA_ENDPOINT, $multipart); return $response->json(); } public static function getStatus(?string $id) { ini_set('max_execution_time', 440); set_time_limit(0); $response = Http::withHeaders([ 'Content-Type' => 'application/json', 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . ApiHelper::setOpenAiKey(), ])->get(self::SORA_ENDPOINT . '/' . $id); return $response->json(); } public static function getVideo(?string $id): string { ini_set('max_execution_time', 440); set_time_limit(0); $endpoint = self::SORA_ENDPOINT . '/' . $id . '/content'; $apiKey = ApiHelper::setOpenAiKey(); $userId = auth()->id(); // Example: sora_video_abc123.mp4 $fileName = "sora_video_{$id}.mp4"; $relativePath = "media/videos/u-{$userId}/{$fileName}"; $absolutePath = Storage::disk('uploads')->path($relativePath); // Ensure directory exists if (! is_dir(dirname($absolutePath)) && ! mkdir($concurrentDirectory = dirname($absolutePath), 0755, true) && ! is_dir($concurrentDirectory)) { throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); } // Stream MP4 directly to file (no buffering) Http::withHeaders([ 'Authorization' => 'Bearer ' . $apiKey, 'Accept' => 'application/octet-stream', ])->sink($absolutePath)->get($endpoint); // Return full public URL to video return Storage::disk('uploads')->url($relativePath); } } System/AiVideoProServiceProvider.php 0000644 00000005321 15266620554 0013612 0 ustar 00 registerTranslations() ->registerViews() ->registerRoutes() ->registerMigrations(); } protected function registerTranslations(): static { $this->loadTranslationsFrom(__DIR__ . '/../resources/lang', 'ai-video-pro'); return $this; } public function registerViews(): static { $this->loadViewsFrom([__DIR__ . '/../resources/views'], 'ai-video-pro'); return $this; } public function registerMigrations(): static { $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); return $this; } private function registerRoutes(): static { $this->router() ->group([ 'middleware' => ['web', 'auth'], ], function (Router $router) { $router ->prefix('dashboard') ->name('dashboard.') ->group(function (Router $router) { $router->prefix('user') ->name('user.') ->group(function (Router $router) { $router->resource('ai-video-pro', AiVideoProController::class)->except('destroy', 'show'); $router->get('ai-video-pro-delete/{id}', [AiVideoProController::class, 'delete'])->name('ai-video-pro.delete'); $router->get('ai-video-pro-check', [AiVideoProController::class, 'checkVideoStatus'])->name('ai-video-pro.check'); }); $router->controller(FalAISettingController::class) ->prefix('admin/settings') ->middleware(['auth', 'admin']) ->name('admin.settings.')->group(function (Router $router) { $router->get('fal-ai', 'index')->name('fal-ai'); $router->post('fal-ai', 'update')->name('fal-ai.update'); }); }); }); return $this; } private function router(): Router|Route { return $this->app['router']; } } System/Models/UserFall.php 0000644 00000001126 15266620554 0011514 0 ustar 00 where('user_id', auth()->user()->id)->get()->toArray(); $inProgress = collect($list)->filter(function ($entry) { return in_array($entry['status'], ['IN_QUEUE', 'queued']); })->pluck('id')->toArray(); return view('ai-video-pro::index', compact(['list', 'inProgress'])); } /** * Store a newly created resource in storage. */ public function store(Request $request): JsonResponse|RedirectResponse { if (Helper::appIsDemo()) { return back()->with(['message' => 'This feature is disabled in demo mode.', 'type' => 'error']); } if (! ApiHelper::setFalAIKey()) { return back()->with(['message' => 'Please set FAL AI key.', 'type' => 'error']); } $validated = $request->validate([ 'action' => 'required', 'prompt' => 'required', 'photo' => 'required_if:action,klingImage,klingV21', 'aspect_ratio' => 'required_if:action,veo3,veo3-fast', 'enhance_prompt' => 'sometimes', 'generate_audio' => 'sometimes', 'seed' => 'sometimes|nullable|integer', 'negative_prompt' => 'sometimes|nullable|string', ]); $action = $request->get('action'); $model = $action; $seconds = 0; if ($action === 'sora') { $model = EntityEnum::fromSlug($request->input('sora_model') ?? EntityEnum::SORA_2->value); $seconds = (int) $request->input('sora_seconds', 4); $driver = Entity::driver($model)->inputSecond($seconds)->calculateCredit(); } elseif ($action === 'veo3.1') { $driver = Entity::driver($this->detectVeo31EntityEnum($request->get('veo3_1_mode')))->inputVideoCount(1)->calculateCredit(); } elseif ($action === 'kling25Turbo') { $driver = Entity::driver($this->detectKling25EntityEnum($request->get('kling25_mode')))->inputVideoCount(1)->calculateCredit(); } else { $driver = Entity::driver(EntityEnum::fromSlug($action))->inputVideoCount(1)->calculateCredit(); } try { $driver->redirectIfNoCreditBalance(); } catch (Exception $e) { return redirect()->back()->with([ 'message' => $e->getMessage(), 'type' => 'error', ]); } $prompt = $request->get('prompt'); $userId = auth()->id(); switch ($action) { case 'sora': $image = $request->file('photo'); $param = [ 'prompt' => $prompt, 'model' => $model->value, 'seconds' => $seconds, 'size' => $request->input('sora_size') ?? '720x1280', 'image_url' => null, ]; if ($image && $image->isValid()) { $param['image_url'] = $image; } $response = SoraService::generate($param); if (isset($response['error'])) { return back()->with([ 'message' => $response['error']['message'] ?? __('Generation Failed'), 'type' => 'error', ]); } if (isset($response['status']) && $response['status'] === 'failed') { return back()->with(['message' => $response['message'] ?? __('Generation Failed'), 'type' => 'error']); } $this->createUserFall($userId, $prompt, $action, $response, $param['image_url']); $driver->decreaseCredit(); return back()->with(['message' => __('Created Successfully.'), 'type' => 'success']); case 'klingImage': case 'klingV21': case 'haiper': $image = $request->file('photo'); $name = Str::random(12) . '.' . $image?->guessExtension(); Storage::disk('public')->put($name, file_get_contents($image->getRealPath())); $url = Helper::parseUrl(config('app.url') . '/uploads/' . $name); $response = FalAIService::{$action . 'Generate'}($prompt, $url); if (isset($response['status']) && $response['status'] === 'error') { return back()->with(['message' => $response['message'], 'type' => 'error']); } $this->createUserFall($userId, $prompt, $action, $response, $url); $driver->decreaseCredit(); return back()->with(['message' => 'Created Successfully.', 'type' => 'success']); case 'luma-dream-machine': case 'kling': case 'minimax': $response = FalAIService::minimaxGenerate($prompt); $this->createUserFall($userId, $prompt, $action, $response); $driver->decreaseCredit(); return back()->with(['message' => 'Created Successfully.', 'type' => 'success']); case 'veo2': $response = FalAIService::veo2Generate($prompt); if ($response->failed()) { return back()->with([ 'message' => $response->status() . ' ' . $response->reason() . ': ' . $response->json('detail', __('Unknown error occurred')), 'type' => 'error', ]); } $jsonRes = $response->json(); if (isset($jsonRes['status']) && $jsonRes['status'] === 'error') { return back()->with(['message' => $jsonRes['message'], 'type' => 'error']); } $this->createUserFall($userId, $prompt, $action, $jsonRes); $driver->decreaseCredit(); return back()->with(['message' => 'Created Successfully.', 'type' => 'success']); case 'veo3': case 'veo3-fast': $validated = array_filter($validated, static function ($value) { return ! is_null($value) && $value !== ''; }); if (isset($validated['generate_audio'])) { $validated['generate_audio'] = $validated['generate_audio'] == 'on' ? true : false; } if (isset($validated['enhance_prompt'])) { $validated['enhance_prompt'] = $validated['enhance_prompt'] == 'on' ? true : false; } unset($validated['action']); $service = new PackageFalAIService(ApiHelper::setFalAIKey()); $response = $service->textToVideoModel($action == 'veo3' ? EntityEnum::VEO_3 : EntityEnum::VEO_3_FAST)->submit($validated); $resData = $response->getData(); if (isset($resData->status) && $resData->status === 'error') { return back()->with(['message' => $resData->message ?? 'Unexpected issue happen', 'type' => 'error']); } $this->createUserFall($userId, $prompt, $action, (array) $resData->resData); $driver->decreaseCredit(); return back()->with(['message' => 'Created Successfully.', 'type' => 'success']); case 'veo3.1': $mode = $request->input('veo3_1_mode'); // Convert checkbox values to boolean BEFORE validation if ($request->has('generate_audio')) { $request->merge([ 'generate_audio' => $request->input('generate_audio') === 'on' ? true : false, ]); } if ($request->has('enhance_prompt')) { $request->merge([ 'enhance_prompt' => $request->input('enhance_prompt') === 'on' ? true : false, ]); } if ($request->has('auto_fix')) { $request->merge([ 'auto_fix' => $request->input('auto_fix') === 'on' ? true : false, ]); } // Base validation rules $validationRules = [ 'veo3_1_mode' => 'required|in:text-to-video,text-to-video-fast,first-last-frame-to-video,first-last-frame-to-video-fast,image-to-video,image-to-video-fast,reference-to-video', 'generate_audio' => 'sometimes|boolean', ]; // Duration validation - depends on mode if (in_array($mode, ['text-to-video', 'text-to-video-fast'])) { $validationRules['duration'] = 'required|in:4s,6s,8s'; } else { $validationRules['duration'] = 'required|in:8s'; } // Resolution validation - all modes $validationRules['resolution'] = 'required|in:720p,1080p'; // Aspect ratio validation - depends on mode if (in_array($mode, ['text-to-video', 'text-to-video-fast'])) { $validationRules['aspect_ratio'] = 'required|in:16:9,9:16,1:1'; } elseif (in_array($mode, ['first-last-frame-to-video', 'first-last-frame-to-video-fast'])) { $validationRules['aspect_ratio'] = 'required|in:auto,16:9,9:16,1:1'; } elseif (in_array($mode, ['image-to-video', 'image-to-video-fast'])) { $validationRules['aspect_ratio'] = 'required|in:16:9,9:16'; } // reference-to-video mode doesn't have aspect_ratio // Advanced options - only for text-to-video modes if (in_array($mode, ['text-to-video', 'text-to-video-fast'])) { $validationRules['enhance_prompt'] = 'sometimes|boolean'; $validationRules['auto_fix'] = 'sometimes|boolean'; $validationRules['seed'] = 'sometimes|nullable|integer'; $validationRules['negative_prompt'] = 'sometimes|nullable|string|max:1000'; } // File validation for different modes if (in_array($mode, ['image-to-video', 'image-to-video-fast'])) { $validationRules['image_url'] = 'required|file|mimes:jpg,jpeg,png|max:10240'; } if (in_array($mode, ['first-last-frame-to-video', 'first-last-frame-to-video-fast'])) { $validationRules['first_frame_image_url'] = 'required|file|mimes:jpg,jpeg,png|max:10240'; $validationRules['last_frame_image_url'] = 'required|file|mimes:jpg,jpeg,png|max:10240'; } if ($mode === 'reference-to-video') { // Manually check and limit the array before validation if ($request->hasFile('image_urls')) { $files = $request->file('image_urls'); // Remove duplicates based on file content hash $uniqueFiles = []; $seenHashes = []; foreach ($files as $file) { $hash = md5_file($file->getRealPath()); if (! in_array($hash, $seenHashes)) { $seenHashes[] = $hash; $uniqueFiles[] = $file; } } // Take only first 3 files if more are submitted $limitedFiles = array_slice($uniqueFiles, 0, 3); $request->files->set('image_urls', $limitedFiles); } $validationRules['image_urls'] = 'required|array|min:1|max:3'; $validationRules['image_urls.*'] = 'file|mimes:jpg,jpeg,png|max:10240'; } $veo31Validation = $request->validate($validationRules); // Build payload $payload = [ 'prompt' => $prompt, 'mode' => $mode, 'duration' => $veo31Validation['duration'] ?? '8s', 'resolution' => $veo31Validation['resolution'] ?? '720p', ]; // Add aspect_ratio only if it exists for this mode if (isset($veo31Validation['aspect_ratio'])) { $payload['aspect_ratio'] = $veo31Validation['aspect_ratio']; } // Generate audio (default true if not specified) $payload['generate_audio'] = $veo31Validation['generate_audio'] ?? true; // Advanced options - only for text-to-video modes if (in_array($mode, ['text-to-video', 'text-to-video-fast'])) { if (isset($veo31Validation['enhance_prompt'])) { $payload['enhance_prompt'] = $veo31Validation['enhance_prompt']; } if (isset($veo31Validation['auto_fix'])) { $payload['auto_fix'] = $veo31Validation['auto_fix']; } if (isset($veo31Validation['seed']) && $veo31Validation['seed'] !== null) { $payload['seed'] = (int) $veo31Validation['seed']; } if (isset($veo31Validation['negative_prompt']) && $veo31Validation['negative_prompt'] !== null) { $payload['negative_prompt'] = $veo31Validation['negative_prompt']; } } try { switch ($mode) { case 'image-to-video': case 'image-to-video-fast': // Single image upload if ($request->hasFile('image_url')) { $image = $request->file('image_url'); $imageName = Str::random(12) . '.' . $image->guessExtension(); Storage::disk('public')->put($imageName, file_get_contents($image->getRealPath())); $payload['image_url'] = Helper::parseUrl(config('app.url') . '/uploads/' . $imageName); } break; case 'first-last-frame-to-video': case 'first-last-frame-to-video-fast': // First and last frame uploads if ($request->hasFile('first_frame_image_url')) { $firstFrame = $request->file('first_frame_image_url'); $firstName = Str::random(12) . '.' . $firstFrame->guessExtension(); Storage::disk('public')->put($firstName, file_get_contents($firstFrame->getRealPath())); $payload['first_frame_image_url'] = Helper::parseUrl(config('app.url') . '/uploads/' . $firstName); } if ($request->hasFile('last_frame_image_url')) { $lastFrame = $request->file('last_frame_image_url'); $lastName = Str::random(12) . '.' . $lastFrame->guessExtension(); Storage::disk('public')->put($lastName, file_get_contents($lastFrame->getRealPath())); $payload['last_frame_image_url'] = Helper::parseUrl(config('app.url') . '/uploads/' . $lastName); } break; case 'reference-to-video': // Multiple reference images (1-3) if ($request->hasFile('image_urls')) { $referenceImages = []; $files = $request->file('image_urls'); foreach ($files as $refImage) { $refName = Str::random(12) . '.' . $refImage->guessExtension(); Storage::disk('public')->put($refName, file_get_contents($refImage->getRealPath())); $referenceImages[] = Helper::parseUrl(config('app.url') . '/uploads/' . $refName); } $payload['image_urls'] = $referenceImages; } break; } } catch (Exception $e) { return back()->with([ 'message' => 'File upload error: ' . $e->getMessage(), 'type' => 'error', ]); } // Remove null values and empty arrays $payload = array_filter($payload, static function ($value) { return ! is_null($value) && $value !== '' && (! is_array($value) || ! empty($value)); }); // Submit to VEO 3.1 $service = new PackageFalAIService(ApiHelper::setFalAIKey()); $response = $service->textToVideoModel($this->detectVeo31EntityEnum($mode))->submit($payload); $resData = $response->getData(); if (isset($resData->status) && $resData->status === 'error') { return back()->with([ 'message' => $resData->message ?? 'VEO 3.1 generation failed', 'type' => 'error', ]); } $this->createUserFall($userId, $prompt, ($action . '/' . $mode), (array) $resData->resData); $driver->decreaseCredit(); return back()->with([ 'message' => 'VEO 3.1 video generation started successfully.', 'type' => 'success', ]); break; case 'kling25Turbo': $kling25Validation = $request->validate([ 'kling25_mode' => 'required|in:text-to-video-pro,image-to-video,image-to-video-pro', 'kling25turbo_duration' => 'required|in:5,10', 'kling25turbo_aspect_ratio' => 'required|in:16:9,9:16,1:1', 'camera_movement' => 'sometimes|nullable|in:horizontal,vertical,pan,tilt,zoom,roll', 'seed' => 'sometimes|nullable|integer|min:0|max:2147483647', 'negative_prompt' => 'sometimes|nullable|string', 'cfg_scale' => 'sometimes|nullable|numeric|min:0|max:1', 'loop' => 'sometimes', 'image_url' => 'required_if:kling25_mode,image-to-video,image-to-video-pro|file|mimes:jpg,jpeg,png', ]); $mode = $kling25Validation['kling25_mode']; // Build payload $payload = [ 'prompt' => $prompt, 'duration' => $kling25Validation['kling25turbo_duration'] ?? '5', 'aspect_ratio' => $kling25Validation['kling25turbo_aspect_ratio'] ?? '16:9', ]; // Handle image upload for image-to-video modes if (in_array($mode, ['image-to-video', 'image-to-video-pro']) && $request->hasFile('image_url')) { try { $image = $request->file('image_url'); $imageName = Str::random(12) . '.' . $image->guessExtension(); Storage::disk('public')->put($imageName, file_get_contents($image->getRealPath())); $payload['image_url'] = Helper::parseUrl(config('app.url') . '/uploads/' . $imageName); } catch (Exception $e) { return back()->with([ 'message' => 'Image upload error: ' . $e->getMessage(), 'type' => 'error', ]); } } // Add optional parameters for text-to-video modes if ($mode === 'text-to-video-pro') { if (! empty($kling25Validation['camera_movement'])) { $payload['camera_movement'] = [ 'type' => $kling25Validation['camera_movement'], ]; } if (isset($kling25Validation['cfg_scale'])) { $payload['cfg_scale'] = (float) $kling25Validation['cfg_scale']; } } // Add seed if provided if (isset($kling25Validation['seed'])) { $payload['seed'] = (int) $kling25Validation['seed']; } // Add negative prompt if provided if (! empty($kling25Validation['negative_prompt'])) { $payload['negative_prompt'] = $kling25Validation['negative_prompt']; } // Add loop parameter for Pro modes only if (isset($kling25Validation['loop']) && str_contains($mode, 'pro')) { $payload['loop'] = $kling25Validation['loop'] === 'on'; } // Remove null/empty values $payload = array_filter($payload, static function ($value) { return ! is_null($value) && $value !== '' && (! is_array($value) || ! empty($value)); }); // Submit to Kling 2.5 Turbo API $service = new PackageFalAIService(ApiHelper::setFalAIKey()); $response = $service->textToVideoModel($this->detectKling25EntityEnum($mode))->submit($payload); $resData = $response->getData(); if (isset($resData->status) && $resData->status === 'error') { return back()->with([ 'message' => $resData->message ?? __('Kling 2.5 Turbo generation failed'), 'type' => 'error', ]); } $this->createUserFall($userId, $prompt, ($action . '/' . $mode), (array) $resData->resData, $payload['image_url'] ?? null); $driver->decreaseCredit(); return back()->with([ 'message' => __('Kling 2.5 Turbo video generation started successfully.'), 'type' => 'success', ]); default: return back()->with(['message' => __('Api key Error.'), 'type' => 'error']); } } private function createUserFall($userId, $prompt, $action, $response, $imageUrl = null): void { UserFall::create([ 'user_id' => $userId, 'prompt' => $prompt, 'prompt_image_url' => $imageUrl, 'status' => $response['status'] ?? 'IN_QUEUE', 'request_id' => $response['request_id'] ?? $response['id'] ?? null, 'response_url' => $response['response_url'] ?? null, 'model' => $action, ]); } public function delete(string $id): RedirectResponse { if (Helper::appIsDemo()) { return back()->with(['message' => 'This feature is disabled in demo mode.', 'type' => 'error']); } $model = UserFall::query()->findOrFail($id); $model->delete(); return back()->with(['message' => 'Deleted Successfully.', 'type' => 'success']); } public function checkVideoStatus(Request $request): JsonResponse { $userId = auth()->id(); $ids = (array) $request->get('ids', []); $entries = UserFall::where('user_id', $userId) ->where('status', '!=', 'complete') ->whereIn('id', $ids) ->get(); if ($entries->isEmpty()) { return response()->json(['data' => []]); } // Delete error entries in one go $errorIds = $entries->where('status', 'error')->pluck('id'); if ($errorIds->isNotEmpty()) { UserFall::whereIn('id', $errorIds)->delete(); $entries = $entries->reject(fn ($e) => $errorIds->contains($e->id)); } $data = collect(); foreach ($entries as $entry) { $result = match (true) { $entry->model === 'sora' => $this->handleSoraEntry($entry), str_starts_with($entry->model ?? '', 'veo3.1/') => $this->handleVeoEntry($entry), str_starts_with($entry->model ?? '', 'kling25Turbo/') => $this->handleKling25Entry($entry), default => $this->handleFalEntry($entry), }; if ($result) { $data->push($result); } } return response()->json(['data' => $data]); } private function handleSoraEntry($entry): ?array { $response = SoraService::getStatus($entry->request_id); $status = $response['status'] ?? null; if ($status === 'completed') { $vidUrl = SoraService::getVideo($entry->request_id); if ($vidUrl) { $entry->update(['status' => 'complete', 'video_url' => $vidUrl]); return $this->renderVideoItem($entry, $vidUrl); } $entry->delete(); } if ($status === 'failed') { $entry->delete(); } return null; } private function handleVeoEntry($entry): ?array { // Extract mode from model string (e.g., "veo3.1/text-to-video-fast" -> "text-to-video-fast") $mode = str_replace('veo3.1/', '', $entry->model); $entity = $this->detectVeo31EntityEnum($mode); $service = new PackageFalAIService(ApiHelper::setFalAIKey()); $check = $service->textToVideoModel($entity)->checkStatus($entry->request_id)->getData(); $status = $check->resData->status ?? null; if ($status === 'NOT_FOUND') { $entry->delete(); return null; } if ($status !== 'COMPLETED') { return null; } $result = $service->textToVideoModel($entity)->getResult($entry->request_id)->getData(); if (($result->status ?? null) === 'success') { $videoUrl = $result->resData->video->url ?? null; if ($videoUrl) { $entry->update(['status' => 'complete', 'video_url' => $videoUrl]); return $this->renderVideoItem($entry, $videoUrl); } } if (in_array(($result->status ?? null), ['failed', 'error'])) { $entry->delete(); } return null; } private function handleKling25Entry($entry): ?array { // Extract mode from model string (e.g., "kling25Turbo/text-to-video" -> "text-to-video") $mode = str_replace('kling25Turbo/', '', $entry->model); $entity = $this->detectKling25EntityEnum($mode); $service = new PackageFalAIService(ApiHelper::setFalAIKey()); $check = $service->textToVideoModel($entity)->checkStatus($entry->request_id)->getData(); $status = $check->resData->status ?? null; if ($status !== 'COMPLETED') { return null; } $result = $service->textToVideoModel($entity)->getResult($entry->request_id)->getData(); if (($result->status ?? null) === 'success') { $videoUrl = $result->resData->video->url ?? null; if ($videoUrl) { $entry->update(['status' => 'complete', 'video_url' => $videoUrl]); return $this->renderVideoItem($entry, $videoUrl); } } if (in_array(($result->status ?? null), ['failed', 'error'])) { $entry->delete(); } return null; } private function handleFalEntry($entry): ?array { $response = FalAIService::getStatus($entry->response_url); if (! empty($response['video']['url'])) { $url = $response['video']['url']; $entry->update(['status' => 'complete', 'video_url' => $url]); return $this->renderVideoItem($entry, $url); } $detail = $response['detail'] ?? null; // Handle failed or invalid responses if ( in_array($detail, [ 'Internal Server Error', 'Luma API timed out', "Luma API failed: generation.state='failed' generation.failure_reason='400: prompt not allowed because advanced moderation failed'", ]) || (isset($detail[0]['type']) && $detail[0]['type'] === 'image_load_error') ) { $entry->delete(); } return null; } private function renderVideoItem($entry, string $url): array { $entry->video_url = $url; $entry->status = 'complete'; return [ 'divId' => "video-{$entry->id}", 'html' => view('ai-video-pro::video-item', ['entry' => $entry])->render(), ]; } private function detectKling25EntityEnum(?string $mode): EntityEnum { return match ($mode) { 'image-to-video' => EntityEnum::KLING_2_5_TURBO_STANDARD_ITV, 'image-to-video-pro' => EntityEnum::KLING_2_5_TURBO_PRO_ITV, default => EntityEnum::KLING_2_5_TURBO_PRO_TTV, // text-to-video as default }; } private function detectVeo31EntityEnum(?string $mode): EntityEnum { return match ($mode) { 'image-to-video' => EntityEnum::VEO_3_1_IMAGE_TO_VIDEO, 'image-to-video-fast' => EntityEnum::VEO_3_1_IMAGE_TO_VIDEO_FAST, 'first-last-frame-to-video' => EntityEnum::VEO_3_1_FIRST_LAST_FRAME_TO_VIDEO, 'first-last-frame-to-video-fast' => EntityEnum::VEO_3_1_FIRST_LAST_FRAME_TO_VIDEO_FAST, 'reference-to-video' => EntityEnum::VEO_3_1_REFERENCE_TO_VIDEO, 'text-to-video-fast' => EntityEnum::VEO_3_1_TEXT_TO_VIDEO_FAST, default => EntityEnum::VEO_3_1_TEXT_TO_VIDEO, }; } } System/Http/Controllers/FalAISettingController.php 0000644 00000002062 15266620554 0016317 0 ustar 00 with([ 'status' => 'error', 'message' => trans('This feature is disabled in demo mode.'), ]); } return view('ai-video-pro::setting'); } public function update(Request $request): RedirectResponse { $data = $request->validate([ 'fal_ai_api_secret' => 'required|string', 'fal_ai_default_model' => 'required|string', ]); setting($data)->save(); return back()->with([ 'type' => 'success', 'message' => trans('Settings updated successfully.'), ]); } } System/Http/Resources/.gitkeep 0000644 00000000016 15266620554 0012362 0 ustar 00 * !.gitignore resources/views/setting.blade.php 0000644 00000007231 15266620554 0013165 0 ustar 00 @php use App\Domains\Entity\Enums\EntityEnum; @endphp @extends('panel.layout.settings', ['layout' => 'wide']) @section('title', __('FalAI Settings')) @section('titlebar_actions', '') @section('additional_css') @endsection @section('settings')
@endsection @push('script') @endpush resources/views/index.blade.php 0000644 00000102744 15266620554 0012624 0 ustar 00 @php if (setting('sora_active', 1) == 1) { $actions = ['sora' => __('Generate video with Sora')]; } else { $actions = []; } $actions += [ 'veo3.1' => __('Generate video with Google VEO 3.1'), 'veo3' => __('Generate video with Google VEO 3'), 'veo3-fast' => __('Generate video with FAST Google VEO 3'), 'veo2' => __('Generate video with Google VEO 2'), 'luma-dream-machine' => __('Generate video with Luma'), 'kling25Turbo' => __('Generate video with Kling 2.5 Turbo'), 'kling' => __('Generate video with Kling'), 'klingImage' => __('Generate video with Kling from image'), 'klingV21' => __('Generate video with Kling V2.1'), 'minimax' => __('Generate video with Minimax'), ]; @endphp @extends('panel.layout.app') @section('title', __('AI Video Pro')) @section('titlebar_subtitle', __('You can create amazing videos with AI Video Pro')) @section('titlebar_actions', __('')) @section('content')
@lang('Created') {{ \Carbon\Carbon::parse($entry['created_at'])->diffForHumans() }}