Source path: app/Jobs/GenerateImage.php
Extracted from supplied specification. Review before copying into application.

<?php
namespace App\Jobs;

use App\Models\Media;
use App\Services\Ai\AiManager;
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 Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class GenerateImage implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 1;
    public int $timeout = 400;

    public function __construct(public Media $media, public string $size = '1024x1024') {}

    public function handle(AiManager $ai): void
    {
        $this->media->update(['status' => 'processing']);
        $errors = [];

        foreach ($ai->chain('image', $this->media->organization) as [$driver, $cfg, $name]) {
            try {
                $bytes = $driver->generate($this->media->prompt, $this->size, $cfg);
                if (strlen($bytes) < 1000) throw new \RuntimeException('Empty image.');

                $ext = str_starts_with($bytes, "\xFF\xD8") ? 'jpg' : 'png';
                $path = "media/{$this->media->organization_id}/" . Str::uuid() . ".$ext";
                Storage::disk(config('filesystems.default'))->put($path, $bytes);

                $this->media->update(['path' => $path, 'status' => 'ready', 'provider' => $name, 'model' => $cfg['model'] ?? null]);
                $this->log($name, $cfg['model'] ?? null, 'ok');
                return;
            } catch (\Throwable $e) {
                $errors[] = "$name: " . substr($e->getMessage(), 0, 200);
                $this->log($name, $cfg['model'] ?? null, 'failed');
            }
        }

        $this->media->update(['status' => 'failed', 'error' => implode(' | ', $errors) ?: 'No provider configured.']);
        $this->media->organization->refund(config('ai.cost.image'));
    }

    private function log(string $provider, ?string $model, string $status): void
    {
        DB::table('ai_usage')->insert([
            'organization_id' => $this->media->organization_id, 'kind' => 'image',
            'provider' => $provider, 'model' => $model, 'status' => $status,
            'credits' => $status === 'ok' ? config('ai.cost.image') : 0, 'created_at' => now(),
        ]);
    }
}
