where('provider', 'openai')->where('is_active', true)->first(); if (! $provider) { $engine->failStep($step, 'No active OpenAI provider configured.'); return; } $script = $this->resolveScript($step); if (! $script) { $engine->failStep($step, 'No script available to storyboard — complete a script_generation step first.'); return; } try { $result = $this->aiProviders->generate($provider, $this->buildPrompt($step, $script), ['json_mode' => true]); } catch (AiGenerationException $e) { $engine->failStep($step, $e->getMessage()); return; } $decoded = json_decode($result->content, true); $scenes = $decoded['scenes'] ?? null; if (! is_array($scenes) || empty($scenes)) { $engine->failStep($step, 'AI provider returned an unusable storyboard: no scenes found.'); return; } $asset = $this->assets->store( $step->videoProject, 'other', json_encode(['scenes' => $scenes], JSON_PRETTY_PRINT), 'json', step: $step, mimeType: 'application/json', metadata: ['kind' => 'storyboard', 'scene_count' => count($scenes)], ); $engine->completeStep($step, [ 'asset_id' => $asset->id, 'scene_count' => count($scenes), ]); } private function resolveScript(WorkflowStep $step): ?string { $input = $step->input ?? []; if (! empty($input['script'])) { return $input['script']; } $scriptAsset = $step->videoProject->assets() ->where('type', 'script') ->orderByDesc('version') ->first(); return $scriptAsset ? $this->assets->contents($scriptAsset) : null; } private function buildPrompt(WorkflowStep $step, string $script): string { $input = $step->input ?? []; $prompt = "Break the following video script into a numbered scene-by-scene storyboard.\n\n" ."Script:\n{$script}\n\n" .'For each scene provide: scene_number (integer), description (what happens on screen), ' ."and visual_prompt (a concise, self-contained image-generation prompt describing this scene's visual)."; if (! empty($input['style'])) { $prompt .= " Every visual_prompt must consistently reflect this visual style: {$input['style']}."; } $prompt .= "\n\nRespond with JSON only, in exactly this shape: " .'{"scenes": [{"scene_number": 1, "description": "...", "visual_prompt": "..."}]}'; return $prompt; } }