From 092f98b24fa0f79fc3860f0b4acafbf1c5b967c2 Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Tue, 26 May 2026 19:33:21 -0300 Subject: [PATCH 1/9] refactor(ai): replace Guava cache with Caffeine in LangChain4jAIClient --- .../langchain4j/LangChain4jAIClient.java | 47 +++++++++---------- .../langchain4j/LangChain4jAIClientTest.java | 6 +-- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jAIClient.java b/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jAIClient.java index 06fd2b541e50..a475db263b62 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jAIClient.java +++ b/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jAIClient.java @@ -16,8 +16,8 @@ import com.dotmarketing.util.json.JSONObject; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.image.Image; import dev.langchain4j.data.message.AiMessage; @@ -47,12 +47,9 @@ import java.util.List; import java.util.function.Function; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import com.google.common.util.concurrent.UncheckedExecutionException; - /** * {@link AIClient} implementation backed by LangChain4J. * @@ -76,16 +73,16 @@ public class LangChain4jAIClient implements AIClient { private static final long MODEL_CACHE_TTL_HOURS = 1; private static final long STREAMING_TIMEOUT_SECONDS = 300; - private final Cache chatModelCache = CacheBuilder.newBuilder() + private final Cache chatModelCache = Caffeine.newBuilder() .expireAfterWrite(MODEL_CACHE_TTL_HOURS, TimeUnit.HOURS) .build(); - private final Cache streamingChatModelCache = CacheBuilder.newBuilder() + private final Cache streamingChatModelCache = Caffeine.newBuilder() .expireAfterWrite(MODEL_CACHE_TTL_HOURS, TimeUnit.HOURS) .build(); - private final Cache embeddingModelCache = CacheBuilder.newBuilder() + private final Cache embeddingModelCache = Caffeine.newBuilder() .expireAfterWrite(MODEL_CACHE_TTL_HOURS, TimeUnit.HOURS) .build(); - private final Cache imageModelCache = CacheBuilder.newBuilder() + private final Cache imageModelCache = Caffeine.newBuilder() .expireAfterWrite(MODEL_CACHE_TTL_HOURS, TimeUnit.HOURS) .build(); @@ -199,13 +196,12 @@ private StreamingChatModel initStreamingModel( final ProviderConfig modelConfig = ImmutableProviderConfig.copyOf(baseConfig).withModel(modelName); return streamingChatModelCache.get( cacheKeyPrefix + ":chat:streaming:" + modelName, - () -> LangChain4jModelFactory.buildStreamingChatModel(modelConfig)); - } catch (ExecutionException | UncheckedExecutionException e) { - final Throwable cause = e.getCause() != null ? e.getCause() : e; + k -> LangChain4jModelFactory.buildStreamingChatModel(modelConfig)); + } catch (RuntimeException e) { lastException = new IllegalArgumentException( - "Failed to initialize streaming model '" + modelName + "': " + cause.getMessage(), cause); + "Failed to initialize streaming model '" + modelName + "': " + e.getMessage(), e); Logger.warn(LangChain4jAIClient.class, - "Streaming model '" + modelName + "' init failed: " + cause.getMessage() + "Streaming model '" + modelName + "' init failed: " + e.getMessage() + (models.size() > 1 ? " — trying next model" : "")); } } @@ -345,24 +341,27 @@ String executeWithFallback( // all configured fallback models have been attempted. RuntimeException lastException = null; for (final String modelName : models) { + final ProviderConfig modelConfig = ImmutableProviderConfig.copyOf(baseConfig).withModel(modelName); + final M model; try { - final ProviderConfig modelConfig = ImmutableProviderConfig.copyOf(baseConfig).withModel(modelName); - final M model = modelCache.get( + model = modelCache.get( cacheKeyPrefix + ":" + section + ":" + modelName, - () -> modelBuilder.apply(modelConfig)); + k -> modelBuilder.apply(modelConfig)); + } catch (RuntimeException e) { + lastException = new IllegalArgumentException( + "Failed to initialize " + section + " model '" + modelName + "': " + e.getMessage(), e); + Logger.warn(LangChain4jAIClient.class, + section + " model '" + modelName + "' init failed: " + e.getMessage() + + (models.size() > 1 ? " — trying next model" : "")); + continue; + } + try { final long start = System.currentTimeMillis(); final String result = executor.apply(model); Logger.info(LangChain4jAIClient.class, section + " model '" + modelName + "' responded in " + (System.currentTimeMillis() - start) + "ms"); return result; - } catch (ExecutionException | UncheckedExecutionException e) { - final Throwable cause = e.getCause() != null ? e.getCause() : e; - lastException = new IllegalArgumentException( - "Failed to initialize " + section + " model '" + modelName + "': " + cause.getMessage(), cause); - Logger.warn(LangChain4jAIClient.class, - section + " model '" + modelName + "' init failed: " + cause.getMessage() - + (models.size() > 1 ? " — trying next model" : "")); } catch (RuntimeException e) { lastException = e; Logger.warn(LangChain4jAIClient.class, diff --git a/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jAIClientTest.java b/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jAIClientTest.java index e35552923a74..5a1b05096758 100644 --- a/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jAIClientTest.java +++ b/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jAIClientTest.java @@ -6,8 +6,8 @@ import com.dotcms.ai.exception.DotAIAppConfigDisabledException; import com.dotmarketing.util.json.JSONArray; import com.dotmarketing.util.json.JSONObject; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.image.Image; import dev.langchain4j.data.message.AiMessage; @@ -282,7 +282,7 @@ private static ProviderConfig configWithModels(final String models) { } private static Cache freshCache() { - return CacheBuilder.newBuilder().build(); + return Caffeine.newBuilder().build(); } @Test From 700d2306fce1e8b84dca3be5a8c9160930eca066 Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Tue, 26 May 2026 20:05:15 -0300 Subject: [PATCH 2/9] feat(ai): replace image size dropdown with free-text input in block editor and dotAI portlet --- .../dotcms-models/src/lib/dot-ai.model.ts | 2 +- .../ai-image-prompt-form.component.html | 13 ++++---- .../ai-image-prompt-form.component.ts | 32 +++---------------- .../main/webapp/WEB-INF/jsp/dotai/render.jsp | 5 ++- 4 files changed, 16 insertions(+), 36 deletions(-) diff --git a/core-web/libs/dotcms-models/src/lib/dot-ai.model.ts b/core-web/libs/dotcms-models/src/lib/dot-ai.model.ts index 9bbb46e12626..ea52a95e0663 100644 --- a/core-web/libs/dotcms-models/src/lib/dot-ai.model.ts +++ b/core-web/libs/dotcms-models/src/lib/dot-ai.model.ts @@ -49,7 +49,7 @@ export interface DotAIImageResponse { export interface AIImagePrompt { text: string; type: PromptType; - size: DotAIImageOrientation; + size: string; } /** diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.html b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.html index a73037ef8287..f851a1eeb65f 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.html +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.html @@ -35,14 +35,15 @@ }
-
diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts index 7db1696f316e..80908d1ba58f 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts @@ -20,21 +20,15 @@ import { } from '@angular/forms'; import { Accordion, AccordionPanel, AccordionHeader, AccordionContent } from 'primeng/accordion'; -import { SelectItem } from 'primeng/api'; import { Button } from 'primeng/button'; +import { InputTextModule } from 'primeng/inputtext'; import { RadioButton } from 'primeng/radiobutton'; -import { Select } from 'primeng/select'; import { Textarea } from 'primeng/textarea'; import { filter } from 'rxjs/operators'; import { DotMessageService } from '@dotcms/data-access'; -import { - AIImagePrompt, - DotAIImageOrientation, - DotGeneratedAIImage, - PromptType -} from '@dotcms/dotcms-models'; +import { AIImagePrompt, DotGeneratedAIImage, PromptType } from '@dotcms/dotcms-models'; import { DotCopyButtonComponent } from './../../../../components/dot-copy-button/dot-copy-button.component'; import { DotFieldRequiredDirective } from './../../../../dot-field-required/dot-field-required.directive'; @@ -53,7 +47,7 @@ import { DotValidators } from './../../../../validators/dotValidators'; RadioButton, ReactiveFormsModule, FormsModule, - Select, + InputTextModule, Textarea, DotFieldRequiredDirective, DotMessagePipe, @@ -95,24 +89,6 @@ export class AiImagePromptFormComponent implements OnChanges { submitButtonLabel = 'block-editor.extension.ai-image.generate'; requiredPrompt = true; tooltipText: string = null; - orientationOptions: SelectItem[] = [ - { - value: DotAIImageOrientation.HORIZONTAL, - label: this.dotMessageService.get( - 'block-editor.extension.ai-image.orientation.horizontal' - ) - }, - { - value: DotAIImageOrientation.SQUARE, - label: this.dotMessageService.get('block-editor.extension.ai-image.orientation.square') - }, - { - value: DotAIImageOrientation.VERTICAL, - label: this.dotMessageService.get( - 'block-editor.extension.ai-image.orientation.vertical' - ) - } - ]; private isUpdatingValidators = false; private destroyRef = inject(DestroyRef); @@ -134,7 +110,7 @@ export class AiImagePromptFormComponent implements OnChanges { this.form = new FormGroup({ text: new FormControl('', [Validators.required, DotValidators.noWhitespace]), type: new FormControl(PromptType.INPUT, Validators.required), - size: new FormControl(DotAIImageOrientation.HORIZONTAL, Validators.required) + size: new FormControl('1024x1024', Validators.required) }); const typeControl = this.form.get('type'); diff --git a/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp b/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp index 559637e06850..57fb24a89f44 100644 --- a/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp +++ b/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp @@ -356,7 +356,10 @@ placeholder="Image prompt"> - + + Size: + +
From f70426116d8bc5281b8b2637ee6dfc3ba66fdab4 Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Tue, 26 May 2026 20:10:24 -0300 Subject: [PATCH 3/9] feat(ai): add i18n key for image size label --- dotCMS/src/main/webapp/WEB-INF/messages/Language.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties index 2af698fc7c78..b7c5d80ca465 100644 --- a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties +++ b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties @@ -5927,6 +5927,7 @@ block-editor.extension.ai-image.custom.prompt.desc=Write your own prompt and hav block-editor.extension.ai-image.existing.content=Create based on Existing Content block-editor.extension.ai-image.existing.content.desc=Generate and image based on provided content. You may Customize with mood, colors and size. block-editor.extension.ai-image.orientation=Orientation +block-editor.extension.ai-image.size=Size block-editor.extension.ai-image.prompt=Prompt block-editor.extension.ai-image.custom.props=Customize images Properties ( Optional ) From 5d80289224e8170df2a77358dcd5be07cfc6061c Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Wed, 27 May 2026 20:10:34 -0300 Subject: [PATCH 4/9] =?UTF-8?q?fix(ai):=20address=20PR=20review=20?= =?UTF-8?q?=E2=80=94=20fix=20unit=20test,=20align=20store=20defaults,=20re?= =?UTF-8?q?type=20gallery=20input,=20add=20size=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dot-ai-image-prompt/ai-image-prompt.store.ts | 3 +-- .../ai-image-prompt-form.component.spec.ts | 8 +++----- .../ai-image-prompt-form.component.ts | 5 ++++- .../ai-image-prompt-gallery.component.ts | 6 +++--- .../dot-ai-image-prompt/store/ai-image-prompt.store.ts | 3 +-- dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp | 2 +- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/ai-image-prompt.store.ts b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/ai-image-prompt.store.ts index 402830023561..dca288b7f863 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/ai-image-prompt.store.ts +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/ai-image-prompt.store.ts @@ -11,7 +11,6 @@ import { ComponentStatus, AIImagePrompt, DotAIImageContent, - DotAIImageOrientation, DotGeneratedAIImage, PromptType } from '@dotcms/dotcms-models'; @@ -48,7 +47,7 @@ const initialState: DotAiImagePromptComponentState = { formValue: { text: '', type: DEFAULT_INPUT_PROMPT, - size: DotAIImageOrientation.HORIZONTAL + size: '1024x1024' } }; diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.spec.ts index 266cead7d4bd..8db73557ec76 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.spec.ts @@ -6,7 +6,7 @@ import { ReactiveFormsModule } from '@angular/forms'; import { ButtonModule } from 'primeng/button'; import { DotMessageService } from '@dotcms/data-access'; -import { DotAIImageOrientation, DotGeneratedAIImage, PromptType } from '@dotcms/dotcms-models'; +import { DotGeneratedAIImage, PromptType } from '@dotcms/dotcms-models'; import { AiImagePromptFormComponent } from './ai-image-prompt-form.component'; @@ -17,7 +17,7 @@ import { DotCopyButtonComponent } from '../../../dot-copy-button/dot-copy-button const MOCK_FORM_VALUE = { text: 'Test', type: PromptType.INPUT, - size: DotAIImageOrientation.HORIZONTAL + size: '1024x1024' }; const MOCK_AI_VALUE = { @@ -61,9 +61,7 @@ describe('DotAiImagePromptFormComponent', () => { spectator.detectChanges(); expect(spectator.component.form.get('text').value).toEqual(''); expect(spectator.component.form.get('type').value).toEqual(PromptType.INPUT); - expect(spectator.component.form.get('size').value).toEqual( - DotAIImageOrientation.HORIZONTAL - ); + expect(spectator.component.form.get('size').value).toEqual('1024x1024'); }); it('should emit value when form value change', () => { diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts index 80908d1ba58f..4b8ec64c590a 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts @@ -110,7 +110,10 @@ export class AiImagePromptFormComponent implements OnChanges { this.form = new FormGroup({ text: new FormControl('', [Validators.required, DotValidators.noWhitespace]), type: new FormControl(PromptType.INPUT, Validators.required), - size: new FormControl('1024x1024', Validators.required) + size: new FormControl('1024x1024', [ + Validators.required, + Validators.pattern(/^\d+x\d+$/) + ]) }); const typeControl = this.form.get('type'); diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.ts b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.ts index 269dd8d5eead..5f56f7b92e52 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.ts @@ -14,7 +14,7 @@ import { ImageModule } from 'primeng/image'; import { SkeletonModule } from 'primeng/skeleton'; import { DotMessageService } from '@dotcms/data-access'; -import { DotAIImageOrientation, DotGeneratedAIImage } from '@dotcms/dotcms-models'; +import { DotGeneratedAIImage } from '@dotcms/dotcms-models'; import { DotEmptyContainerComponent, @@ -52,10 +52,10 @@ export class AiImagePromptGalleryComponent implements OnChanges { activeImageIndex = 0; /** - * The orientation of the images. helps to define the initial placeholder + * The image size string (e.g. "1024x1024"). Used to set the placeholder CSS class. */ @Input() - orientation = DotAIImageOrientation.HORIZONTAL; + orientation = '1024x1024'; /** * An event that is emitted when the active image index changes. diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/store/ai-image-prompt.store.ts b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/store/ai-image-prompt.store.ts index 788f42f8cc99..dc5f75a1c4a9 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/store/ai-image-prompt.store.ts +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/store/ai-image-prompt.store.ts @@ -11,7 +11,6 @@ import { DotAiService } from '@dotcms/data-access'; import { AIImagePrompt, ComponentStatus, - DotAIImageOrientation, DotGeneratedAIImage, PromptType } from '@dotcms/dotcms-models'; @@ -34,7 +33,7 @@ const initialState: AiImagePromptdState = { formValue: { text: '', type: PromptType.INPUT, - size: DotAIImageOrientation.HORIZONTAL + size: '1024x1024' } }; diff --git a/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp b/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp index 57fb24a89f44..1f48cea38cad 100644 --- a/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp +++ b/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp @@ -358,7 +358,7 @@ Size: - + From 170acf9716abe76e891381d1cc7b823e0e5da6db Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Thu, 28 May 2026 15:53:13 -0300 Subject: [PATCH 5/9] test(ai): fix signal store spec after size type change to string --- .../store/ai-image-prompt.store.spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/store/ai-image-prompt.store.spec.ts b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/store/ai-image-prompt.store.spec.ts index 04f36d2e6f1e..a1eca14e9fa5 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/store/ai-image-prompt.store.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/store/ai-image-prompt.store.spec.ts @@ -5,7 +5,7 @@ import { of, throwError } from 'rxjs'; import { TestBed } from '@angular/core/testing'; import { DotAiService } from '@dotcms/data-access'; -import { ComponentStatus, DotAIImageOrientation, PromptType } from '@dotcms/dotcms-models'; +import { ComponentStatus, PromptType } from '@dotcms/dotcms-models'; import { DotAiImagePromptStore } from './ai-image-prompt.store'; @@ -38,7 +38,7 @@ describe('DotAiImagePromptStore', () => { expect(store.formValue()).toEqual({ text: '', type: PromptType.INPUT, - size: DotAIImageOrientation.HORIZONTAL + size: '1024x1024' }); }); }); @@ -80,7 +80,7 @@ describe('DotAiImagePromptStore', () => { const formValue = { text: 'new text', type: PromptType.INPUT, - size: DotAIImageOrientation.VERTICAL + size: '1024x1792' }; store.setFormValue(formValue); expect(store.formValue()).toEqual(formValue); @@ -92,13 +92,13 @@ describe('DotAiImagePromptStore', () => { store.setFormValue({ text: 'prompt', type: PromptType.INPUT, - size: DotAIImageOrientation.HORIZONTAL + size: '1792x1024' }); store.generateImage(); expect(dotAiService.generateAndPublishImage).toHaveBeenCalledWith( 'prompt', - DotAIImageOrientation.HORIZONTAL + '1792x1024' ); expect(store.status()).toBe(ComponentStatus.IDLE); expect(store.images().length).toBe(1); @@ -110,13 +110,13 @@ describe('DotAiImagePromptStore', () => { store.setFormValue({ text: 'prompt', type: PromptType.INPUT, - size: DotAIImageOrientation.HORIZONTAL + size: '1792x1024' }); store.generateImage(); expect(dotAiService.generateAndPublishImage).toHaveBeenCalledWith( 'prompt', - DotAIImageOrientation.HORIZONTAL + '1792x1024' ); expect(store.status()).toBe(ComponentStatus.ERROR); expect(store.error()).toBe('error'); From 0d54a31e9ac35f4d0edc77a1623ba2140a85bf0b Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Thu, 28 May 2026 18:04:46 -0300 Subject: [PATCH 6/9] fix(ai): tighten size validation, align service default, rename orientation to size, add size hint --- core-web/libs/data-access/src/lib/dot-ai/dot-ai.service.ts | 3 +-- .../dot-ai-image-prompt/ai-image-prompt.component.html | 2 +- .../ai-image-prompt-form/ai-image-prompt-form.component.html | 3 +++ .../ai-image-prompt-form/ai-image-prompt-form.component.ts | 2 +- .../ai-image-prompt-gallery.component.html | 2 +- .../ai-image-prompt-gallery.component.ts | 2 +- dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp | 2 +- dotCMS/src/main/webapp/WEB-INF/messages/Language.properties | 1 + 8 files changed, 10 insertions(+), 7 deletions(-) diff --git a/core-web/libs/data-access/src/lib/dot-ai/dot-ai.service.ts b/core-web/libs/data-access/src/lib/dot-ai/dot-ai.service.ts index 1e712b3d6698..d7d0e33170d1 100644 --- a/core-web/libs/data-access/src/lib/dot-ai/dot-ai.service.ts +++ b/core-web/libs/data-access/src/lib/dot-ai/dot-ai.service.ts @@ -9,7 +9,6 @@ import { DotCMSContentlet, AiPluginResponse, DotAIImageContent, - DotAIImageOrientation, DotAIImageResponse, DotAiProviderConfig } from '@dotcms/dotcms-models'; @@ -80,7 +79,7 @@ export class DotAiService { */ public generateAndPublishImage( prompt: string, - size: string = DotAIImageOrientation.HORIZONTAL + size: string = '1024x1024' ): Observable { return this.#http .post( diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/ai-image-prompt.component.html b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/ai-image-prompt.component.html index 6b6ebbde2019..bd6bf6aa2cd4 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/ai-image-prompt.component.html +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/ai-image-prompt.component.html @@ -15,7 +15,7 @@ [isLoading]="store.isLoading()" [images]="store.images()" [activeImageIndex]="store.galleryActiveIndex()" - [orientation]="store.formValue()?.size" /> + [size]="store.formValue()?.size" /> @if (store.isLoading() || store.hasImages()) {
+ + {{ 'block-editor.extension.ai-image.size.hint' | dm }} +
diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts index 4b8ec64c590a..a2ebc64992ab 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.ts @@ -112,7 +112,7 @@ export class AiImagePromptFormComponent implements OnChanges { type: new FormControl(PromptType.INPUT, Validators.required), size: new FormControl('1024x1024', [ Validators.required, - Validators.pattern(/^\d+x\d+$/) + Validators.pattern(/^[1-9]\d{1,3}x[1-9]\d{1,3}$/) ]) }); diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.html b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.html index 760a09021838..0c02aa5b0f1a 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.html +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.html @@ -1,6 +1,6 @@ @if (!isLoading && !images?.length) {
Size: - + diff --git a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties index b7c5d80ca465..3dad6815642f 100644 --- a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties +++ b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties @@ -5928,6 +5928,7 @@ block-editor.extension.ai-image.existing.content=Create based on Existing Conten block-editor.extension.ai-image.existing.content.desc=Generate and image based on provided content. You may Customize with mood, colors and size. block-editor.extension.ai-image.orientation=Orientation block-editor.extension.ai-image.size=Size +block-editor.extension.ai-image.size.hint=Common sizes: 1024x1024, 1792x1024, 1024x1792 block-editor.extension.ai-image.prompt=Prompt block-editor.extension.ai-image.custom.props=Customize images Properties ( Optional ) From 67495e7f514bf8444e355aa4b354a6caa1f75fb5 Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Thu, 28 May 2026 18:41:20 -0300 Subject: [PATCH 7/9] fix(ai): remove redundant type annotation on size default parameter --- core-web/libs/data-access/src/lib/dot-ai/dot-ai.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-web/libs/data-access/src/lib/dot-ai/dot-ai.service.ts b/core-web/libs/data-access/src/lib/dot-ai/dot-ai.service.ts index d7d0e33170d1..79c4f0e185d3 100644 --- a/core-web/libs/data-access/src/lib/dot-ai/dot-ai.service.ts +++ b/core-web/libs/data-access/src/lib/dot-ai/dot-ai.service.ts @@ -79,7 +79,7 @@ export class DotAiService { */ public generateAndPublishImage( prompt: string, - size: string = '1024x1024' + size = '1024x1024' ): Observable { return this.#http .post( From 5f81e869b3ff5c58f8b56d65bca3aa9a025eafda Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Fri, 29 May 2026 12:07:51 -0300 Subject: [PATCH 8/9] fix(ai): remove size hint, fix gallery image centering and error state layout --- .../ai-image-prompt-form.component.html | 3 --- .../ai-image-prompt-gallery.component.scss | 19 ++++++++++++++++--- .../main/webapp/WEB-INF/jsp/dotai/render.jsp | 2 +- .../WEB-INF/messages/Language.properties | 1 - 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.html b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.html index b14f0fe61481..f851a1eeb65f 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.html +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-form/ai-image-prompt-form.component.html @@ -44,9 +44,6 @@ formControlName="size" placeholder="e.g. 1024x1024" class="w-full" /> - - {{ 'block-editor.extension.ai-image.size.hint' | dm }} -
diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.scss b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.scss index 4b6ba0e30b13..0fc2ee3dacd6 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.scss +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.scss @@ -47,12 +47,25 @@ p-galleria { width: 100%; } - dot-empty-container .message__icon { - color: colors.$red; + .p-galleria-item { + display: flex; + justify-content: center; + align-items: center; + } + + dot-empty-container { + width: 100%; + display: flex; + justify-content: center; + + .message__icon { + color: colors.$red; + } } .message__principal-wrapper { - padding: 0 4.5rem; + padding: 0 2rem; + text-align: center; } .p-image-preview { diff --git a/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp b/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp index 87b88f1cc5a6..7394ab69e52c 100644 --- a/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp +++ b/dotCMS/src/main/webapp/WEB-INF/jsp/dotai/render.jsp @@ -358,7 +358,7 @@ Size: - + diff --git a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties index 3dad6815642f..b7c5d80ca465 100644 --- a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties +++ b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties @@ -5928,7 +5928,6 @@ block-editor.extension.ai-image.existing.content=Create based on Existing Conten block-editor.extension.ai-image.existing.content.desc=Generate and image based on provided content. You may Customize with mood, colors and size. block-editor.extension.ai-image.orientation=Orientation block-editor.extension.ai-image.size=Size -block-editor.extension.ai-image.size.hint=Common sizes: 1024x1024, 1792x1024, 1024x1792 block-editor.extension.ai-image.prompt=Prompt block-editor.extension.ai-image.custom.props=Customize images Properties ( Optional ) From 29566476b7b5a18621ec2c67c262bf8c98ebc3f3 Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Fri, 29 May 2026 15:22:57 -0300 Subject: [PATCH 9/9] fix(ai): fix gallery image and error state centering --- .../ai-image-prompt-gallery.component.scss | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.scss b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.scss index 0fc2ee3dacd6..39e6a1c975a7 100644 --- a/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.scss +++ b/core-web/libs/ui/src/lib/components/dot-ai-image-prompt/components/ai-image-prompt-gallery/ai-image-prompt-gallery.component.scss @@ -14,6 +14,7 @@ align-items: center; border-radius: common.$border-radius-md; align-self: center; + aspect-ratio: 1/1; &.s1024x1792 { aspect-ratio: 1/1.25; @@ -38,12 +39,16 @@ p-galleria { :host ::ng-deep { .ai-image-gallery__img { - width: 100%; + max-width: 100%; max-height: 32rem; border-radius: common.$border-radius-md; } - p-galleriacontent { + p-galleriacontent, + .p-galleria, + .p-galleria-content, + .p-galleria-items-container, + .p-galleria-items { width: 100%; } @@ -51,15 +56,24 @@ p-galleria { display: flex; justify-content: center; align-items: center; + width: 100%; + } + + .p-image { + display: flex; + justify-content: center; + width: 100%; } dot-empty-container { width: 100%; + min-height: 20rem; display: flex; justify-content: center; + align-items: center; - .message__icon { - color: colors.$red; + .pi { + width: inherit; } }