trypost/tests/Unit/Services/Image/TemplateImageGeneratorTest.php

141 lines
5.3 KiB
PHP
Raw Permalink Normal View History

<?php
declare(strict_types=1);
use App\Models\SocialAccount;
use App\Models\Workspace;
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
use App\Services\Ai\AiImageClient;
use App\Services\Image\BrandColorMapper;
use App\Services\Image\TemplateImageGenerator;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
use Laravel\Ai\Image;
$minimalPng = fn () => base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
beforeEach(function () {
Storage::fake();
Cache::flush();
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
Image::fake();
});
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
test('returns null when AI client cannot generate (no keywords)', function () {
$service = new TemplateImageGenerator(new BrandColorMapper, new AiImageClient);
$result = $service->render(
workspace: Workspace::factory()->make(),
socialAccount: SocialAccount::factory()->make(['username' => 'testuser', 'display_name' => 'Test User']),
title: 'Hello',
body: 'World',
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
imageKeywords: [],
);
expect($result)->toBeNull();
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
Image::assertNothingGenerated();
});
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
test('returns null when AI generation throws', function () {
Image::fake(fn () => throw new RuntimeException('upstream outage'));
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
$service = new TemplateImageGenerator(new BrandColorMapper, new AiImageClient);
$result = $service->render(
workspace: Workspace::factory()->make(),
socialAccount: SocialAccount::factory()->make(),
title: 'Hello',
body: 'World',
imageKeywords: ['kitchen'],
);
expect($result)->toBeNull();
});
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
test('renders a slide and stores webp when AI returns bytes', function () use ($minimalPng) {
Image::fake([base64_encode($minimalPng())]);
if (! file_exists(base_path('resources/fonts/Inter-Bold.ttf'))) {
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
$this->markTestSkipped('Inter fonts not available — skipping render-dependent test.');
}
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
$service = new TemplateImageGenerator(new BrandColorMapper, new AiImageClient);
$result = $service->render(
workspace: Workspace::factory()->make([
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
'image_style' => 'illustration',
'brand_color' => '#0000ff',
'background_color' => '#ffffff',
'text_color' => '#000000',
]),
socialAccount: SocialAccount::factory()->make([
'username' => 'testuser',
'display_name' => 'Test User',
]),
title: 'Hello World',
body: 'This is a test slide body.',
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
imageKeywords: ['kitchen', 'morning'],
);
if ($result !== null) {
expect($result['path'])->toStartWith('ai-images/')->toEndWith('.webp');
expect($result['source_meta'])
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
->toHaveKey('keywords')
->toHaveKey('style', 'illustration')
->toHaveKey('model', 'gpt-image-2')
->toHaveKey('title', 'Hello World')
->toHaveKey('brand_color', '#0000ff')
->toHaveKey('background_color', '#ffffff')
->toHaveKey('text_color', '#000000')
->toHaveKey('background_path');
}
Image::assertGenerated(fn ($prompt) => $prompt->contains('kitchen')
&& $prompt->contains('BRAND COLOR PALETTE')
&& $prompt->contains('blue'));
feat: AI image generation pipeline and post creation overhaul Core changes: - Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK. New AiImageClient builds prompts from a Blade template seeded by the workspace's ImageStyle enum, content language, brand color (mapped to a human-readable name via HexColorName helper) and brand description. - Drop Template B from TemplateImageGenerator: every slide now renders as Template A (full-bleed photo + bottom gradient + white/grey overlay). Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the closing-slide pipeline. - StreamPostCreation creates the Post directly and dispatches PostCreationReady with post_id; the wizard kills its preview step and redirects straight to the post editor on completion. Finalize endpoint removed. - New Workspace.image_style enum field with an 8-option visual picker shared by /workspaces/create and /settings/workspace/brand via a single BrandForm component (autofill is a prop). 8 sample webp thumbs ship under public/images/branding/image-styles/. - Media items gain optional source ('ai'|'unsplash'|'giphy') and source_meta (recipe needed to regenerate AI images later); the gallery picker tags Unsplash/Giphy attachments. - Brand-color autofill: new CssColorFrequencyExtractor parses every hex/rgb/hsl value in the homepage CSS, clusters perceptually similar shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the most frequent cluster. Solves Tailwind/utility-CSS sites where no semantic --primary variable is exposed. - Credits: gpt-image-2 metered at 15 credits/image (low quality default). - Layout: AuthSplitLayout right column is sticky/h-svh so the form textarea growth no longer stretches the marketing slider. - i18n cleanup: localized labels follow the no-em-dash convention.
2026-05-08 16:38:30 +00:00
})->skip(fn () => ! extension_loaded('gd'), 'GD extension required');
test('omits the brand colour palette from the AI image prompt when brand visuals are off', function () use ($minimalPng) {
Image::fake([base64_encode($minimalPng())]);
if (! file_exists(base_path('resources/fonts/Inter-Bold.ttf'))) {
$this->markTestSkipped('Inter fonts not available — skipping render-dependent test.');
}
$service = new TemplateImageGenerator(new BrandColorMapper, new AiImageClient);
$service->render(
workspace: Workspace::factory()->make([
'brand_color' => '#0000ff',
'background_color' => '#ffffff',
'text_color' => '#000000',
]),
socialAccount: SocialAccount::factory()->make(['username' => 'u', 'display_name' => 'U']),
title: 'Hello',
body: 'Body',
imageKeywords: ['kitchen'],
applyBrandVisuals: false,
);
// Despite the workspace having brand colours, the prompt must stay neutral.
Image::assertGenerated(fn ($prompt) => $prompt->contains('kitchen')
&& ! $prompt->contains('BRAND COLOR PALETTE'));
})->skip(fn () => ! extension_loaded('gd'), 'GD extension required');
test('reuses existing background path when provided', function () use ($minimalPng) {
if (! file_exists(base_path('resources/fonts/Inter-Bold.ttf'))) {
$this->markTestSkipped('Inter fonts not available — skipping render-dependent test.');
}
$backgroundPath = 'ai-images/reuse-bg.webp';
Storage::put($backgroundPath, $minimalPng());
$service = new TemplateImageGenerator(new BrandColorMapper, new AiImageClient);
$result = $service->render(
workspace: Workspace::factory()->make(),
socialAccount: SocialAccount::factory()->make(['username' => 'testuser', 'display_name' => 'Test User']),
title: 'Updated title',
body: 'Updated body',
imageKeywords: [],
backgroundPath: $backgroundPath,
);
expect($result)->not->toBeNull();
expect(data_get($result, 'source_meta.background_path'))->toBe($backgroundPath);
Image::assertNothingGenerated();
})->skip(fn () => ! extension_loaded('gd'), 'GD extension required');