Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dotCMS/core#26520 Add translations to AI Extensions on Block Editor #26871

Merged
merged 11 commits into from
Dec 4, 2023
3 changes: 2 additions & 1 deletion core-web/apps/dotcms-block-editor/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { NgModule, Injector, DoBootstrap } from '@angular/core';
import { DoBootstrap, Injector, NgModule } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
Expand All @@ -13,6 +13,7 @@ import { BlockEditorModule, DotBlockEditorComponent } from '@dotcms/block-editor
import { DotPropertiesService } from '@dotcms/data-access';

import { AppComponent } from './app.component';

@NgModule({
declarations: [AppComponent],
imports: [
Expand Down
10 changes: 9 additions & 1 deletion core-web/apps/dotcms-ui/.storybook/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ module.exports = function expressMiddleware(router) {
})
);

console.info(`\x1b[35m[dotCMS]\x1b[0m`, 'Using middleware for storybook');
router.use(
'/api/v2/languages/default/keys',
createProxyMiddleware({
target: 'http://localhost:8080',
changeOrigin: true
})
);

console.info(`\x1b[32m[dotCMS]\x1b[0m`, 'Using middleware for storybook');
}
};
33 changes: 25 additions & 8 deletions core-web/libs/block-editor/src/lib/block-editor.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

// DotCMS JS
import { DotMessageService } from '@dotcms/data-access';
import { LoggerService, StringUtils } from '@dotcms/dotcms-js';
import { DotFieldRequiredDirective } from '@dotcms/ui';
import { DotFieldRequiredDirective, DotMessagePipe } from '@dotcms/ui';

//Editor
import { DotBlockEditorComponent } from './components/dot-block-editor/dot-block-editor.component';
Expand All @@ -13,23 +14,26 @@ import {
AIContentActionsComponent,
AIContentPromptComponent,
AIImagePromptComponent,
BubbleFormComponent,
BubbleLinkFormComponent,
BubbleMenuButtonComponent,
BubbleMenuComponent,
DragHandlerComponent,
FloatingButtonComponent,
FormActionsComponent,
SuggestionPageComponent,
UploadPlaceholderComponent
} from './extensions';
import { AssetFormModule } from './extensions/asset-form/asset-form.module';
import { BubbleFormComponent } from './extensions/bubble-form/bubble-form.component';
import { FloatingButtonComponent } from './extensions/floating-button/floating-button.component';
import { ContentletBlockComponent } from './nodes';
import { DotAiService, DotUploadFileService } from './shared';
import { EditorDirective } from './shared/directives';
import { DotAiService, DotUploadFileService, EditorDirective } from './shared';
import { PrimengModule } from './shared/primeng.module';
import { SharedModule } from './shared/shared.module';

const initTranslations = (dotMessageService: DotMessageService) => {
return () => dotMessageService.init();
};

