error('Missing OpenAI API key. Set OPENAI_API_KEY in your environment.'); return self::FAILURE; } $categoryLabels = [ 'computers-laptop' => 'laptop computer', 'computers-server' => 'rack server hardware', 'components-motherboard' => 'computer motherboard', 'components-graphicscard' => 'graphics card', 'components-memory' => 'RAM memory sticks', 'components-harddrives-ssd' => 'NVMe SSD drive', 'components-harddrives-mechanical' => 'mechanical hard disk drive', 'accessories-keyboard' => 'computer keyboard', 'accessories-mouse' => 'computer mouse', 'accessories-streaming' => 'streaming equipment', 'accessories-bags' => 'laptop backpack', 'monitor-gaming-monitors' => 'gaming monitor', 'monitor-mounting' => 'monitor arm mount', 'monitor-accessories' => 'monitor desk accessory', 'components-soundcard' => 'USB audio interface', 'components-tools' => 'PC repair tools', ]; $products = Product::query() ->with('category:id,slug') ->get(); $updated = 0; $skipped = 0; $failed = 0; foreach ($products as $product) { $categorySlug = $product->category?->slug; if (! is_string($categorySlug) || ! isset($categoryLabels[$categorySlug])) { $failed++; $this->warn("No category image mapping for product #{$product->id} ({$product->sku})"); continue; } $filename = Str::slug($product->sku).'.jpg'; $path = $directory.'/'.$filename; if (! $this->option('force') && is_string($product->thumbnail_path) && $product->thumbnail_path !== '' && $disk->exists($product->thumbnail_path)) { $skipped++; continue; } $prompt = sprintf( 'Photorealistic product photo of %s for an ecommerce thumbnail. Product: %s. Clean studio lighting, centered subject, neutral background, no text, no logos, no watermark.', $categoryLabels[$categorySlug], $product->name, ); /** @var \Illuminate\Http\Client\Response $response */ $response = Http::timeout(120) ->withToken($openAiApiKey) ->post('https://api.openai.com/v1/images/generations', [ 'model' => 'gpt-image-1', 'prompt' => $prompt, 'size' => '1024x1024', ]); if (! $response->successful()) { $failed++; $error = $response->json('error.message'); $status = $response->status(); $message = is_string($error) && $error !== '' ? $error : $response->body(); $this->error("Image generation failed for {$product->sku} ({$categorySlug}) [HTTP {$status}]: {$message}"); continue; } $payload = $response->json(); $encodedImage = is_array($payload) ? data_get($payload, 'data.0.b64_json') : null; if (! is_string($encodedImage) || $encodedImage === '') { $failed++; $this->error("Image payload missing for {$product->sku}"); continue; } $binaryImage = base64_decode($encodedImage, true); if (! is_string($binaryImage) || $binaryImage === '') { $failed++; $this->error("Image decode failed for {$product->sku}"); continue; } $disk->put($path, $binaryImage); $product->thumbnail_path = $path; $product->save(); $updated++; } $this->info("Completed: updated={$updated}, skipped={$skipped}, failed={$failed}"); return self::SUCCESS; })->purpose('Generate category-based product thumbnails into image storage');