Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/Http/Controllers/App/PostAiCreateController.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public function start(StartPostCreationRequest $request): JsonResponse
prompt: $request->string('prompt')->toString(),
date: $request->input('date'),
template: $request->input('template', 'image_card'),
applyBrandVisuals: $request->boolean('apply_brand_visuals', true),
);

return response()->json([
Expand Down
1 change: 1 addition & 0 deletions app/Http/Requests/App/Ai/StartPostCreationRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function rules(): array
'prompt' => ['required', 'string', 'max:2000'],
'date' => ['nullable', 'date_format:Y-m-d'],
'template' => ['sometimes', 'string', Rule::enum(ContentStyle::class)],
'apply_brand_visuals' => ['sometimes', 'boolean'],
];
}

Expand Down
2 changes: 2 additions & 0 deletions app/Jobs/Ai/StreamPostCreation.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public function __construct(
public string $prompt,
public ?string $date = null,
public string $template = 'image_card',
public bool $applyBrandVisuals = true,
) {
$this->onQueue('ai');
}
Expand All @@ -64,6 +65,7 @@ public function handle(): void
format: $this->format,
imageCount: $this->imageCount,
isCarousel: $isCarousel,
applyBrandVisuals: $this->applyBrandVisuals,
);

$agent = new PostContentGenerator(
Expand Down
3 changes: 3 additions & 0 deletions lang/en/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,9 @@
'media_optional_label' => 'How many images?',
'media_none' => 'None',
'media_count_label' => 'Number of images',
'brand_colors_label' => 'Image colors',
'brand_colors_on' => 'Brand colors',
'brand_colors_off' => 'Let AI decide',
'prompt_title' => 'Describe your post',
'prompt_label' => 'What is this post about?',
'prompt_placeholder' => 'e.g. Announce our new carousel feature for Instagram',
Expand Down
3 changes: 3 additions & 0 deletions lang/es/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,9 @@
'media_optional_label' => '¿Cuántas imágenes?',
'media_none' => 'Ninguna',
'media_count_label' => 'Número de imágenes',
'brand_colors_label' => 'Colores de la imagen',
'brand_colors_on' => 'Colores de marca',
'brand_colors_off' => 'La IA decide',
'prompt_title' => 'Describe tu post',
'prompt_label' => '¿De qué trata este post?',
'prompt_placeholder' => 'Ej. Anuncia nuestra nueva función de carrusel para Instagram',
Expand Down
3 changes: 3 additions & 0 deletions lang/pt-BR/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,9 @@
'media_optional_label' => 'Quantas imagens?',
'media_none' => 'Nenhuma',
'media_count_label' => 'Número de imagens',
'brand_colors_label' => 'Cores da imagem',
'brand_colors_on' => 'Cores da marca',
'brand_colors_off' => 'IA decide',
'prompt_title' => 'Descreva seu post',
'prompt_label' => 'Sobre o que é este post?',
'prompt_placeholder' => 'Ex. Anunciar nossa nova função de carrossel para o Instagram',
Expand Down
27 changes: 26 additions & 1 deletion resources/js/components/posts/create/AiPostWizard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ const selectedAccountId = ref<string | null>(null);
const includeImages = ref(true);
const imageCount = ref(2);
const promptText = ref('');
// true = images use the workspace brand palette; false = the AI picks colors freely.
const useBrandColors = ref(true);

const submitting = ref(false);

Expand All @@ -73,7 +75,8 @@ const httpStart = useHttp<{
prompt: string;
date: string | null;
template: string;
}>({ format: null, social_account_id: null, image_count: 0, prompt: '', date: null, template: 'image_card' });
apply_brand_visuals: boolean;
}>({ format: null, social_account_id: null, image_count: 0, prompt: '', date: null, template: 'image_card', apply_brand_visuals: true });

const AI_FORMATS: Array<{ value: AiFormat; platforms: string[] }> = [
{ value: ContentType.InstagramFeed, platforms: ['instagram', 'instagram-facebook'] },
Expand Down Expand Up @@ -207,6 +210,7 @@ const startGeneration = async () => {
httpStart.prompt = promptText.value.trim();
httpStart.date = props.date;
httpStart.template = resolvedTemplate.value;
httpStart.apply_brand_visuals = useBrandColors.value;

try {
const data = await httpStart.post(startRoute.url()) as { creation_id: string; channel: string };
Expand Down Expand Up @@ -349,6 +353,27 @@ const startGeneration = async () => {
</div>
</div>

<!-- Colors: use the brand palette or let the AI decide (only when images are generated) -->
<div v-if="selectedFormat && submittedImageCount > 0" class="space-y-2">
<Label class="text-sm font-bold">{{ $t('posts.create.steps.brand_colors_label') }}</Label>
<div class="flex flex-wrap gap-2">
<Button
type="button"
:variant="useBrandColors ? 'default' : 'outline'"
@click="useBrandColors = true"
>
{{ $t('posts.create.steps.brand_colors_on') }}
</Button>
<Button
type="button"
:variant="!useBrandColors ? 'default' : 'outline'"
@click="useBrandColors = false"
>
{{ $t('posts.create.steps.brand_colors_off') }}
</Button>
</div>
</div>

<!-- Prompt -->
<div v-if="selectedFormat" class="space-y-2">
<Label for="ai-prompt" class="text-sm font-bold">{{ $t('posts.create.steps.prompt_label') }}</Label>
Expand Down
27 changes: 27 additions & 0 deletions tests/Feature/Ai/PostAiCreateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,33 @@
Bus::assertDispatched(StreamPostCreation::class, fn ($job) => $job->date === '2026-06-15');
});

test('start carries apply_brand_visuals=false when the user lets the AI decide colors', function () {
Bus::fake();

$this->actingAs($this->user)
->postJson(route('app.posts.ai.create'), [
'prompt' => 'hello',
'format' => 'x_post',
'apply_brand_visuals' => false,
])
->assertAccepted();

Bus::assertDispatched(StreamPostCreation::class, fn ($job) => $job->applyBrandVisuals === false);
});

test('start defaults apply_brand_visuals to true when omitted', function () {
Bus::fake();

$this->actingAs($this->user)
->postJson(route('app.posts.ai.create'), [
'prompt' => 'hello',
'format' => 'x_post',
])
->assertAccepted();

Bus::assertDispatched(StreamPostCreation::class, fn ($job) => $job->applyBrandVisuals === true);
});

test('start rejects invalid date format', function () {
Bus::fake();

Expand Down