Skip to content

Commit

Permalink
chore(edit-content): generate images with ai #30062
Browse files Browse the repository at this point in the history
  • Loading branch information
nicobytes committed Oct 11, 2024
1 parent 3c5afd7 commit a6e98d3
Show file tree
Hide file tree
Showing 14 changed files with 161 additions and 152 deletions.
4 changes: 3 additions & 1 deletion core-web/libs/block-editor/src/lib/block-editor.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ConfirmationService } from 'primeng/api';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DialogModule } from 'primeng/dialog';
import { DynamicDialogModule } from 'primeng/dynamicdialog';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { PaginatorModule } from 'primeng/paginator';

Expand Down Expand Up @@ -57,6 +58,7 @@ const initTranslations = (dotMessageService: DotMessageService) => {
ReactiveFormsModule,
SharedModule,
PrimengModule,
DynamicDialogModule,
AssetFormModule,
DotFieldRequiredDirective,
UploadPlaceholderComponent,
Expand All @@ -66,7 +68,7 @@ const initTranslations = (dotMessageService: DotMessageService) => {
DialogModule,
InputTextareaModule,
PaginatorModule,
DotSpinnerModule
DotSpinnerModule,
],
declarations: [
EditorDirective,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

import { DialogService } from 'primeng/dynamicdialog';

import { debounceTime, map, take, takeUntil } from 'rxjs/operators';

import { AnyExtension, Content, Editor, JSONContent } from '@tiptap/core';
Expand Down Expand Up @@ -75,11 +77,12 @@ import {
templateUrl: './dot-block-editor.component.html',
styleUrls: ['./dot-block-editor.component.scss'],
providers: [
DialogService,
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DotBlockEditorComponent),
multi: true
}
},
]
})
export class DotBlockEditorComponent implements OnInit, OnDestroy, ControlValueAccessor {
Expand Down Expand Up @@ -117,6 +120,7 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy, ControlValueA
private readonly cd = inject(ChangeDetectorRef);
private readonly dotPropertiesService = inject(DotPropertiesService);
private isAIPluginInstalled$: Observable<boolean>;
readonly #dialogService = inject(DialogService);

constructor(
private readonly viewContainerRef: ViewContainerRef,
Expand Down Expand Up @@ -458,7 +462,7 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy, ControlValueA
if (isAIPluginInstalled) {
extensions.push(
AIContentPromptExtension(this.viewContainerRef),
AIImagePromptExtension(this.viewContainerRef)
AIImagePromptExtension(this.#dialogService)
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { PluginKey } from 'prosemirror-state';

import { ViewContainerRef } from '@angular/core';
import { DialogService } from 'primeng/dynamicdialog';

import { Extension } from '@tiptap/core';

import { DotAIImagePromptComponent } from '@dotcms/ui';

import { aiImagePromptPlugin } from './ai-image-prompt.plugin';

export interface AIImagePromptOptions {
Expand All @@ -28,7 +26,7 @@ export const AI_IMAGE_PROMPT_PLUGIN_KEY = new PluginKey('aiImagePrompt-form');

export const AI_IMAGE_PROMPT_EXTENSION_NAME = 'aiImagePrompt';

export const AIImagePromptExtension = (viewContainerRef: ViewContainerRef) => {
export const AIImagePromptExtension = (dialogService: DialogService) => {
return Extension.create<AIImagePromptOptions>({
name: AI_IMAGE_PROMPT_EXTENSION_NAME,

Expand Down Expand Up @@ -71,15 +69,11 @@ export const AIImagePromptExtension = (viewContainerRef: ViewContainerRef) => {
},

addProseMirrorPlugins() {
const component = viewContainerRef.createComponent(DotAIImagePromptComponent);
component.changeDetectorRef.detectChanges();

return [
aiImagePromptPlugin({
pluginKey: this.options.pluginKey,
editor: this.editor,
element: component.location.nativeElement,
component: component
dialogService: dialogService
})
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ import { EditorView } from 'prosemirror-view';
import { Subject } from 'rxjs';
import { Instance, Props } from 'tippy.js';

import { ComponentRef } from '@angular/core';
import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';

import { filter, skip, takeUntil } from 'rxjs/operators';
import { takeUntil } from 'rxjs/operators';

import { Editor } from '@tiptap/core';

import { DotAIImagePromptComponent, DotAiImagePromptStore } from '@dotcms/ui';
import { DotGeneratedAIImage } from '@dotcms/dotcms-models';
import { DotAIImagePromptComponent } from '@dotcms/ui';

import { AI_IMAGE_PROMPT_PLUGIN_KEY } from './ai-image-prompt.extension';

interface AIImagePromptProps {
pluginKey: PluginKey;
editor: Editor;
element: HTMLElement;
component: ComponentRef<DotAIImagePromptComponent>;
dialogService: DialogService;
}

interface PluginState {
Expand All @@ -34,8 +34,6 @@ export class AIImagePromptView {

public node: Node;

public element: HTMLElement;

public view: EditorView;

public tippy: Instance | undefined;
Expand All @@ -44,58 +42,24 @@ export class AIImagePromptView {

public pluginKey: PluginKey;

public component: ComponentRef<DotAIImagePromptComponent>;

private destroy$ = new Subject<boolean>();

private store: DotAiImagePromptStore;
#dialogService: DialogService | null = null;
#dialogRef: DynamicDialogRef | null = null;

/**
* Creates a new instance of the AIImagePromptView class.
* @param {AIImagePromptViewProps} props - The properties for the component.
*/
constructor(props: AIImagePromptViewProps) {
const { editor, element, view, pluginKey, component } = props;

const { editor, view, pluginKey, dialogService } = props;

this.editor = editor;
this.element = element;
this.view = view;

this.element.remove();
this.pluginKey = pluginKey;
this.component = component;

this.store = this.component.injector.get(DotAiImagePromptStore);

/**
* Subscription fired by the store when the dialog change of the state
* Handle the manual close of the dialog (esc, click outside, x button)
*/
this.store.isOpenDialog$
.pipe(
skip(1),
filter((value) => value === false),
takeUntil(this.destroy$)
)
.subscribe(() => {
this.editor.commands.closeImagePrompt();
});

/**
* Subscription fired by the store when image is seleted
* from the gallery to be inserted it into the editor
*/
this.store.selectedImage$
.pipe(
filter((selectedImage) => !!selectedImage),
takeUntil(this.destroy$)
)
.subscribe((selectedImage) => {
this.editor.chain().insertImage(selectedImage.response.contentlet).run();
// A new image is being inserted
this.store.hideDialog();
this.editor.chain().closeImagePrompt().run();
});
this.#dialogService = dialogService;
}

update(view: EditorView, prevState: EditorState) {
Expand All @@ -104,15 +68,38 @@ export class AIImagePromptView {

// show the dialog
if (next.aIImagePromptOpen && prev.aIImagePromptOpen === false) {
this.store.showDialog(this.editor.getText());
}
const context = this.editor.getText();

this.#dialogRef = this.#dialogService.open(DotAIImagePromptComponent, {
header: 'AI Image Prompt',
appendTo: 'body',
closeOnEscape: false,
draggable: false,
keepInViewport: false,
maskStyleClass: 'p-dialog-mask-transparent-ai',
resizable: false,
modal: true,
width: '90%',
style: { 'max-width': '1040px' },
data: { context }
});

this.#dialogRef.onClose
.pipe(takeUntil(this.destroy$))
.subscribe((selectedImage: DotGeneratedAIImage) => {
if (selectedImage) {
this.editor.chain().insertImage(selectedImage.response.contentlet).run();
}

// hide the dialog handled by isOpenDialog$ subscription
this.editor.commands.closeImagePrompt();
})
}
}

destroy() {
this.destroy$.next(true);
this.destroy$.complete();
this.#dialogRef?.close();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@
background-color: transparent;
-webkit-backdrop-filter: blur($blur-md);
backdrop-filter: blur($blur-md);
padding: 0;
align-items: center;
}

.p-dialog-mask.p-dialog-mask-transparent-ai.p-component-overlay {
background-color: transparent;
-webkit-backdrop-filter: blur($blur-md);
backdrop-filter: blur($blur-md);
padding: 0;
align-items: center;
}

.p-dialog-mask.p-dialog-mask-transparent-nested.p-component-overlay {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@if (vm$ | async; as vm) {
<dot-ai-image-prompt />
<div
[ngClass]="{
'binary-field__container--uploading': vm.status === BinaryFieldStatus.UPLOADING,
Expand Down
Loading

0 comments on commit a6e98d3

Please sign in to comment.