From 22cda2287b7faec5f4537200c5e86a12de627a3e Mon Sep 17 00:00:00 2001 From: Frankly George <54015-franklygeorge@users.noreply.gitgud.io> Date: Thu, 6 Feb 2025 22:39:55 +0000 Subject: [PATCH] AI Art: Basic Illustrious support; Better prompting for SDXL and Pony --- devTools/dictionaries/misc.txt | 4 ++ js/003-data/gameVariableData.js | 38 ++++++++---- src/art/genAI/buildPrompt.js | 2 +- .../ponyPrompts/demographicsPromptPart.js | 19 +++++- src/art/genAI/prompts/androidPromptPart.js | 24 ++++---- src/art/genAI/prompts/breastsPromptPart.js | 4 +- src/art/genAI/prompts/earsPromptPart.js | 59 +++++++++++++++++-- src/art/genAI/prompts/expressionPromptPart.js | 2 +- src/art/genAI/prompts/eyebrowPromptPart.js | 2 +- src/art/genAI/prompts/genderPromptPart.js | 14 +++-- src/art/genAI/prompts/hairPromptPart.js | 25 ++++++-- src/art/genAI/prompts/musclesPromptPart.js | 8 +-- src/art/genAI/prompts/racePromptPart.js | 2 +- src/art/genAI/prompts/skinPromptPart.js | 11 ++-- src/art/genAI/prompts/stylePromptPart.js | 45 ++++++++++++-- src/art/genAI/prompts/weightPromptPart.js | 17 ++++-- src/art/genAI/stableDiffusion.js | 23 +++++--- src/art/genAI/stableDiffusionHelpers.js | 16 +++-- src/art/genAI/uiOptions.js | 41 +++++++++++-- 19 files changed, 273 insertions(+), 83 deletions(-) diff --git a/devTools/dictionaries/misc.txt b/devTools/dictionaries/misc.txt index 4dac21b18d7..5cec5cbe748 100644 --- a/devTools/dictionaries/misc.txt +++ b/devTools/dictionaries/misc.txt @@ -1,6 +1,10 @@ Engineerix +ltdrdata +mcmonkeyprojects orthodontal +rgthree SDXL swinir unapprovingly uncircumcise +webui diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js index b365d938f58..2a74acd4bc8 100644 --- a/js/003-data/gameVariableData.js +++ b/js/003-data/gameVariableData.js @@ -308,9 +308,16 @@ App.Data.defaultGameStateVariables = { aiOpenPose: false, aiOpenPoseModel: "", aiSamplingMethod: "DPM++ 2M SDE", - aiSamplingSteps: 20, - aiSamplingStepsEvent: 20, - aiStyle: 1, + aiSamplingSteps: 28, + aiSamplingStepsEvent: 28, + /** + * * 0 = Custom + * * 1 = Photorealistic + * * 2 = Anime/Hentai + * * 3 = None + * @type {0|1|2|3} + */ + aiStyle: 3, aiSchedulingMethod: 'Karras', aiRestoreFaces: false, aiUpscale: false, @@ -330,8 +337,8 @@ App.Data.defaultGameStateVariables = { * * 3: SD 3 - Unimplemented * * 4: Flux.1 - Unimplemented * * 5: Flow - Unimplemented - * * 6: Illustrious - Unimplemented - * @type {0 | 1 | 2} + * * 6: Illustrious + * @type {0 | 1 | 2 | 6} */ aiBaseModel: 0, /** @@ -343,8 +350,8 @@ App.Data.defaultGameStateVariables = { aiPrebuiltWorkflow: 0, aiPonyTurbo: false, aiPonyLoraStack: `{ "Styles\\Concept Art DarkSide Style LoRA_Pony XL v6.safetensors": 0.5, "Styles\\Digital Art Style SDXL_LoRA_Pony Diffusion V6 XL.safetensors": 0.2, "Styles\\Photo 2 Style SDXL_LoRA_Pony Diffusion V6 XL.safetensors": 0.2 }`, - aiHeight: 768, - aiWidth: 512, + aiHeight: 640, + aiWidth: 384, aiAgeFilter: true, customClothesPrompts: {}, @@ -1763,7 +1770,14 @@ App.Data.defaultGameOptions = { aiApiUrl: "http://localhost:7860", aiLoraPack: true, aiDisabledLoRAs: [], - aiStyle: 1, + /** + * * 0 = Custom + * * 1 = Photorealistic + * * 2 = Anime/Hentai + * * 3 = None + * @type {0|1|2|3} + */ + aiStyle: 3, aiSchedulingMethod: 'Karras', aiCustomStylePos: "", aiCustomStyleNeg: "", @@ -1780,10 +1794,10 @@ App.Data.defaultGameOptions = { aiSamplingMethod: "DPM++ 2M SDE", aiCfgScale: 5, aiTimeoutPerStep: 5, - aiSamplingSteps: 20, - aiSamplingStepsEvent: 20, - aiHeight: 768, - aiWidth: 512, + aiSamplingSteps: 28, + aiSamplingStepsEvent: 28, + aiHeight: 640, + aiWidth: 384, aiRestoreFaces: false, aiFaceDetailer: false, aiUpscale: false, diff --git a/src/art/genAI/buildPrompt.js b/src/art/genAI/buildPrompt.js index 9c4aa6b83f2..20b7d7135d8 100644 --- a/src/art/genAI/buildPrompt.js +++ b/src/art/genAI/buildPrompt.js @@ -9,7 +9,7 @@ globalThis.buildPrompt = (slave) => { } let prompts = []; switch (V.aiBaseModel) { - case 1: case 2: // SDXL/Pony + case 1: case 2: case 6: // SDXL/Pony/Illustrious prompts = [ new App.Art.GenAI.StylePromptPart(slave), new App.Art.GenAI.StructurePromptPart(slave), diff --git a/src/art/genAI/ponyPrompts/demographicsPromptPart.js b/src/art/genAI/ponyPrompts/demographicsPromptPart.js index 7b6ec40f836..645c9c293a5 100644 --- a/src/art/genAI/ponyPrompts/demographicsPromptPart.js +++ b/src/art/genAI/ponyPrompts/demographicsPromptPart.js @@ -29,6 +29,10 @@ App.Art.GenAI.DemographicsPromptPart = class DemographicsPromptPart extends App. if (perceivedGender(this.slave) < -1) { parts.push('butch'); } + + if (this.slave.race === "catgirl") { + parts.push("catgirl"); + } } else if (this.isMasculine) { if ((slave?.visualAge < 18) && !V.aiAgeFilter) { parts.push(height + 'young boy', 'shota'); @@ -43,6 +47,14 @@ App.Art.GenAI.DemographicsPromptPart = class DemographicsPromptPart extends App. if (perceivedGender(this.slave) > 1) { parts.push('feminine'); } + + if (this.slave.race === "catgirl") { + parts.push("catboy"); + } + } else { + if (this.slave.race === "catgirl") { + parts.push("catperson"); + } } if ((slave?.visualAge < 10) && !V.aiAgeFilter) { @@ -80,7 +92,7 @@ App.Art.GenAI.DemographicsPromptPart = class DemographicsPromptPart extends App. } if (slave?.boobs < 300) { - breastShape += V.aiBaseModel === 2 ? 'flat chested' : 'flat chest'; // Pony uses a slightly different tag + breastShape += this.helper.isPony() ? 'flat chested' : 'flat chest'; // Pony uses a slightly different tag } else if (slave?.boobs < 400) { breastShape += 'tiny '; } else if (slave?.boobs < 500) { @@ -164,10 +176,11 @@ App.Art.GenAI.DemographicsPromptPart = class DemographicsPromptPart extends App. switch (slave?.nipples) { case "huge": case "puffy": - parts.push(V.aiBaseModel === 2 ? 'nipple outline' : 'covered nipples'); // Using covered nipples on Pony is detrimental when clothed + parts.push(this.helper.isPonyOrIll() ? 'nipple outline' : 'covered nipples'); // Using covered nipples on Pony is detrimental when clothed break; default: if (slave?.visualAge < 18 && V.aiAgeFilter) { + // TODO: this prompting makes the clothes transparent on Illustrious (and maybe other models). If the goal is to censor the nipples why are we even prompting for them at all? parts.push('covered nipples'); } } @@ -272,7 +285,7 @@ App.Art.GenAI.DemographicsPromptPart = class DemographicsPromptPart extends App. if (perceivedGender(this.slave) > 1) { switch (V.aiBaseModel) { - case 1: case 2: + case 1: case 2: case 6: parts.push('girly, femboy'); break; default: diff --git a/src/art/genAI/prompts/androidPromptPart.js b/src/art/genAI/prompts/androidPromptPart.js index a8dc77ce31b..71cce4a5ba7 100644 --- a/src/art/genAI/prompts/androidPromptPart.js +++ b/src/art/genAI/prompts/androidPromptPart.js @@ -6,8 +6,8 @@ App.Art.GenAI.AndroidPromptPart = class AndroidPromptPart extends App.Art.GenAI. const parts = []; if (asSlave(this.slave)?.fuckdoll > 0) { - // limbs covered by fuckdoll suit - } else if (App.Art.GenAI.sdClient.hasLora("hololive_roboco-san") || V.aiBaseModel === 2) { + return; // limbs covered by fuckdoll suit + } else if (App.Art.GenAI.sdClient.hasLora("hololive_roboco-san") || this.helper.isPonyOrIll()) { if (App.Art.GenAI.sdClient.hasLora("hololive_roboco-san") && (hasBothProstheticArms(this.slave) || hasBothProstheticLegs(this.slave))) { parts.push(this.helper.lora("hololive_roboco-san", 1, ", android")); } @@ -32,15 +32,19 @@ App.Art.GenAI.AndroidPromptPart = class AndroidPromptPart extends App.Art.GenAI. if (asSlave(this.slave)?.fuckdoll > 0) { return; // limbs covered by fuckdoll suit } - if (App.Art.GenAI.sdClient.hasLora("hololive_roboco-san") || V.aiBaseModel === 2) { - if (hasBothProstheticArms(this.slave) && hasBothProstheticLegs(this.slave)) { - return; // space for negative prompt if needed NG - } else if (hasBothProstheticArms(this.slave)) { - return `mechanical legs`; - } else if (hasBothProstheticLegs(this.slave) && !this.censored) { - return `mechanical arms`; + const parts = []; + if (App.Art.GenAI.sdClient.hasLora("hololive_roboco-san") || this.helper.isPonyOrIll()) { + if (!hasBothProstheticArms(this.slave) && !hasBothProstheticLegs(this.slave)) { + return; // they have no prostetics so we don't need to worry about prompt bleeding + } else if (!hasBothProstheticArms(this.slave)) { + parts.push(`mechanical arms`); + } else if (!hasBothProstheticLegs(this.slave)) { + parts.push(`mechanical legs`); + } + if (this.helper.isIll()) { + parts.push(`robot`); } } - return undefined; + return parts.join(`, `); } }; diff --git a/src/art/genAI/prompts/breastsPromptPart.js b/src/art/genAI/prompts/breastsPromptPart.js index e9acf0b89f3..64cb3b79411 100644 --- a/src/art/genAI/prompts/breastsPromptPart.js +++ b/src/art/genAI/prompts/breastsPromptPart.js @@ -18,7 +18,7 @@ App.Art.GenAI.BreastsPromptPart = class BreastsPromptPart extends App.Art.GenAI. if (boobs < 300) { breastDescriptors.push("flat"); - breastWord = V.aiBaseModel === 2 ? "chested" : "chest"; + breastWord = this.helper.isPonyOrIll() ? "chested" : "chest"; } else if (boobs < 400) { breastDescriptors.push("very small"); } else if (boobs < 500) { @@ -31,7 +31,7 @@ App.Art.GenAI.BreastsPromptPart = class BreastsPromptPart extends App.Art.GenAI. breastDescriptors.push("very large"); } else if (boobs < 1400) { breastDescriptors.push("huge"); - } else if (V.aiBaseModel === 2) { + } else if (this.helper.isPonyOrIll()) { if (boobs < 1600) { breastDescriptors.push("gigantic"); } else { diff --git a/src/art/genAI/prompts/earsPromptPart.js b/src/art/genAI/prompts/earsPromptPart.js index aea8f1888d6..a1c3f09b567 100644 --- a/src/art/genAI/prompts/earsPromptPart.js +++ b/src/art/genAI/prompts/earsPromptPart.js @@ -4,19 +4,70 @@ App.Art.GenAI.EarsPromptPart = class EarsPromptPart extends App.Art.GenAI.Prompt */ positive() { if (this.slave.faceAccessory === "cat ears") { - return `cat ears`; + return `cat ears`; // TODO: this probably needs a lora as it is a cat face mask with cat ears, not actual cat ears } + const parts = []; if (this.slave.earT !== "none" && this.slave.earT !== "normal") { - return `${this.slave.earT} ears`; + parts.push(`${this.slave.earT} ears`); } - return undefined; + if (this.helper.isIll()) { + switch (this.slave.earShape) { + case "bird": + break; // TODO: needs a lora + case "cow": case "deer": case "robot": case "sheep": + // TODO: "deer" and "sheep" need LoRAs since the model adds deer/sheep horns and negative prompting doesn't remove them + parts.push(`${this.slave.earShape} ears`); + break; + case "damaged": + if (this.slave.earT !== "none") { + parts.push(`cut human ear`); + } else { + parts.push(`cut ear`); + } + break; + case "dragon": + break; // TODO: needs a lora + case "elven": case "pointy": + parts.push(`elf ears`); + break; + case "gazelle": + break; // TODO: needs a lora for reliable results + case "holes": + break; // TODO: will need a lora + case "none": + break; // TODO: currently handled in negative prompt, but that doesn't work very reliably. We need a lora + case "normal": + if (this.slave.earT !== "none") { + parts.push(`human ears`); + } + break; + case "orcish": + break; // TODO: nees a lora; model attempts to make an orc; adding orc to negatives makes the model remove the ears + } + } + return parts.join(`, `); } /** * @override */ negative() { - return undefined; + const parts = []; + if (this.slave.tail === "none") { + parts.push(`tail`); // animal ears bleed into tail + } + if (this.helper.isIll()) { + if (this.slave.earT === "none" && this.slave.earShape === "damaged") { + parts.push(`animal ears`); + } + if (this.slave.earShape === "robot") { + parts.push(`robot`); + } + } + if (this.slave.earT === "none" && this.slave.earShape === "none") { + parts.push(`ear`, `ears`); + } + return parts.join(`, `); } /** diff --git a/src/art/genAI/prompts/expressionPromptPart.js b/src/art/genAI/prompts/expressionPromptPart.js index 46eb476b39f..11f1d38d064 100644 --- a/src/art/genAI/prompts/expressionPromptPart.js +++ b/src/art/genAI/prompts/expressionPromptPart.js @@ -62,7 +62,7 @@ App.Art.GenAI.ExpressionPromptPart = class ExpressionPromptPart extends App.Art. expression = `${trustPart}`; } - if (this.helper.isXLModel()) { + if (this.helper.isXLBased()) { return `${expression}`; } return `(${expression})`; diff --git a/src/art/genAI/prompts/eyebrowPromptPart.js b/src/art/genAI/prompts/eyebrowPromptPart.js index ac0e4a874d6..a0226f3ab1b 100644 --- a/src/art/genAI/prompts/eyebrowPromptPart.js +++ b/src/art/genAI/prompts/eyebrowPromptPart.js @@ -3,7 +3,7 @@ App.Art.GenAI.EyebrowPromptPart = class EyebrowPromptPart extends App.Art.GenAI. * @override */ positive() { - if (this.helper.isXLModel()) { + if (this.helper.isXLBased()) { return; } const slave = asSlave(this.slave); diff --git a/src/art/genAI/prompts/genderPromptPart.js b/src/art/genAI/prompts/genderPromptPart.js index 0ea2712f9ce..33b082b499e 100644 --- a/src/art/genAI/prompts/genderPromptPart.js +++ b/src/art/genAI/prompts/genderPromptPart.js @@ -5,20 +5,22 @@ App.Art.GenAI.GenderPromptPart = class GenderPromptPart extends App.Art.GenAI.Pr positive() { let prompt = undefined; if (this.isFeminine) { + prompt = "1girl, "; if (this.slave.race === "catgirl") { - prompt = "catgirl, catperson" + this.helper.lora("CatgirlLoraV7", .8, "", " "); + prompt += "catgirl, catperson" + this.helper.lora("CatgirlLoraV7", .8, "", " "); } else if (this.slave.visualAge >= 20) { - prompt = "woman"; + prompt += "woman"; } else { - prompt = "girl"; + prompt += "girl"; } } else if (this.isMasculine) { + prompt = "1boy, "; if (this.slave.race === "catgirl") { - prompt = "catboy, catperson" + this.helper.lora("CatgirlLoraV7", .8, "", " "); + prompt += "catboy, catperson" + this.helper.lora("CatgirlLoraV7", .8, "", " "); } else if (this.slave.visualAge >= 20) { - prompt = "man"; + prompt += "man"; } else { - prompt = "boy"; + prompt += "boy"; } } else { if (this.slave.race === "catgirl") { diff --git a/src/art/genAI/prompts/hairPromptPart.js b/src/art/genAI/prompts/hairPromptPart.js index eeff48209de..8df588339a0 100644 --- a/src/art/genAI/prompts/hairPromptPart.js +++ b/src/art/genAI/prompts/hairPromptPart.js @@ -14,13 +14,13 @@ App.Art.GenAI.HairPromptPart = class HairPromptPart extends App.Art.GenAI.Prompt const heightVhLength = this.slave.hLength / this.slave.height; let hairLength = ''; if (heightVhLength > 0.9) { - if (V.aiBaseModel === 2) { + if (this.helper.isPonyOrIll()) { hairLength = `absurdly long`; } else { hairLength = `(very long:1.2)`; } } else if (heightVhLength > 0.7) { - if (V.aiBaseModel === 2) { + if (this.helper.isPonyOrIll()) { hairLength = `extremely long`; } else { hairLength = `(very long:1.1)`; @@ -34,13 +34,23 @@ App.Art.GenAI.HairPromptPart = class HairPromptPart extends App.Art.GenAI.Prompt } else { hairLength = `short`; } + const parts = []; if (stylePostfix) { - return `${hairLength} ${this.slave.hColor} hair ${styleStr}`; + parts.push(`${hairLength} ${this.slave.hColor} hair ${styleStr}`); + } else if (this.helper.isXLBased()) { + parts.push(`${hairLength} ${this.slave.hStyle} hair, ${this.slave.hColor} hair`); + } else { + parts.push(`${this.slave.hStyle} hair, ${hairLength} ${this.slave.hColor} hair`); } - if (this.helper.isXLModel()) { - return `${hairLength} ${this.slave.hStyle} hair, ${this.slave.hColor} hair`; + if (this.helper.isIll()) { + if (this.slave.hEffect.includes(`undercoloring`) || this.slave.hEffect.includes(`highlights`)) { + parts.push(this.slave.hEffect); // color is already included in hEffect + } else if (this.slave.hEffect.includes(`stripes`)) { + parts.push(`${this.slave.hEffect} in hair`); + } } - return `${this.slave.hStyle} hair, ${hairLength} ${this.slave.hColor} hair`; + + return parts.join(`, `); } /** @@ -50,6 +60,9 @@ App.Art.GenAI.HairPromptPart = class HairPromptPart extends App.Art.GenAI.Prompt if (this.slave.hStyle === "bald" || this.slave.hStyle === "shaved" || this.slave.hLength === 0) { return `hair, long hair, short hair`; } + if (this.helper.isIll() && this.slave.hEffect.includes(`highlights`)) { + return `${this.slave.hEffectColor} undercoloring`; + } return; } diff --git a/src/art/genAI/prompts/musclesPromptPart.js b/src/art/genAI/prompts/musclesPromptPart.js index ad7196578d0..8357fce06db 100644 --- a/src/art/genAI/prompts/musclesPromptPart.js +++ b/src/art/genAI/prompts/musclesPromptPart.js @@ -3,7 +3,7 @@ App.Art.GenAI.MusclesPromptPart = class MusclesPromptPart extends App.Art.GenAI. * @override */ positive() { - if (this.helper.isXLModel()) { + if (this.helper.isXLOrPony()) { if (this.slave.muscles > 95) { return 'huge muscles'; } else if (this.slave.muscles > 50) { @@ -17,9 +17,9 @@ App.Art.GenAI.MusclesPromptPart = class MusclesPromptPart extends App.Art.GenAI. } else if (this.slave.muscles > 50) { return `(muscular:1.3)`; } else if (this.slave.muscles > 30) { - return `(muscular:1.2)`; + return this.helper.isIll() ? `muscular` : `(muscular:1.2)`; } else if (this.slave.muscles > 10) { - return `muscular`; + return this.helper.isIll() ? `toned` : `muscular`; } else if (this.slave.muscles > -10) { return null; } else if (this.slave.muscles > -95) { @@ -33,7 +33,7 @@ App.Art.GenAI.MusclesPromptPart = class MusclesPromptPart extends App.Art.GenAI. * @override */ negative() { - if (this.slave.muscles < -30) { + if (this.slave.muscles < -5) { return `muscular`; } } diff --git a/src/art/genAI/prompts/racePromptPart.js b/src/art/genAI/prompts/racePromptPart.js index b1f4ff01b03..1119e387969 100644 --- a/src/art/genAI/prompts/racePromptPart.js +++ b/src/art/genAI/prompts/racePromptPart.js @@ -8,7 +8,7 @@ App.Art.GenAI.RacePromptPart = class RacePromptPart extends App.Art.GenAI.Prompt } else if (this.slave.race === "black") { return "african"; } else if (this.slave.race === "catgirl") { - return undefined; // catgirl/catboy race is covered by gender prompt + return undefined; // catgirl/catboy race is covered by gender prompt (SD 1.X)/demographics prompt } return this.slave.race; } diff --git a/src/art/genAI/prompts/skinPromptPart.js b/src/art/genAI/prompts/skinPromptPart.js index cb58a4e4466..c46da445963 100644 --- a/src/art/genAI/prompts/skinPromptPart.js +++ b/src/art/genAI/prompts/skinPromptPart.js @@ -22,6 +22,9 @@ App.Art.GenAI.SkinPromptPart = class SkinPromptPart extends App.Art.GenAI.Prompt case "pale": case "light beige": case "very fair": + if (this.helper.isIll()) { + return "fair skin"; // pale skin makes the skin almost pure white + } return "pale skin"; case "fair": case "light": @@ -35,14 +38,14 @@ App.Art.GenAI.SkinPromptPart = class SkinPromptPart extends App.Art.GenAI.Prompt case "olive": case "bronze": case "dark beige": - if (this.helper.isXLModel()) { + if (this.helper.isXLOrPony()) { return "olive skin"; } return "tan skin"; case "dark olive": case "light brown": case "brown": - if (this.helper.isXLModel()) { + if (this.helper.isXLBased()) { return "brown skin"; } return "tan skin"; @@ -62,8 +65,8 @@ App.Art.GenAI.SkinPromptPart = class SkinPromptPart extends App.Art.GenAI.Prompt * @override */ negative() { - if (this.helper.isXLModel()) { - if (this.positive() === "tan skin") { + if (this.helper.isXLBased()) { + if (this.positive()?.includes("tan skin")) { return "tan lines"; } return; diff --git a/src/art/genAI/prompts/stylePromptPart.js b/src/art/genAI/prompts/stylePromptPart.js index 747e5f20cbd..e6ad3c25c88 100644 --- a/src/art/genAI/prompts/stylePromptPart.js +++ b/src/art/genAI/prompts/stylePromptPart.js @@ -1,19 +1,25 @@ +// cSpell: ignore derpibooru + App.Art.GenAI.StylePromptPart = class StylePromptPart extends App.Art.GenAI.PromptPart { /** * @override */ positive() { let positive = ""; + if (this.helper.isPony()) { + // this is a negative LoRA but it has to go in the positive prompt to be loaded + positive += this.helper.lora("badanatomy_SDXL_negative_LORA_AutismMix_v1", -0.2, " "); + } // Check if filter is on and needed, then pick model-appropriate framing tags. if (this.censored) { - if (V.aiBaseModel === 2) { + if (this.helper.isPonyOrIll()) { positive += "front view, half-length portrait, "; } else { positive += "straight-on, cowboy shot, "; } positive += "face focus, "; } else { - if (V.aiBaseModel === 2) { + if (this.helper.isPonyOrIll()) { positive += "full-length portrait, "; } else { positive += "full body, portrait, "; @@ -23,9 +29,19 @@ App.Art.GenAI.StylePromptPart = class StylePromptPart extends App.Art.GenAI.Prom case 0: // Custom Style return V.aiCustomStylePos; case 1: // Photorealistic + if (this.helper.isPony()) { + positive += "score_9, score_8_up, score_7_up, "; + } return positive + "photorealistic, dark theme, black background"; case 2: // Anime/Hentai + if (this.helper.isXL()) { + return positive + "masterpiece, best quality"; // SDXL gets flat when asked for 2d or anime, and gets old looking when asked for hentai + } else if (this.helper.isPony()) { + return positive + "score_9, score_8_up, score_7_up, source_anime, source_cartoon"; // Pony does better with scoring; `source_anime, source_cartoon` is much better than `2d, anime, hentai` + } return positive + "2d, anime, hentai"; + case 3: // No extra styling + return positive; } } @@ -34,16 +50,22 @@ App.Art.GenAI.StylePromptPart = class StylePromptPart extends App.Art.GenAI.Prom */ negative() { let negative = ""; + if (V.aiStyle !== 0) { + negative += "text, watermark, low quality, censored, mosaic_censoring, bar_censor, lowres, bad anatomy, bad hands, "; + if (this.helper.isPony()) { + negative += "score_4, score_5, score_6, source_pony, derpibooru_p_low, "; + } + } if (this.censored) { negative += "NSFW, nude, "; - if (V.aiBaseModel === 2) { + if (this.helper.isPonyOrIll()) { negative += "full-length portrait, "; } else { negative += "full body, "; } } else { negative += "close-up, "; - if (V.aiBaseModel === 2) { + if (this.helper.isPonyOrIll()) { negative += "half-length portrait, "; } else { negative += "cowboy shot, "; @@ -53,9 +75,17 @@ App.Art.GenAI.StylePromptPart = class StylePromptPart extends App.Art.GenAI.Prom case 0: // Custom Style return negative + V.aiCustomStyleNeg; case 1: // Photorealistic + if (this.helper.isPony()) { + negative += "source_anime, source_cartoon, "; + } return negative + "greyscale, monochrome, cg, render"; case 2: // Anime/Hentai + if (this.helper.isXL()) { + negative += "worst quality, low quality, "; + } return negative + "greyscale, monochrome, photography, 3d render, speech bubble"; + case 3: // None + return negative; } } @@ -69,7 +99,14 @@ App.Art.GenAI.StylePromptPart = class StylePromptPart extends App.Art.GenAI.Prom case 1: // Photorealistic return "photorealistic, dark theme, black background"; case 2: // Anime/Hentai + if (this.helper.isXL()) { + return "masterpiece, best quality"; + } else if (this.helper.isPony()) { + return "score_9, score_8_up, score_7_up, score_6_up, source_anime, source_cartoon"; + } return "2d, anime, hentai"; + case 3: // None + return ""; } } }; diff --git a/src/art/genAI/prompts/weightPromptPart.js b/src/art/genAI/prompts/weightPromptPart.js index dab761adcc1..6cdd4240b67 100644 --- a/src/art/genAI/prompts/weightPromptPart.js +++ b/src/art/genAI/prompts/weightPromptPart.js @@ -3,7 +3,7 @@ App.Art.GenAI.WeightPromptPart = class WeightPromptPart extends App.Art.GenAI.Pr * @override */ positive() { - if (this.helper.isXLModel()) { + if (this.helper.isXLOrPony()) { if (this.slave.weight < -95) { return 'skinny, emaciated'; } else if (this.slave.weight < -30) { @@ -43,18 +43,23 @@ App.Art.GenAI.WeightPromptPart = class WeightPromptPart extends App.Art.GenAI.Pr * @override */ negative() { - if (this.helper.isXLModel()) { + const parts = []; + if (this.helper.isIll() && this.positive()?.includes("emaciated")) { + parts.push(`skeleton`); + } + if (this.helper.isXLOrPony()) { if (this.slave.weight < -30) { - return 'fat'; + parts.push('fat'); } } if (this.slave.weight < -30) { - return `plump, chubby`; + parts.push(`plump, chubby`); } else if (this.slave.weight < 50) { - return null; + // do nothing } else { - return `thin, skinny`; + parts.push(`thin, skinny`); } + return parts.join(`, `); } /** diff --git a/src/art/genAI/stableDiffusion.js b/src/art/genAI/stableDiffusion.js index d98e802803d..a9bc7e0a03c 100644 --- a/src/art/genAI/stableDiffusion.js +++ b/src/art/genAI/stableDiffusion.js @@ -694,19 +694,23 @@ App.Art.GenAI.StableDiffusionClient = class { return this.getCheckpointMetadata(modelPath) .then((metadata) => { /** @type {FC.GameVariables["aiBaseModel"][]} */ - const models = [0, 1, 2]; + const matches = []; let hasUsefulMetadata = false; const filenameGuess = () => { - // we are going to attempt to detect sdxl/pony models based off their filenames + // we are going to attempt to detect base model types based off their filenames + if (modelPath.toLowerCase().includes("ill")) { + // assume it is a Illustrious model + matches.push(6); + } if (modelPath.toLowerCase().includes("pony")) { // assume it is a pony model - models.deleteAll(0, 1); - } else if (modelPath.toLowerCase().includes("sdxl")) { + matches.push(2); + } + if (matches.length === 0 && modelPath.toLowerCase().includes("xl")) { // assume it is a SDXL model - models.deleteAll(0, 2); + matches.push(1); } - // assume nothing }; if (metadata) { @@ -714,8 +718,8 @@ App.Art.GenAI.StableDiffusionClient = class { if ("modelspec.architecture" in metadata) { hasUsefulMetadata = true; if (metadata["modelspec.architecture"] === "stable-diffusion-xl-v1-base") { - models.deleteAll(0); - filenameGuess(); + // cannot be a sd 1.x model + filenameGuess(); // but it can be some other model } else { console.error(new Error(`metadata["modelspec.architecture"] === "${metadata["modelspec.architecture"]}" was not handled in getCheckpointPotentialBaseModels`)); } @@ -723,8 +727,9 @@ App.Art.GenAI.StableDiffusionClient = class { } if (!hasUsefulMetadata) { filenameGuess(); + matches.push(0); // this may be an sd 1.x model so we add it by default } - return models; + return matches; }) .catch(err => { console.log(`Failed to get checkpoint base model from Stable Diffusion - ${err}`); diff --git a/src/art/genAI/stableDiffusionHelpers.js b/src/art/genAI/stableDiffusionHelpers.js index 5d37cda2047..09dbbb2396b 100644 --- a/src/art/genAI/stableDiffusionHelpers.js +++ b/src/art/genAI/stableDiffusionHelpers.js @@ -101,9 +101,11 @@ App.Art.GenAI.PromptHelpers = (() => { const validateAgainst = (description, targets) => targets.has(sanitizeDescriptor(description)); - const XLModel = () => V.aiBaseModel === (1||2); // this could be more general when other models are implemented + const XLCheckpoint = () => V.aiBaseModel === 1; - const ponyModel = () => V.aiBaseModel === 2; + const ponyCheckpoint = () => V.aiBaseModel === 2; + + const illustriousCheckpoint = () => V.aiBaseModel === 6; const isFeminine = (slave) => { if (V.aiGenderHint === 1) { // Hormone balance @@ -172,8 +174,14 @@ App.Art.GenAI.PromptHelpers = (() => { exposesCrotch: (description) => validateAgainst( description, CROTCH_EXPOSING_OUTFITS ), - isXLModel: XLModel, - isPonyModel: ponyModel, + isXL: XLCheckpoint, + isPony: ponyCheckpoint, + isXLOrPony: () => { return (XLCheckpoint() || ponyCheckpoint()); }, + isIll: illustriousCheckpoint, + isXLOrIll: () => { return (XLCheckpoint() || illustriousCheckpoint()); }, + isPonyOrIll: () => { return (ponyCheckpoint() || illustriousCheckpoint()); }, + /** @returns {boolean} true if the Checkpoint is an XL checkpoint or based off of XL (Pony, Illustrious, etc) */ + isXLBased: () => { return XLCheckpoint() || ponyCheckpoint() || illustriousCheckpoint(); }, isFeminine, isMasculine, lora: loraBuilder, diff --git a/src/art/genAI/uiOptions.js b/src/art/genAI/uiOptions.js index cd3a39acda5..243096e7e67 100644 --- a/src/art/genAI/uiOptions.js +++ b/src/art/genAI/uiOptions.js @@ -207,7 +207,16 @@ App.Art.GenAI.UI.Options.recommendedLoRAs = new Map([ ], usage: "Helps with chastity belt generation", baseModel: [0] - }] + }], + ["badanatomy_SDXL_negative_LORA_AutismMix_v1", { + name: "badanatomy_SDXL_negative_LORA_AutismMix_v1", + urls: [ + "https://civitai.com/models/430961?modelVersionId=486308", + "https://civitai.com/api/download/models/486308?type=Model&format=SafeTensor", + ], + usage: "Helps make Pony checkpoints look better; Less artifacts and better hands", + baseModel: [2] + }], ]); App.Art.GenAI.UI.Options.aiClearCaches = () => { @@ -369,6 +378,7 @@ App.Art.GenAI.UI.Options.aiCheckpointSettings = () => { 0: "SD 1.X", 1: "SDXL", 2: "Pony", + 6: "Illustrious", }; /** @type {string[]} */ const baseModelStrings = baseModelData.map(val => baseModelMap[val]); @@ -377,7 +387,11 @@ App.Art.GenAI.UI.Options.aiCheckpointSettings = () => { ["SD 1.X", 0], ["SDXL", 1], ["Pony", 2], + ["Illustrious", 6], ]); + if (V.aiBaseModel === 6) { + option.addComment("Note: FC currently has no support for 'v-prediction' based models"); + } if (!baseModelData.includes(V.aiBaseModel)) { option.addComment(App.UI.DOM.makeElement("span", `This may be incorrect! Best guess${baseModelData.length === 1 ? "" : "es"}: ${baseModelStrings.toString()}`, ["orange"])); } @@ -464,11 +478,27 @@ App.Art.GenAI.UI.Options.aiCheckpointSettings = () => { .addComment("The number of steps used when generating an image during events. Generally between 20 to 50 to maintain a reasonable speed."); V.aiHeight = Math.max(V.aiHeight, 10); + let suggestedHeight = "640"; + let suggestedWidth = "384"; + switch (V.aiBaseModel) { + case 0: + if ((V.aiWidth + V.aiHeight) !== 1024) { + suggestedHeight += "; Width + height should equal 1024 for best results"; + } + break; + case 1: case 2: case 6: + suggestedHeight = "1216"; + suggestedWidth = "832"; + if ((V.aiWidth + V.aiHeight) !== 2048) { + suggestedHeight += "; Width + height should equal 2048 for best results"; + } + break; + } options.addOption("Height", "aiHeight").showTextBox() - .addComment("The height of the image."); + .addComment(`The height of the image; We suggest ${suggestedHeight}`); V.aiWidth = Math.max(V.aiWidth, 10); options.addOption("Width", "aiWidth").showTextBox() - .addComment("The width of the image."); + .addComment(`The width of the image; We suggest ${suggestedWidth}`); if (V.aiUserInterface === 0) { const rfCheckSpan = App.UI.DOM.makeElement('span', `Validating Restore Faces...`); @@ -887,9 +917,10 @@ App.Art.GenAI.UI.Options.aiPromptingSettings = () => { options.addOption("AI style prompting", "aiStyle") .addValueList([ - ["Photorealistic", 1], + ["None", 3], ["Anime/Hentai", 2], - ["Custom", 0] + ["Photorealistic", 1], + ["Custom", 0], ]); if (V.aiStyle === 0) { -- GitLab