diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index bc5174e16..288e45aaf 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -26,6 +26,7 @@ import { SECRET_KEYS, secret_state } from '../../secrets.js'; import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js'; import { getMultimodalCaption } from '../shared.js'; import { registerSlashCommand } from '../../slash-commands.js'; +import { resolveVariable } from '../../variables.js'; export { MODULE_NAME }; // Wraps a string into monospace font-face span @@ -589,15 +590,17 @@ async function expandPrompt(prompt) { * Modifies prompt based on auto-expansion and user inputs. * @param {string} prompt Prompt to refine * @param {boolean} allowExpand Whether to allow auto-expansion + * @param {boolean} isNegative Whether the prompt is a negative one * @returns {Promise} Refined prompt */ -async function refinePrompt(prompt, allowExpand) { +async function refinePrompt(prompt, allowExpand, isNegative = false) { if (allowExpand && extension_settings.sd.expand) { prompt = await expandPrompt(prompt); } if (extension_settings.sd.refine_mode) { - const refinedPrompt = await callPopup('

Review and edit the prompt:

Press "Cancel" to abort the image generation.', 'input', prompt.trim(), { rows: 5, okButton: 'Generate' }); + const text = isNegative ? '

Review and edit the negative prompt:

' : '

Review and edit the prompt:

