Add Variety Boost option for NovelAI image generation. (#4417) Co-authored-by: sirius422 <sirius42@gfw.moe>
Signed| @@ -306,6 +306,7 @@ const defaultSettings = { | |||
| 306 | novel_sm: false, | 306 | novel_sm: false, |
| 307 | novel_sm_dyn: false, | 307 | novel_sm_dyn: false, |
| 308 | novel_decrisper: false, | 308 | novel_decrisper: false, |
| 309 | novel_variety_boost: false, | ||
| 309 | 310 | ||
| 310 | // OpenAI settings | 311 | // OpenAI settings |
| 311 | openai_style: 'vivid', | 312 | openai_style: 'vivid', |
| @@ -482,6 +483,7 @@ async function loadSettings() { | |||
| 482 | $('#sd_novel_sm_dyn').prop('checked', extension_settings.sd.novel_sm_dyn); | 483 | $('#sd_novel_sm_dyn').prop('checked', extension_settings.sd.novel_sm_dyn); |
| 483 | $('#sd_novel_sm_dyn').prop('disabled', !extension_settings.sd.novel_sm); | 484 | $('#sd_novel_sm_dyn').prop('disabled', !extension_settings.sd.novel_sm); |
| 484 | $('#sd_novel_decrisper').prop('checked', extension_settings.sd.novel_decrisper); | 485 | $('#sd_novel_decrisper').prop('checked', extension_settings.sd.novel_decrisper); |
| 486 | $('#sd_novel_variety_boost').prop('checked', extension_settings.sd.novel_variety_boost); | ||
| 485 | $('#sd_pollinations_enhance').prop('checked', extension_settings.sd.pollinations_enhance); | 487 | $('#sd_pollinations_enhance').prop('checked', extension_settings.sd.pollinations_enhance); |
| 486 | $('#sd_horde').prop('checked', extension_settings.sd.horde); | 488 | $('#sd_horde').prop('checked', extension_settings.sd.horde); |
| 487 | $('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw); | 489 | $('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw); |
| @@ -1055,6 +1057,11 @@ function onNovelDecrisperInput() { | |||
| 1055 | saveSettingsDebounced(); | 1057 | saveSettingsDebounced(); |
| 1056 | } | 1058 | } |
| 1057 | 1059 | ||
| 1060 | function onNovelVarietyBoostInput() { | ||
| 1061 | extension_settings.sd.novel_variety_boost = !!$('#sd_novel_variety_boost').prop('checked'); | ||
| 1062 | saveSettingsDebounced(); | ||
| 1063 | } | ||
| 1064 | |||
| 1058 | function onPollinationsEnhanceInput() { | 1065 | function onPollinationsEnhanceInput() { |
| 1059 | extension_settings.sd.pollinations_enhance = !!$('#sd_pollinations_enhance').prop('checked'); | 1066 | extension_settings.sd.pollinations_enhance = !!$('#sd_pollinations_enhance').prop('checked'); |
| 1060 | saveSettingsDebounced(); | 1067 | saveSettingsDebounced(); |
| @@ -3234,6 +3241,7 @@ async function generateNovelImage(prompt, negativePrompt, signal) { | |||
| 3234 | negative_prompt: negativePrompt, | 3241 | negative_prompt: negativePrompt, |
| 3235 | upscale_ratio: extension_settings.sd.hr_scale, | 3242 | upscale_ratio: extension_settings.sd.hr_scale, |
| 3236 | decrisper: extension_settings.sd.novel_decrisper, | 3243 | decrisper: extension_settings.sd.novel_decrisper, |
| 3244 | variety_boost: extension_settings.sd.novel_variety_boost, | ||
| 3237 | sm: sm, | 3245 | sm: sm, |
| 3238 | sm_dyn: sm_dyn, | 3246 | sm_dyn: sm_dyn, |
| 3239 | seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined, | 3247 | seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined, |
| @@ -4654,6 +4662,7 @@ jQuery(async () => { | |||
| 4654 | $('#sd_novel_sm').on('input', onNovelSmInput); | 4662 | $('#sd_novel_sm').on('input', onNovelSmInput); |
| 4655 | $('#sd_novel_sm_dyn').on('input', onNovelSmDynInput); | 4663 | $('#sd_novel_sm_dyn').on('input', onNovelSmDynInput); |
| 4656 | $('#sd_novel_decrisper').on('input', onNovelDecrisperInput); | 4664 | $('#sd_novel_decrisper').on('input', onNovelDecrisperInput); |
| 4665 | $('#sd_novel_variety_boost').on('input', onNovelVarietyBoostInput); | ||
| 4657 | $('#sd_pollinations_enhance').on('input', onPollinationsEnhanceInput); | 4666 | $('#sd_pollinations_enhance').on('input', onPollinationsEnhanceInput); |
| 4658 | $('#sd_comfy_validate').on('click', validateComfyUrl); | 4667 | $('#sd_comfy_validate').on('click', validateComfyUrl); |
| 4659 | $('#sd_comfy_url').on('input', onComfyUrlInput); | 4668 | $('#sd_comfy_url').on('input', onComfyUrlInput); |
| @@ -451,6 +451,10 @@ | |||
| 451 | <input id="sd_novel_decrisper" type="checkbox" /> | 451 | <input id="sd_novel_decrisper" type="checkbox" /> |
| 452 | <small data-i18n="Decrisper">Decrisper</small> | 452 | <small data-i18n="Decrisper">Decrisper</small> |
| 453 | </label> | 453 | </label> |
| 454 | <label class="flex1 checkbox_label" data-i18n="[title]Enable guidance only after body has been formed, to improve diversity and saturation of samples. May reduce relevance" title="Enable guidance only after body has been formed, to improve diversity and saturation of samples. May reduce relevance"> | ||
| 455 | <input id="sd_novel_variety_boost" type="checkbox" /> | ||
| 456 | <small data-i18n="Variety+">Variety+</small> | ||
| 457 | </label> | ||
| 454 | </div> | 458 | </div> |
| 455 | 459 | ||
| 456 | <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,bfl" class="marginTop5"> | 460 | <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,bfl" class="marginTop5"> |
| @@ -11,6 +11,11 @@ const API_NOVELAI = 'https://api.novelai.net'; | |||
| 11 | const TEXT_NOVELAI = 'https://text.novelai.net'; | 11 | const TEXT_NOVELAI = 'https://text.novelai.net'; |
| 12 | const IMAGE_NOVELAI = 'https://image.novelai.net'; | 12 | const IMAGE_NOVELAI = 'https://image.novelai.net'; |
| 13 | 13 | ||
| 14 | // Constants for skip_cfg_above_sigma (Variety+) calculation | ||
| 15 | const REFERENCE_PIXEL_COUNT = 1011712; // 832 * 1216 reference image size | ||
| 16 | const SIGMA_MAGIC_NUMBER = 19; // Base sigma multiplier for V3 and V4 models | ||
| 17 | const SIGMA_MAGIC_NUMBER_V4_5 = 58; // Base sigma multiplier for V4.5 models | ||
| 18 | |||
| 14 | // Ban bracket generation, plus defaults | 19 | // Ban bracket generation, plus defaults |
| 15 | const badWordsList = [ | 20 | const badWordsList = [ |
| 16 | [3], [49356], [1431], [31715], [34387], [20765], [30702], [10691], [49333], [1266], | 21 | [3], [49356], [1431], [31715], [34387], [20765], [30702], [10691], [49333], [1266], |
| @@ -112,6 +117,17 @@ function getRepPenaltyWhitelist(model) { | |||
| 112 | return null; | 117 | return null; |
| 113 | } | 118 | } |
| 114 | 119 | ||
| 120 | function calculateSkipCfgAboveSigma(width, height, modelName) { | ||
| 121 | const magicConstant = modelName?.includes('nai-diffusion-4-5') | ||
| 122 | ? SIGMA_MAGIC_NUMBER_V4_5 | ||
| 123 | : SIGMA_MAGIC_NUMBER; | ||
| 124 | |||
| 125 | const pixelCount = width * height; | ||
| 126 | const ratio = pixelCount / REFERENCE_PIXEL_COUNT; | ||
| 127 | |||
| 128 | return Math.pow(ratio, 0.5) * magicConstant; | ||
| 129 | } | ||
| 130 | |||
| 115 | export const router = express.Router(); | 131 | export const router = express.Router(); |
| 116 | 132 | ||
| 117 | router.post('/status', async function (req, res) { | 133 | router.post('/status', async function (req, res) { |
| @@ -332,6 +348,13 @@ router.post('/generate-image', async (request, response) => { | |||
| 332 | sm: request.body.sm ?? false, | 348 | sm: request.body.sm ?? false, |
| 333 | sm_dyn: request.body.sm_dyn ?? false, | 349 | sm_dyn: request.body.sm_dyn ?? false, |
| 334 | uncond_scale: 1, | 350 | uncond_scale: 1, |
| 351 | skip_cfg_above_sigma: request.body.variety_boost | ||
| 352 | ? calculateSkipCfgAboveSigma( | ||
| 353 | request.body.width ?? 512, | ||
| 354 | request.body.height ?? 512, | ||
| 355 | request.body.model ?? 'nai-diffusion', | ||
| 356 | ) | ||
| 357 | : null, | ||
| 335 | use_coords: false, | 358 | use_coords: false, |
| 336 | characterPrompts: [], | 359 | characterPrompts: [], |
| 337 | reference_image_multiple: [], | 360 | reference_image_multiple: [], |