Source

client/ux/aiImage/aiImage.js

/**
 * 
 * The aiImage derives from [Field](kiss.ui.Attachment.html).
 * 
 * **AI** image field allows to generate an image with AI.
 * 
 * It's basically an attachment dedicated to store images generated by an AI.
 * 
 * @param {object} config
 * @returns this
 * 
 */
kiss.ux.AiImage = class AiImage extends kiss.ui.Attachment {
    constructor() {
        super()
    }

    /**
     * @ignore
     */
    init(config = {}) {
        config.type = "aiImage"
        config.buttonText = txtTitleCase("generate an image")
        super.init(config)
        return this
    }

    /**
     * Handle click event
     * 
     * @private
     * @ignore
     */
    _initClickEvent() {
        this.onclick = function (event) {
            if (event.target.classList.contains("field-upload-button")) {
                this.showPromptWindow()
            } else if (event.target.classList.contains("display-as-list")) {
                this.renderAs("list")
            } else if (event.target.classList.contains("display-as-thumbnails")) {
                this.renderAs("thumbnails")
            } else if (event.target.classList.contains("display-as-thumbnails-large")) {
                this.renderAs("thumbnails-large")
            }
        }
    }

    /**
     * Add a button to open an AI assistant
     * 
     * @private
     * @ignore
     */
    showPromptWindow() {
        createPanel({
            id: "AI-panel",
            title: txtTitleCase("#image generator"),
            icon: "fas fa-images",
            modal: true,
            closable: true,
            draggable: true,
            width: 500,
            align: "center",
            verticalAlign: "center",

            // Prevent from closing if the user started to work with a prompt
            events: {
                close: (forceClose) => {
                    if (forceClose) return true

                    if ($("prompt").getValue() != "") {
                        createDialog({
                            type: "danger",
                            message: txtTitleCase("are you sure you want to cancel your input?"),
                            buttonOKPosition: "left",
                            action: () => $("AI-panel").close("remove", true)
                        })
                        return false
                    }
                }
            },

            defaultConfig: {
                labelPosition: "top",
                width: "100%"
            },

            items: [
                // IMAGE SIZE
                {
                    id: "size",
                    type: "select",
                    label: txtTitleCase("image format"),
                    value: "1792x1024",
                    allowValuesNotInList: true,
                    options: [{
                        value: "1024x1024",
                        label: txtTitleCase("square")
                    }, {
                        value: "1792x1024",
                        label: txtTitleCase("landscape")
                    }, {
                        value: "1024x1792",
                        label: txtTitleCase("portrait")
                    }]
                },
                // AI PROMPT
                {
                    id: "prompt",
                    type: "textarea",
                    label: txtTitleCase("#AI image instructions"),
                    required: true,
                    rows: 10,
                    value: localStorage.getItem("config-ai-image-prompt")
                },
                // BUTTON TO SEND THE PROMPT
                {
                    type: "button",
                    text: txtTitleCase("generate image..."),
                    icon: "fas fa-bolt",
                    iconColor: "var(--orange)",
                    margin: "20px 0 0 0",
                    height: 40,
                    action: async () => {
                        if (!$("AI-panel").validate()) {
                            return
                        }

                        // Call the OpenAI service
                        const data = $("AI-panel").getData()
                        const result = await this._executePrompt({
                            prompt: data.prompt,
                            size: data.size
                        })

                        // Save the prompt for the next time
                        localStorage.setItem("config-ai-image-prompt", data.prompt)

                        if (!result.success) {
                            createDialog({
                                type: "danger",
                                message: txtTitleCase("#openAI error"),
                                noCancel: true
                            })
                            return
                        }

                        $("AI-panel").close("remove", true)
                    }
                }
            ]
        }).setAnimation({
            name: "jackInTheBox",
            speed: "fast"
        }).render()
    }

    /**
     * Execute the prompt calling OpenAI service
     * 
     * @private
     * @ignore
     * @param {string} prompt 
     * @param {string} size - A size supported by Dall-E (1024x1024, 1792x1024, 1024x1792)
     * @returns {object} The OpenAI service response, or an error
     */
    async _executePrompt({prompt, size}) {
        return await kiss.ajax.request({
            url: "/command/openai/createImageToField",
            method: "post",
            showLoading: true,
            timeout: 3 * 60 * 1000, // Give OpenAI 3mn to answer
            body: JSON.stringify({
                modelId: this.record.model.id,
                recordId: this.record.id,
                fieldId: this.id,
                prompt,
                size
            })
        })
    }
}

// Create a Custom Element
customElements.define("a-aiimage", kiss.ux.AiImage)
const createAiImageField = (config) => document.createElement("a-aiimage").init(config)

;