diff --git a/modules/app/src/main/resources/admin/tools/main/main.html b/modules/app/src/main/resources/admin/tools/main/main.html
index b12fbe9015..188aa063af 100644
--- a/modules/app/src/main/resources/admin/tools/main/main.html
+++ b/modules/app/src/main/resources/admin/tools/main/main.html
@@ -43,6 +43,7 @@
{{#aiTranslatorAssetsUrl}}
+
{{/aiTranslatorAssetsUrl}}
diff --git a/modules/lib/src/main/resources/assets/js/app/ai/AI.ts b/modules/lib/src/main/resources/assets/js/app/ai/AI.ts
index 72f8118b46..952954416f 100644
--- a/modules/lib/src/main/resources/assets/js/app/ai/AI.ts
+++ b/modules/lib/src/main/resources/assets/js/app/ai/AI.ts
@@ -2,18 +2,19 @@ import {AiHelper} from '@enonic/lib-admin-ui/ai/AiHelper';
import {AiHelperState} from '@enonic/lib-admin-ui/ai/AiHelperState';
import {ApplicationConfig} from '@enonic/lib-admin-ui/application/ApplicationConfig';
import {PropertyTree} from '@enonic/lib-admin-ui/data/PropertyTree';
+import {DefaultErrorHandler} from '@enonic/lib-admin-ui/DefaultErrorHandler';
+import {Locale} from '@enonic/lib-admin-ui/locale/Locale';
import {IsAuthenticatedRequest} from '@enonic/lib-admin-ui/security/auth/IsAuthenticatedRequest';
import {LoginResult} from '@enonic/lib-admin-ui/security/auth/LoginResult';
import {CONFIG} from '@enonic/lib-admin-ui/util/Config';
import {StringHelper} from '@enonic/lib-admin-ui/util/StringHelper';
import {Content} from '../content/Content';
import {ContentType} from '../inputtype/schema/ContentType';
+import {GetLocalesRequest} from '../resource/GetLocalesRequest';
+import {ContentData, ContentLanguage, ContentSchema} from './event/data/AiData';
import {EnonicAiAppliedData} from './event/data/EnonicAiAppliedData';
-import {ContentData} from './event/data/EnonicAiContentData';
import {EnonicAiContentOperatorSetupData} from './event/data/EnonicAiContentOperatorSetupData';
import {EnonicAiTranslatorSetupData} from './event/data/EnonicAiTranslatorSetupData';
-import {AiContentOperatorDialogShownEvent} from './event/incoming/AiContentOperatorDialogShownEvent';
-import {AiContentOperatorRenderedEvent} from './event/incoming/AiContentOperatorRenderedEvent';
import {AiContentOperatorResultAppliedEvent} from './event/incoming/AiContentOperatorResultAppliedEvent';
import {AiTranslatorCompletedEvent} from './event/incoming/AiTranslatorCompletedEvent';
import {AiTranslatorStartedEvent} from './event/incoming/AiTranslatorStartedEvent';
@@ -38,7 +39,6 @@ interface EnonicAi {
setup(setupData: EnonicAiTranslatorSetupData): void;
render(container: HTMLElement): void;
translate(language?: string): Promise;
- isAvailable(): boolean;
}
}
@@ -53,12 +53,14 @@ export class AI {
private static instance: AI;
- private content: Content;
-
private currentData: ContentData | undefined;
+ private content: Content;
+
private contentType: ContentType;
+ private locales: Locale[];
+
private instructions: Record;
private resultReceivedListeners: ((data: EnonicAiAppliedData) => void)[] = [];
@@ -71,8 +73,6 @@ export class AI {
return;
}
- AiContentOperatorRenderedEvent.on(this.showContentOperatorEventListener);
- AiContentOperatorDialogShownEvent.on(this.showContentOperatorEventListener);
AiContentOperatorResultAppliedEvent.on(this.applyContentOperatorEventListener);
AiTranslatorStartedEvent.on(this.translatorStartedEventListener);
AiTranslatorCompletedEvent.on(this.translatorCompletedEventListener);
@@ -85,33 +85,42 @@ export class AI {
const fullName = currentUser.getDisplayName();
const names = fullName.split(' ').map(word => word.substring(0, 1));
const shortName = (names.length >= 2 ? names.join('') : fullName).substring(0, 2).toUpperCase();
+ const user = {fullName, shortName} as const;
+ new AiContentOperatorConfigureEvent({user}).fire();
+ }).catch(DefaultErrorHandler.handle);
- new AiContentOperatorConfigureEvent({
- user: {
- fullName,
- shortName,
- }
- }).fire();
- });
+ new GetLocalesRequest().sendAndParse().then((locales) => {
+ this.setLocales(locales);
+ }).catch(DefaultErrorHandler.handle);
}
static get(): AI {
return AI.instance ?? (AI.instance = new AI());
}
- setContentContext(content: Content): void {
+ setContent(content: Content): void {
this.content = content;
+ new AiUpdateDataEvent({
+ data: this.createContentData(),
+ language: this.createContentLanguage(),
+ }).fire();
this.checkAndNotifyReady();
}
- setContentTypeContext(contentType: ContentType): void {
+ setContentType(contentType: ContentType): void {
this.contentType = contentType;
+ new AiUpdateDataEvent({schema: this.createContentSchema()}).fire();
this.checkAndNotifyReady();
}
setCurrentData(data: ContentData): void {
this.currentData = data;
- new AiUpdateDataEvent({data}).fire();
+ new AiUpdateDataEvent({data: this.createContentData()}).fire();
+ }
+
+ setLocales(locales: Locale[]): void {
+ this.locales = locales;
+ new AiUpdateDataEvent({language: this.createContentLanguage()}).fire();
}
updateInstructions(configs: ApplicationConfig[]): void {
@@ -163,12 +172,12 @@ export class AI {
this.getContentOperator()?.render(container);
}
- translate(language: string): Promise {
- return this.getTranslator()?.translate(language) ?? Promise.resolve(false);
+ renderTranslator(container: HTMLElement): void {
+ this.getTranslator()?.render(container);
}
- canTranslate(): boolean {
- return this.getTranslator()?.isAvailable() ?? false;
+ translate(language: string): Promise {
+ return this.getTranslator()?.translate(language) ?? Promise.resolve(false);
}
private translatorStartedEventListener = (event: AiTranslatorStartedEvent) => {
@@ -181,23 +190,32 @@ export class AI {
helper?.setState(AiHelperState.COMPLETED);
};
- private showContentOperatorEventListener = () => {
- new AiUpdateDataEvent({
- data: {
- fields: this.content.getContentData().toJson(),
- topic: this.content.getDisplayName(),
- language: this.content.getLanguage(),
- },
- schema: {
- form: this.contentType.getForm().toJson(),
- name: this.contentType.getDisplayName()
- },
- }).fire();
+ private createContentData(): ContentData | undefined {
+ // TODO: Add structuredClone, when target upgraded to ES2022
+ return this.currentData || (this.content && {
+ fields: this.content.getContentData().toJson(),
+ topic: this.content.getDisplayName(),
+ });
+ }
- if (this.currentData) {
- new AiUpdateDataEvent({data: this.currentData}).fire();
+ private createContentLanguage(): ContentLanguage | undefined {
+ if (!this.content) {
+ return;
}
- };
+
+ const tag = this.content.getLanguage();
+ const locale = this.locales?.find(l => l.getTag() === tag);
+ const name = locale ? locale.getDisplayName() : tag;
+
+ return {tag, name};
+ }
+
+ private createContentSchema(): ContentSchema | undefined {
+ return this.contentType && {
+ form: this.contentType.getForm().toJson(),
+ name: this.contentType.getDisplayName(),
+ };
+ }
private applyContentOperatorEventListener = (event: AiContentOperatorResultAppliedEvent) => {
const {topic} = event.result;
@@ -226,7 +244,7 @@ export class AI {
}
private isReady(): boolean {
- return this.content != null && this.contentType != null && this.instructions != null;
+ return this.content != null && this.contentType != null && this.instructions != null && this.locales != null;
}
private checkAndNotifyReady(): void {
diff --git a/modules/lib/src/main/resources/assets/js/app/ai/event/data/AiData.ts b/modules/lib/src/main/resources/assets/js/app/ai/event/data/AiData.ts
new file mode 100644
index 0000000000..cc81a74bc8
--- /dev/null
+++ b/modules/lib/src/main/resources/assets/js/app/ai/event/data/AiData.ts
@@ -0,0 +1,23 @@
+import {PropertyArrayJson} from '@enonic/lib-admin-ui/data/PropertyArrayJson';
+import {FormJson} from '@enonic/lib-admin-ui/form/json/FormJson';
+
+export interface AiData {
+ data?: ContentData;
+ schema?: ContentSchema;
+ language?: ContentLanguage;
+}
+
+export interface ContentData {
+ fields: PropertyArrayJson[];
+ topic: string;
+}
+
+export interface ContentSchema {
+ form: FormJson;
+ name: string;
+}
+
+export interface ContentLanguage {
+ tag: string;
+ name: string;
+}
diff --git a/modules/lib/src/main/resources/assets/js/app/ai/event/data/EnonicAiContentData.ts b/modules/lib/src/main/resources/assets/js/app/ai/event/data/EnonicAiContentData.ts
deleted file mode 100644
index e437feb02a..0000000000
--- a/modules/lib/src/main/resources/assets/js/app/ai/event/data/EnonicAiContentData.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import {PropertyArrayJson} from '@enonic/lib-admin-ui/data/PropertyArrayJson';
-import {FormJson} from '@enonic/lib-admin-ui/form/json/FormJson';
-
-export interface EnonicAiContentData {
- data: ContentData;
- schema?: {
- form: FormJson;
- name: string;
- },
-}
-
-export interface ContentData {
- fields: PropertyArrayJson[];
- topic: string;
- language: string;
-}
diff --git a/modules/lib/src/main/resources/assets/js/app/ai/event/incoming/AiContentOperatorDialogShownEvent.ts b/modules/lib/src/main/resources/assets/js/app/ai/event/incoming/AiContentOperatorDialogShownEvent.ts
deleted file mode 100644
index 8e3956acac..0000000000
--- a/modules/lib/src/main/resources/assets/js/app/ai/event/incoming/AiContentOperatorDialogShownEvent.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import {ClassHelper} from '@enonic/lib-admin-ui/ClassHelper';
-import {Event} from '@enonic/lib-admin-ui/event/Event';
-
-export class AiContentOperatorDialogShownEvent
- extends Event {
-
- private constructor() {
- super();
- }
-
- static on(handler: (event: AiContentOperatorDialogShownEvent) => void) {
- Event.bind(ClassHelper.getFullName(this), handler);
- }
-
- static un(handler?: (event: AiContentOperatorDialogShownEvent) => void) {
- Event.unbind(ClassHelper.getFullName(this), handler);
- }
-
-}
diff --git a/modules/lib/src/main/resources/assets/js/app/ai/event/incoming/AiContentOperatorRenderedEvent.ts b/modules/lib/src/main/resources/assets/js/app/ai/event/incoming/AiContentOperatorRenderedEvent.ts
deleted file mode 100644
index c855e05b69..0000000000
--- a/modules/lib/src/main/resources/assets/js/app/ai/event/incoming/AiContentOperatorRenderedEvent.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import {ClassHelper} from '@enonic/lib-admin-ui/ClassHelper';
-import {Event} from '@enonic/lib-admin-ui/event/Event';
-
-export class AiContentOperatorRenderedEvent
- extends Event {
-
- private constructor() {
- super();
- }
-
- static on(handler: (event: AiContentOperatorRenderedEvent) => void) {
- Event.bind(ClassHelper.getFullName(this), handler);
- }
-
- static un(handler?: (event: AiContentOperatorRenderedEvent) => void) {
- Event.unbind(ClassHelper.getFullName(this), handler);
- }
-
-}
diff --git a/modules/lib/src/main/resources/assets/js/app/ai/event/incoming/AiTranslatorDialogShownEvent.ts b/modules/lib/src/main/resources/assets/js/app/ai/event/outgoing/AiTranslatorOpenDialogEvent.ts
similarity index 55%
rename from modules/lib/src/main/resources/assets/js/app/ai/event/incoming/AiTranslatorDialogShownEvent.ts
rename to modules/lib/src/main/resources/assets/js/app/ai/event/outgoing/AiTranslatorOpenDialogEvent.ts
index 208ec0eab4..6fb9f25ef3 100644
--- a/modules/lib/src/main/resources/assets/js/app/ai/event/incoming/AiTranslatorDialogShownEvent.ts
+++ b/modules/lib/src/main/resources/assets/js/app/ai/event/outgoing/AiTranslatorOpenDialogEvent.ts
@@ -1,18 +1,14 @@
import {ClassHelper} from '@enonic/lib-admin-ui/ClassHelper';
import {Event} from '@enonic/lib-admin-ui/event/Event';
-export class AiTranslatorDialogShownEvent
+export class AiTranslatorOpenDialogEvent
extends Event {
- private constructor() {
- super();
- }
-
- static on(handler: (event: AiTranslatorDialogShownEvent) => void) {
+ static on(handler: (event: AiTranslatorOpenDialogEvent) => void) {
Event.bind(ClassHelper.getFullName(this), handler);
}
- static un(handler?: (event: AiTranslatorDialogShownEvent) => void) {
+ static un(handler?: (event: AiTranslatorOpenDialogEvent) => void) {
Event.unbind(ClassHelper.getFullName(this), handler);
}
diff --git a/modules/lib/src/main/resources/assets/js/app/ai/event/outgoing/AiUpdateDataEvent.ts b/modules/lib/src/main/resources/assets/js/app/ai/event/outgoing/AiUpdateDataEvent.ts
index c3f4d48284..c339cf9b8c 100644
--- a/modules/lib/src/main/resources/assets/js/app/ai/event/outgoing/AiUpdateDataEvent.ts
+++ b/modules/lib/src/main/resources/assets/js/app/ai/event/outgoing/AiUpdateDataEvent.ts
@@ -1,19 +1,19 @@
import {ClassHelper} from '@enonic/lib-admin-ui/ClassHelper';
import {Event} from '@enonic/lib-admin-ui/event/Event';
-import {EnonicAiContentData} from '../data/EnonicAiContentData';
+import {AiData} from '../data/AiData';
export class AiUpdateDataEvent
extends Event {
- private readonly payload: EnonicAiContentData;
+ private readonly payload: AiData;
- constructor(data: EnonicAiContentData) {
+ constructor(data: AiData) {
super();
this.payload = data;
}
- getData(): EnonicAiContentData {
+ getData(): AiData {
return this.payload;
}
diff --git a/modules/lib/src/main/resources/assets/js/app/wizard/ContentWizardPanel.ts b/modules/lib/src/main/resources/assets/js/app/wizard/ContentWizardPanel.ts
index 56e22729a0..cd04470b9f 100644
--- a/modules/lib/src/main/resources/assets/js/app/wizard/ContentWizardPanel.ts
+++ b/modules/lib/src/main/resources/assets/js/app/wizard/ContentWizardPanel.ts
@@ -11,6 +11,7 @@ import {Property} from '@enonic/lib-admin-ui/data/Property';
import {PropertyTree} from '@enonic/lib-admin-ui/data/PropertyTree';
import {PropertyTreeComparator} from '@enonic/lib-admin-ui/data/PropertyTreeComparator';
import {DefaultErrorHandler} from '@enonic/lib-admin-ui/DefaultErrorHandler';
+import {Body} from '@enonic/lib-admin-ui/dom/Body';
import {DivEl} from '@enonic/lib-admin-ui/dom/DivEl';
import {LangDirection} from '@enonic/lib-admin-ui/dom/Element';
import {Form, FormBuilder} from '@enonic/lib-admin-ui/form/Form';
@@ -51,6 +52,7 @@ import {LiveEditModel} from '../../page-editor/LiveEditModel';
import {Permission} from '../access/Permission';
import {AI} from '../ai/AI';
import {EnonicAiAppliedData} from '../ai/event/data/EnonicAiAppliedData';
+import {AiTranslatorOpenDialogEvent} from '../ai/event/outgoing/AiTranslatorOpenDialogEvent';
import {MovedContentItem} from '../browse/MovedContentItem';
import {CompareStatus} from '../content/CompareStatus';
import {Content, ContentBuilder} from '../content/Content';
@@ -96,7 +98,6 @@ import {GetApplicationsRequest} from '../resource/GetApplicationsRequest';
import {GetApplicationXDataRequest} from '../resource/GetApplicationXDataRequest';
import {GetContentByIdRequest} from '../resource/GetContentByIdRequest';
import {GetContentXDataRequest} from '../resource/GetContentXDataRequest';
-import {GetLocalesRequest} from '../resource/GetLocalesRequest';
import {GetPageTemplateByKeyRequest} from '../resource/GetPageTemplateByKeyRequest';
import {IsRenderableRequest} from '../resource/IsRenderableRequest';
import {Router} from '../Router';
@@ -316,7 +317,6 @@ export class ContentWizardPanel
AI.get().setCurrentData({
fields: this.contentWizardStepForm.getData().toJson(),
topic: this.getWizardHeader().getDisplayName(),
- language: this.peristedLanguage,
});
}, 300);
@@ -587,7 +587,7 @@ export class ContentWizardPanel
this.wizardHeader.setName(existing.getName().toString());
}
- AI.get().setContentTypeContext(this.contentType);
+ AI.get().setContentType(this.contentType);
AI.get().updateInstructions(this.getApplicationsConfigs());
return this.loadAndSetPageState(loader.content?.getPage()?.clone());
@@ -1929,10 +1929,7 @@ export class ContentWizardPanel
if (this.params.localized) {
this.onRendered(() => {
NotifyManager.get().showFeedback(i18n('notify.content.localized'));
-
- if (this.isTranslatable()) {
- this.openTranslateConfirmationDialog();
- }
+ this.renderAndOpenTranslatorDialog();
});
}
@@ -2622,7 +2619,7 @@ export class ContentWizardPanel
this.contentAfterLayout = this.getPersistedItem();
this.wizardHeader?.setPersistedPath(newPersistedItem);
- AI.get().setContentContext(newPersistedItem);
+ AI.get().setContent(newPersistedItem);
}
isHeaderValidForSaving(): boolean {
@@ -2698,10 +2695,7 @@ export class ContentWizardPanel
}
isTranslatable(): boolean {
- const content = this.getContent();
-
- return AI.get().canTranslate() &&
- (this.isContentExistsInParentProject() && content.hasOriginProject()) &&
+ return this.isContentExistsInParentProject() && this.getContent().hasOriginProject() &&
!!ProjectContext.get().getProject().getLanguage();
}
@@ -2852,19 +2846,17 @@ export class ContentWizardPanel
this.formsContexts.set('live', liveFormContext);
}
- openTranslateConfirmationDialog(): void {
- new GetLocalesRequest().sendAndParse().then((locales) => {
- const layerLang = ProjectContext.get().getProject().getLanguage();
- const locale = locales.find(l => l.getTag() === layerLang);
- const displayLang = locale ? StringHelper.format('{0} ({1})', locale.getDisplayName(), locale.getProcessedTag()) : layerLang;
- const translateDialog = new ConfirmationDialog();
- translateDialog.setQuestion(i18n('dialog.translate.question', displayLang));
- translateDialog.setYesCallback(() => {
- if (AI.get().canTranslate()) {
- void AI.get().translate(layerLang);
- }
- });
- translateDialog.open();
- }).catch(DefaultErrorHandler.handle);
+ renderAndOpenTranslatorDialog(): void {
+ if (!this.isTranslatable()) {
+ return;
+ }
+
+ const aiTranslatorContainer = new DivEl('ai-translator-container');
+ Body.get().appendChild(aiTranslatorContainer);
+ AI.get().renderTranslator(aiTranslatorContainer.getHTMLElement());
+
+ AI.get().whenReady(() => {
+ new AiTranslatorOpenDialogEvent().fire();
+ });
}
}
diff --git a/modules/lib/src/main/resources/assets/js/app/wizard/ContentWizardToolbar.ts b/modules/lib/src/main/resources/assets/js/app/wizard/ContentWizardToolbar.ts
index d93ef9a31d..decb903d83 100644
--- a/modules/lib/src/main/resources/assets/js/app/wizard/ContentWizardToolbar.ts
+++ b/modules/lib/src/main/resources/assets/js/app/wizard/ContentWizardToolbar.ts
@@ -135,7 +135,7 @@ export class ContentWizardToolbar
private addEnonicAiContentOperatorButton(): void {
AI.get().whenReady(() => {
if (AI.get().has('contentOperator')) {
- this.aiContentOperatorContainer = new DivEl('ai-assistant-container');
+ this.aiContentOperatorContainer = new DivEl('ai-content-operator-container');
this.addElement(this.aiContentOperatorContainer);
this.addContentOperatorIntoCollaborationBlock();
AI.get().renderContentOperator(this.aiContentOperatorContainer.getHTMLElement());
diff --git a/modules/lib/src/main/resources/assets/js/app/wizard/action/LocalizeContentAction.ts b/modules/lib/src/main/resources/assets/js/app/wizard/action/LocalizeContentAction.ts
index fc4135a253..8ce254f1a3 100644
--- a/modules/lib/src/main/resources/assets/js/app/wizard/action/LocalizeContentAction.ts
+++ b/modules/lib/src/main/resources/assets/js/app/wizard/action/LocalizeContentAction.ts
@@ -22,9 +22,7 @@ export class LocalizeContentAction
new LocalizeContentsRequest([contentId], language).sendAndParse().then(() => {
NotifyManager.get().showFeedback(i18n('notify.content.localized'));
wizardPanel.setEnabled(true);
- if (wizardPanel.isTranslatable()) {
- wizardPanel.openTranslateConfirmationDialog();
- }
+ wizardPanel.renderAndOpenTranslatorDialog();
}).catch(DefaultErrorHandler.handle);
});
}
diff --git a/modules/lib/src/main/resources/i18n/dialogs.properties b/modules/lib/src/main/resources/i18n/dialogs.properties
index 9f2035c298..df0b7d741b 100644
--- a/modules/lib/src/main/resources/i18n/dialogs.properties
+++ b/modules/lib/src/main/resources/i18n/dialogs.properties
@@ -150,7 +150,6 @@ dialog.state.fail=Failed to check for errors...
dialog.state.checking=Running checks...
dialog.state.editing=Click apply when selection is completed
dialog.state.resolved=Sweet, everything is good to go
-dialog.translate.question=Translate all texts to "{0}"?
#
# HTML Area Dialogs
#