$structured * @return array> */ public function forSingle(Workspace $workspace, SocialAccount $account, array $structured, ?ContentType $contentType, bool $applyBrandVisuals = true): array { ['width' => $width, 'height' => $height] = $this->dimensionsForContentType($contentType); $rendered = $this->generator->render( workspace: $workspace, socialAccount: $account, title: (string) data_get($structured, 'image_title', ''), body: (string) data_get($structured, 'image_body', ''), imageKeywords: data_get($structured, 'image_keywords', []), width: $width, height: $height, applyBrandVisuals: $applyBrandVisuals, ); if (! $rendered) { return []; } return [$this->buildAiMediaItem($workspace, $rendered)]; } /** * Render one AI image per slide in the structured carousel output. Slides * that render nothing are skipped. * * @param array $structured * @return array> */ public function forCarousel(Workspace $workspace, SocialAccount $account, array $structured, ?ContentType $contentType, bool $applyBrandVisuals = true): array { ['width' => $width, 'height' => $height] = $this->dimensionsForContentType($contentType); $media = []; foreach (data_get($structured, 'slides', []) as $slide) { $rendered = $this->generator->render( workspace: $workspace, socialAccount: $account, title: (string) data_get($slide, 'title', ''), body: (string) data_get($slide, 'body', ''), imageKeywords: data_get($slide, 'image_keywords', []), width: $width, height: $height, applyBrandVisuals: $applyBrandVisuals, ); if ($rendered) { $media[] = $this->buildAiMediaItem($workspace, $rendered); } } return $media; } /** * Render a tweet-card image for the given text and return a one-element * media-item array, or an empty array when the generator renders nothing. * * When $imageKeywords is non-null a blurred AI-photo background is used * (tweet_card_image); null produces the solid brand-color background (tweet_card). * * @param array|null $imageKeywords * @return array> */ public function forTweetCard(Workspace $workspace, SocialAccount $account, string $tweetText, ?array $imageKeywords = null): array { $rendered = $this->generator->renderTweetCard($workspace, $account, $tweetText, $imageKeywords); if (! $rendered) { return []; } return [$this->buildAiMediaItem($workspace, $rendered)]; } /** * Render one tweet-card image per slide and return the media-item array. * Slides that render nothing are skipped. * * Each entry in $slides is either a plain string (tweet text, solid background) * or an array with keys `tweet_text` and optionally `image_keywords` (image bg). * * @param array> $slides * @return array> */ public function forTweetCardCarousel(Workspace $workspace, SocialAccount $account, array $slides): array { $media = []; foreach ($slides as $slide) { if (is_string($slide)) { $tweetText = $slide; $imageKeywords = null; } else { $tweetText = (string) data_get($slide, 'tweet_text', ''); $imageKeywords = data_get($slide, 'image_keywords'); } $rendered = $this->generator->renderTweetCard($workspace, $account, $tweetText, $imageKeywords); if ($rendered) { $media[] = $this->buildAiMediaItem($workspace, $rendered); } } return $media; } /** * Resolve the AI image dimensions for the given content type, falling back * to the generator defaults (4:5 portrait) when no content type is known. * * @return array{width: int, height: int} */ private function dimensionsForContentType(?ContentType $contentType): array { return $contentType ? $contentType->aiImageDimensions() : ['width' => TemplateImageGenerator::DEFAULT_WIDTH, 'height' => TemplateImageGenerator::DEFAULT_HEIGHT]; } /** * @param array{path: string, source_meta: array} $rendered * @return array */ private function buildAiMediaItem(Workspace $workspace, array $rendered): array { $media = $workspace->media()->create([ 'collection' => 'ai-generated', 'type' => MediaType::Image, 'path' => $rendered['path'], 'original_filename' => basename($rendered['path']), 'mime_type' => 'image/webp', 'size' => Storage::size($rendered['path']), 'order' => 0, ]); return [ 'id' => $media->id, 'path' => $media->path, 'url' => $media->url, 'type' => 'image', 'mime_type' => 'image/webp', 'source' => Source::Ai->value, 'source_meta' => $rendered['source_meta'], ]; } }