'; + const refinedPrompt = await callPopup(text + 'Press "Cancel" to abort the image generation.', 'input', prompt.trim(), { rows: 5, okButton: 'Continue' }); if (refinedPrompt) { return refinedPrompt; @@ -1967,9 +1970,9 @@ async function generatePicture(args, trigger, message, callback) { eventSource.emit(event_types.FORCE_SET_BACKGROUND, { url: imgUrl, path: imagePath }); if (typeof callbackOriginal === 'function') { - callbackOriginal(prompt, imagePath, generationType); + callbackOriginal(prompt, imagePath, generationType, negativePromptPrefix); } else { - sendMessage(prompt, imagePath, generationType); + sendMessage(prompt, imagePath, generationType, negativePromptPrefix); } }; } @@ -1978,6 +1981,7 @@ async function generatePicture(args, trigger, message, callback) { callback = () => { }; } + const negativePromptPrefix = resolveVariable(args?.negative) || ''; const dimensions = setTypeSpecificDimensions(generationType); let imagePath = ''; @@ -1988,7 +1992,7 @@ async function generatePicture(args, trigger, message, callback) { context.deactivateSendButtons(); hideSwipeButtons(); - imagePath = await sendGenerationRequest(generationType, prompt, characterName, callback); + imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback); } catch (err) { console.trace(err); throw new Error('SD prompt text generation failed.'); @@ -2183,17 +2187,26 @@ async function generatePrompt(quietPrompt) { return processedReply; } -async function sendGenerationRequest(generationType, prompt, characterName = null, callback) { +/** + * Sends a request to image generation endpoint and processes the result. + * @param {number} generationType Type of image generation + * @param {string} prompt Prompt to be used for image generation + * @param {string} additionalNegativePrefix Additional negative prompt to be used for image generation + * @param {string} [characterName] Name of the character + * @param {function} [callback] Callback function to be called after image generation + * @returns + */ +async function sendGenerationRequest(generationType, prompt, additionalNegativePrefix, characterName = null, callback) { const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL]; const prefix = noCharPrefix.includes(generationType) ? extension_settings.sd.prompt_prefix : combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()); + const negativePrefix = noCharPrefix.includes(generationType) + ? extension_settings.sd.negative_prompt + : combinePrefixes(extension_settings.sd.negative_prompt, getCharacterNegativePrefix()); const prefixedPrompt = substituteParams(combinePrefixes(prefix, prompt, '{prompt}')); - - const negativePrompt = substituteParams(noCharPrefix.includes(generationType) - ? extension_settings.sd.negative_prompt - : combinePrefixes(extension_settings.sd.negative_prompt, getCharacterNegativePrefix())); + const negativePrompt = substituteParams(combinePrefixes(additionalNegativePrefix, negativePrefix)); let result = { format: '', data: '' }; const currentChatId = getCurrentChatId(); @@ -2249,7 +2262,7 @@ async function sendGenerationRequest(generationType, prompt, characterName = nul const filename = `${characterName}_${humanizedDateTime()}`; const base64Image = await saveBase64AsFile(result.data, characterName, filename, result.format); - callback ? callback(prompt, base64Image, generationType) : sendMessage(prompt, base64Image, generationType); + callback ? callback(prompt, base64Image, generationType, additionalNegativePrefix) : sendMessage(prompt, base64Image, generationType, additionalNegativePrefix); return base64Image; } @@ -2722,6 +2735,9 @@ async function onComfyOpenWorkflowEditorClick() { $('#sd_comfy_workflow_editor_placeholder_list_custom').append(el); el.find('.sd_comfy_workflow_editor_custom_find').val(placeholder.find); el.find('.sd_comfy_workflow_editor_custom_find').on('input', function () { + if (!(this instanceof HTMLInputElement)) { + return; + } placeholder.find = this.value; el.find('.sd_comfy_workflow_editor_custom_final').text(`"%${this.value}%"`); el.attr('data-placeholder', `${this.value}`); @@ -2730,6 +2746,9 @@ async function onComfyOpenWorkflowEditorClick() { }); el.find('.sd_comfy_workflow_editor_custom_replace').val(placeholder.replace); el.find('.sd_comfy_workflow_editor_custom_replace').on('input', function () { + if (!(this instanceof HTMLInputElement)) { + return; + } placeholder.replace = this.value; saveSettingsDebounced(); }); @@ -2819,7 +2838,14 @@ async function onComfyDeleteWorkflowClick() { onComfyWorkflowChange(); } -async function sendMessage(prompt, image, generationType) { +/** + * Sends a chat message with the generated image. + * @param {string} prompt Prompt used for the image generation + * @param {string} image Base64 encoded image + * @param {number} generationType Generation type of the image + * @param {string} additionalNegativePrefix Additional negative prompt used for the image generation + */ +async function sendMessage(prompt, image, generationType, additionalNegativePrefix) { const context = getContext(); const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`; const message = { @@ -2832,6 +2858,7 @@ async function sendMessage(prompt, image, generationType) { image: image, title: prompt, generationType: generationType, + negative: additionalNegativePrefix, }, }; context.chat.push(message); @@ -2952,6 +2979,7 @@ async function sdMessageButton(e) { const characterFileName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString(); const messageText = message?.mes; const hasSavedImage = message?.extra?.image && message?.extra?.title; + const hasSavedNegative = message?.extra?.negative; if ($icon.hasClass(busyClass)) { console.log('Previous image is still being generated...'); @@ -2963,13 +2991,14 @@ async function sdMessageButton(e) { try { setBusyIcon(true); if (hasSavedImage) { - const prompt = await refinePrompt(message.extra.title, false); + const prompt = await refinePrompt(message.extra.title, false, false); + const negative = hasSavedNegative ? await refinePrompt(message.extra.negative, false, true) : ''; message.extra.title = prompt; const generationType = message?.extra?.generationType ?? generationMode.FREE; console.log('Regenerating an image, using existing prompt:', prompt); dimensions = setTypeSpecificDimensions(generationType); - await sendGenerationRequest(generationType, prompt, characterFileName, saveGeneratedImage); + await sendGenerationRequest(generationType, prompt, negative, characterFileName, saveGeneratedImage); } else { console.log('doing /sd raw last'); @@ -2987,7 +3016,7 @@ async function sdMessageButton(e) { } } - function saveGeneratedImage(prompt, image, generationType) { + function saveGeneratedImage(prompt, image, generationType, negative) { // Some message sources may not create the extra object if (typeof message.extra !== 'object') { message.extra = {}; @@ -2998,6 +3027,7 @@ async function sdMessageButton(e) { message.extra.image = image; message.extra.title = prompt; message.extra.generationType = generationType; + message.extra.negative = negative; appendMediaToMessage(message, $mes); context.saveChat();