/**
*
* 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)
;
Source