2023-07-20 20:32:15 +03:00
import {
saveSettingsDebounced ,
systemUserName ,
hideSwipeButtons ,
showSwipeButtons ,
callPopup ,
getRequestHeaders ,
event _types ,
eventSource ,
2023-07-22 23:57:48 +03:00
generateQuietPrompt ,
this _chid ,
2023-09-03 00:41:26 +03:00
getCurrentChatId ,
2023-11-19 00:40:21 +02:00
animation _duration ,
appendMediaToMessage ,
2023-11-19 15:24:43 +02:00
getUserAvatar ,
user _avatar ,
getCharacterAvatar ,
formatCharacterAvatar ,
2023-12-17 22:17:08 +00:00
substituteParams ,
2023-12-02 13:04:51 -05:00
} from '../../../script.js' ;
2024-05-30 00:21:27 +03:00
import { getApiUrl , getContext , extension _settings , doExtrasFetch , modules , renderExtensionTemplateAsync , writeExtensionField } from '../../extensions.js' ;
2023-12-02 13:04:51 -05:00
import { selected _group } from '../../group-chats.js' ;
2024-05-30 00:21:27 +03:00
import { stringFormat , initScrollHeight , resetScrollHeight , getCharaFilename , saveBase64AsFile , getBase64Async , delay , isTrueBoolean , debounce } from '../../utils.js' ;
2023-12-02 13:04:51 -05:00
import { getMessageTimeStamp , humanizedDateTime } from '../../RossAscends-mods.js' ;
import { SECRET _KEYS , secret _state } from '../../secrets.js' ;
import { getNovelUnlimitedImageGeneration , getNovelAnlas , loadNovelSubscriptionData } from '../../nai-settings.js' ;
import { getMultimodalCaption } from '../shared.js' ;
2024-05-12 15:15:05 -04:00
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js' ;
import { SlashCommand } from '../../slash-commands/SlashCommand.js' ;
import { ARGUMENT _TYPE , SlashCommandArgument , SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js' ;
2024-05-06 21:39:07 +03:00
import { resolveVariable } from '../../variables.js' ;
2024-05-30 00:21:27 +03:00
import { debounce _timeout } from '../../constants.js' ;
2023-07-20 20:32:15 +03:00
export { MODULE _NAME } ;
const MODULE _NAME = 'sd' ;
const UPDATE _INTERVAL = 1000 ;
2024-04-21 21:13:50 +03:00
// This is a 1x1 transparent PNG
const PNG _PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' ;
2023-07-20 20:32:15 +03:00
2023-09-03 00:41:26 +03:00
const sources = {
extras : 'extras' ,
horde : 'horde' ,
auto : 'auto' ,
novel : 'novel' ,
2023-10-07 18:30:06 +03:00
vlad : 'vlad' ,
2023-11-06 21:47:00 +02:00
openai : 'openai' ,
2023-11-19 12:18:48 +00:00
comfy : 'comfy' ,
2023-12-18 03:33:05 +02:00
togetherai : 'togetherai' ,
2024-03-30 01:12:29 -03:00
drawthings : 'drawthings' ,
2024-04-04 20:40:47 +03:00
pollinations : 'pollinations' ,
2023-12-02 21:11:06 +02:00
} ;
2023-07-20 20:32:15 +03:00
const generationMode = {
CHARACTER : 0 ,
USER : 1 ,
SCENARIO : 2 ,
RAW _LAST : 3 ,
NOW : 4 ,
FACE : 5 ,
FREE : 6 ,
2023-08-18 00:41:21 +03:00
BACKGROUND : 7 ,
2023-11-19 15:24:43 +02:00
CHARACTER _MULTIMODAL : 8 ,
USER _MULTIMODAL : 9 ,
FACE _MULTIMODAL : 10 ,
2023-12-02 21:11:06 +02:00
} ;
2023-11-19 15:24:43 +02:00
const multimodalMap = {
[ generationMode . CHARACTER ] : generationMode . CHARACTER _MULTIMODAL ,
[ generationMode . USER ] : generationMode . USER _MULTIMODAL ,
[ generationMode . FACE ] : generationMode . FACE _MULTIMODAL ,
2023-12-02 21:11:06 +02:00
} ;
2023-07-20 20:32:15 +03:00
2023-07-22 21:12:23 +03:00
const modeLabels = {
[ generationMode . CHARACTER ] : 'Character ("Yourself")' ,
[ generationMode . FACE ] : 'Portrait ("Your Face")' ,
[ generationMode . USER ] : 'User ("Me")' ,
[ generationMode . SCENARIO ] : 'Scenario ("The Whole Story")' ,
[ generationMode . NOW ] : 'Last Message' ,
[ generationMode . RAW _LAST ] : 'Raw Last Message' ,
2023-08-18 00:41:21 +03:00
[ generationMode . BACKGROUND ] : 'Background' ,
2023-11-19 15:24:43 +02:00
[ generationMode . CHARACTER _MULTIMODAL ] : 'Character (Multimodal Mode)' ,
[ generationMode . FACE _MULTIMODAL ] : 'Portrait (Multimodal Mode)' ,
[ generationMode . USER _MULTIMODAL ] : 'User (Multimodal Mode)' ,
2023-12-02 21:11:06 +02:00
} ;
2023-07-22 21:12:23 +03:00
2023-07-20 20:32:15 +03:00
const triggerWords = {
[ generationMode . CHARACTER ] : [ 'you' ] ,
[ generationMode . USER ] : [ 'me' ] ,
[ generationMode . SCENARIO ] : [ 'scene' ] ,
[ generationMode . RAW _LAST ] : [ 'raw_last' ] ,
[ generationMode . NOW ] : [ 'last' ] ,
[ generationMode . FACE ] : [ 'face' ] ,
2023-08-18 00:41:21 +03:00
[ generationMode . BACKGROUND ] : [ 'background' ] ,
2023-12-02 21:11:06 +02:00
} ;
2023-07-20 20:32:15 +03:00
2023-11-01 22:58:59 +02:00
const messageTrigger = {
2024-05-21 01:11:40 +03:00
activationRegex : /\b(send|mail|imagine|generate|make|create|draw|paint|render)\b.{0,10}\b(pic|picture|image|drawing|painting|photo|photograph)\b(?:\s+of)?(?:\s+(?:a|an|the|this|that|those)?)?(.+)/i ,
2023-11-01 22:58:59 +02:00
specialCases : {
[ generationMode . CHARACTER ] : [ 'you' , 'yourself' ] ,
[ generationMode . USER ] : [ 'me' , 'myself' ] ,
[ generationMode . SCENARIO ] : [ 'story' , 'scenario' , 'whole story' ] ,
[ generationMode . NOW ] : [ 'last message' ] ,
[ generationMode . FACE ] : [ 'your face' , 'your portrait' , 'your selfie' ] ,
[ generationMode . BACKGROUND ] : [ 'background' , 'scene background' , 'scene' , 'scenery' , 'surroundings' , 'environment' ] ,
} ,
2023-12-02 21:11:06 +02:00
} ;
2023-11-01 22:58:59 +02:00
2023-07-22 21:12:23 +03:00
const promptTemplates = {
2023-07-20 20:32:15 +03:00
/*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */
2023-12-02 13:04:51 -05:00
[ generationMode . CHARACTER ] : '[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'full body portrait,\']' ,
2023-07-20 20:32:15 +03:00
//face-specific prompt
2023-12-02 13:04:51 -05:00
[ generationMode . FACE ] : '[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'close up facial portrait,\']' ,
2023-07-20 20:32:15 +03:00
//prompt for only the last message
2023-12-02 13:04:51 -05:00
[ generationMode . USER ] : '[Pause your roleplay and provide a detailed description of {{user}}\'s physical appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'full body portrait,\'. Ignore the rest of the story when crafting this description. Do not roleplay as {{char}} when writing this description, and do not attempt to continue the story.]' ,
[ generationMode . SCENARIO ] : '[Pause your roleplay and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}\'s appearance, and {{char}}\'s surroundings. Do not roleplay while writing this description.]' ,
2023-07-20 20:32:15 +03:00
[ generationMode . NOW ] : ` [Pause your roleplay. Your next response must be formatted as a single comma-delimited list of concise keywords. The list will describe of the visual details included in the last chat message.
Only mention characters by using pronouns ( 'he' , 'his' , 'she' , 'her' , 'it' , 'its' ) or neutral nouns ( 'male' , 'the man' , 'female' , 'the woman' ) .
Ignore non - visible things such as feelings , personality traits , thoughts , and spoken dialog .
Add keywords in this precise order :
a keyword to describe the location of the scene ,
a keyword to mention how many characters of each gender or type are present in the scene ( minimum of two characters :
{ { user } } and { { char } } , example : '2 men ' or '1 man 1 woman ' , '1 man 3 robots' ) ,
keywords to describe the relative physical positioning of the characters to each other ( if a commonly known term for the positioning is known use it instead of describing the positioning in detail ) + 'POV' ,
a single keyword or phrase to describe the primary act taking place in the last chat message ,
keywords to describe { { char } } ' s physical appearance and facial expression ,
keywords to describe { { char } } ' s actions ,
keywords to describe { { user } } ' s physical appearance and actions .
If character actions involve direct physical interaction with another character , mention specifically which body parts interacting and how .
A correctly formatted example response would be :
'(location),(character list by gender),(primary action), (relative character position) POV, (character 1' s description and actions ) , ( character 2 's description and actions)' ] ` ,
2023-12-02 13:04:51 -05:00
[ generationMode . RAW _LAST ] : '[Pause your roleplay and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not roleplay at all in your response. Do not continue the roleplay story.]' ,
[ generationMode . BACKGROUND ] : '[Pause your roleplay and provide a detailed description of {{char}}\'s surroundings in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: location, time of day, weather, lighting, and any other relevant details. Do not include descriptions of characters and non-visual qualities such as names, personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase \'background,\'. Ignore the rest of the story when crafting this description. Do not roleplay as {{user}} when writing this description, and do not attempt to continue the story.]' ,
[ generationMode . FACE _MULTIMODAL ] : 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "close-up portrait".' ,
[ generationMode . CHARACTER _MULTIMODAL ] : 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".' ,
[ generationMode . USER _MULTIMODAL ] : 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".' ,
2023-12-02 21:11:06 +02:00
} ;
2023-07-20 20:32:15 +03:00
2023-10-23 10:41:39 +03:00
const defaultPrefix = 'best quality, absurdres, aesthetic,' ;
2023-10-22 00:10:48 +03:00
const defaultNegative = 'lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry' ;
const defaultStyles = [
{
name : 'Default' ,
negative : defaultNegative ,
prefix : defaultPrefix ,
} ,
] ;
2023-07-20 20:32:15 +03:00
const defaultSettings = {
2023-09-03 00:41:26 +03:00
source : sources . extras ,
2023-07-20 20:32:15 +03:00
// CFG Scale
scale _min : 1 ,
scale _max : 30 ,
scale _step : 0.5 ,
scale : 7 ,
// Sampler steps
steps _min : 1 ,
steps _max : 150 ,
steps _step : 1 ,
steps : 20 ,
2023-11-19 12:18:48 +00:00
// Scheduler
scheduler : 'normal' ,
2023-07-20 20:32:15 +03:00
// Image dimensions (Width & Height)
dimension _min : 64 ,
dimension _max : 2048 ,
dimension _step : 64 ,
width : 512 ,
height : 512 ,
2023-10-22 00:10:48 +03:00
prompt _prefix : defaultPrefix ,
negative _prompt : defaultNegative ,
2023-07-20 20:32:15 +03:00
sampler : 'DDIM' ,
model : '' ,
2023-11-20 10:35:11 +00:00
vae : '' ,
2024-05-29 23:29:45 +03:00
seed : - 1 ,
2023-07-20 20:32:15 +03:00
// Automatic1111/Horde exclusives
restore _faces : false ,
enable _hr : false ,
// Horde settings
horde : false ,
horde _nsfw : false ,
horde _karras : true ,
2023-10-08 22:29:33 +03:00
horde _sanitize : true ,
2023-07-20 20:32:15 +03:00
// Refine mode
refine _mode : false ,
2023-10-20 15:03:26 +03:00
expand : false ,
2023-11-01 22:58:59 +02:00
interactive _mode : false ,
2023-11-19 15:24:43 +02:00
multimodal _captioning : false ,
2024-02-12 17:28:39 +02:00
snap : false ,
2023-07-22 21:12:23 +03:00
prompts : promptTemplates ,
2023-09-03 00:41:26 +03:00
// AUTOMATIC1111 settings
auto _url : 'http://localhost:7860' ,
2023-09-03 01:19:31 +03:00
auto _auth : '' ,
2023-09-03 14:56:02 +03:00
2023-10-07 18:30:06 +03:00
vlad _url : 'http://localhost:7860' ,
vlad _auth : '' ,
2024-03-30 01:12:29 -03:00
drawthings _url : 'http://localhost:7860' ,
drawthings _auth : '' ,
2023-09-03 14:56:02 +03:00
hr _upscaler : 'Latent' ,
hr _scale : 2.0 ,
hr _scale _min : 1.0 ,
hr _scale _max : 4.0 ,
hr _scale _step : 0.1 ,
denoising _strength : 0.7 ,
denoising _strength _min : 0.0 ,
denoising _strength _max : 1.0 ,
denoising _strength _step : 0.01 ,
hr _second _pass _steps : 0 ,
hr _second _pass _steps _min : 0 ,
hr _second _pass _steps _max : 150 ,
hr _second _pass _steps _step : 1 ,
2023-09-04 18:00:15 +03:00
2024-05-29 02:14:08 +03:00
// CLIP skip
clip _skip _min : 1 ,
clip _skip _max : 12 ,
clip _skip _step : 1 ,
clip _skip : 1 ,
2023-09-04 18:00:15 +03:00
// NovelAI settings
novel _upscale _ratio _min : 1.0 ,
novel _upscale _ratio _max : 4.0 ,
novel _upscale _ratio _step : 0.1 ,
novel _upscale _ratio : 1.0 ,
novel _anlas _guard : false ,
2024-03-20 21:23:56 +02:00
novel _sm : false ,
novel _sm _dyn : false ,
2024-05-29 03:00:42 +03:00
novel _decrisper : false ,
2023-10-22 00:10:48 +03:00
2023-11-06 21:47:00 +02:00
// OpenAI settings
openai _style : 'vivid' ,
openai _quality : 'standard' ,
2023-10-22 00:10:48 +03:00
style : 'Default' ,
styles : defaultStyles ,
2023-11-19 12:18:48 +00:00
// ComyUI settings
comfy _url : 'http://127.0.0.1:8188' ,
2023-11-20 15:59:38 +00:00
comfy _workflow : 'Default_Comfy_Workflow.json' ,
2024-04-04 20:40:47 +03:00
// Pollinations settings
pollinations _enhance : false ,
pollinations _refine : false ,
2023-12-02 21:11:06 +02:00
} ;
2023-09-03 00:41:26 +03:00
2024-05-30 00:21:27 +03:00
const writePromptFieldsDebounced = debounce ( writePromptFields , debounce _timeout . relaxed ) ;
2023-11-01 22:58:59 +02:00
function processTriggers ( chat , _ , abort ) {
if ( ! extension _settings . sd . interactive _mode ) {
return ;
}
const lastMessage = chat [ chat . length - 1 ] ;
if ( ! lastMessage ) {
return ;
}
const message = lastMessage . mes ;
const isUser = lastMessage . is _user ;
if ( ! message || ! isUser ) {
return ;
}
const messageLower = message . toLowerCase ( ) ;
try {
const activationRegex = new RegExp ( messageTrigger . activationRegex , 'i' ) ;
const activationMatch = messageLower . match ( activationRegex ) ;
if ( ! activationMatch ) {
return ;
}
let subject = activationMatch [ 3 ] . trim ( ) ;
if ( ! subject ) {
return ;
}
console . log ( ` SD: Triggered by " ${ message } ", detected subject: ${ subject } " ` ) ;
2023-11-12 02:35:37 +02:00
outer : for ( const [ specialMode , triggers ] of Object . entries ( messageTrigger . specialCases ) ) {
2023-11-01 22:58:59 +02:00
for ( const trigger of triggers ) {
if ( subject === trigger ) {
subject = triggerWords [ specialMode ] [ 0 ] ;
console . log ( ` SD: Detected special case " ${ trigger } ", switching to mode ${ specialMode } ` ) ;
2023-11-12 02:35:37 +02:00
break outer ;
2023-11-01 22:58:59 +02:00
}
}
}
abort ( true ) ;
setTimeout ( ( ) => generatePicture ( 'sd' , subject , message ) , 1 ) ;
} catch {
console . log ( 'SD: Failed to process triggers.' ) ;
return ;
}
}
window [ 'SD_ProcessTriggers' ] = processTriggers ;
2023-10-07 18:30:06 +03:00
function getSdRequestBody ( ) {
switch ( extension _settings . sd . source ) {
case sources . vlad :
return { url : extension _settings . sd . vlad _url , auth : extension _settings . sd . vlad _auth } ;
case sources . auto :
return { url : extension _settings . sd . auto _url , auth : extension _settings . sd . auto _auth } ;
2024-03-30 01:12:29 -03:00
case sources . drawthings :
2024-03-30 14:23:50 +02:00
return { url : extension _settings . sd . drawthings _url , auth : extension _settings . sd . drawthings _auth } ;
2023-10-07 18:30:06 +03:00
default :
throw new Error ( 'Invalid SD source.' ) ;
}
}
2023-09-03 01:19:31 +03:00
2023-09-03 00:41:26 +03:00
function toggleSourceControls ( ) {
$ ( '.sd_settings [data-sd-source]' ) . each ( function ( ) {
2023-10-07 18:30:06 +03:00
const source = $ ( this ) . data ( 'sd-source' ) . split ( ',' ) ;
$ ( this ) . toggle ( source . includes ( extension _settings . sd . source ) ) ;
2023-09-03 00:41:26 +03:00
} ) ;
2023-07-20 20:32:15 +03:00
}
async function loadSettings ( ) {
2023-09-03 00:41:26 +03:00
// Initialize settings
2023-07-20 20:32:15 +03:00
if ( Object . keys ( extension _settings . sd ) . length === 0 ) {
Object . assign ( extension _settings . sd , defaultSettings ) ;
}
2023-09-03 00:41:26 +03:00
// Insert missing settings
for ( const [ key , value ] of Object . entries ( defaultSettings ) ) {
if ( extension _settings . sd [ key ] === undefined ) {
extension _settings . sd [ key ] = value ;
}
}
2023-07-22 21:12:23 +03:00
if ( extension _settings . sd . prompts === undefined ) {
extension _settings . sd . prompts = promptTemplates ;
}
2023-08-18 00:41:21 +03:00
// Insert missing templates
for ( const [ key , value ] of Object . entries ( promptTemplates ) ) {
if ( extension _settings . sd . prompts [ key ] === undefined ) {
extension _settings . sd . prompts [ key ] = value ;
}
}
2023-07-22 23:57:48 +03:00
if ( extension _settings . sd . character _prompts === undefined ) {
extension _settings . sd . character _prompts = { } ;
}
2023-12-23 16:19:22 +00:00
if ( extension _settings . sd . character _negative _prompts === undefined ) {
extension _settings . sd . character _negative _prompts = { } ;
}
2023-10-22 00:10:48 +03:00
if ( ! Array . isArray ( extension _settings . sd . styles ) ) {
extension _settings . sd . styles = defaultStyles ;
}
2023-09-03 00:41:26 +03:00
$ ( '#sd_source' ) . val ( extension _settings . sd . source ) ;
2023-07-20 20:32:15 +03:00
$ ( '#sd_scale' ) . val ( extension _settings . sd . scale ) . trigger ( 'input' ) ;
$ ( '#sd_steps' ) . val ( extension _settings . sd . steps ) . trigger ( 'input' ) ;
$ ( '#sd_prompt_prefix' ) . val ( extension _settings . sd . prompt _prefix ) . trigger ( 'input' ) ;
$ ( '#sd_negative_prompt' ) . val ( extension _settings . sd . negative _prompt ) . trigger ( 'input' ) ;
$ ( '#sd_width' ) . val ( extension _settings . sd . width ) . trigger ( 'input' ) ;
$ ( '#sd_height' ) . val ( extension _settings . sd . height ) . trigger ( 'input' ) ;
2023-09-03 14:56:02 +03:00
$ ( '#sd_hr_scale' ) . val ( extension _settings . sd . hr _scale ) . trigger ( 'input' ) ;
$ ( '#sd_denoising_strength' ) . val ( extension _settings . sd . denoising _strength ) . trigger ( 'input' ) ;
$ ( '#sd_hr_second_pass_steps' ) . val ( extension _settings . sd . hr _second _pass _steps ) . trigger ( 'input' ) ;
2023-09-04 18:00:15 +03:00
$ ( '#sd_novel_upscale_ratio' ) . val ( extension _settings . sd . novel _upscale _ratio ) . trigger ( 'input' ) ;
$ ( '#sd_novel_anlas_guard' ) . prop ( 'checked' , extension _settings . sd . novel _anlas _guard ) ;
2024-03-20 21:23:56 +02:00
$ ( '#sd_novel_sm' ) . prop ( 'checked' , extension _settings . sd . novel _sm ) ;
$ ( '#sd_novel_sm_dyn' ) . prop ( 'checked' , extension _settings . sd . novel _sm _dyn ) ;
$ ( '#sd_novel_sm_dyn' ) . prop ( 'disabled' , ! extension _settings . sd . novel _sm ) ;
2024-05-29 03:00:42 +03:00
$ ( '#sd_novel_decrisper' ) . prop ( 'checked' , extension _settings . sd . novel _decrisper ) ;
2024-04-04 20:40:47 +03:00
$ ( '#sd_pollinations_enhance' ) . prop ( 'checked' , extension _settings . sd . pollinations _enhance ) ;
$ ( '#sd_pollinations_refine' ) . prop ( 'checked' , extension _settings . sd . pollinations _refine ) ;
2023-07-20 20:32:15 +03:00
$ ( '#sd_horde' ) . prop ( 'checked' , extension _settings . sd . horde ) ;
$ ( '#sd_horde_nsfw' ) . prop ( 'checked' , extension _settings . sd . horde _nsfw ) ;
$ ( '#sd_horde_karras' ) . prop ( 'checked' , extension _settings . sd . horde _karras ) ;
2023-10-08 22:29:33 +03:00
$ ( '#sd_horde_sanitize' ) . prop ( 'checked' , extension _settings . sd . horde _sanitize ) ;
2023-07-20 20:32:15 +03:00
$ ( '#sd_restore_faces' ) . prop ( 'checked' , extension _settings . sd . restore _faces ) ;
$ ( '#sd_enable_hr' ) . prop ( 'checked' , extension _settings . sd . enable _hr ) ;
$ ( '#sd_refine_mode' ) . prop ( 'checked' , extension _settings . sd . refine _mode ) ;
2023-10-20 15:03:26 +03:00
$ ( '#sd_expand' ) . prop ( 'checked' , extension _settings . sd . expand ) ;
2023-11-19 15:24:43 +02:00
$ ( '#sd_multimodal_captioning' ) . prop ( 'checked' , extension _settings . sd . multimodal _captioning ) ;
2023-09-03 00:41:26 +03:00
$ ( '#sd_auto_url' ) . val ( extension _settings . sd . auto _url ) ;
2023-09-03 01:19:31 +03:00
$ ( '#sd_auto_auth' ) . val ( extension _settings . sd . auto _auth ) ;
2023-10-07 18:30:06 +03:00
$ ( '#sd_vlad_url' ) . val ( extension _settings . sd . vlad _url ) ;
$ ( '#sd_vlad_auth' ) . val ( extension _settings . sd . vlad _auth ) ;
2024-03-30 01:12:29 -03:00
$ ( '#sd_drawthings_url' ) . val ( extension _settings . sd . drawthings _url ) ;
$ ( '#sd_drawthings_auth' ) . val ( extension _settings . sd . drawthings _auth ) ;
2023-11-01 22:58:59 +02:00
$ ( '#sd_interactive_mode' ) . prop ( 'checked' , extension _settings . sd . interactive _mode ) ;
2023-11-06 21:47:00 +02:00
$ ( '#sd_openai_style' ) . val ( extension _settings . sd . openai _style ) ;
$ ( '#sd_openai_quality' ) . val ( extension _settings . sd . openai _quality ) ;
2023-11-19 12:18:48 +00:00
$ ( '#sd_comfy_url' ) . val ( extension _settings . sd . comfy _url ) ;
$ ( '#sd_comfy_prompt' ) . val ( extension _settings . sd . comfy _prompt ) ;
2024-02-12 17:28:39 +02:00
$ ( '#sd_snap' ) . prop ( 'checked' , extension _settings . sd . snap ) ;
2024-05-29 02:14:08 +03:00
$ ( '#sd_clip_skip' ) . val ( extension _settings . sd . clip _skip ) ;
$ ( '#sd_clip_skip_value' ) . text ( extension _settings . sd . clip _skip ) ;
2024-05-29 23:29:45 +03:00
$ ( '#sd_seed' ) . val ( extension _settings . sd . seed ) ;
2023-07-20 20:32:15 +03:00
2023-10-22 00:10:48 +03:00
for ( const style of extension _settings . sd . styles ) {
const option = document . createElement ( 'option' ) ;
option . value = style . name ;
option . text = style . name ;
option . selected = style . name === extension _settings . sd . style ;
$ ( '#sd_style' ) . append ( option ) ;
}
2024-02-12 17:28:39 +02:00
const resolutionId = getClosestKnownResolution ( ) ;
2023-11-26 18:19:37 +02:00
$ ( '#sd_resolution' ) . val ( resolutionId ) ;
2023-09-03 00:41:26 +03:00
toggleSourceControls ( ) ;
2023-07-22 21:12:23 +03:00
addPromptTemplates ( ) ;
2023-11-20 22:00:40 +00:00
await loadSettingOptions ( ) ;
}
2024-02-12 17:28:39 +02:00
/ * *
* Find a closest resolution option match for the current width and height .
* /
function getClosestKnownResolution ( ) {
let resolutionId = null ;
let minTotalDiff = Infinity ;
const targetAspect = extension _settings . sd . width / extension _settings . sd . height ;
const targetResolution = extension _settings . sd . width * extension _settings . sd . height ;
const diffs = Object . entries ( resolutionOptions ) . map ( ( [ id , resolution ] ) => {
const aspectDiff = Math . abs ( ( resolution . width / resolution . height ) - targetAspect ) / targetAspect ;
const resolutionDiff = Math . abs ( resolution . width * resolution . height - targetResolution ) / targetResolution ;
return { id , totalDiff : aspectDiff + resolutionDiff } ;
} ) ;
for ( const { id , totalDiff } of diffs ) {
if ( totalDiff < minTotalDiff ) {
minTotalDiff = totalDiff ;
resolutionId = id ;
}
}
return resolutionId ;
}
2023-11-20 22:00:40 +00:00
async function loadSettingOptions ( ) {
return Promise . all ( [
loadSamplers ( ) ,
loadModels ( ) ,
loadSchedulers ( ) ,
loadVaes ( ) ,
2023-12-02 22:06:57 +02:00
loadComfyWorkflows ( ) ,
2023-11-20 22:00:40 +00:00
] ) ;
2023-07-20 20:32:15 +03:00
}
2023-07-22 21:12:23 +03:00
function addPromptTemplates ( ) {
$ ( '#sd_prompt_templates' ) . empty ( ) ;
for ( const [ name , prompt ] of Object . entries ( extension _settings . sd . prompts ) ) {
const label = $ ( '<label></label>' )
. text ( modeLabels [ name ] )
2024-05-20 18:49:33 +08:00
. attr ( 'for' , ` sd_prompt_ ${ name } ` )
2024-05-29 02:14:08 +03:00
. attr ( 'data-i18n' , ` sd_prompt_ ${ name } ` ) ;
2023-07-22 21:12:23 +03:00
const textarea = $ ( '<textarea></textarea>' )
2023-07-22 23:57:48 +03:00
. addClass ( 'textarea_compact text_pole' )
2023-07-22 21:12:23 +03:00
. attr ( 'id' , ` sd_prompt_ ${ name } ` )
2023-10-31 22:16:33 +02:00
. attr ( 'rows' , 3 )
2023-07-22 21:12:23 +03:00
. val ( prompt ) . on ( 'input' , ( ) => {
extension _settings . sd . prompts [ name ] = textarea . val ( ) ;
saveSettingsDebounced ( ) ;
} ) ;
const button = $ ( '<button></button>' )
. addClass ( 'menu_button fa-solid fa-undo' )
. attr ( 'title' , 'Restore default' )
2024-05-29 02:14:08 +03:00
. attr ( 'data-i18n' , 'Restore default' )
2023-07-22 21:12:23 +03:00
. on ( 'click' , ( ) => {
textarea . val ( promptTemplates [ name ] ) ;
extension _settings . sd . prompts [ name ] = promptTemplates [ name ] ;
saveSettingsDebounced ( ) ;
} ) ;
const container = $ ( '<div></div>' )
. addClass ( 'title_restorable' )
. append ( label )
2023-12-02 21:11:06 +02:00
. append ( button ) ;
2023-07-22 21:12:23 +03:00
$ ( '#sd_prompt_templates' ) . append ( container ) ;
$ ( '#sd_prompt_templates' ) . append ( textarea ) ;
}
}
2023-11-01 22:58:59 +02:00
function onInteractiveModeInput ( ) {
extension _settings . sd . interactive _mode = ! ! $ ( this ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2023-11-19 15:24:43 +02:00
function onMultimodalCaptioningInput ( ) {
extension _settings . sd . multimodal _captioning = ! ! $ ( this ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2024-02-12 17:28:39 +02:00
function onSnapInput ( ) {
extension _settings . sd . snap = ! ! $ ( this ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2023-10-22 00:10:48 +03:00
function onStyleSelect ( ) {
const selectedStyle = String ( $ ( '#sd_style' ) . find ( ':selected' ) . val ( ) ) ;
const styleObject = extension _settings . sd . styles . find ( x => x . name === selectedStyle ) ;
if ( ! styleObject ) {
console . warn ( ` Could not find style object for ${ selectedStyle } ` ) ;
return ;
}
$ ( '#sd_prompt_prefix' ) . val ( styleObject . prefix ) . trigger ( 'input' ) ;
$ ( '#sd_negative_prompt' ) . val ( styleObject . negative ) . trigger ( 'input' ) ;
extension _settings . sd . style = selectedStyle ;
saveSettingsDebounced ( ) ;
}
2024-05-29 23:44:11 +03:00
async function onDeleteStyleClick ( ) {
const selectedStyle = String ( $ ( '#sd_style' ) . find ( ':selected' ) . val ( ) ) ;
const styleObject = extension _settings . sd . styles . find ( x => x . name === selectedStyle ) ;
if ( ! styleObject ) {
return ;
}
const confirmed = await callPopup ( ` Are you sure you want to delete the style " ${ selectedStyle } "? ` , 'confirm' , '' , { okButton : 'Delete' } ) ;
if ( ! confirmed ) {
return ;
}
const index = extension _settings . sd . styles . indexOf ( styleObject ) ;
if ( index === - 1 ) {
return ;
}
extension _settings . sd . styles . splice ( index , 1 ) ;
$ ( '#sd_style' ) . find ( ` option[value=" ${ selectedStyle } "] ` ) . remove ( ) ;
if ( extension _settings . sd . styles . length > 0 ) {
extension _settings . sd . style = extension _settings . sd . styles [ 0 ] . name ;
$ ( '#sd_style' ) . val ( extension _settings . sd . style ) . trigger ( 'change' ) ;
} else {
extension _settings . sd . style = '' ;
$ ( '#sd_prompt_prefix' ) . val ( '' ) . trigger ( 'input' ) ;
$ ( '#sd_negative_prompt' ) . val ( '' ) . trigger ( 'input' ) ;
$ ( '#sd_style' ) . val ( '' ) ;
}
saveSettingsDebounced ( ) ;
}
2023-10-22 00:10:48 +03:00
async function onSaveStyleClick ( ) {
const userInput = await callPopup ( 'Enter style name:' , 'input' , '' , { okButton : 'Save' } ) ;
if ( ! userInput ) {
return ;
}
const name = String ( userInput ) . trim ( ) ;
const prefix = String ( $ ( '#sd_prompt_prefix' ) . val ( ) ) ;
const negative = String ( $ ( '#sd_negative_prompt' ) . val ( ) ) ;
const existingStyle = extension _settings . sd . styles . find ( x => x . name === name ) ;
if ( existingStyle ) {
existingStyle . prefix = prefix ;
existingStyle . negative = negative ;
$ ( '#sd_style' ) . val ( name ) ;
saveSettingsDebounced ( ) ;
return ;
}
const styleObject = {
name : name ,
prefix : prefix ,
negative : negative ,
} ;
extension _settings . sd . styles . push ( styleObject ) ;
const option = document . createElement ( 'option' ) ;
option . value = styleObject . name ;
option . text = styleObject . name ;
option . selected = true ;
$ ( '#sd_style' ) . append ( option ) ;
$ ( '#sd_style' ) . val ( styleObject . name ) ;
saveSettingsDebounced ( ) ;
}
2023-10-20 15:03:26 +03:00
async function expandPrompt ( prompt ) {
try {
const response = await fetch ( '/api/sd/expand' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { prompt : prompt } ) ,
} ) ;
if ( ! response . ok ) {
throw new Error ( 'API returned an error.' ) ;
}
const data = await response . json ( ) ;
return data . prompt ;
} catch {
return prompt ;
}
}
2023-10-20 15:43:55 +03:00
/ * *
* Modifies prompt based on auto - expansion and user inputs .
* @ param { string } prompt Prompt to refine
* @ param { boolean } allowExpand Whether to allow auto - expansion
2024-05-06 21:39:07 +03:00
* @ param { boolean } isNegative Whether the prompt is a negative one
2023-10-20 15:43:55 +03:00
* @ returns { Promise < string > } Refined prompt
* /
2024-05-06 21:39:07 +03:00
async function refinePrompt ( prompt , allowExpand , isNegative = false ) {
2023-10-20 15:43:55 +03:00
if ( allowExpand && extension _settings . sd . expand ) {
2023-10-20 15:03:26 +03:00
prompt = await expandPrompt ( prompt ) ;
}
2023-07-22 20:32:58 +03:00
if ( extension _settings . sd . refine _mode ) {
2024-05-06 21:39:07 +03:00
const text = isNegative ? '<h3>Review and edit the <i>negative</i> prompt:</h3>' : '<h3>Review and edit the prompt:</h3>' ;
const refinedPrompt = await callPopup ( text + 'Press "Cancel" to abort the image generation.' , 'input' , prompt . trim ( ) , { rows : 5 , okButton : 'Continue' } ) ;
2023-07-22 20:32:58 +03:00
if ( refinedPrompt ) {
return refinedPrompt ;
} else {
throw new Error ( 'Generation aborted by user.' ) ;
}
}
return prompt ;
}
2023-07-22 23:57:48 +03:00
function onChatChanged ( ) {
if ( this _chid === undefined || selected _group ) {
$ ( '#sd_character_prompt_block' ) . hide ( ) ;
return ;
}
$ ( '#sd_character_prompt_block' ) . show ( ) ;
2024-05-30 00:21:27 +03:00
2023-07-22 23:57:48 +03:00
const key = getCharaFilename ( this _chid ) ;
2024-05-30 00:21:27 +03:00
let characterPrompt = key ? ( extension _settings . sd . character _prompts [ key ] || '' ) : '' ;
let negativePrompt = key ? ( extension _settings . sd . character _negative _prompts [ key ] || '' ) : '' ;
const context = getContext ( ) ;
const sharedPromptData = context ? . characters [ this _chid ] ? . data ? . extensions ? . sd _character _prompt ;
const hasSharedData = sharedPromptData && typeof sharedPromptData === 'object' ;
if ( typeof sharedPromptData ? . positive === 'string' && ! characterPrompt && sharedPromptData . positive ) {
characterPrompt = sharedPromptData . positive ;
extension _settings . sd . character _prompts [ key ] = characterPrompt ;
}
if ( typeof sharedPromptData ? . negative === 'string' && ! negativePrompt && sharedPromptData . negative ) {
negativePrompt = sharedPromptData . negative ;
extension _settings . sd . character _negative _prompts [ key ] = negativePrompt ;
}
$ ( '#sd_character_prompt' ) . val ( characterPrompt ) ;
$ ( '#sd_character_negative_prompt' ) . val ( negativePrompt ) ;
$ ( '#sd_character_prompt_share' ) . prop ( 'checked' , hasSharedData ) ;
2023-07-22 23:57:48 +03:00
}
function onCharacterPromptInput ( ) {
const key = getCharaFilename ( this _chid ) ;
extension _settings . sd . character _prompts [ key ] = $ ( '#sd_character_prompt' ) . val ( ) ;
2023-07-23 00:28:23 +03:00
resetScrollHeight ( $ ( this ) ) ;
2023-07-22 23:57:48 +03:00
saveSettingsDebounced ( ) ;
2024-05-30 00:21:27 +03:00
writePromptFieldsDebounced ( this _chid ) ;
2023-07-22 23:57:48 +03:00
}
2023-12-23 16:19:22 +00:00
function onCharacterNegativePromptInput ( ) {
const key = getCharaFilename ( this _chid ) ;
extension _settings . sd . character _negative _prompts [ key ] = $ ( '#sd_character_negative_prompt' ) . val ( ) ;
resetScrollHeight ( $ ( this ) ) ;
saveSettingsDebounced ( ) ;
2024-05-30 00:21:27 +03:00
writePromptFieldsDebounced ( this _chid ) ;
2023-12-23 16:19:22 +00:00
}
2023-07-22 23:57:48 +03:00
function getCharacterPrefix ( ) {
2023-10-07 18:30:06 +03:00
if ( ! this _chid || selected _group ) {
2023-07-22 23:57:48 +03:00
return '' ;
}
const key = getCharaFilename ( this _chid ) ;
if ( key ) {
return extension _settings . sd . character _prompts [ key ] || '' ;
}
return '' ;
}
2023-12-23 16:19:22 +00:00
function getCharacterNegativePrefix ( ) {
if ( ! this _chid || selected _group ) {
return '' ;
}
const key = getCharaFilename ( this _chid ) ;
if ( key ) {
return extension _settings . sd . character _negative _prompts [ key ] || '' ;
}
return '' ;
}
2023-10-22 00:10:48 +03:00
/ * *
* Combines two prompt prefixes into one .
* @ param { string } str1 Base string
* @ param { string } str2 Secondary string
* @ param { string } macro Macro to replace with the secondary string
* @ returns { string } Combined string with a comma between them
* /
function combinePrefixes ( str1 , str2 , macro = '' ) {
2023-11-27 16:10:42 +02:00
// Remove leading/trailing white spaces and commas from the strings
const process = ( s ) => s . trim ( ) . replace ( /^,|,$/g , '' ) . trim ( ) ;
2023-07-22 23:57:48 +03:00
if ( ! str2 ) {
return str1 ;
}
2023-12-02 21:11:06 +02:00
str1 = process ( str1 ) ;
2023-11-27 16:10:42 +02:00
str2 = process ( str2 ) ;
2023-07-22 23:57:48 +03:00
2023-10-22 00:10:48 +03:00
// Combine the strings with a comma between them)
const result = macro && str1 . includes ( macro ) ? str1 . replace ( macro , str2 ) : ` ${ str1 } , ${ str2 } , ` ;
2023-11-27 16:10:42 +02:00
return process ( result ) ;
2023-07-22 23:57:48 +03:00
}
2023-10-20 15:03:26 +03:00
function onExpandInput ( ) {
extension _settings . sd . expand = ! ! $ ( this ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2023-07-20 20:32:15 +03:00
function onRefineModeInput ( ) {
extension _settings . sd . refine _mode = ! ! $ ( '#sd_refine_mode' ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2024-05-29 02:14:08 +03:00
function onClipSkipInput ( ) {
extension _settings . sd . clip _skip = Number ( $ ( '#sd_clip_skip' ) . val ( ) ) ;
$ ( '#sd_clip_skip_value' ) . text ( extension _settings . sd . clip _skip ) ;
saveSettingsDebounced ( ) ;
}
2024-05-29 23:29:45 +03:00
function onSeedInput ( ) {
extension _settings . sd . seed = Number ( $ ( '#sd_seed' ) . val ( ) ) ;
saveSettingsDebounced ( ) ;
}
2023-07-20 20:32:15 +03:00
function onScaleInput ( ) {
extension _settings . sd . scale = Number ( $ ( '#sd_scale' ) . val ( ) ) ;
$ ( '#sd_scale_value' ) . text ( extension _settings . sd . scale . toFixed ( 1 ) ) ;
saveSettingsDebounced ( ) ;
}
function onStepsInput ( ) {
extension _settings . sd . steps = Number ( $ ( '#sd_steps' ) . val ( ) ) ;
$ ( '#sd_steps_value' ) . text ( extension _settings . sd . steps ) ;
saveSettingsDebounced ( ) ;
}
function onPromptPrefixInput ( ) {
extension _settings . sd . prompt _prefix = $ ( '#sd_prompt_prefix' ) . val ( ) ;
resetScrollHeight ( $ ( this ) ) ;
saveSettingsDebounced ( ) ;
}
function onNegativePromptInput ( ) {
extension _settings . sd . negative _prompt = $ ( '#sd_negative_prompt' ) . val ( ) ;
resetScrollHeight ( $ ( this ) ) ;
saveSettingsDebounced ( ) ;
}
function onSamplerChange ( ) {
extension _settings . sd . sampler = $ ( '#sd_sampler' ) . find ( ':selected' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
2023-11-26 16:55:49 +01:00
const resolutionOptions = {
2023-11-26 18:19:37 +02:00
sd _res _512x512 : { width : 512 , height : 512 , name : '512x512 (1:1, icons, profile pictures)' } ,
sd _res _600x600 : { width : 600 , height : 600 , name : '600x600 (1:1, icons, profile pictures)' } ,
sd _res _512x768 : { width : 512 , height : 768 , name : '512x768 (2:3, vertical character card)' } ,
sd _res _768x512 : { width : 768 , height : 512 , name : '768x512 (3:2, horizontal 35-mm movie film)' } ,
sd _res _960x540 : { width : 960 , height : 540 , name : '960x540 (16:9, horizontal wallpaper)' } ,
sd _res _540x960 : { width : 540 , height : 960 , name : '540x960 (9:16, vertical wallpaper)' } ,
sd _res _1920x1088 : { width : 1920 , height : 1088 , name : '1920x1088 (16:9, 1080p, horizontal wallpaper)' } ,
sd _res _1088x1920 : { width : 1088 , height : 1920 , name : '1088x1920 (9:16, 1080p, vertical wallpaper)' } ,
sd _res _1280x720 : { width : 1280 , height : 720 , name : '1280x720 (16:9, 720p, horizontal wallpaper)' } ,
sd _res _720x1280 : { width : 720 , height : 1280 , name : '720x1280 (9:16, 720p, vertical wallpaper)' } ,
sd _res _1024x1024 : { width : 1024 , height : 1024 , name : '1024x1024 (1:1, SDXL)' } ,
sd _res _1152x896 : { width : 1152 , height : 896 , name : '1152x896 (9:7, SDXL)' } ,
sd _res _896x1152 : { width : 896 , height : 1152 , name : '896x1152 (7:9, SDXL)' } ,
sd _res _1216x832 : { width : 1216 , height : 832 , name : '1216x832 (19:13, SDXL)' } ,
sd _res _832x1216 : { width : 832 , height : 1216 , name : '832x1216 (13:19, SDXL)' } ,
sd _res _1344x768 : { width : 1344 , height : 768 , name : '1344x768 (4:3, SDXL)' } ,
sd _res _768x1344 : { width : 768 , height : 1344 , name : '768x1344 (3:4, SDXL)' } ,
sd _res _1536x640 : { width : 1536 , height : 640 , name : '1536x640 (24:10, SDXL)' } ,
sd _res _640x1536 : { width : 640 , height : 1536 , name : '640x1536 (10:24, SDXL)' } ,
2023-11-26 16:55:49 +01:00
} ;
function onResolutionChange ( ) {
2023-12-02 13:04:51 -05:00
const selectedOption = $ ( '#sd_resolution' ) . val ( ) ;
2023-11-26 16:55:49 +01:00
const selectedResolution = resolutionOptions [ selectedOption ] ;
2023-11-26 18:19:37 +02:00
if ( ! selectedResolution ) {
console . warn ( ` Could not find resolution option for ${ selectedOption } ` ) ;
return ;
}
2023-12-02 13:04:51 -05:00
$ ( '#sd_height' ) . val ( selectedResolution . height ) . trigger ( 'input' ) ;
$ ( '#sd_width' ) . val ( selectedResolution . width ) . trigger ( 'input' ) ;
2023-11-26 16:55:49 +01:00
}
2023-11-19 12:18:48 +00:00
function onSchedulerChange ( ) {
extension _settings . sd . scheduler = $ ( '#sd_scheduler' ) . find ( ':selected' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
2023-07-20 20:32:15 +03:00
function onWidthInput ( ) {
extension _settings . sd . width = Number ( $ ( '#sd_width' ) . val ( ) ) ;
$ ( '#sd_width_value' ) . text ( extension _settings . sd . width ) ;
saveSettingsDebounced ( ) ;
}
function onHeightInput ( ) {
extension _settings . sd . height = Number ( $ ( '#sd_height' ) . val ( ) ) ;
$ ( '#sd_height_value' ) . text ( extension _settings . sd . height ) ;
saveSettingsDebounced ( ) ;
}
2023-09-03 00:41:26 +03:00
async function onSourceChange ( ) {
extension _settings . sd . source = $ ( '#sd_source' ) . find ( ':selected' ) . val ( ) ;
2023-07-20 20:32:15 +03:00
extension _settings . sd . model = null ;
extension _settings . sd . sampler = null ;
2024-05-30 21:04:22 +03:00
extension _settings . sd . scheduler = null ;
2023-09-03 00:41:26 +03:00
toggleSourceControls ( ) ;
2023-07-20 20:32:15 +03:00
saveSettingsDebounced ( ) ;
2023-11-21 02:00:50 +02:00
await loadSettingOptions ( ) ;
2023-07-20 20:32:15 +03:00
}
2023-11-06 21:47:00 +02:00
async function onOpenAiStyleSelect ( ) {
extension _settings . sd . openai _style = String ( $ ( '#sd_openai_style' ) . find ( ':selected' ) . val ( ) ) ;
saveSettingsDebounced ( ) ;
}
async function onOpenAiQualitySelect ( ) {
extension _settings . sd . openai _quality = String ( $ ( '#sd_openai_quality' ) . find ( ':selected' ) . val ( ) ) ;
saveSettingsDebounced ( ) ;
}
2023-09-04 18:00:15 +03:00
async function onViewAnlasClick ( ) {
const result = await loadNovelSubscriptionData ( ) ;
if ( ! result ) {
toastr . warning ( 'Are you subscribed?' , 'Could not load NovelAI subscription data' ) ;
return ;
}
const anlas = getNovelAnlas ( ) ;
const unlimitedGeneration = getNovelUnlimitedImageGeneration ( ) ;
toastr . info ( ` Free image generation: ${ unlimitedGeneration ? 'Yes' : 'No' } ` , ` Anlas: ${ anlas } ` ) ;
}
function onNovelUpscaleRatioInput ( ) {
extension _settings . sd . novel _upscale _ratio = Number ( $ ( '#sd_novel_upscale_ratio' ) . val ( ) ) ;
$ ( '#sd_novel_upscale_ratio_value' ) . text ( extension _settings . sd . novel _upscale _ratio . toFixed ( 1 ) ) ;
saveSettingsDebounced ( ) ;
}
function onNovelAnlasGuardInput ( ) {
extension _settings . sd . novel _anlas _guard = ! ! $ ( '#sd_novel_anlas_guard' ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2024-03-20 21:23:56 +02:00
function onNovelSmInput ( ) {
extension _settings . sd . novel _sm = ! ! $ ( '#sd_novel_sm' ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
if ( ! extension _settings . sd . novel _sm ) {
$ ( '#sd_novel_sm_dyn' ) . prop ( 'checked' , false ) . prop ( 'disabled' , true ) . trigger ( 'input' ) ;
} else {
$ ( '#sd_novel_sm_dyn' ) . prop ( 'disabled' , false ) ;
}
}
function onNovelSmDynInput ( ) {
extension _settings . sd . novel _sm _dyn = ! ! $ ( '#sd_novel_sm_dyn' ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2024-05-29 03:00:42 +03:00
function onNovelDecrisperInput ( ) {
extension _settings . sd . novel _decrisper = ! ! $ ( '#sd_novel_decrisper' ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2024-04-04 20:40:47 +03:00
function onPollinationsEnhanceInput ( ) {
extension _settings . sd . pollinations _enhance = ! ! $ ( '#sd_pollinations_enhance' ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
function onPollinationsRefineInput ( ) {
extension _settings . sd . pollinations _refine = ! ! $ ( '#sd_pollinations_refine' ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2023-10-08 22:29:33 +03:00
function onHordeNsfwInput ( ) {
2023-07-20 20:32:15 +03:00
extension _settings . sd . horde _nsfw = ! ! $ ( this ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2023-10-08 22:29:33 +03:00
function onHordeKarrasInput ( ) {
2023-07-20 20:32:15 +03:00
extension _settings . sd . horde _karras = ! ! $ ( this ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2023-10-08 22:29:33 +03:00
function onHordeSanitizeInput ( ) {
extension _settings . sd . horde _sanitize = ! ! $ ( this ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2023-07-20 20:32:15 +03:00
function onRestoreFacesInput ( ) {
extension _settings . sd . restore _faces = ! ! $ ( this ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
function onHighResFixInput ( ) {
extension _settings . sd . enable _hr = ! ! $ ( this ) . prop ( 'checked' ) ;
saveSettingsDebounced ( ) ;
}
2023-09-03 00:41:26 +03:00
function onAutoUrlInput ( ) {
extension _settings . sd . auto _url = $ ( '#sd_auto_url' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
2023-09-03 01:19:31 +03:00
function onAutoAuthInput ( ) {
extension _settings . sd . auto _auth = $ ( '#sd_auto_auth' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
2023-10-07 18:30:06 +03:00
function onVladUrlInput ( ) {
extension _settings . sd . vlad _url = $ ( '#sd_vlad_url' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
function onVladAuthInput ( ) {
extension _settings . sd . vlad _auth = $ ( '#sd_vlad_auth' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
2024-03-30 01:12:29 -03:00
function onDrawthingsUrlInput ( ) {
extension _settings . sd . drawthings _url = $ ( '#sd_drawthings_url' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
function onDrawthingsAuthInput ( ) {
extension _settings . sd . drawthings _auth = $ ( '#sd_drawthings_auth' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
2023-09-03 14:56:02 +03:00
function onHrUpscalerChange ( ) {
extension _settings . sd . hr _upscaler = $ ( '#sd_hr_upscaler' ) . find ( ':selected' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
function onHrScaleInput ( ) {
extension _settings . sd . hr _scale = Number ( $ ( '#sd_hr_scale' ) . val ( ) ) ;
$ ( '#sd_hr_scale_value' ) . text ( extension _settings . sd . hr _scale . toFixed ( 1 ) ) ;
saveSettingsDebounced ( ) ;
}
function onDenoisingStrengthInput ( ) {
extension _settings . sd . denoising _strength = Number ( $ ( '#sd_denoising_strength' ) . val ( ) ) ;
$ ( '#sd_denoising_strength_value' ) . text ( extension _settings . sd . denoising _strength . toFixed ( 2 ) ) ;
saveSettingsDebounced ( ) ;
}
function onHrSecondPassStepsInput ( ) {
extension _settings . sd . hr _second _pass _steps = Number ( $ ( '#sd_hr_second_pass_steps' ) . val ( ) ) ;
$ ( '#sd_hr_second_pass_steps_value' ) . text ( extension _settings . sd . hr _second _pass _steps ) ;
saveSettingsDebounced ( ) ;
}
2023-11-19 12:18:48 +00:00
function onComfyUrlInput ( ) {
extension _settings . sd . comfy _url = $ ( '#sd_comfy_url' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
2023-11-20 12:13:28 +00:00
function onComfyWorkflowChange ( ) {
extension _settings . sd . comfy _workflow = $ ( '#sd_comfy_workflow' ) . find ( ':selected' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
2023-12-17 22:57:10 +00:00
async function changeComfyWorkflow ( _ , name ) {
name = name . replace ( /(\.json)?$/i , '.json' ) ;
if ( $ ( ` #sd_comfy_workflow > [value=" ${ name } "] ` ) . length > 0 ) {
extension _settings . sd . comfy _workflow = name ;
$ ( '#sd_comfy_workflow' ) . val ( extension _settings . sd . comfy _workflow ) ;
saveSettingsDebounced ( ) ;
} else {
toastr . error ( ` ComfyUI Workflow " ${ name } " does not exist. ` ) ;
}
2023-12-17 22:24:22 +00:00
}
2023-11-20 12:13:28 +00:00
2023-09-03 00:41:26 +03:00
async function validateAutoUrl ( ) {
try {
if ( ! extension _settings . sd . auto _url ) {
throw new Error ( 'URL is not set.' ) ;
}
const result = await fetch ( '/api/sd/ping' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
2023-10-07 18:30:06 +03:00
body : JSON . stringify ( getSdRequestBody ( ) ) ,
2023-09-03 00:41:26 +03:00
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD WebUI returned an error.' ) ;
}
2023-11-21 02:00:50 +02:00
await loadSettingOptions ( ) ;
2023-09-03 00:41:26 +03:00
toastr . success ( 'SD WebUI API connected.' ) ;
} catch ( error ) {
toastr . error ( ` Could not validate SD WebUI API: ${ error . message } ` ) ;
}
}
2024-03-30 01:12:29 -03:00
async function validateDrawthingsUrl ( ) {
try {
if ( ! extension _settings . sd . drawthings _url ) {
throw new Error ( 'URL is not set.' ) ;
}
const result = await fetch ( '/api/sd/drawthings/ping' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( getSdRequestBody ( ) ) ,
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD Drawthings returned an error.' ) ;
}
await loadSettingOptions ( ) ;
toastr . success ( 'SD Drawthings API connected.' ) ;
} catch ( error ) {
toastr . error ( ` Could not validate SD Drawthings API: ${ error . message } ` ) ;
}
}
2023-10-07 18:30:06 +03:00
async function validateVladUrl ( ) {
try {
if ( ! extension _settings . sd . vlad _url ) {
throw new Error ( 'URL is not set.' ) ;
}
const result = await fetch ( '/api/sd/ping' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( getSdRequestBody ( ) ) ,
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD.Next returned an error.' ) ;
}
2023-11-21 02:00:50 +02:00
await loadSettingOptions ( ) ;
2023-10-07 18:30:06 +03:00
toastr . success ( 'SD.Next API connected.' ) ;
} catch ( error ) {
toastr . error ( ` Could not validate SD.Next API: ${ error . message } ` ) ;
}
}
2023-11-19 12:18:48 +00:00
async function validateComfyUrl ( ) {
try {
if ( ! extension _settings . sd . comfy _url ) {
throw new Error ( 'URL is not set.' ) ;
}
2023-12-02 13:04:51 -05:00
const result = await fetch ( '/api/sd/comfy/ping' , {
2023-11-19 18:29:41 +00:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
url : extension _settings . sd . comfy _url ,
2023-12-02 22:06:57 +02:00
} ) ,
2023-11-19 18:29:41 +00:00
} ) ;
2023-11-19 12:18:48 +00:00
if ( ! result . ok ) {
throw new Error ( 'ComfyUI returned an error.' ) ;
}
2023-11-21 02:00:50 +02:00
await loadSettingOptions ( ) ;
2023-11-19 12:18:48 +00:00
toastr . success ( 'ComfyUI API connected.' ) ;
} catch ( error ) {
toastr . error ( ` Could not validate ComfyUI API: ${ error . message } ` ) ;
}
}
2023-07-20 20:32:15 +03:00
async function onModelChange ( ) {
extension _settings . sd . model = $ ( '#sd_model' ) . find ( ':selected' ) . val ( ) ;
saveSettingsDebounced ( ) ;
2024-04-04 20:40:47 +03:00
const cloudSources = [ sources . horde , sources . novel , sources . openai , sources . togetherai , sources . pollinations ] ;
2023-09-03 00:41:26 +03:00
if ( cloudSources . includes ( extension _settings . sd . source ) ) {
return ;
}
toastr . info ( 'Updating remote model...' , 'Please wait' ) ;
if ( extension _settings . sd . source === sources . extras ) {
2023-07-20 20:32:15 +03:00
await updateExtrasRemoteModel ( ) ;
}
2023-10-07 18:30:06 +03:00
if ( extension _settings . sd . source === sources . auto || extension _settings . sd . source === sources . vlad ) {
2023-09-03 00:41:26 +03:00
await updateAutoRemoteModel ( ) ;
}
2023-11-08 10:57:37 +02:00
toastr . success ( 'Model successfully loaded!' , 'Image Generation' ) ;
2023-09-03 00:41:26 +03:00
}
async function getAutoRemoteModel ( ) {
try {
const result = await fetch ( '/api/sd/get-model' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
2023-10-07 18:30:06 +03:00
body : JSON . stringify ( getSdRequestBody ( ) ) ,
2023-09-03 00:41:26 +03:00
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD WebUI returned an error.' ) ;
}
const data = await result . text ( ) ;
return data ;
} catch ( error ) {
console . error ( error ) ;
return null ;
}
}
2024-03-30 01:12:29 -03:00
async function getDrawthingsRemoteModel ( ) {
try {
const result = await fetch ( '/api/sd/drawthings/get-model' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( getSdRequestBody ( ) ) ,
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD DrawThings API returned an error.' ) ;
}
const data = await result . text ( ) ;
return data ;
} catch ( error ) {
console . error ( error ) ;
return null ;
}
}
2023-11-20 10:35:11 +00:00
async function onVaeChange ( ) {
extension _settings . sd . vae = $ ( '#sd_vae' ) . find ( ':selected' ) . val ( ) ;
}
2023-09-03 14:56:02 +03:00
async function getAutoRemoteUpscalers ( ) {
try {
const result = await fetch ( '/api/sd/upscalers' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
2023-10-07 18:30:06 +03:00
body : JSON . stringify ( getSdRequestBody ( ) ) ,
2023-09-03 14:56:02 +03:00
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD WebUI returned an error.' ) ;
}
const data = await result . json ( ) ;
return data ;
} catch ( error ) {
console . error ( error ) ;
return [ extension _settings . sd . hr _upscaler ] ;
}
}
2024-05-30 21:04:22 +03:00
async function getAutoRemoteSchedulers ( ) {
try {
const result = await fetch ( '/api/sd/schedulers' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( getSdRequestBody ( ) ) ,
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD WebUI returned an error.' ) ;
}
const data = await result . json ( ) ;
return data ;
} catch ( error ) {
console . error ( error ) ;
return [ 'N/A' ] ;
}
}
2023-10-07 18:30:06 +03:00
async function getVladRemoteUpscalers ( ) {
try {
2023-12-03 09:25:09 -05:00
const result = await fetch ( '/api/sd/sd-next/upscalers' , {
2023-10-07 18:30:06 +03:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( getSdRequestBody ( ) ) ,
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD.Next returned an error.' ) ;
}
const data = await result . json ( ) ;
return data ;
} catch ( error ) {
console . error ( error ) ;
return [ extension _settings . sd . hr _upscaler ] ;
}
}
2024-05-29 02:49:13 +03:00
async function getDrawthingsRemoteUpscalers ( ) {
try {
const result = await fetch ( '/api/sd/drawthings/get-upscaler' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( getSdRequestBody ( ) ) ,
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD DrawThings API returned an error.' ) ;
}
const data = await result . text ( ) ;
return data ? [ data ] : [ 'N/A' ] ;
} catch ( error ) {
console . error ( error ) ;
return [ 'N/A' ] ;
}
}
2023-09-03 00:41:26 +03:00
async function updateAutoRemoteModel ( ) {
try {
const result = await fetch ( '/api/sd/set-model' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
2023-10-07 18:30:06 +03:00
body : JSON . stringify ( { ... getSdRequestBody ( ) , model : extension _settings . sd . model } ) ,
2023-09-03 00:41:26 +03:00
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD WebUI returned an error.' ) ;
}
console . log ( 'Model successfully updated on SD WebUI remote.' ) ;
} catch ( error ) {
console . error ( error ) ;
toastr . error ( ` Could not update SD WebUI model: ${ error . message } ` ) ;
}
2023-07-20 20:32:15 +03:00
}
async function updateExtrasRemoteModel ( ) {
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/image/model' ;
const getCurrentModelResult = await doExtrasFetch ( url , {
method : 'POST' ,
body : JSON . stringify ( { model : extension _settings . sd . model } ) ,
} ) ;
if ( getCurrentModelResult . ok ) {
console . log ( 'Model successfully updated on SD remote.' ) ;
}
}
async function loadSamplers ( ) {
$ ( '#sd_sampler' ) . empty ( ) ;
let samplers = [ ] ;
2023-09-03 00:41:26 +03:00
switch ( extension _settings . sd . source ) {
case sources . extras :
samplers = await loadExtrasSamplers ( ) ;
break ;
case sources . horde :
samplers = await loadHordeSamplers ( ) ;
break ;
case sources . auto :
samplers = await loadAutoSamplers ( ) ;
break ;
2024-03-30 01:12:29 -03:00
case sources . drawthings :
samplers = await loadDrawthingsSamplers ( ) ;
break ;
2023-09-03 00:41:26 +03:00
case sources . novel :
samplers = await loadNovelSamplers ( ) ;
break ;
2023-10-07 18:30:06 +03:00
case sources . vlad :
samplers = await loadVladSamplers ( ) ;
break ;
2023-11-06 21:47:00 +02:00
case sources . openai :
2023-12-18 03:33:05 +02:00
samplers = [ 'N/A' ] ;
2023-11-06 21:47:00 +02:00
break ;
2023-11-19 12:18:48 +00:00
case sources . comfy :
samplers = await loadComfySamplers ( ) ;
break ;
2023-12-18 03:33:05 +02:00
case sources . togetherai :
samplers = [ 'N/A' ] ;
break ;
2024-04-04 20:40:47 +03:00
case sources . pollinations :
samplers = [ 'N/A' ] ;
break ;
2023-07-20 20:32:15 +03:00
}
for ( const sampler of samplers ) {
const option = document . createElement ( 'option' ) ;
option . innerText = sampler ;
option . value = sampler ;
option . selected = sampler === extension _settings . sd . sampler ;
$ ( '#sd_sampler' ) . append ( option ) ;
}
2023-12-18 03:33:05 +02:00
if ( ! extension _settings . sd . sampler && samplers . length > 0 ) {
extension _settings . sd . sampler = samplers [ 0 ] ;
$ ( '#sd_sampler' ) . val ( extension _settings . sd . sampler ) . trigger ( 'change' ) ;
}
2023-07-20 20:32:15 +03:00
}
async function loadHordeSamplers ( ) {
2023-09-12 20:45:36 +03:00
const result = await fetch ( '/api/horde/sd-samplers' , {
2023-07-20 20:32:15 +03:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
} ) ;
if ( result . ok ) {
const data = await result . json ( ) ;
return data ;
}
return [ ] ;
}
async function loadExtrasSamplers ( ) {
if ( ! modules . includes ( 'sd' ) ) {
return [ ] ;
}
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/image/samplers' ;
const result = await doExtrasFetch ( url ) ;
if ( result . ok ) {
const data = await result . json ( ) ;
return data . samplers ;
}
return [ ] ;
}
2023-09-03 00:41:26 +03:00
async function loadAutoSamplers ( ) {
if ( ! extension _settings . sd . auto _url ) {
return [ ] ;
}
try {
const result = await fetch ( '/api/sd/samplers' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
2023-10-07 18:30:06 +03:00
body : JSON . stringify ( getSdRequestBody ( ) ) ,
2023-09-03 00:41:26 +03:00
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD WebUI returned an error.' ) ;
}
const data = await result . json ( ) ;
return data ;
} catch ( error ) {
return [ ] ;
}
}
2024-03-30 01:12:29 -03:00
async function loadDrawthingsSamplers ( ) {
// The app developer doesn't provide an API to get these yet
2024-04-02 13:15:31 +03:00
return [
'UniPC' ,
'DPM++ 2M Karras' ,
'Euler a' ,
'DPM++ SDE Karras' ,
'PLMS' ,
'DDIM' ,
'LCM' ,
'Euler A Substep' ,
'DPM++ SDE Substep' ,
'TCD' ,
] ;
2024-03-30 01:12:29 -03:00
}
2023-10-07 18:30:06 +03:00
async function loadVladSamplers ( ) {
if ( ! extension _settings . sd . vlad _url ) {
return [ ] ;
}
try {
const result = await fetch ( '/api/sd/samplers' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( getSdRequestBody ( ) ) ,
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD.Next returned an error.' ) ;
}
const data = await result . json ( ) ;
return data ;
} catch ( error ) {
return [ ] ;
}
}
2023-09-03 00:41:26 +03:00
async function loadNovelSamplers ( ) {
if ( ! secret _state [ SECRET _KEYS . NOVEL ] ) {
2023-09-04 18:00:15 +03:00
console . debug ( 'NovelAI API key is not set.' ) ;
2023-09-03 00:41:26 +03:00
return [ ] ;
}
return [
'k_dpmpp_2m' ,
'k_dpmpp_sde' ,
'k_dpmpp_2s_ancestral' ,
'k_euler' ,
'k_euler_ancestral' ,
'k_dpm_fast' ,
'ddim' ,
] ;
}
2023-11-19 12:18:48 +00:00
async function loadComfySamplers ( ) {
if ( ! extension _settings . sd . comfy _url ) {
return [ ] ;
}
try {
2023-12-02 13:04:51 -05:00
const result = await fetch ( '/api/sd/comfy/samplers' , {
2023-11-19 18:29:41 +00:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
url : extension _settings . sd . comfy _url ,
2023-12-02 22:06:57 +02:00
} ) ,
2023-11-19 18:29:41 +00:00
} ) ;
2023-11-19 12:18:48 +00:00
if ( ! result . ok ) {
throw new Error ( 'ComfyUI returned an error.' ) ;
}
2023-11-19 18:29:41 +00:00
return await result . json ( ) ;
2023-11-19 12:18:48 +00:00
} catch ( error ) {
return [ ] ;
}
}
2023-07-20 20:32:15 +03:00
async function loadModels ( ) {
$ ( '#sd_model' ) . empty ( ) ;
let models = [ ] ;
2023-09-03 00:41:26 +03:00
switch ( extension _settings . sd . source ) {
case sources . extras :
models = await loadExtrasModels ( ) ;
break ;
case sources . horde :
models = await loadHordeModels ( ) ;
break ;
case sources . auto :
models = await loadAutoModels ( ) ;
break ;
2024-03-30 01:12:29 -03:00
case sources . drawthings :
models = await loadDrawthingsModels ( ) ;
break ;
2023-09-03 00:41:26 +03:00
case sources . novel :
models = await loadNovelModels ( ) ;
break ;
2023-10-07 18:30:06 +03:00
case sources . vlad :
models = await loadVladModels ( ) ;
break ;
2023-11-06 21:47:00 +02:00
case sources . openai :
models = await loadOpenAiModels ( ) ;
break ;
2023-11-19 12:18:48 +00:00
case sources . comfy :
models = await loadComfyModels ( ) ;
break ;
2023-12-18 03:33:05 +02:00
case sources . togetherai :
models = await loadTogetherAIModels ( ) ;
break ;
2024-04-04 20:40:47 +03:00
case sources . pollinations :
models = await loadPollinationsModels ( ) ;
break ;
2023-07-20 20:32:15 +03:00
}
for ( const model of models ) {
const option = document . createElement ( 'option' ) ;
option . innerText = model . text ;
option . value = model . value ;
option . selected = model . value === extension _settings . sd . model ;
$ ( '#sd_model' ) . append ( option ) ;
}
2023-12-18 03:33:05 +02:00
if ( ! extension _settings . sd . model && models . length > 0 ) {
extension _settings . sd . model = models [ 0 ] . value ;
$ ( '#sd_model' ) . val ( extension _settings . sd . model ) . trigger ( 'change' ) ;
}
}
2024-04-04 20:40:47 +03:00
async function loadPollinationsModels ( ) {
return [
{
value : 'pixart' ,
text : 'PixArt-α lpha' ,
} ,
{
value : 'playground' ,
text : 'Playground v2' ,
} ,
{
value : 'dalle3xl' ,
text : 'DALL•E 3 XL' ,
} ,
{
value : 'formulaxl' ,
text : 'FormulaXL' ,
} ,
{
value : 'dreamshaper' ,
text : 'DreamShaper' ,
} ,
{
value : 'deliberate' ,
text : 'Deliberate' ,
} ,
{
value : 'dpo' ,
text : 'SDXL-DPO' ,
} ,
{
value : 'swizz8' ,
text : 'Swizz8' ,
} ,
{
value : 'juggernaut' ,
text : 'Juggernaut' ,
} ,
{
value : 'turbo' ,
text : 'SDXL Turbo' ,
} ,
{
value : 'realvis' ,
text : 'Realistic Vision' ,
} ,
] ;
}
2023-12-18 03:33:05 +02:00
async function loadTogetherAIModels ( ) {
if ( ! secret _state [ SECRET _KEYS . TOGETHERAI ] ) {
console . debug ( 'TogetherAI API key is not set.' ) ;
return [ ] ;
}
const result = await fetch ( '/api/sd/together/models' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
} ) ;
if ( result . ok ) {
const data = await result . json ( ) ;
return data ;
}
return [ ] ;
2023-07-20 20:32:15 +03:00
}
async function loadHordeModels ( ) {
2023-09-12 20:45:36 +03:00
const result = await fetch ( '/api/horde/sd-models' , {
2023-07-20 20:32:15 +03:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
} ) ;
if ( result . ok ) {
const data = await result . json ( ) ;
data . sort ( ( a , b ) => b . count - a . count ) ;
const models = data . map ( x => ( { value : x . name , text : ` ${ x . name } (ETA: ${ x . eta } s, Queue: ${ x . queued } , Workers: ${ x . count } ) ` } ) ) ;
return models ;
}
return [ ] ;
}
async function loadExtrasModels ( ) {
if ( ! modules . includes ( 'sd' ) ) {
return [ ] ;
}
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/image/model' ;
const getCurrentModelResult = await doExtrasFetch ( url ) ;
if ( getCurrentModelResult . ok ) {
const data = await getCurrentModelResult . json ( ) ;
extension _settings . sd . model = data . model ;
}
url . pathname = '/api/image/models' ;
const getModelsResult = await doExtrasFetch ( url ) ;
if ( getModelsResult . ok ) {
const data = await getModelsResult . json ( ) ;
const view _models = data . models . map ( x => ( { value : x , text : x } ) ) ;
return view _models ;
}
return [ ] ;
}
2023-09-03 00:41:26 +03:00
async function loadAutoModels ( ) {
if ( ! extension _settings . sd . auto _url ) {
return [ ] ;
}
try {
const currentModel = await getAutoRemoteModel ( ) ;
if ( currentModel ) {
extension _settings . sd . model = currentModel ;
}
const result = await fetch ( '/api/sd/models' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
2023-10-07 18:30:06 +03:00
body : JSON . stringify ( getSdRequestBody ( ) ) ,
2023-09-03 00:41:26 +03:00
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD WebUI returned an error.' ) ;
}
2023-09-03 14:56:02 +03:00
const upscalers = await getAutoRemoteUpscalers ( ) ;
if ( Array . isArray ( upscalers ) && upscalers . length > 0 ) {
$ ( '#sd_hr_upscaler' ) . empty ( ) ;
for ( const upscaler of upscalers ) {
const option = document . createElement ( 'option' ) ;
option . innerText = upscaler ;
option . value = upscaler ;
option . selected = upscaler === extension _settings . sd . hr _upscaler ;
$ ( '#sd_hr_upscaler' ) . append ( option ) ;
}
}
2023-09-03 00:41:26 +03:00
const data = await result . json ( ) ;
return data ;
} catch ( error ) {
return [ ] ;
}
}
2024-03-30 01:12:29 -03:00
async function loadDrawthingsModels ( ) {
if ( ! extension _settings . sd . drawthings _url ) {
return [ ] ;
}
try {
const currentModel = await getDrawthingsRemoteModel ( ) ;
if ( currentModel ) {
extension _settings . sd . model = currentModel ;
}
2024-04-02 13:15:31 +03:00
const data = [ { value : currentModel , text : currentModel } ] ;
2024-03-30 01:12:29 -03:00
2024-05-29 02:49:13 +03:00
const upscalers = await getDrawthingsRemoteUpscalers ( ) ;
if ( Array . isArray ( upscalers ) && upscalers . length > 0 ) {
$ ( '#sd_hr_upscaler' ) . empty ( ) ;
for ( const upscaler of upscalers ) {
const option = document . createElement ( 'option' ) ;
option . innerText = upscaler ;
option . value = upscaler ;
option . selected = upscaler === extension _settings . sd . hr _upscaler ;
$ ( '#sd_hr_upscaler' ) . append ( option ) ;
}
}
2024-03-30 01:12:29 -03:00
return data ;
} catch ( error ) {
2024-04-02 13:15:31 +03:00
console . log ( 'Error loading DrawThings API models:' , error ) ;
2024-03-30 01:12:29 -03:00
return [ ] ;
}
}
2023-11-06 21:47:00 +02:00
async function loadOpenAiModels ( ) {
return [
{ value : 'dall-e-3' , text : 'DALL-E 3' } ,
2023-11-27 16:10:42 +02:00
{ value : 'dall-e-2' , text : 'DALL-E 2' } ,
2023-11-06 21:47:00 +02:00
] ;
}
2023-10-07 18:30:06 +03:00
async function loadVladModels ( ) {
if ( ! extension _settings . sd . vlad _url ) {
return [ ] ;
}
try {
const currentModel = await getAutoRemoteModel ( ) ;
if ( currentModel ) {
extension _settings . sd . model = currentModel ;
}
const result = await fetch ( '/api/sd/models' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( getSdRequestBody ( ) ) ,
} ) ;
if ( ! result . ok ) {
throw new Error ( 'SD WebUI returned an error.' ) ;
}
const upscalers = await getVladRemoteUpscalers ( ) ;
if ( Array . isArray ( upscalers ) && upscalers . length > 0 ) {
$ ( '#sd_hr_upscaler' ) . empty ( ) ;
for ( const upscaler of upscalers ) {
const option = document . createElement ( 'option' ) ;
option . innerText = upscaler ;
option . value = upscaler ;
option . selected = upscaler === extension _settings . sd . hr _upscaler ;
$ ( '#sd_hr_upscaler' ) . append ( option ) ;
}
}
const data = await result . json ( ) ;
return data ;
} catch ( error ) {
return [ ] ;
}
}
2023-09-03 00:41:26 +03:00
async function loadNovelModels ( ) {
if ( ! secret _state [ SECRET _KEYS . NOVEL ] ) {
2023-09-04 18:00:15 +03:00
console . debug ( 'NovelAI API key is not set.' ) ;
2023-09-03 00:41:26 +03:00
return [ ] ;
}
return [
2023-11-16 02:15:28 +02:00
{
value : 'nai-diffusion-3' ,
text : 'NAI Diffusion Anime V3' ,
} ,
2023-10-23 01:06:49 +03:00
{
value : 'nai-diffusion-2' ,
text : 'NAI Diffusion Anime V2' ,
} ,
2023-09-03 00:41:26 +03:00
{
value : 'nai-diffusion' ,
2023-10-23 01:06:49 +03:00
text : 'NAI Diffusion Anime V1 (Full)' ,
2023-09-03 00:41:26 +03:00
} ,
{
value : 'safe-diffusion' ,
2023-10-23 01:06:49 +03:00
text : 'NAI Diffusion Anime V1 (Curated)' ,
2023-09-03 00:41:26 +03:00
} ,
2024-04-23 03:18:45 +03:00
{
value : 'nai-diffusion-furry-3' ,
text : 'NAI Diffusion Furry V3' ,
} ,
2023-09-03 00:41:26 +03:00
{
value : 'nai-diffusion-furry' ,
2023-10-23 01:06:49 +03:00
text : 'NAI Diffusion Furry' ,
2023-09-03 00:41:26 +03:00
} ,
] ;
}
2023-11-19 12:18:48 +00:00
async function loadComfyModels ( ) {
if ( ! extension _settings . sd . comfy _url ) {
return [ ] ;
}
try {
2023-12-02 13:04:51 -05:00
const result = await fetch ( '/api/sd/comfy/models' , {
2023-11-19 18:29:41 +00:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
url : extension _settings . sd . comfy _url ,
2023-12-02 22:06:57 +02:00
} ) ,
2023-11-19 18:29:41 +00:00
} ) ;
2023-11-19 12:18:48 +00:00
if ( ! result . ok ) {
throw new Error ( 'ComfyUI returned an error.' ) ;
}
2023-11-19 18:29:41 +00:00
return await result . json ( ) ;
2023-11-19 12:18:48 +00:00
} catch ( error ) {
return [ ] ;
}
}
async function loadSchedulers ( ) {
$ ( '#sd_scheduler' ) . empty ( ) ;
let schedulers = [ ] ;
switch ( extension _settings . sd . source ) {
case sources . extras :
schedulers = [ 'N/A' ] ;
break ;
case sources . horde :
schedulers = [ 'N/A' ] ;
break ;
case sources . auto :
2024-05-30 21:04:22 +03:00
schedulers = await getAutoRemoteSchedulers ( ) ;
2023-11-19 12:18:48 +00:00
break ;
case sources . novel :
schedulers = [ 'N/A' ] ;
break ;
case sources . vlad :
2024-05-30 22:42:21 +03:00
schedulers = [ 'N/A' ] ;
2023-11-19 12:18:48 +00:00
break ;
2024-03-30 01:12:29 -03:00
case sources . drawthings :
schedulers = [ 'N/A' ] ;
break ;
2023-11-19 12:18:48 +00:00
case sources . openai :
schedulers = [ 'N/A' ] ;
break ;
2023-12-18 03:33:05 +02:00
case sources . togetherai :
schedulers = [ 'N/A' ] ;
break ;
2024-04-04 20:40:47 +03:00
case sources . pollinations :
schedulers = [ 'N/A' ] ;
break ;
2023-11-19 12:18:48 +00:00
case sources . comfy :
schedulers = await loadComfySchedulers ( ) ;
break ;
}
for ( const scheduler of schedulers ) {
const option = document . createElement ( 'option' ) ;
option . innerText = scheduler ;
option . value = scheduler ;
option . selected = scheduler === extension _settings . sd . scheduler ;
$ ( '#sd_scheduler' ) . append ( option ) ;
}
2024-05-30 21:04:22 +03:00
if ( ! extension _settings . sd . scheduler && schedulers . length > 0 && schedulers [ 0 ] !== 'N/A' ) {
extension _settings . sd . scheduler = schedulers [ 0 ] ;
$ ( '#sd_scheduler' ) . val ( extension _settings . sd . scheduler ) . trigger ( 'change' ) ;
}
2023-11-19 12:18:48 +00:00
}
async function loadComfySchedulers ( ) {
if ( ! extension _settings . sd . comfy _url ) {
return [ ] ;
}
try {
2023-12-02 13:04:51 -05:00
const result = await fetch ( '/api/sd/comfy/schedulers' , {
2023-11-19 18:29:41 +00:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
url : extension _settings . sd . comfy _url ,
2023-12-02 22:06:57 +02:00
} ) ,
2023-11-19 18:29:41 +00:00
} ) ;
2023-11-19 12:18:48 +00:00
if ( ! result . ok ) {
throw new Error ( 'ComfyUI returned an error.' ) ;
}
2023-11-19 18:29:41 +00:00
return await result . json ( ) ;
2023-11-19 12:18:48 +00:00
} catch ( error ) {
return [ ] ;
}
}
2023-11-20 10:35:11 +00:00
async function loadVaes ( ) {
$ ( '#sd_vae' ) . empty ( ) ;
let vaes = [ ] ;
switch ( extension _settings . sd . source ) {
case sources . extras :
vaes = [ 'N/A' ] ;
break ;
case sources . horde :
vaes = [ 'N/A' ] ;
break ;
case sources . auto :
vaes = [ 'N/A' ] ;
break ;
case sources . novel :
vaes = [ 'N/A' ] ;
break ;
case sources . vlad :
vaes = [ 'N/A' ] ;
break ;
2024-03-30 01:12:29 -03:00
case sources . drawthings :
vaes = [ 'N/A' ] ;
break ;
2023-11-20 10:35:11 +00:00
case sources . openai :
vaes = [ 'N/A' ] ;
break ;
2023-12-18 03:33:05 +02:00
case sources . togetherai :
vaes = [ 'N/A' ] ;
break ;
2024-04-04 20:40:47 +03:00
case sources . pollinations :
vaes = [ 'N/A' ] ;
break ;
2023-11-20 10:35:11 +00:00
case sources . comfy :
vaes = await loadComfyVaes ( ) ;
break ;
}
for ( const vae of vaes ) {
const option = document . createElement ( 'option' ) ;
option . innerText = vae ;
option . value = vae ;
option . selected = vae === extension _settings . sd . vae ;
$ ( '#sd_vae' ) . append ( option ) ;
}
}
async function loadComfyVaes ( ) {
if ( ! extension _settings . sd . comfy _url ) {
return [ ] ;
}
try {
2023-12-02 13:04:51 -05:00
const result = await fetch ( '/api/sd/comfy/vaes' , {
2023-11-20 10:35:11 +00:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
url : extension _settings . sd . comfy _url ,
2023-12-02 22:06:57 +02:00
} ) ,
2023-11-20 10:35:11 +00:00
} ) ;
if ( ! result . ok ) {
throw new Error ( 'ComfyUI returned an error.' ) ;
}
return await result . json ( ) ;
} catch ( error ) {
return [ ] ;
}
}
2023-11-20 12:13:28 +00:00
async function loadComfyWorkflows ( ) {
if ( ! extension _settings . sd . comfy _url ) {
return ;
}
try {
$ ( '#sd_comfy_workflow' ) . empty ( ) ;
2023-12-02 13:04:51 -05:00
const result = await fetch ( '/api/sd/comfy/workflows' , {
2023-11-20 12:13:28 +00:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
url : extension _settings . sd . comfy _url ,
2023-12-02 22:06:57 +02:00
} ) ,
2023-11-20 12:13:28 +00:00
} ) ;
if ( ! result . ok ) {
throw new Error ( 'ComfyUI returned an error.' ) ;
}
const workflows = await result . json ( ) ;
for ( const workflow of workflows ) {
const option = document . createElement ( 'option' ) ;
option . innerText = workflow ;
option . value = workflow ;
option . selected = workflow === extension _settings . sd . comfy _workflow ;
$ ( '#sd_comfy_workflow' ) . append ( option ) ;
}
} catch ( error ) {
return ;
}
}
2023-07-20 20:32:15 +03:00
function getGenerationType ( prompt ) {
2023-11-19 15:24:43 +02:00
let mode = generationMode . FREE ;
2023-07-20 20:32:15 +03:00
for ( const [ key , values ] of Object . entries ( triggerWords ) ) {
for ( const value of values ) {
if ( value . toLowerCase ( ) === prompt . toLowerCase ( ) . trim ( ) ) {
2023-11-19 15:24:43 +02:00
mode = Number ( key ) ;
break ;
2023-07-20 20:32:15 +03:00
}
}
}
2023-11-19 15:24:43 +02:00
if ( extension _settings . sd . multimodal _captioning && multimodalMap [ mode ] !== undefined ) {
mode = multimodalMap [ mode ] ;
}
return mode ;
2023-07-20 20:32:15 +03:00
}
function getQuietPrompt ( mode , trigger ) {
if ( mode === generationMode . FREE ) {
return trigger ;
}
2023-08-20 12:29:20 +03:00
return stringFormat ( extension _settings . sd . prompts [ mode ] , trigger ) ;
2023-07-20 20:32:15 +03:00
}
function processReply ( str ) {
if ( ! str ) {
return '' ;
}
2023-12-02 21:11:06 +02:00
str = str . replaceAll ( '"' , '' ) ;
str = str . replaceAll ( '“' , '' ) ;
str = str . replaceAll ( '.' , ',' ) ;
str = str . replaceAll ( '\n' , ', ' ) ;
2024-05-21 16:51:11 +03:00
str = str . replace ( /[^a-zA-Z0-9,:_(){}[\]\-']+/g , ' ' ) ;
2023-07-20 20:32:15 +03:00
str = str . replace ( /\s+/g , ' ' ) ; // Collapse multiple whitespaces into one
str = str . trim ( ) ;
str = str
. split ( ',' ) // list split by commas
. map ( x => x . trim ( ) ) // trim each entry
. filter ( x => x ) // remove empty entries
. join ( ', ' ) ; // join it back with proper spacing
return str ;
}
function getRawLastMessage ( ) {
2023-08-29 18:33:30 +03:00
const getLastUsableMessage = ( ) => {
for ( const message of context . chat . slice ( ) . reverse ( ) ) {
if ( message . is _system ) {
continue ;
}
2024-04-02 13:15:31 +03:00
return {
mes : message . mes ,
original _avatar : message . original _avatar ,
} ;
2023-08-29 18:33:30 +03:00
}
2023-11-08 10:57:37 +02:00
toastr . warning ( 'No usable messages found.' , 'Image Generation' ) ;
2023-08-29 18:33:30 +03:00
throw new Error ( 'No usable messages found.' ) ;
2023-12-02 21:11:06 +02:00
} ;
2023-08-29 18:33:30 +03:00
2023-07-20 20:32:15 +03:00
const context = getContext ( ) ;
2024-04-02 13:15:31 +03:00
const lastMessage = getLastUsableMessage ( ) ;
const character = context . groupId
? context . characters . find ( c => c . avatar === lastMessage . original _avatar )
: context . characters [ context . characterId ] ;
if ( ! character ) {
console . debug ( 'Character not found, using raw message.' ) ;
return processReply ( lastMessage . mes ) ;
}
return ` (( ${ processReply ( lastMessage . mes ) } )), ( ${ processReply ( character . scenario ) } :0.7), ( ${ processReply ( character . description ) } :0.5) ` ;
2023-07-20 20:32:15 +03:00
}
2024-01-24 20:15:10 +02:00
async function generatePicture ( args , trigger , message , callback ) {
2023-07-20 20:32:15 +03:00
if ( ! trigger || trigger . trim ( ) . length === 0 ) {
console . log ( 'Trigger word empty, aborting' ) ;
return ;
}
2023-09-03 00:41:26 +03:00
if ( ! isValidState ( ) ) {
2024-05-13 23:11:07 +03:00
toastr . warning ( 'Image generation is not available. Check your settings and try again.' ) ;
2023-07-20 20:32:15 +03:00
return ;
}
extension _settings . sd . sampler = $ ( '#sd_sampler' ) . find ( ':selected' ) . val ( ) ;
extension _settings . sd . model = $ ( '#sd_model' ) . find ( ':selected' ) . val ( ) ;
trigger = trigger . trim ( ) ;
const generationType = getGenerationType ( trigger ) ;
console . log ( 'Generation mode' , generationType , 'triggered with' , trigger ) ;
2023-11-19 15:24:43 +02:00
const quietPrompt = getQuietPrompt ( generationType , trigger ) ;
2023-07-20 20:32:15 +03:00
const context = getContext ( ) ;
2023-08-19 23:01:09 -04:00
// if context.characterId is not null, then we get context.characters[context.characterId].avatar, else we get groupId and context.groups[groupId].id
// sadly, groups is not an array, but is a dict with keys being index numbers, so we have to filter it
2023-10-07 18:30:06 +03:00
const characterName = context . characterId ? context . characters [ context . characterId ] . name : context . groups [ Object . keys ( context . groups ) . filter ( x => context . groups [ x ] . id === context . groupId ) [ 0 ] ] ? . id ? . toString ( ) ;
2023-08-19 23:01:09 -04:00
2023-09-04 12:18:37 +03:00
if ( generationType == generationMode . BACKGROUND ) {
2023-08-18 00:41:21 +03:00
const callbackOriginal = callback ;
2023-11-01 22:58:59 +02:00
callback = async function ( prompt , imagePath , generationType ) {
2023-10-21 17:43:25 +03:00
const imgUrl = ` url(" ${ encodeURI ( imagePath ) } ") ` ;
eventSource . emit ( event _types . FORCE _SET _BACKGROUND , { url : imgUrl , path : imagePath } ) ;
2023-08-18 00:41:21 +03:00
if ( typeof callbackOriginal === 'function' ) {
2024-05-06 21:39:07 +03:00
callbackOriginal ( prompt , imagePath , generationType , negativePromptPrefix ) ;
2023-08-18 00:41:21 +03:00
} else {
2024-05-06 21:39:07 +03:00
sendMessage ( prompt , imagePath , generationType , negativePromptPrefix ) ;
2023-08-18 00:41:21 +03:00
}
2023-12-02 21:11:06 +02:00
} ;
2023-07-20 20:32:15 +03:00
}
2024-01-24 20:15:10 +02:00
if ( isTrueBoolean ( args ? . quiet ) ) {
callback = ( ) => { } ;
}
2024-05-06 21:39:07 +03:00
const negativePromptPrefix = resolveVariable ( args ? . negative ) || '' ;
2023-11-01 22:58:59 +02:00
const dimensions = setTypeSpecificDimensions ( generationType ) ;
2024-01-24 20:15:10 +02:00
let imagePath = '' ;
2023-11-01 22:58:59 +02:00
2023-07-20 20:32:15 +03:00
try {
2023-11-19 15:24:43 +02:00
const prompt = await getPrompt ( generationType , message , trigger , quietPrompt ) ;
2023-11-08 10:57:37 +02:00
console . log ( 'Processed image prompt:' , prompt ) ;
2023-07-20 20:32:15 +03:00
context . deactivateSendButtons ( ) ;
hideSwipeButtons ( ) ;
2024-05-06 21:39:07 +03:00
imagePath = await sendGenerationRequest ( generationType , prompt , negativePromptPrefix , characterName , callback ) ;
2023-07-20 20:32:15 +03:00
} catch ( err ) {
console . trace ( err ) ;
2023-12-02 21:11:06 +02:00
throw new Error ( 'SD prompt text generation failed.' ) ;
2023-07-20 20:32:15 +03:00
}
finally {
2023-11-01 22:58:59 +02:00
restoreOriginalDimensions ( dimensions ) ;
2023-07-20 20:32:15 +03:00
context . activateSendButtons ( ) ;
showSwipeButtons ( ) ;
}
2024-01-24 20:15:10 +02:00
return imagePath ;
2023-07-20 20:32:15 +03:00
}
2023-11-01 22:58:59 +02:00
function setTypeSpecificDimensions ( generationType ) {
const prevSDHeight = extension _settings . sd . height ;
const prevSDWidth = extension _settings . sd . width ;
const aspectRatio = extension _settings . sd . width / extension _settings . sd . height ;
// Face images are always portrait (pun intended)
2024-02-12 17:28:39 +02:00
if ( ( generationType == generationMode . FACE || generationType == generationMode . FACE _MULTIMODAL ) && aspectRatio >= 1 ) {
2023-11-01 22:58:59 +02:00
// Round to nearest multiple of 64
extension _settings . sd . height = Math . round ( extension _settings . sd . width * 1.5 / 64 ) * 64 ;
}
if ( generationType == generationMode . BACKGROUND ) {
// Background images are always landscape
if ( aspectRatio <= 1 ) {
// Round to nearest multiple of 64
extension _settings . sd . width = Math . round ( extension _settings . sd . height * 1.8 / 64 ) * 64 ;
}
}
2024-02-12 17:28:39 +02:00
if ( extension _settings . sd . snap ) {
// Force to use roughly the same pixel count as before rescaling
const prevPixelCount = prevSDHeight * prevSDWidth ;
const newPixelCount = extension _settings . sd . height * extension _settings . sd . width ;
2024-02-13 11:40:37 +02:00
if ( prevPixelCount !== newPixelCount ) {
const ratio = Math . sqrt ( prevPixelCount / newPixelCount ) ;
extension _settings . sd . height = Math . round ( extension _settings . sd . height * ratio / 64 ) * 64 ;
extension _settings . sd . width = Math . round ( extension _settings . sd . width * ratio / 64 ) * 64 ;
console . log ( ` Pixel counts after rescaling: ${ prevPixelCount } -> ${ newPixelCount } (ratio: ${ ratio } ) ` ) ;
const resolution = resolutionOptions [ getClosestKnownResolution ( ) ] ;
if ( resolution ) {
extension _settings . sd . height = resolution . height ;
extension _settings . sd . width = resolution . width ;
console . log ( 'Snap to resolution' , JSON . stringify ( resolution ) ) ;
} else {
console . warn ( 'Snap to resolution failed, using custom dimensions' ) ;
}
2024-02-12 17:28:39 +02:00
}
}
2023-11-01 22:58:59 +02:00
return { height : prevSDHeight , width : prevSDWidth } ;
}
function restoreOriginalDimensions ( savedParams ) {
extension _settings . sd . height = savedParams . height ;
extension _settings . sd . width = savedParams . width ;
}
2023-11-19 15:24:43 +02:00
async function getPrompt ( generationType , message , trigger , quietPrompt ) {
2023-07-20 20:32:15 +03:00
let prompt ;
switch ( generationType ) {
case generationMode . RAW _LAST :
prompt = message || getRawLastMessage ( ) ;
break ;
case generationMode . FREE :
2023-12-16 11:29:34 +01:00
prompt = generateFreeModePrompt ( trigger . trim ( ) ) ;
2023-07-20 20:32:15 +03:00
break ;
2023-11-19 15:24:43 +02:00
case generationMode . FACE _MULTIMODAL :
case generationMode . CHARACTER _MULTIMODAL :
case generationMode . USER _MULTIMODAL :
prompt = await generateMultimodalPrompt ( generationType , quietPrompt ) ;
break ;
2023-07-20 20:32:15 +03:00
default :
2023-11-19 15:24:43 +02:00
prompt = await generatePrompt ( quietPrompt ) ;
2023-07-20 20:32:15 +03:00
break ;
}
2023-07-22 20:32:58 +03:00
if ( generationType !== generationMode . FREE ) {
2023-10-20 15:43:55 +03:00
prompt = await refinePrompt ( prompt , true ) ;
2023-07-22 20:32:58 +03:00
}
2023-07-20 20:32:15 +03:00
return prompt ;
}
2023-12-15 15:30:02 +01:00
/ * *
2023-12-16 11:29:34 +01:00
* Generates a free prompt with a character - specific prompt prefix support .
2023-12-15 15:30:02 +01:00
* @ param { string } trigger - The prompt to use for the image generation .
* @ returns { string }
* /
2023-12-16 11:29:34 +01:00
function generateFreeModePrompt ( trigger ) {
return trigger
2023-12-17 02:34:42 +02:00
. replace ( /(?:^char(\s|,)|\{\{charPrefix\}\})/gi , ( _ , suffix ) => {
2023-12-16 11:29:34 +01:00
const getLastCharacterKey = ( ) => {
if ( typeof this _chid !== 'undefined' ) {
return getCharaFilename ( this _chid ) ;
}
const context = getContext ( ) ;
for ( let i = context . chat . length - 1 ; i >= 0 ; i -- ) {
const message = context . chat [ i ] ;
if ( message . is _user || message . is _system ) {
continue ;
} else if ( typeof message . original _avatar === 'string' ) {
return message . original _avatar . replace ( /\.[^/.]+$/ , '' ) ;
}
}
throw new Error ( 'No usable messages found.' ) ;
} ;
2023-12-15 15:30:02 +01:00
2023-12-16 11:29:34 +01:00
const key = getLastCharacterKey ( ) ;
const value = ( extension _settings . sd . character _prompts [ key ] || '' ) . trim ( ) ;
return value ? value + ( suffix || '' ) : '' ;
} ) ;
2023-12-15 15:30:02 +01:00
}
2023-11-19 15:24:43 +02:00
/ * *
* Generates a prompt using multimodal captioning .
* @ param { number } generationType - The type of image generation to perform .
* @ param { string } quietPrompt - The prompt to use for the image generation .
* /
async function generateMultimodalPrompt ( generationType , quietPrompt ) {
let avatarUrl ;
if ( generationType == generationMode . USER _MULTIMODAL ) {
2024-04-19 00:32:38 +03:00
avatarUrl = getUserAvatarUrl ( ) ;
2023-11-19 15:24:43 +02:00
}
if ( generationType == generationMode . CHARACTER _MULTIMODAL || generationType === generationMode . FACE _MULTIMODAL ) {
2024-04-19 00:32:38 +03:00
avatarUrl = getCharacterAvatarUrl ( ) ;
2023-11-19 15:24:43 +02:00
}
2023-12-14 20:36:31 +02:00
try {
2024-05-29 23:38:55 +03:00
const toast = toastr . info ( 'Generating multimodal caption...' , 'Image Generation' ) ;
2023-12-14 20:36:31 +02:00
const response = await fetch ( avatarUrl ) ;
2023-11-19 15:24:43 +02:00
2023-12-14 20:36:31 +02:00
if ( ! response . ok ) {
throw new Error ( 'Could not fetch avatar image.' ) ;
}
2023-11-19 15:24:43 +02:00
2023-12-14 20:36:31 +02:00
const avatarBlob = await response . blob ( ) ;
const avatarBase64 = await getBase64Async ( avatarBlob ) ;
2023-11-19 15:24:43 +02:00
2023-12-14 20:36:31 +02:00
const caption = await getMultimodalCaption ( avatarBase64 , quietPrompt ) ;
2024-05-29 23:38:55 +03:00
toastr . clear ( toast ) ;
2023-11-19 15:24:43 +02:00
2023-12-14 20:36:31 +02:00
if ( ! caption ) {
throw new Error ( 'No caption returned from the API.' ) ;
}
return caption ;
} catch ( error ) {
console . error ( error ) ;
toastr . error ( 'Multimodal captioning failed. Please try again.' , 'Image Generation' ) ;
2023-11-19 15:24:43 +02:00
throw new Error ( 'Multimodal captioning failed.' ) ;
}
}
2024-04-19 00:32:38 +03:00
function getCharacterAvatarUrl ( ) {
const context = getContext ( ) ;
if ( context . groupId ) {
const groupMembers = context . groups . find ( x => x . id === context . groupId ) ? . members ;
const lastMessageAvatar = context . chat ? . filter ( x => ! x . is _system && ! x . is _user ) ? . slice ( - 1 ) [ 0 ] ? . original _avatar ;
const randomMemberAvatar = Array . isArray ( groupMembers ) ? groupMembers [ Math . floor ( Math . random ( ) * groupMembers . length ) ] ? . avatar : null ;
const avatarToUse = lastMessageAvatar || randomMemberAvatar ;
return formatCharacterAvatar ( avatarToUse ) ;
} else {
return getCharacterAvatar ( context . characterId ) ;
}
}
function getUserAvatarUrl ( ) {
return getUserAvatar ( user _avatar ) ;
}
2023-11-19 15:24:43 +02:00
/ * *
* Generates a prompt using the main LLM API .
* @ param { string } quietPrompt - The prompt to use for the image generation .
* @ returns { Promise < string > } - A promise that resolves when the prompt generation completes .
* /
async function generatePrompt ( quietPrompt ) {
const reply = await generateQuietPrompt ( quietPrompt , false , false ) ;
2023-12-11 19:07:33 +02:00
const processedReply = processReply ( reply ) ;
2023-12-11 19:00:42 +02:00
2023-12-11 19:07:33 +02:00
if ( ! processedReply ) {
2023-12-11 19:00:42 +02:00
toastr . error ( 'Prompt generation produced no text. Make sure you\'re using a valid instruct template and try again' , 'Image Generation' ) ;
throw new Error ( 'Prompt generation failed.' ) ;
}
2023-12-11 19:07:33 +02:00
return processedReply ;
2023-07-20 20:32:15 +03:00
}
2024-05-06 21:39:07 +03:00
/ * *
* 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 ) {
2023-11-27 16:10:42 +02:00
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 ( ) ) ;
2024-05-06 21:39:07 +03:00
const negativePrefix = noCharPrefix . includes ( generationType )
? extension _settings . sd . negative _prompt
: combinePrefixes ( extension _settings . sd . negative _prompt , getCharacterNegativePrefix ( ) ) ;
2023-08-18 00:41:21 +03:00
2024-04-29 13:50:55 +03:00
const prefixedPrompt = substituteParams ( combinePrefixes ( prefix , prompt , '{prompt}' ) ) ;
2024-05-06 21:39:07 +03:00
const negativePrompt = substituteParams ( combinePrefixes ( additionalNegativePrefix , negativePrefix ) ) ;
2023-12-23 16:19:22 +00:00
2023-09-03 00:41:26 +03:00
let result = { format : '' , data : '' } ;
const currentChatId = getCurrentChatId ( ) ;
try {
switch ( extension _settings . sd . source ) {
case sources . extras :
2023-12-23 16:19:22 +00:00
result = await generateExtrasImage ( prefixedPrompt , negativePrompt ) ;
2023-09-03 00:41:26 +03:00
break ;
case sources . horde :
2023-12-23 16:19:22 +00:00
result = await generateHordeImage ( prefixedPrompt , negativePrompt ) ;
2023-09-03 00:41:26 +03:00
break ;
2023-10-07 18:30:06 +03:00
case sources . vlad :
2023-12-23 16:19:22 +00:00
result = await generateAutoImage ( prefixedPrompt , negativePrompt ) ;
2023-10-07 18:30:06 +03:00
break ;
2024-03-30 01:12:29 -03:00
case sources . drawthings :
result = await generateDrawthingsImage ( prefixedPrompt , negativePrompt ) ;
break ;
2023-09-03 00:41:26 +03:00
case sources . auto :
2023-12-23 16:19:22 +00:00
result = await generateAutoImage ( prefixedPrompt , negativePrompt ) ;
2023-09-03 00:41:26 +03:00
break ;
case sources . novel :
2023-12-23 16:19:22 +00:00
result = await generateNovelImage ( prefixedPrompt , negativePrompt ) ;
2023-09-03 00:41:26 +03:00
break ;
2023-11-06 21:47:00 +02:00
case sources . openai :
result = await generateOpenAiImage ( prefixedPrompt ) ;
break ;
2023-11-19 12:18:48 +00:00
case sources . comfy :
2023-12-23 16:19:22 +00:00
result = await generateComfyImage ( prefixedPrompt , negativePrompt ) ;
2023-11-19 12:18:48 +00:00
break ;
2023-12-18 03:33:05 +02:00
case sources . togetherai :
2023-12-23 16:19:22 +00:00
result = await generateTogetherAIImage ( prefixedPrompt , negativePrompt ) ;
2023-12-18 03:33:05 +02:00
break ;
2024-04-04 20:40:47 +03:00
case sources . pollinations :
result = await generatePollinationsImage ( prefixedPrompt , negativePrompt ) ;
break ;
2023-09-03 00:41:26 +03:00
}
if ( ! result . data ) {
2023-11-06 21:47:00 +02:00
throw new Error ( 'Endpoint did not return image data.' ) ;
2023-09-03 00:41:26 +03:00
}
} catch ( err ) {
2023-11-06 21:47:00 +02:00
console . error ( err ) ;
2023-11-08 10:57:37 +02:00
toastr . error ( 'Image generation failed. Please try again.' + '\n\n' + String ( err ) , 'Image Generation' ) ;
2023-09-03 00:41:26 +03:00
return ;
}
if ( currentChatId !== getCurrentChatId ( ) ) {
console . warn ( 'Chat changed, aborting SD result saving' ) ;
2023-11-08 10:57:37 +02:00
toastr . warning ( 'Chat changed, generated image discarded.' , 'Image Generation' ) ;
2023-09-03 00:41:26 +03:00
return ;
2023-07-20 20:32:15 +03:00
}
2023-09-03 00:41:26 +03:00
const filename = ` ${ characterName } _ ${ humanizedDateTime ( ) } ` ;
const base64Image = await saveBase64AsFile ( result . data , characterName , filename , result . format ) ;
2024-05-06 21:39:07 +03:00
callback ? callback ( prompt , base64Image , generationType , additionalNegativePrefix ) : sendMessage ( prompt , base64Image , generationType , additionalNegativePrefix ) ;
2024-01-24 20:15:10 +02:00
return base64Image ;
2023-07-20 20:32:15 +03:00
}
2023-12-23 16:19:22 +00:00
async function generateTogetherAIImage ( prompt , negativePrompt ) {
2023-12-18 03:33:05 +02:00
const result = await fetch ( '/api/sd/together/generate' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
prompt : prompt ,
2023-12-23 16:19:22 +00:00
negative _prompt : negativePrompt ,
2023-12-18 03:33:05 +02:00
model : extension _settings . sd . model ,
steps : extension _settings . sd . steps ,
width : extension _settings . sd . width ,
height : extension _settings . sd . height ,
2024-05-29 23:29:45 +03:00
seed : extension _settings . sd . seed >= 0 ? extension _settings . sd . seed : undefined ,
2023-12-18 03:33:05 +02:00
} ) ,
} ) ;
if ( result . ok ) {
const data = await result . json ( ) ;
return { format : 'jpg' , data : data ? . output ? . choices ? . [ 0 ] ? . image _base64 } ;
} else {
const text = await result . text ( ) ;
throw new Error ( text ) ;
}
}
2024-04-04 20:40:47 +03:00
async function generatePollinationsImage ( prompt , negativePrompt ) {
const result = await fetch ( '/api/sd/pollinations/generate' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
prompt : prompt ,
negative _prompt : negativePrompt ,
model : extension _settings . sd . model ,
width : extension _settings . sd . width ,
height : extension _settings . sd . height ,
enhance : extension _settings . sd . pollinations _enhance ,
refine : extension _settings . sd . pollinations _refine ,
2024-05-29 23:29:45 +03:00
seed : extension _settings . sd . seed >= 0 ? extension _settings . sd . seed : undefined ,
2024-04-04 20:40:47 +03:00
} ) ,
} ) ;
if ( result . ok ) {
const data = await result . json ( ) ;
return { format : 'jpg' , data : data ? . image } ;
} else {
const text = await result . text ( ) ;
throw new Error ( text ) ;
}
}
2023-08-20 00:15:57 -04:00
/ * *
2023-09-03 00:41:26 +03:00
* Generates an "extras" image using a provided prompt and other settings .
2023-08-20 12:16:29 +03:00
*
2023-08-20 00:33:37 -04:00
* @ param { string } prompt - The main instruction used to guide the image generation .
2023-12-23 16:19:22 +00:00
* @ param { string } negativePrompt - The instruction used to restrict the image generation .
2023-09-03 00:41:26 +03:00
* @ returns { Promise < { format : string , data : string } > } - A promise that resolves when the image generation and processing are complete .
2023-08-20 00:15:57 -04:00
* /
2023-12-23 16:19:22 +00:00
async function generateExtrasImage ( prompt , negativePrompt ) {
2023-07-20 20:32:15 +03:00
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/image' ;
const result = await doExtrasFetch ( url , {
method : 'POST' ,
2023-09-03 14:04:53 +03:00
headers : {
'Content-Type' : 'application/json' ,
} ,
2023-07-20 20:32:15 +03:00
body : JSON . stringify ( {
prompt : prompt ,
sampler : extension _settings . sd . sampler ,
steps : extension _settings . sd . steps ,
scale : extension _settings . sd . scale ,
width : extension _settings . sd . width ,
height : extension _settings . sd . height ,
2023-12-23 16:19:22 +00:00
negative _prompt : negativePrompt ,
2023-07-20 20:32:15 +03:00
restore _faces : ! ! extension _settings . sd . restore _faces ,
enable _hr : ! ! extension _settings . sd . enable _hr ,
karras : ! ! extension _settings . sd . horde _karras ,
2023-09-03 14:56:02 +03:00
hr _upscaler : extension _settings . sd . hr _upscaler ,
hr _scale : extension _settings . sd . hr _scale ,
denoising _strength : extension _settings . sd . denoising _strength ,
hr _second _pass _steps : extension _settings . sd . hr _second _pass _steps ,
2024-05-29 23:29:45 +03:00
seed : extension _settings . sd . seed >= 0 ? extension _settings . sd . seed : undefined ,
2023-07-20 20:32:15 +03:00
} ) ,
} ) ;
if ( result . ok ) {
const data = await result . json ( ) ;
2023-09-03 00:41:26 +03:00
return { format : 'jpg' , data : data . image } ;
2023-07-20 20:32:15 +03:00
} else {
2023-11-06 21:47:00 +02:00
const text = await result . text ( ) ;
throw new Error ( text ) ;
2023-07-20 20:32:15 +03:00
}
}
2023-08-20 00:15:57 -04:00
/ * *
2023-09-03 00:41:26 +03:00
* Generates a "horde" image using the provided prompt and configuration settings .
2023-08-20 12:16:29 +03:00
*
2023-08-20 00:33:37 -04:00
* @ param { string } prompt - The main instruction used to guide the image generation .
2023-12-23 16:19:22 +00:00
* @ param { string } negativePrompt - The instruction used to restrict the image generation .
2023-09-03 00:41:26 +03:00
* @ returns { Promise < { format : string , data : string } > } - A promise that resolves when the image generation and processing are complete .
2023-08-20 00:15:57 -04:00
* /
2023-12-23 16:19:22 +00:00
async function generateHordeImage ( prompt , negativePrompt ) {
2023-09-12 20:45:36 +03:00
const result = await fetch ( '/api/horde/generate-image' , {
2023-07-20 20:32:15 +03:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
prompt : prompt ,
sampler : extension _settings . sd . sampler ,
steps : extension _settings . sd . steps ,
scale : extension _settings . sd . scale ,
width : extension _settings . sd . width ,
height : extension _settings . sd . height ,
2023-12-23 16:19:22 +00:00
negative _prompt : negativePrompt ,
2023-07-20 20:32:15 +03:00
model : extension _settings . sd . model ,
nsfw : extension _settings . sd . horde _nsfw ,
restore _faces : ! ! extension _settings . sd . restore _faces ,
enable _hr : ! ! extension _settings . sd . enable _hr ,
2023-10-08 22:29:33 +03:00
sanitize : ! ! extension _settings . sd . horde _sanitize ,
2024-05-29 02:14:08 +03:00
clip _skip : extension _settings . sd . clip _skip ,
2024-05-29 23:29:45 +03:00
seed : extension _settings . sd . seed >= 0 ? extension _settings . sd . seed : undefined ,
2023-07-20 20:32:15 +03:00
} ) ,
} ) ;
if ( result . ok ) {
const data = await result . text ( ) ;
2023-09-03 00:41:26 +03:00
return { format : 'webp' , data : data } ;
2023-07-20 20:32:15 +03:00
} else {
2023-11-06 21:47:00 +02:00
const text = await result . text ( ) ;
throw new Error ( text ) ;
2023-09-03 00:41:26 +03:00
}
}
/ * *
* Generates an image in SD WebUI API using the provided prompt and configuration settings .
*
* @ param { string } prompt - The main instruction used to guide the image generation .
2023-12-23 16:19:22 +00:00
* @ param { string } negativePrompt - The instruction used to restrict the image generation .
2023-09-03 00:41:26 +03:00
* @ returns { Promise < { format : string , data : string } > } - A promise that resolves when the image generation and processing are complete .
* /
2023-12-23 16:19:22 +00:00
async function generateAutoImage ( prompt , negativePrompt ) {
2023-09-03 00:41:26 +03:00
const result = await fetch ( '/api/sd/generate' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
2023-10-07 18:30:06 +03:00
... getSdRequestBody ( ) ,
2023-09-03 00:41:26 +03:00
prompt : prompt ,
2023-12-23 16:19:22 +00:00
negative _prompt : negativePrompt ,
2023-09-03 00:41:26 +03:00
sampler _name : extension _settings . sd . sampler ,
2024-05-30 21:04:22 +03:00
scheduler : extension _settings . sd . scheduler ,
2023-09-03 00:41:26 +03:00
steps : extension _settings . sd . steps ,
cfg _scale : extension _settings . sd . scale ,
width : extension _settings . sd . width ,
height : extension _settings . sd . height ,
restore _faces : ! ! extension _settings . sd . restore _faces ,
2023-09-03 14:56:02 +03:00
enable _hr : ! ! extension _settings . sd . enable _hr ,
hr _upscaler : extension _settings . sd . hr _upscaler ,
hr _scale : extension _settings . sd . hr _scale ,
denoising _strength : extension _settings . sd . denoising _strength ,
hr _second _pass _steps : extension _settings . sd . hr _second _pass _steps ,
2024-05-29 23:29:45 +03:00
seed : extension _settings . sd . seed >= 0 ? extension _settings . sd . seed : undefined ,
2024-05-29 02:14:08 +03:00
override _settings : {
CLIP _stop _at _last _layers : extension _settings . sd . clip _skip ,
} ,
override _settings _restore _afterwards : true ,
2023-09-03 00:41:26 +03:00
// Ensure generated img is saved to disk
save _images : true ,
send _images : true ,
do _not _save _grid : false ,
do _not _save _samples : false ,
} ) ,
} ) ;
if ( result . ok ) {
const data = await result . json ( ) ;
return { format : 'png' , data : data . images [ 0 ] } ;
} else {
2023-11-06 21:47:00 +02:00
const text = await result . text ( ) ;
throw new Error ( text ) ;
2023-09-03 00:41:26 +03:00
}
}
2024-03-30 01:12:29 -03:00
/ * *
* Generates an image in Drawthings API using the provided prompt and configuration settings .
*
* @ param { string } prompt - The main instruction used to guide the image generation .
* @ param { string } negativePrompt - The instruction used to restrict the image generation .
* @ returns { Promise < { format : string , data : string } > } - A promise that resolves when the image generation and processing are complete .
* /
async function generateDrawthingsImage ( prompt , negativePrompt ) {
const result = await fetch ( '/api/sd/drawthings/generate' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
... getSdRequestBody ( ) ,
prompt : prompt ,
negative _prompt : negativePrompt ,
sampler _name : extension _settings . sd . sampler ,
steps : extension _settings . sd . steps ,
cfg _scale : extension _settings . sd . scale ,
width : extension _settings . sd . width ,
height : extension _settings . sd . height ,
restore _faces : ! ! extension _settings . sd . restore _faces ,
enable _hr : ! ! extension _settings . sd . enable _hr ,
denoising _strength : extension _settings . sd . denoising _strength ,
2024-05-29 02:14:08 +03:00
clip _skip : extension _settings . sd . clip _skip ,
2024-05-29 02:49:13 +03:00
upscaler _scale : extension _settings . sd . hr _scale ,
2024-05-29 23:29:45 +03:00
seed : extension _settings . sd . seed >= 0 ? extension _settings . sd . seed : undefined ,
2024-03-30 01:12:29 -03:00
// TODO: advanced API parameters: hr, upscaler
} ) ,
} ) ;
if ( result . ok ) {
const data = await result . json ( ) ;
return { format : 'png' , data : data . images [ 0 ] } ;
} else {
const text = await result . text ( ) ;
throw new Error ( text ) ;
}
}
2023-09-03 00:41:26 +03:00
/ * *
* Generates an image in NovelAI API using the provided prompt and configuration settings .
*
* @ param { string } prompt - The main instruction used to guide the image generation .
2023-12-23 16:19:22 +00:00
* @ param { string } negativePrompt - The instruction used to restrict the image generation .
2023-09-03 00:41:26 +03:00
* @ returns { Promise < { format : string , data : string } > } - A promise that resolves when the image generation and processing are complete .
* /
2023-12-23 16:19:22 +00:00
async function generateNovelImage ( prompt , negativePrompt ) {
2024-03-20 21:23:56 +02:00
const { steps , width , height , sm , sm _dyn } = getNovelParams ( ) ;
2023-09-04 18:00:15 +03:00
2023-09-03 00:41:26 +03:00
const result = await fetch ( '/api/novelai/generate-image' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
prompt : prompt ,
model : extension _settings . sd . model ,
sampler : extension _settings . sd . sampler ,
2023-09-04 18:00:15 +03:00
steps : steps ,
2023-09-03 00:41:26 +03:00
scale : extension _settings . sd . scale ,
2023-09-04 18:00:15 +03:00
width : width ,
height : height ,
2023-12-23 16:19:22 +00:00
negative _prompt : negativePrompt ,
2023-09-04 18:00:15 +03:00
upscale _ratio : extension _settings . sd . novel _upscale _ratio ,
2024-05-29 03:00:42 +03:00
decrisper : extension _settings . sd . novel _decrisper ,
2024-03-20 21:23:56 +02:00
sm : sm ,
sm _dyn : sm _dyn ,
2024-05-29 23:29:45 +03:00
seed : extension _settings . sd . seed >= 0 ? extension _settings . sd . seed : undefined ,
2023-09-03 00:41:26 +03:00
} ) ,
} ) ;
if ( result . ok ) {
const data = await result . text ( ) ;
return { format : 'png' , data : data } ;
} else {
2023-11-06 21:47:00 +02:00
const text = await result . text ( ) ;
throw new Error ( text ) ;
2023-07-20 20:32:15 +03:00
}
}
2023-09-04 18:00:15 +03:00
/ * *
* Adjusts extension parameters for NovelAI . Applies Anlas guard if needed .
2024-03-20 21:23:56 +02:00
* @ returns { { steps : number , width : number , height : number , sm : boolean , sm _dyn : boolean } } - A tuple of parameters for NovelAI API .
2023-09-04 18:00:15 +03:00
* /
function getNovelParams ( ) {
let steps = extension _settings . sd . steps ;
let width = extension _settings . sd . width ;
let height = extension _settings . sd . height ;
2024-03-20 21:23:56 +02:00
let sm = extension _settings . sd . novel _sm ;
let sm _dyn = extension _settings . sd . novel _sm _dyn ;
if ( extension _settings . sd . sampler === 'ddim' ) {
sm = false ;
sm _dyn = false ;
}
2023-09-04 18:00:15 +03:00
2023-10-23 10:41:39 +03:00
// Don't apply Anlas guard if it's disabled.
2023-09-04 18:00:15 +03:00
if ( ! extension _settings . sd . novel _anlas _guard ) {
2024-03-20 21:23:56 +02:00
return { steps , width , height , sm , sm _dyn } ;
2023-09-04 18:00:15 +03:00
}
const MAX _STEPS = 28 ;
2023-10-23 10:41:39 +03:00
const MAX _PIXELS = 1024 * 1024 ;
2023-09-04 18:00:15 +03:00
if ( width * height > MAX _PIXELS ) {
const ratio = Math . sqrt ( MAX _PIXELS / ( width * height ) ) ;
// Calculate new width and height while maintaining aspect ratio.
var newWidth = Math . round ( width * ratio ) ;
var newHeight = Math . round ( height * ratio ) ;
// Ensure new dimensions are multiples of 64. If not, reduce accordingly.
if ( newWidth % 64 !== 0 ) {
newWidth = newWidth - newWidth % 64 ;
}
if ( newHeight % 64 !== 0 ) {
newHeight = newHeight - newHeight % 64 ;
}
// If total pixel count after rounding still exceeds MAX_PIXELS, decrease dimension size by 64 accordingly.
while ( newWidth * newHeight > MAX _PIXELS ) {
if ( newWidth > newHeight ) {
newWidth -= 64 ;
} else {
newHeight -= 64 ;
}
}
console . log ( ` Anlas Guard: Image size ( ${ width } x ${ height } ) > ${ MAX _PIXELS } , reducing size to ${ newWidth } x ${ newHeight } ` ) ;
width = newWidth ;
height = newHeight ;
}
if ( steps > MAX _STEPS ) {
console . log ( ` Anlas Guard: Steps ( ${ steps } ) > ${ MAX _STEPS } , reducing steps to ${ MAX _STEPS } ` ) ;
steps = MAX _STEPS ;
}
2024-03-20 21:23:56 +02:00
return { steps , width , height , sm , sm _dyn } ;
2023-09-04 18:00:15 +03:00
}
2023-11-06 21:47:00 +02:00
async function generateOpenAiImage ( prompt ) {
const dalle2PromptLimit = 1000 ;
const dalle3PromptLimit = 4000 ;
const isDalle2 = extension _settings . sd . model === 'dall-e-2' ;
const isDalle3 = extension _settings . sd . model === 'dall-e-3' ;
if ( isDalle2 && prompt . length > dalle2PromptLimit ) {
prompt = prompt . substring ( 0 , dalle2PromptLimit ) ;
}
if ( isDalle3 && prompt . length > dalle3PromptLimit ) {
prompt = prompt . substring ( 0 , dalle3PromptLimit ) ;
}
let width = 1024 ;
let height = 1024 ;
let aspectRatio = extension _settings . sd . width / extension _settings . sd . height ;
if ( isDalle3 && aspectRatio < 1 ) {
height = 1792 ;
}
if ( isDalle3 && aspectRatio > 1 ) {
width = 1792 ;
}
if ( isDalle2 && ( extension _settings . sd . width <= 512 && extension _settings . sd . height <= 512 ) ) {
width = 512 ;
height = 512 ;
}
const result = await fetch ( '/api/openai/generate-image' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
prompt : prompt ,
model : extension _settings . sd . model ,
size : ` ${ width } x ${ height } ` ,
n : 1 ,
quality : isDalle3 ? extension _settings . sd . openai _quality : undefined ,
style : isDalle3 ? extension _settings . sd . openai _style : undefined ,
response _format : 'b64_json' ,
} ) ,
} ) ;
if ( result . ok ) {
const data = await result . json ( ) ;
return { format : 'png' , data : data ? . data [ 0 ] ? . b64 _json } ;
} else {
const text = await result . text ( ) ;
throw new Error ( text ) ;
}
}
2023-11-19 12:18:48 +00:00
/ * *
* Generates an image in ComfyUI using the provided prompt and configuration settings .
2023-11-19 21:17:02 +02:00
*
2023-11-19 12:18:48 +00:00
* @ param { string } prompt - The main instruction used to guide the image generation .
2023-12-23 16:19:22 +00:00
* @ param { string } negativePrompt - The instruction used to restrict the image generation .
2023-11-19 12:18:48 +00:00
* @ returns { Promise < { format : string , data : string } > } - A promise that resolves when the image generation and processing are complete .
* /
2023-12-23 16:19:22 +00:00
async function generateComfyImage ( prompt , negativePrompt ) {
2023-11-19 12:18:48 +00:00
const placeholders = [
'model' ,
2023-11-20 10:35:11 +00:00
'vae' ,
2023-11-19 12:18:48 +00:00
'sampler' ,
'scheduler' ,
'steps' ,
'scale' ,
'width' ,
'height' ,
2024-05-29 02:14:08 +03:00
'clip_skip' ,
2023-11-19 12:18:48 +00:00
] ;
2023-11-20 12:51:36 +00:00
const workflowResponse = await fetch ( '/api/sd/comfy/workflow' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
file _name : extension _settings . sd . comfy _workflow ,
} ) ,
} ) ;
if ( ! workflowResponse . ok ) {
const text = await workflowResponse . text ( ) ;
toastr . error ( ` Failed to load workflow. \n \n ${ text } ` ) ;
}
let workflow = ( await workflowResponse . json ( ) ) . replace ( '"%prompt%"' , JSON . stringify ( prompt ) ) ;
2023-12-24 21:02:04 +00:00
workflow = workflow . replace ( '"%negative_prompt%"' , JSON . stringify ( negativePrompt ) ) ;
2024-05-29 23:29:45 +03:00
const seed = extension _settings . sd . seed >= 0 ? extension _settings . sd . seed : Math . round ( Math . random ( ) * Number . MAX _SAFE _INTEGER ) ;
workflow = workflow . replaceAll ( '"%seed%"' , JSON . stringify ( seed ) ) ;
2023-11-19 21:17:02 +02:00
placeholders . forEach ( ph => {
2023-11-19 12:18:48 +00:00
workflow = workflow . replace ( ` "% ${ ph } %" ` , JSON . stringify ( extension _settings . sd [ ph ] ) ) ;
} ) ;
2023-12-17 22:17:08 +00:00
( extension _settings . sd . comfy _placeholders ? ? [ ] ) . forEach ( ph => {
workflow = workflow . replace ( ` "% ${ ph . find } %" ` , JSON . stringify ( substituteParams ( ph . replace ) ) ) ;
} ) ;
2024-04-19 00:32:38 +03:00
if ( /%user_avatar%/gi . test ( workflow ) ) {
const response = await fetch ( getUserAvatarUrl ( ) ) ;
if ( response . ok ) {
const avatarBlob = await response . blob ( ) ;
const avatarBase64 = await getBase64Async ( avatarBlob ) ;
workflow = workflow . replace ( '"%user_avatar%"' , JSON . stringify ( avatarBase64 ) ) ;
2024-04-21 21:13:50 +03:00
} else {
workflow = workflow . replace ( '"%user_avatar%"' , JSON . stringify ( PNG _PIXEL ) ) ;
2024-04-19 00:32:38 +03:00
}
}
if ( /%char_avatar%/gi . test ( workflow ) ) {
const response = await fetch ( getCharacterAvatarUrl ( ) ) ;
if ( response . ok ) {
const avatarBlob = await response . blob ( ) ;
const avatarBase64 = await getBase64Async ( avatarBlob ) ;
workflow = workflow . replace ( '"%char_avatar%"' , JSON . stringify ( avatarBase64 ) ) ;
2024-04-21 21:13:50 +03:00
} else {
workflow = workflow . replace ( '"%char_avatar%"' , JSON . stringify ( PNG _PIXEL ) ) ;
2024-04-19 00:32:38 +03:00
}
}
2023-11-19 12:18:48 +00:00
console . log ( ` {
"prompt" : $ { workflow }
} ` );
2023-12-02 13:04:51 -05:00
const promptResult = await fetch ( '/api/sd/comfy/generate' , {
2023-11-19 12:18:48 +00:00
method : 'POST' ,
2023-11-19 18:29:41 +00:00
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
url : extension _settings . sd . comfy _url ,
prompt : ` {
"prompt" : $ { workflow }
} ` ,
2023-12-02 22:06:57 +02:00
} ) ,
2023-11-19 12:18:48 +00:00
} ) ;
2024-04-19 00:39:04 +03:00
if ( ! promptResult . ok ) {
const text = await promptResult . text ( ) ;
throw new Error ( text ) ;
}
2023-11-19 21:17:02 +02:00
return { format : 'png' , data : await promptResult . text ( ) } ;
2023-11-19 12:18:48 +00:00
}
async function onComfyOpenWorkflowEditorClick ( ) {
2023-12-02 13:04:51 -05:00
let workflow = await ( await fetch ( '/api/sd/comfy/workflow' , {
2023-11-20 12:13:28 +00:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
file _name : extension _settings . sd . comfy _workflow ,
} ) ,
} ) ) . json ( ) ;
2023-11-19 12:18:48 +00:00
const editorHtml = $ ( await $ . get ( 'scripts/extensions/stable-diffusion/comfyWorkflowEditor.html' ) ) ;
2023-12-02 13:04:51 -05:00
const popupResult = callPopup ( editorHtml , 'confirm' , undefined , { okButton : 'Save' , wide : true , large : true , rows : 1 } ) ;
2023-11-19 21:17:02 +02:00
const checkPlaceholders = ( ) => {
2023-11-20 12:13:28 +00:00
workflow = $ ( '#sd_comfy_workflow_editor_workflow' ) . val ( ) . toString ( ) ;
2023-11-19 21:17:02 +02:00
$ ( '.sd_comfy_workflow_editor_placeholder_list > li[data-placeholder]' ) . each ( function ( idx ) {
2023-11-19 12:18:48 +00:00
const key = this . getAttribute ( 'data-placeholder' ) ;
const found = workflow . search ( ` "% ${ key } %" ` ) != - 1 ;
2023-11-19 21:17:02 +02:00
this . classList [ found ? 'remove' : 'add' ] ( 'sd_comfy_workflow_editor_not_found' ) ;
2023-11-19 12:18:48 +00:00
} ) ;
} ;
2023-11-20 12:13:28 +00:00
$ ( '#sd_comfy_workflow_editor_name' ) . text ( extension _settings . sd . comfy _workflow ) ;
$ ( '#sd_comfy_workflow_editor_workflow' ) . val ( workflow ) ;
2023-12-17 22:17:08 +00:00
const addPlaceholderDom = ( placeholder ) => {
const el = $ ( `
< li class = "sd_comfy_workflow_editor_not_found" data - placeholder = "${placeholder.find}" >
< span class = "sd_comfy_workflow_editor_custom_remove" title = "Remove custom placeholder" > ⊘ < / s p a n >
< span class = "sd_comfy_workflow_editor_custom_final" > "%${placeholder.find}%" < / s p a n > < b r >
< input placeholder = "find" title = "find" type = "text" class = "text_pole sd_comfy_workflow_editor_custom_find" value = "" > < br >
< input placeholder = "replace" title = "replace" type = "text" class = "text_pole sd_comfy_workflow_editor_custom_replace" >
< / l i >
` );
$ ( '#sd_comfy_workflow_editor_placeholder_list_custom' ) . append ( el ) ;
el . find ( '.sd_comfy_workflow_editor_custom_find' ) . val ( placeholder . find ) ;
2024-02-12 17:28:39 +02:00
el . find ( '.sd_comfy_workflow_editor_custom_find' ) . on ( 'input' , function ( ) {
2024-05-06 21:39:07 +03:00
if ( ! ( this instanceof HTMLInputElement ) ) {
return ;
}
2023-12-17 22:17:08 +00:00
placeholder . find = this . value ;
el . find ( '.sd_comfy_workflow_editor_custom_final' ) . text ( ` "% ${ this . value } %" ` ) ;
el . attr ( 'data-placeholder' , ` ${ this . value } ` ) ;
checkPlaceholders ( ) ;
saveSettingsDebounced ( ) ;
} ) ;
el . find ( '.sd_comfy_workflow_editor_custom_replace' ) . val ( placeholder . replace ) ;
2024-02-12 17:28:39 +02:00
el . find ( '.sd_comfy_workflow_editor_custom_replace' ) . on ( 'input' , function ( ) {
2024-05-06 21:39:07 +03:00
if ( ! ( this instanceof HTMLInputElement ) ) {
return ;
}
2023-12-17 22:17:08 +00:00
placeholder . replace = this . value ;
saveSettingsDebounced ( ) ;
} ) ;
el . find ( '.sd_comfy_workflow_editor_custom_remove' ) . on ( 'click' , ( ) => {
el . remove ( ) ;
extension _settings . sd . comfy _placeholders . splice ( extension _settings . sd . comfy _placeholders . indexOf ( placeholder ) ) ;
saveSettingsDebounced ( ) ;
} ) ;
} ;
$ ( '#sd_comfy_workflow_editor_placeholder_add' ) . on ( 'click' , ( ) => {
if ( ! extension _settings . sd . comfy _placeholders ) {
extension _settings . sd . comfy _placeholders = [ ] ;
}
const placeholder = {
find : '' ,
replace : '' ,
} ;
extension _settings . sd . comfy _placeholders . push ( placeholder ) ;
addPlaceholderDom ( placeholder ) ;
saveSettingsDebounced ( ) ;
} ) ;
2024-02-12 17:28:39 +02:00
( extension _settings . sd . comfy _placeholders ? ? [ ] ) . forEach ( placeholder => {
2023-12-17 22:17:08 +00:00
addPlaceholderDom ( placeholder ) ;
} ) ;
2023-11-19 12:18:48 +00:00
checkPlaceholders ( ) ;
$ ( '#sd_comfy_workflow_editor_workflow' ) . on ( 'input' , checkPlaceholders ) ;
if ( await popupResult ) {
2023-12-02 13:04:51 -05:00
const response = await fetch ( '/api/sd/comfy/save-workflow' , {
2023-11-20 12:13:28 +00:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
file _name : extension _settings . sd . comfy _workflow ,
workflow : $ ( '#sd_comfy_workflow_editor_workflow' ) . val ( ) . toString ( ) ,
} ) ,
} ) ;
if ( ! response . ok ) {
const text = await response . text ( ) ;
toastr . error ( ` Failed to save workflow. \n \n ${ text } ` ) ;
}
}
}
async function onComfyNewWorkflowClick ( ) {
let name = await callPopup ( '<h3>Workflow name:</h3>' , 'input' ) ;
if ( ! name ) {
return ;
}
if ( ! name . toLowerCase ( ) . endsWith ( '.json' ) ) {
name += '.json' ;
}
extension _settings . sd . comfy _workflow = name ;
2023-12-02 13:04:51 -05:00
const response = await fetch ( '/api/sd/comfy/save-workflow' , {
2023-11-20 12:13:28 +00:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
file _name : extension _settings . sd . comfy _workflow ,
workflow : '' ,
} ) ,
} ) ;
if ( ! response . ok ) {
const text = await response . text ( ) ;
toastr . error ( ` Failed to save workflow. \n \n ${ text } ` ) ;
}
saveSettingsDebounced ( ) ;
await loadComfyWorkflows ( ) ;
await delay ( 200 ) ;
await onComfyOpenWorkflowEditorClick ( ) ;
}
async function onComfyDeleteWorkflowClick ( ) {
const confirm = await callPopup ( 'Delete the workflow? This action is irreversible.' , 'confirm' ) ;
if ( ! confirm ) {
return ;
}
2023-11-20 18:27:50 +00:00
const response = await fetch ( '/api/sd/comfy/delete-workflow' , {
2023-11-20 12:13:28 +00:00
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
file _name : extension _settings . sd . comfy _workflow ,
} ) ,
} ) ;
if ( ! response . ok ) {
const text = await response . text ( ) ;
toastr . error ( ` Failed to save workflow. \n \n ${ text } ` ) ;
2023-11-19 12:18:48 +00:00
}
2023-11-20 12:13:28 +00:00
await loadComfyWorkflows ( ) ;
onComfyWorkflowChange ( ) ;
2023-11-19 12:18:48 +00:00
}
2024-05-06 21:39:07 +03:00
/ * *
* 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 ) {
2023-07-20 20:32:15 +03:00
const context = getContext ( ) ;
2024-05-21 15:03:57 +03:00
const name = context . groupId ? systemUserName : context . name2 ;
const messageText = ` [ ${ name } sends a picture that contains: ${ prompt } ] ` ;
2023-07-20 20:32:15 +03:00
const message = {
2024-05-21 15:03:57 +03:00
name : name ,
2023-07-20 20:32:15 +03:00
is _user : false ,
2023-08-18 00:41:21 +03:00
is _system : true ,
2023-08-22 18:32:18 +03:00
send _date : getMessageTimeStamp ( ) ,
2024-05-21 15:03:57 +03:00
mes : messageText ,
2023-07-20 20:32:15 +03:00
extra : {
image : image ,
title : prompt ,
2023-11-01 22:58:59 +02:00
generationType : generationType ,
2024-05-06 21:39:07 +03:00
negative : additionalNegativePrefix ,
2024-05-21 15:03:57 +03:00
inline _image : false ,
2023-07-20 20:32:15 +03:00
} ,
} ;
context . chat . push ( message ) ;
context . addOneMessage ( message ) ;
context . saveChat ( ) ;
}
function addSDGenButtons ( ) {
const buttonHtml = `
< div id = "sd_gen" class = "list-group-item flex-container flexGap5" >
< div class = "fa-solid fa-paintbrush extensionsMenuExtensionButton" title = "Trigger Stable Diffusion" / > < / d i v >
2023-11-08 10:57:37 +02:00
Generate Image
2023-07-20 20:32:15 +03:00
< / d i v >
` ;
const waitButtonHtml = `
< div id = "sd_gen_wait" class = "fa-solid fa-hourglass-half" / > < / d i v >
2023-12-02 21:11:06 +02:00
` ;
2023-07-20 20:32:15 +03:00
const dropdownHtml = `
< div id = "sd_dropdown" >
< ul class = "list-group" >
< span > Send me a picture of : < / s p a n >
< li class = "list-group-item" id = "sd_you" data - value = "you" > Yourself < / l i >
< li class = "list-group-item" id = "sd_face" data - value = "face" > Your Face < / l i >
< li class = "list-group-item" id = "sd_me" data - value = "me" > Me < / l i >
< li class = "list-group-item" id = "sd_world" data - value = "world" > The Whole Story < / l i >
< li class = "list-group-item" id = "sd_last" data - value = "last" > The Last Message < / l i >
< li class = "list-group-item" id = "sd_raw_last" data - value = "raw_last" > Raw Last Message < / l i >
2023-08-18 00:41:21 +03:00
< li class = "list-group-item" id = "sd_background" data - value = "background" > Background < / l i >
2023-07-20 20:32:15 +03:00
< / u l >
< / d i v > ` ;
$ ( '#extensionsMenu' ) . prepend ( buttonHtml ) ;
$ ( '#extensionsMenu' ) . prepend ( waitButtonHtml ) ;
$ ( document . body ) . append ( dropdownHtml ) ;
const messageButton = $ ( '.sd_message_gen' ) ;
const button = $ ( '#sd_gen' ) ;
2023-12-02 13:04:51 -05:00
const waitButton = $ ( '#sd_gen_wait' ) ;
2023-07-20 20:32:15 +03:00
const dropdown = $ ( '#sd_dropdown' ) ;
waitButton . hide ( ) ;
dropdown . hide ( ) ;
button . hide ( ) ;
messageButton . hide ( ) ;
let popper = Popper . createPopper ( button . get ( 0 ) , dropdown . get ( 0 ) , {
placement : 'top' ,
} ) ;
$ ( document ) . on ( 'click' , '.sd_message_gen' , sdMessageButton ) ;
$ ( document ) . on ( 'click touchend' , function ( e ) {
const target = $ ( e . target ) ;
if ( target . is ( dropdown ) ) return ;
2023-12-02 13:04:51 -05:00
if ( target . is ( button ) && ! dropdown . is ( ':visible' ) && $ ( '#send_but' ) . is ( ':visible' ) ) {
2023-07-20 20:32:15 +03:00
e . preventDefault ( ) ;
2023-11-19 00:40:21 +02:00
dropdown . fadeIn ( animation _duration ) ;
2023-07-20 20:32:15 +03:00
popper . update ( ) ;
} else {
2023-11-19 00:40:21 +02:00
dropdown . fadeOut ( animation _duration ) ;
2023-07-20 20:32:15 +03:00
}
} ) ;
}
2023-09-03 00:41:26 +03:00
function isValidState ( ) {
switch ( extension _settings . sd . source ) {
case sources . extras :
return modules . includes ( 'sd' ) ;
case sources . horde :
return true ;
case sources . auto :
return ! ! extension _settings . sd . auto _url ;
2024-03-30 01:12:29 -03:00
case sources . drawthings :
return ! ! extension _settings . sd . drawthings _url ;
2023-10-07 18:30:06 +03:00
case sources . vlad :
return ! ! extension _settings . sd . vlad _url ;
2023-09-03 00:41:26 +03:00
case sources . novel :
return secret _state [ SECRET _KEYS . NOVEL ] ;
2023-11-06 21:47:00 +02:00
case sources . openai :
return secret _state [ SECRET _KEYS . OPENAI ] ;
2023-11-19 12:18:48 +00:00
case sources . comfy :
return true ;
2023-12-18 03:33:05 +02:00
case sources . togetherai :
return secret _state [ SECRET _KEYS . TOGETHERAI ] ;
2024-04-04 20:40:47 +03:00
case sources . pollinations :
return true ;
2023-09-03 00:41:26 +03:00
}
2023-07-20 20:32:15 +03:00
}
async function moduleWorker ( ) {
2023-09-03 00:41:26 +03:00
if ( isValidState ( ) ) {
2023-07-20 20:32:15 +03:00
$ ( '#sd_gen' ) . show ( ) ;
$ ( '.sd_message_gen' ) . show ( ) ;
}
else {
$ ( '#sd_gen' ) . hide ( ) ;
$ ( '.sd_message_gen' ) . hide ( ) ;
}
}
addSDGenButtons ( ) ;
setInterval ( moduleWorker , UPDATE _INTERVAL ) ;
async function sdMessageButton ( e ) {
function setBusyIcon ( isBusy ) {
$icon . toggleClass ( 'fa-paintbrush' , ! isBusy ) ;
$icon . toggleClass ( busyClass , isBusy ) ;
}
const busyClass = 'fa-hourglass' ;
const context = getContext ( ) ;
const $icon = $ ( e . currentTarget ) ;
const $mes = $icon . closest ( '.mes' ) ;
const message _id = $mes . attr ( 'mesid' ) ;
const message = context . chat [ message _id ] ;
2023-10-07 18:30:06 +03:00
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 ( ) ;
2023-08-20 12:29:20 +03:00
const messageText = message ? . mes ;
2023-07-20 20:32:15 +03:00
const hasSavedImage = message ? . extra ? . image && message ? . extra ? . title ;
2024-05-06 21:39:07 +03:00
const hasSavedNegative = message ? . extra ? . negative ;
2023-07-20 20:32:15 +03:00
if ( $icon . hasClass ( busyClass ) ) {
console . log ( 'Previous image is still being generated...' ) ;
return ;
}
2023-11-01 22:58:59 +02:00
let dimensions = null ;
2023-07-20 20:32:15 +03:00
try {
setBusyIcon ( true ) ;
if ( hasSavedImage ) {
2024-05-06 21:39:07 +03:00
const prompt = await refinePrompt ( message . extra . title , false , false ) ;
const negative = hasSavedNegative ? await refinePrompt ( message . extra . negative , false , true ) : '' ;
2023-07-22 20:32:58 +03:00
message . extra . title = prompt ;
2023-11-01 22:58:59 +02:00
const generationType = message ? . extra ? . generationType ? ? generationMode . FREE ;
2023-07-20 20:32:15 +03:00
console . log ( 'Regenerating an image, using existing prompt:' , prompt ) ;
2023-11-01 22:58:59 +02:00
dimensions = setTypeSpecificDimensions ( generationType ) ;
2024-05-06 21:39:07 +03:00
await sendGenerationRequest ( generationType , prompt , negative , characterFileName , saveGeneratedImage ) ;
2023-07-20 20:32:15 +03:00
}
else {
2023-12-02 13:04:51 -05:00
console . log ( 'doing /sd raw last' ) ;
2023-11-30 01:22:31 +02:00
await generatePicture ( 'sd' , 'raw_last' , messageText , saveGeneratedImage ) ;
2023-07-20 20:32:15 +03:00
}
}
catch ( error ) {
console . error ( 'Could not generate inline image: ' , error ) ;
}
finally {
setBusyIcon ( false ) ;
2023-11-01 22:58:59 +02:00
if ( dimensions ) {
restoreOriginalDimensions ( dimensions ) ;
}
2023-07-20 20:32:15 +03:00
}
2024-05-06 21:39:07 +03:00
function saveGeneratedImage ( prompt , image , generationType , negative ) {
2023-07-20 20:32:15 +03:00
// Some message sources may not create the extra object
if ( typeof message . extra !== 'object' ) {
message . extra = { } ;
}
// If already contains an image and it's not inline - leave it as is
message . extra . inline _image = message . extra . image && ! message . extra . inline _image ? false : true ;
message . extra . image = image ;
message . extra . title = prompt ;
2023-11-01 22:58:59 +02:00
message . extra . generationType = generationType ;
2024-05-06 21:39:07 +03:00
message . extra . negative = negative ;
2023-11-19 00:40:21 +02:00
appendMediaToMessage ( message , $mes ) ;
2023-07-20 20:32:15 +03:00
context . saveChat ( ) ;
}
2023-12-02 10:15:03 -05:00
}
2023-07-20 20:32:15 +03:00
2023-12-02 13:04:51 -05:00
$ ( '#sd_dropdown [id]' ) . on ( 'click' , function ( ) {
const id = $ ( this ) . attr ( 'id' ) ;
2023-08-18 00:41:21 +03:00
const idParamMap = {
2023-12-02 13:04:51 -05:00
'sd_you' : 'you' ,
'sd_face' : 'face' ,
'sd_me' : 'me' ,
'sd_world' : 'scene' ,
'sd_last' : 'last' ,
'sd_raw_last' : 'raw_last' ,
2023-12-02 22:06:57 +02:00
'sd_background' : 'background' ,
2023-08-18 00:41:21 +03:00
} ;
2023-07-20 20:32:15 +03:00
2023-08-18 00:41:21 +03:00
const param = idParamMap [ id ] ;
2023-07-20 20:32:15 +03:00
2023-08-18 00:41:21 +03:00
if ( param ) {
2023-12-02 21:11:06 +02:00
console . log ( 'doing /sd ' + param ) ;
2023-08-18 00:41:21 +03:00
generatePicture ( 'sd' , param ) ;
2023-07-20 20:32:15 +03:00
}
} ) ;
2024-05-30 00:21:27 +03:00
async function onCharacterPromptShareInput ( ) {
// Not a valid state to share character prompt
if ( this _chid === undefined || selected _group ) {
return ;
}
const shouldShare = ! ! $ ( '#sd_character_prompt_share' ) . prop ( 'checked' ) ;
if ( shouldShare ) {
await writePromptFields ( this _chid ) ;
} else {
await writeExtensionField ( this _chid , 'sd_character_prompt' , null ) ;
}
}
async function writePromptFields ( characterId ) {
const key = getCharaFilename ( characterId ) ;
const promptPrefix = key ? ( extension _settings . sd . character _prompts [ key ] || '' ) : '' ;
const negativePromptPrefix = key ? ( extension _settings . sd . character _negative _prompts [ key ] || '' ) : '' ;
const promptObject = {
positive : promptPrefix ,
negative : negativePromptPrefix ,
} ;
await writeExtensionField ( characterId , 'sd_character_prompt' , promptObject ) ;
}
2023-07-20 20:32:15 +03:00
jQuery ( async ( ) => {
2024-05-30 00:21:27 +03:00
SlashCommandParser . addCommandObject ( SlashCommand . fromProps ( {
name : 'imagine' ,
2024-05-12 15:15:05 -04:00
callback : generatePicture ,
aliases : [ 'sd' , 'img' , 'image' ] ,
namedArgumentList : [
new SlashCommandNamedArgument (
'quiet' , 'whether to post the generated image to chat' , [ ARGUMENT _TYPE . BOOLEAN ] , false , false , 'false' , [ 'false' , 'true' ] ,
) ,
new SlashCommandNamedArgument (
'negative' , 'negative prompt prefix' , [ ARGUMENT _TYPE . STRING ] , false , false , '' ,
) ,
] ,
unnamedArgumentList : [
new SlashCommandArgument (
'argument' , [ ARGUMENT _TYPE . STRING ] , false , false , null , Object . values ( triggerWords ) . flat ( ) ,
) ,
] ,
helpString : `
< div >
Requests to generate an image and posts it to chat ( unless quiet = true argument is specified ) . Supported arguments : < code > $ { Object . values ( triggerWords ) . flat ( ) . join ( ', ' ) } < / c o d e > .
< / d i v >
< div >
Anything else would trigger a "free mode" to make generate whatever you prompted . Example : < code > / i m a g i n e a p p l e t r e e < / c o d e > w o u l d g e n e r a t e a p i c t u r e o f a n a p p l e t r e e . R e t u r n s a l i n k t o t h e g e n e r a t e d i m a g e .
< / d i v >
` ,
} ) ) ;
2024-05-30 00:21:27 +03:00
SlashCommandParser . addCommandObject ( SlashCommand . fromProps ( {
name : 'imagine-comfy-workflow' ,
2024-05-12 15:15:05 -04:00
callback : changeComfyWorkflow ,
aliases : [ 'icw' ] ,
unnamedArgumentList : [
new SlashCommandArgument (
'workflowName' , [ ARGUMENT _TYPE . STRING ] , true ,
) ,
] ,
helpString : '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g. <pre><code>/imagine-comfy-workflow MyWorkflow</code></pre>' ,
} ) ) ;
2023-07-20 20:32:15 +03:00
2024-04-11 23:38:44 +03:00
const template = await renderExtensionTemplateAsync ( 'stable-diffusion' , 'settings' , defaultSettings ) ;
$ ( '#extensions_settings' ) . append ( template ) ;
2023-09-03 00:41:26 +03:00
$ ( '#sd_source' ) . on ( 'change' , onSourceChange ) ;
2023-07-20 20:32:15 +03:00
$ ( '#sd_scale' ) . on ( 'input' , onScaleInput ) ;
$ ( '#sd_steps' ) . on ( 'input' , onStepsInput ) ;
$ ( '#sd_model' ) . on ( 'change' , onModelChange ) ;
2023-11-20 10:35:11 +00:00
$ ( '#sd_vae' ) . on ( 'change' , onVaeChange ) ;
2023-07-20 20:32:15 +03:00
$ ( '#sd_sampler' ) . on ( 'change' , onSamplerChange ) ;
2023-11-26 16:55:49 +01:00
$ ( '#sd_resolution' ) . on ( 'change' , onResolutionChange ) ;
2023-11-19 12:18:48 +00:00
$ ( '#sd_scheduler' ) . on ( 'change' , onSchedulerChange ) ;
2023-07-20 20:32:15 +03:00
$ ( '#sd_prompt_prefix' ) . on ( 'input' , onPromptPrefixInput ) ;
$ ( '#sd_negative_prompt' ) . on ( 'input' , onNegativePromptInput ) ;
$ ( '#sd_width' ) . on ( 'input' , onWidthInput ) ;
$ ( '#sd_height' ) . on ( 'input' , onHeightInput ) ;
$ ( '#sd_horde_nsfw' ) . on ( 'input' , onHordeNsfwInput ) ;
$ ( '#sd_horde_karras' ) . on ( 'input' , onHordeKarrasInput ) ;
2023-10-08 22:29:33 +03:00
$ ( '#sd_horde_sanitize' ) . on ( 'input' , onHordeSanitizeInput ) ;
2023-07-20 20:32:15 +03:00
$ ( '#sd_restore_faces' ) . on ( 'input' , onRestoreFacesInput ) ;
$ ( '#sd_enable_hr' ) . on ( 'input' , onHighResFixInput ) ;
$ ( '#sd_refine_mode' ) . on ( 'input' , onRefineModeInput ) ;
2023-07-22 23:57:48 +03:00
$ ( '#sd_character_prompt' ) . on ( 'input' , onCharacterPromptInput ) ;
2023-12-23 16:19:22 +00:00
$ ( '#sd_character_negative_prompt' ) . on ( 'input' , onCharacterNegativePromptInput ) ;
2023-09-03 00:41:26 +03:00
$ ( '#sd_auto_validate' ) . on ( 'click' , validateAutoUrl ) ;
$ ( '#sd_auto_url' ) . on ( 'input' , onAutoUrlInput ) ;
2023-09-03 01:19:31 +03:00
$ ( '#sd_auto_auth' ) . on ( 'input' , onAutoAuthInput ) ;
2024-03-30 01:12:29 -03:00
$ ( '#sd_drawthings_validate' ) . on ( 'click' , validateDrawthingsUrl ) ;
$ ( '#sd_drawthings_url' ) . on ( 'input' , onDrawthingsUrlInput ) ;
$ ( '#sd_drawthings_auth' ) . on ( 'input' , onDrawthingsAuthInput ) ;
2023-10-07 18:30:06 +03:00
$ ( '#sd_vlad_validate' ) . on ( 'click' , validateVladUrl ) ;
$ ( '#sd_vlad_url' ) . on ( 'input' , onVladUrlInput ) ;
$ ( '#sd_vlad_auth' ) . on ( 'input' , onVladAuthInput ) ;
2023-09-03 14:56:02 +03:00
$ ( '#sd_hr_upscaler' ) . on ( 'change' , onHrUpscalerChange ) ;
$ ( '#sd_hr_scale' ) . on ( 'input' , onHrScaleInput ) ;
$ ( '#sd_denoising_strength' ) . on ( 'input' , onDenoisingStrengthInput ) ;
$ ( '#sd_hr_second_pass_steps' ) . on ( 'input' , onHrSecondPassStepsInput ) ;
2023-09-04 18:00:15 +03:00
$ ( '#sd_novel_upscale_ratio' ) . on ( 'input' , onNovelUpscaleRatioInput ) ;
$ ( '#sd_novel_anlas_guard' ) . on ( 'input' , onNovelAnlasGuardInput ) ;
$ ( '#sd_novel_view_anlas' ) . on ( 'click' , onViewAnlasClick ) ;
2024-03-20 21:23:56 +02:00
$ ( '#sd_novel_sm' ) . on ( 'input' , onNovelSmInput ) ;
2024-04-02 13:15:31 +03:00
$ ( '#sd_novel_sm_dyn' ) . on ( 'input' , onNovelSmDynInput ) ;
2024-05-29 03:00:42 +03:00
$ ( '#sd_novel_decrisper' ) . on ( 'input' , onNovelDecrisperInput ) ;
2024-04-04 20:40:47 +03:00
$ ( '#sd_pollinations_enhance' ) . on ( 'input' , onPollinationsEnhanceInput ) ;
$ ( '#sd_pollinations_refine' ) . on ( 'input' , onPollinationsRefineInput ) ;
2023-11-19 12:18:48 +00:00
$ ( '#sd_comfy_validate' ) . on ( 'click' , validateComfyUrl ) ;
$ ( '#sd_comfy_url' ) . on ( 'input' , onComfyUrlInput ) ;
2023-11-20 12:13:28 +00:00
$ ( '#sd_comfy_workflow' ) . on ( 'change' , onComfyWorkflowChange ) ;
2023-11-19 12:18:48 +00:00
$ ( '#sd_comfy_open_workflow_editor' ) . on ( 'click' , onComfyOpenWorkflowEditorClick ) ;
2023-11-20 12:13:28 +00:00
$ ( '#sd_comfy_new_workflow' ) . on ( 'click' , onComfyNewWorkflowClick ) ;
$ ( '#sd_comfy_delete_workflow' ) . on ( 'click' , onComfyDeleteWorkflowClick ) ;
2023-10-20 15:03:26 +03:00
$ ( '#sd_expand' ) . on ( 'input' , onExpandInput ) ;
2023-10-22 00:10:48 +03:00
$ ( '#sd_style' ) . on ( 'change' , onStyleSelect ) ;
$ ( '#sd_save_style' ) . on ( 'click' , onSaveStyleClick ) ;
2024-05-29 23:44:11 +03:00
$ ( '#sd_delete_style' ) . on ( 'click' , onDeleteStyleClick ) ;
2023-07-22 23:57:48 +03:00
$ ( '#sd_character_prompt_block' ) . hide ( ) ;
2023-11-01 22:58:59 +02:00
$ ( '#sd_interactive_mode' ) . on ( 'input' , onInteractiveModeInput ) ;
2023-11-06 21:47:00 +02:00
$ ( '#sd_openai_style' ) . on ( 'change' , onOpenAiStyleSelect ) ;
$ ( '#sd_openai_quality' ) . on ( 'change' , onOpenAiQualitySelect ) ;
2023-11-19 15:24:43 +02:00
$ ( '#sd_multimodal_captioning' ) . on ( 'input' , onMultimodalCaptioningInput ) ;
2024-02-12 17:28:39 +02:00
$ ( '#sd_snap' ) . on ( 'input' , onSnapInput ) ;
2024-05-29 02:14:08 +03:00
$ ( '#sd_clip_skip' ) . on ( 'input' , onClipSkipInput ) ;
2024-05-29 23:29:45 +03:00
$ ( '#sd_seed' ) . on ( 'input' , onSeedInput ) ;
2024-05-30 00:21:27 +03:00
$ ( '#sd_character_prompt_share' ) . on ( 'input' , onCharacterPromptShareInput ) ;
2023-07-20 20:32:15 +03:00
$ ( '.sd_settings .inline-drawer-toggle' ) . on ( 'click' , function ( ) {
2023-12-02 13:04:51 -05:00
initScrollHeight ( $ ( '#sd_prompt_prefix' ) ) ;
initScrollHeight ( $ ( '#sd_negative_prompt' ) ) ;
initScrollHeight ( $ ( '#sd_character_prompt' ) ) ;
2023-12-23 16:19:22 +00:00
initScrollHeight ( $ ( '#sd_character_negative_prompt' ) ) ;
2023-12-02 21:11:06 +02:00
} ) ;
2023-07-20 20:32:15 +03:00
2023-11-26 18:19:37 +02:00
for ( const [ key , value ] of Object . entries ( resolutionOptions ) ) {
const option = document . createElement ( 'option' ) ;
option . value = key ;
option . text = value . name ;
$ ( '#sd_resolution' ) . append ( option ) ;
}
2023-07-20 20:32:15 +03:00
eventSource . on ( event _types . EXTRAS _CONNECTED , async ( ) => {
2024-05-22 16:14:42 +03:00
if ( extension _settings . sd . source === sources . extras ) {
await loadSettingOptions ( ) ;
}
2023-07-20 20:32:15 +03:00
} ) ;
2023-07-22 23:57:48 +03:00
eventSource . on ( event _types . CHAT _CHANGED , onChatChanged ) ;
2023-07-20 20:32:15 +03:00
await loadSettings ( ) ;
$ ( 'body' ) . addClass ( 'sd' ) ;
} ) ;