@NgModule({
imports: [
CommonModule,
Expand All @@ -40,7 +44,8 @@ import { SharedModule } from './shared/shared.module';
AssetFormModule,
DotFieldRequiredDirective,
UploadPlaceholderComponent,
AIImagePromptComponent
AIImagePromptComponent,
DotMessagePipe
],
declarations: [
EditorDirective,
Expand All @@ -58,7 +63,19 @@ import { SharedModule } from './shared/shared.module';
AIContentPromptComponent,
AIContentActionsComponent
],
providers: [DotUploadFileService, LoggerService, StringUtils, DotAiService],
providers: [
DotUploadFileService,
LoggerService,
StringUtils,
DotAiService,
DotMessageService,
{
provide: APP_INITIALIZER,
useFactory: initTranslations,
deps: [DotMessageService],
multi: true
}
],
oidacra marked this conversation as resolved.
Show resolved Hide resolved
exports: [
EditorDirective,
BubbleMenuComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { OrderListModule } from 'primeng/orderlist';

import { debounceTime, delay, tap } from 'rxjs/operators';

import { DotPropertiesService } from '@dotcms/data-access';
import { DotMessageService, DotPropertiesService } from '@dotcms/data-access';

import { DotBlockEditorComponent } from './dot-block-editor.component';

Expand All @@ -24,16 +24,17 @@ import {
} from '../../extensions';
import { ContentletBlockComponent } from '../../nodes';
import {
DotAiService,
ASSET_MOCK,
CONTENTLETS_MOCK,
DotAiService,
DotLanguageService,
DotUploadFileService,
FileStatus,
SearchService,
SuggestionsComponent,
SuggestionsService
} from '../../shared';
import { DotMessageServiceMock } from '../../shared/mocks/dot-message.service.mock';
import { DotAiServiceMock } from '../../shared/services/dot-ai/dot-ai-service.mock';

export default {
Expand Down Expand Up @@ -194,6 +195,13 @@ export const primary = () => ({
{
provide: DotAiService,
useClass: process.env.USE_MIDDLEWARE === 'true' ? DotAiService : DotAiServiceMock
},
{
provide: DotMessageService,
useClass:
process.env.USE_MIDDLEWARE === 'true'
? DotMessageService
: DotMessageServiceMock
}
],
// We need these here because they are dynamically rendered
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { Component, EventEmitter, Output, OnInit, ChangeDetectionStrategy } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
inject,
OnInit,
Output
} from '@angular/core';

import { DotMessageService } from '@dotcms/data-access';

interface ActionOption {
label: string;
Expand All @@ -21,26 +30,26 @@ export enum ACTIONS {
})
export class AIContentActionsComponent implements OnInit {
@Output() actionEmitter = new EventEmitter<ACTIONS>();

actionOptions!: ActionOption[];
tooltipContent = 'Describe the size, color palette, style, mood, etc.';
private dotMessageService: DotMessageService = inject(DotMessageService);

ngOnInit() {
this.actionOptions = [
{
label: 'Accept',
label: this.dotMessageService.get('block-editor.common.accept'),
icon: 'pi pi-check',
callback: () => this.emitAction(ACTIONS.ACCEPT),
selectedOption: true
},
{
label: 'Regenerate',
label: this.dotMessageService.get('block-editor.common.regenerate'),
icon: 'pi pi-sync',
callback: () => this.emitAction(ACTIONS.REGENERATE),
selectedOption: false
},
{
label: 'Delete',
label: this.dotMessageService.get('block-editor.common.delete'),
icon: 'pi pi-trash',
callback: () => this.emitAction(ACTIONS.DELETE),
selectedOption: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@
<input
#input
[disabled]="vm.loading"
autofocus
formControlName="textPrompt"
type="text"
pInputText
autofocus
placeholder="Ask AI to write something" />
placeholder="{{
'block-editor.extension.ai-content.ask-ai-to-write-something' | dm
}}"
type="text" />

<ng-container *ngIf="vm.loading; else submitButton">
<span class="pending-label">Pending</span>
<span class="pending-label">{{ 'block-editor.common.pending' | dm }}</span>
</ng-container>

<ng-template #submitButton>
<button
class="p-button-rounded p-button-text"
icon="pi pi-send"
pButton
type="submit"
icon="pi pi-send"></button>
type="submit"></button>
</ng-template>
</span>
</form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
[style]="{ width: '800px' }"
(onHide)="hideDialog()"
appendTo="body"
header="Generate AI Image">
header="{{ 'block-editor.extension.ai-image.dialog-title' | dm }}">
<div class="dialog-prompts__wrapper grid">
<dot-ai-image-prompt-input
class="col"
[isLoading]="vm.status === ComponentStatus.LOADING"
[selected]="vm.selectedPromptType === 'input'"
(click)="selectType('input', vm.selectedPromptType)"
(promptChanged)="generateImage($event)"
placeholder="Create a realistic image of a cow in the snow"
placeholder="{{ 'block-editor.extension.ai-image.input-text.placeholder' | dm }}"
type="input" />

<dot-ai-image-prompt-input
Expand All @@ -24,7 +24,7 @@
[selected]="vm.selectedPromptType === 'auto'"
(click)="selectType('auto', vm.selectedPromptType)"
(promptChanged)="generateImage($event)"
placeholder="E.g. 1200x800px, vibrant colors, impressionistic, adventurous."
placeholder="{{ 'block-editor.extension.ai-image.auto-text.placeholder' | dm }}"
type="auto" />
</div>
</p-dialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { OverlayPanelModule } from 'primeng/overlaypanel';
import { TooltipModule } from 'primeng/tooltip';

import { ComponentStatus } from '@dotcms/dotcms-models';
import { DotMessagePipe } from '@dotcms/ui';

import { PromptType } from './ai-image-prompt.models';
import { DotAiImagePromptStore, VmAiImagePrompt } from './ai-image-prompt.store';
Expand All @@ -29,12 +30,13 @@ import { AiImagePromptInputComponent } from './components/ai-image-prompt-input/
DialogModule,
AiImagePromptInputComponent,
AiImagePromptInputComponent,
AsyncPipe
AsyncPipe,
DotMessagePipe
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AIImagePromptComponent {
vm$: Observable<VmAiImagePrompt> = inject(DotAiImagePromptStore).vm$;
protected readonly vm$: Observable<VmAiImagePrompt> = inject(DotAiImagePromptStore).vm$;

protected readonly ComponentStatus = ComponentStatus;
private store = inject(DotAiImagePromptStore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@

<div class="flex gap-1">
<p class="text-center">
<strong>Generate</strong> an AI image based on your input and requests.
{{ 'block-editor.extension.ai-image.input-text.title' | dm }}
<i
class="pi pi-info-circle"
pTooltip="Describe the type of image you want to generate."
pTooltip="{{ 'block-editor.extension.ai-image.input-text.tooltip' | dm }}"
tooltipZIndex="999999"></i>
</p>
</div>
Expand All @@ -50,14 +50,14 @@
</div>
<dot-field-validation-message
[field]="promptControl"
message="The information provided is insufficient" />
message=" {{ 'block-editor.common.input-prompt-required-error' | dm }}" />

<button
class="p-button-outlined"
[loading]="isLoading"
(click)="generateImage()"
icon="pi pi-send"
label="Generate"
label="{{ 'block-editor.common.generate' | dm }}"
pButton
type="submit"></button>
</form>
Expand All @@ -84,11 +84,10 @@

<div class="flex gap-1">
<p class="text-center">
<strong>Auto-Generate</strong> an Image based on the content created within the
Block Editor.
{{ 'block-editor.extension.ai-image.auto-text.title' | dm }}
<i
class="pi pi-info-circle"
pTooltip="Describe the size, color palette, style, mood, etc."
pTooltip="{{ 'block-editor.extension.ai-image.auto-text.tooltip' | dm }}"
tooltipZIndex="999999"></i>
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Injectable } from '@angular/core';

import { formatMessage } from '@dotcms/utils';

// Move all this translations to Language.properties
export const MessageServiceMock: Record<string, string> = {
'block-editor.common.accept': 'Accept',
'block-editor.common.delete': 'Delete',
'block-editor.common.regenerate': 'Regenerate',
'block-editor.common.pending': 'Pending',
'block-editor.common.generate': 'Generate',
'block-editor.common.input-prompt-required-error': 'The information provided is insufficient',

'block-editor.extension.ai-content.ask-ai-to-write-something': 'Ask AI to write something',

'block-editor.extension.ai-image.dialog-title': 'Generate AI Image',

'block-editor.extension.ai-image.input-text.title':
'Generate an AI image based on your input and requests.',
'block-editor.extension.ai-image.input-text.placeholder':
'Create a realistic image of a cow in the snow',
'block-editor.extension.ai-image.input-text.tooltip':
'Describe the type of image you want to generate.',

'block-editor.extension.ai-image.auto-text.title':
'Auto-Generate an Image based on the content created within the Block Editor.',
'block-editor.extension.ai-image.auto-text.tooltip':
'Describe the size, color palette, style, mood, etc.',
'block-editor.extension.ai-image.auto-text.placeholder':
'E.g. 1200x800px, vibrant colors, impressionistic, adventurous.'
};

@Injectable()
export class DotMessageServiceMock {
init() {
// fake init
}
get(key: string, ...args: string[]): string {
return MessageServiceMock[key]
? args.length
? formatMessage(MessageServiceMock[key], args)
: MessageServiceMock[key]
: key;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Observable, of } from 'rxjs';

import { delay } from 'rxjs/operators';

const DEFAULT_DELAY = 2000;
export class DotAiServiceMock {
getIAContent(): Observable<string> {
generateContent(): Observable<string> {
return of(
`
Title: Understanding the Inner Workings of an Internal Combustion Engine\n\nIntroduction:\nAn internal combustion engine, commonly known as a "motor," is a fundamental component powering various modes of transportation today. Whether it is used in cars, motorcycles, or even power generators, this remarkable mechanical device plays a pivotal role in our modern society. In this post, we will delve into the intricate workings of an internal combustion engine, shedding light on its main components and the amazing combustion process that propels our vehicles forward.\n\n1. The Components:\nTypically consisting of multiple intricate parts, an internal combustion engine can be simplified into four main components:\n\na. Cylinder Block:\nA robust casing houses the cylinder, piston, and other associated components. This block not only provides structural strength but also assists in dissipating the heat produced during the engine's operation.\n\nb. Pistons:\nPlaced inside each cylinder, pistons move up and down during the engine's operation. Connected to the crankshaft, these essential components transform the linear motion of the pistons into the rotary motion necessary to turn the wheels.\n\nc. Valves:\nIntake and exhaust valves control the flow of air-fuel mixture into the combustion chamber and the expulsion of combustion byproducts. These valves play a crucial role in optimizing engine performance and ensuring efficient fuel combustion.\n\nd. Spark Plugs:\nElectrically ignited by the engine control unit (ECU), spark plugs generate a spark within the combustion chamber, initiating the combustion process. This controlled ignition ensures the synchronized release of energy within the engine.\n\n2. The Combustion Process:\nThe combustion process within an internal combustion engine can be summarized into four essential steps:\n\na. Intake:\nWith the intake valve open, a carefully regulated mixture of air and fuel is drawn into the combustion chamber during the piston's downward stroke.\n\nb. Compression:\nDuring the upward stroke of the piston, the intake valve closes, sealing the combustion chamber. The piston compresses the air-fuel mixture, significantly increasing its pressure and temperature.\n\nc. Combustion and Power Stroke:\nAt the desired moment, the spark plug sparks, igniting the compressed air-fuel mixture. This rapid combustion creates an explosion, generating a force that drives the piston back down, converting the expanding high-pressure gases into mechanical energy.\n\nd. Exhaust:\nWhen the piston reaches the bottom of its stroke, the exhaust valve opens, allowing the expulsion of spent gases resulting from the combustion process. The piston then moves back up to restart the cycle, and the process repeats.\n\nConclusion:\nThe internal combustion engine's marvel lies in its ability to convert chemical energy stored in fuel into mechanical energy, enabling the propulsion of vehicles we rely on daily. Understanding its intricate components and the combustion process that takes place within can help us appreciate the engineering brilliance behind this staple technology. From the rhythmic motion of pistons to the precisely timed ignition of fuel, every aspect of an internal combustion engine harmoniously collaborates to fuel our ever-advancing world
`
);
).pipe(delay(DEFAULT_DELAY));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const headers = new HttpHeaders({

@Injectable()
export class DotAiService {

constructor(private http: HttpClient) {}

generateContent(prompt: string): Observable<string> {
Expand Down
Loading
Loading