diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index 1db18b37aa2279d63558468e2d4ce670334ada1e..2c8f1162e5d6995373fa246ba220e9836619d7af 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -260,11 +260,11 @@ App.Data.defaultGameStateVariables = {
 	aiGenderHint: 1,
 	aiOpenPose: false,
 	aiOpenPoseModel: "",
-	aiSamplingMethod: "DPM++ 2M SDE Karras",
+	aiSamplingMethod: "DPM++ 2M SDE",
 	aiSamplingSteps: 20,
 	aiSamplingStepsEvent: 20,
 	aiStyle: 1,
-	aiSchedulingMethod: 'karras',
+	aiSchedulingMethod: 'Karras',
 	aiRestoreFaces: false,
 	aiUpscale: false,
 	aiUpscaleScale: 1.75,
@@ -283,6 +283,7 @@ App.Data.defaultGameStateVariables = {
 	 * * 3: SD 3 - Unimplemented
 	 * * 4: Flux.1 - Unimplemented
 	 * * 5: Flow - Unimplemented
+	 * @type {0 | 1 | 2}
 	 */
 	aiBaseModel: 0,
 	/**
@@ -1730,7 +1731,7 @@ App.Data.defaultGameOptions = {
 	aiLoraPack: true,
 	aiDisabledLoRAs: [],
 	aiStyle: 1,
-	aiSchedulingMethod: 'karras',
+	aiSchedulingMethod: 'Karras',
 	aiCustomStylePos: "",
 	aiCustomStyleNeg: "",
 	aiNationality: 2,
@@ -1743,7 +1744,7 @@ App.Data.defaultGameOptions = {
 	aiAutoGen: true,
 	aiAutoGenFrequency: 10,
 	aiUseRAForEvents: false,
-	aiSamplingMethod: "DPM++ 2M SDE Karras",
+	aiSamplingMethod: "DPM++ 2M SDE",
 	aiCfgScale: 5,
 	aiTimeoutPerStep: 2.5,
 	aiSamplingSteps: 20,
diff --git a/src/art/artJS.js b/src/art/artJS.js
index 08ab157a34ec366f9709123e039810c9486f2d7c..33fd47244961a33f96486220f96f2451cd8bc1ac 100644
--- a/src/art/artJS.js
+++ b/src/art/artJS.js
@@ -666,7 +666,7 @@ App.Art.aiArtElement = function(slave, imageSize, isEventImage = null) {
 				.then(() => {
 					staticSpecific.refresh();
 				}).catch(error => {
-					console.log(error.message || error);
+					console.log(error?.message || error);
 				}).finally(() => {
 					container.classList.remove("refreshing");
 				});
diff --git a/src/art/genAI/prompts/amputationPromptPart.js b/src/art/genAI/prompts/amputationPromptPart.js
index 18d39d44eeb8e2f06e00fabaa68b6b8c3f2b34f6..179652fe15af730de648b08b5d7fd83ebc64def9 100644
--- a/src/art/genAI/prompts/amputationPromptPart.js
+++ b/src/art/genAI/prompts/amputationPromptPart.js
@@ -3,8 +3,8 @@ App.Art.GenAI.AmputationPromptPart = class AmputationPromptPart extends App.Art.
 	 * @override
 	 */
 	positive() {
-		if (isAmputee(this.slave) && App.Art.GenAI.sdClient.hasLora("amputee-000003")) {
-			return `<lora:amputee-000003:1>`;
+		if (isAmputee(this.slave)) {
+			return this.helper.lora("amputee-000003", 1);
 		}
 	}
 
diff --git a/src/art/genAI/prompts/androidPromptPart.js b/src/art/genAI/prompts/androidPromptPart.js
index e45a0cbd0b39b4b43a92e2e7afd148a828d2ea4f..a8dc77ce31b0bcdeffdfdf6f692e41d433ed109c 100644
--- a/src/art/genAI/prompts/androidPromptPart.js
+++ b/src/art/genAI/prompts/androidPromptPart.js
@@ -7,9 +7,9 @@ App.Art.GenAI.AndroidPromptPart = class AndroidPromptPart extends App.Art.GenAI.
 
 		if (asSlave(this.slave)?.fuckdoll > 0) {
 			// limbs covered by fuckdoll suit
-		} else if (App.Art.GenAI.sdClient.hasLora("hololive_roboco-san-10") || V.aiBaseModel === 2) {
-			if (App.Art.GenAI.sdClient.hasLora("hololive_roboco-san-10") && (hasBothProstheticArms(this.slave) || hasBothProstheticLegs(this.slave))) {
-				parts.push(`<lora:hololive_roboco-san-10:1>, android`);
+		} else if (App.Art.GenAI.sdClient.hasLora("hololive_roboco-san") || V.aiBaseModel === 2) {
+			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"));
 			}
 			if (hasBothProstheticArms(this.slave)) {
 				parts.push(`mechanical arms`);
@@ -19,7 +19,7 @@ App.Art.GenAI.AndroidPromptPart = class AndroidPromptPart extends App.Art.GenAI.
 			}
 		}
 		if (App.Art.GenAI.sdClient.hasLora('RobotDog0903') && isQuadrupedal(this.slave)) {
-			parts.push(`quadruped, <lora:RobotDog0903:.8>`);
+			parts.push(this.helper.lora("RobotDog0903", .8, "", "quadruped, "));
 		}
 
 		return parts.join(`, `);
@@ -32,7 +32,7 @@ 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-10") || V.aiBaseModel === 2) {
+		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)) {
diff --git a/src/art/genAI/prompts/breastsPromptPart.js b/src/art/genAI/prompts/breastsPromptPart.js
index 8771dc97f8388bc56ff99a3fe75fdb59812d2e05..e9acf0b89f324e90c69eb0195f09f37a8784ba5d 100644
--- a/src/art/genAI/prompts/breastsPromptPart.js
+++ b/src/art/genAI/prompts/breastsPromptPart.js
@@ -39,7 +39,7 @@ App.Art.GenAI.BreastsPromptPart = class BreastsPromptPart extends App.Art.GenAI.
 			}
 		} else {
 			if (App.Art.GenAI.sdClient.hasLora("BEReaction")) {
-				return `<lora:BEReaction:1>, bereaction, breast expansion, (gigantic breasts:1.2)`;
+				return this.helper.lora("BEReaction", 1, ", bereaction, breast expansion, (gigantic breasts:1.2)");
 			} else {
 				if (boobs < 1600) {
 					breastDescriptors.push("very huge");
diff --git a/src/art/genAI/prompts/clothesPromptPart.js b/src/art/genAI/prompts/clothesPromptPart.js
index a54508820aaba9ed1f626ba4bd847393d6bf3b3c..8176e421e81fe164a25c38c38a8c8f8bfb9c0740 100644
--- a/src/art/genAI/prompts/clothesPromptPart.js
+++ b/src/art/genAI/prompts/clothesPromptPart.js
@@ -1,699 +1,698 @@
 // cSpell:ignore xxmaskedxx, nopussy, flaccidfutanarimix, micropp
-
-const clothesPrompts = {
-	"no clothing": {
-		"positive": "nude",
-		"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties",
-	},
-	"a Fuckdoll suit": {  // NG good gen requires LoRA, but below will work without LoRA as well
-		"positive": "black latex bodysuit, long sleeves, <lora:xxmaskedxx_lora_v01:0.8> xxmaskedxx",
-		"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy, nipples",
-	},
-	"conservative clothing": {
-		"positive": "slacks, pants, silk blouse",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"chains": {
-		"positive": "(metal chains:1.1), nude, navel",
-		"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties",
-	},
-	"Western clothing": {
-		"positive": "flannel shirt, chaps, cowboy hat",
-		"negative": "nude, pussy, nipples",
-	},
-	"body oil": {  // Doesn't work well
-		"positive": "body oil, nude, navel",
-		"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties",
-	},
-	"a toga": {  // Doesn't work well
-		"positive": "white toga",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a huipil": {  // Doesn't work well
-		"positive": "huipil, mexican clothing",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a slutty qipao": {
-		"positive": "qipao, chinese clothing, thighs",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a kimono": {
-		"positive": "kimono",
-		"negative": "jeans, nude, nipples",
-	},
-	"spats and a tank top": {  // Spats don't work well
-		"positive": "bike shorts, tank top",
-		"negative": "bike, jeans, nude, pussy, nipples",
-	},
-	"uncomfortable straps": {
-		"positive": "(leather straps, bondage:1.1), nude, navel",
-		"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties",
-	},
-	"shibari ropes": {
-		"positive": "shibari rope, bondage, nude, navel",
-		"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties",
-	},
-	"restrictive latex": {  // Doesn't work well
-		"positive": "latex bodysuit, long sleeves",
-		"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy",
-	},
-	"a latex catsuit": {  // Doesn't work well
-		"positive": "latex bodysuit, long sleeves",
-		"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy",
-	},
-	"attractive lingerie": {  // Cupless part doesn't work well
-		"positive": "lingerie, cupless bra, thong",
-		"negative": "clothes, jeans, pants",
-	},
-	"attractive lingerie for a pregnant woman": {  // Cupless part doesn't work well
-		"positive": "lingerie, cupless bra, thong",
-		"negative": "clothes, jeans, pants",
-	},
-	"kitty lingerie": {  // Broken for photorealistic models, probably works for anime models
-		"positive": "kitty lingerie, cat lingerie, kawaii lingerie",
-		"negative": "cat ears, jeans, nude, pussy, nipples",
-	},
-	"a maternity dress": {
-		"positive": "maternity dress, loose dress",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"stretch pants and a crop-top": {
-		"positive": "crop top, midriff, navel, leggings",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a succubus outfit": {
-		"positive": "succubus costume, red leather corset, red leather miniskirt, black demon horns",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a fallen nuns habit": {
-		"positive": "(latex nun habit:1.1), thighs",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a penitent nuns habit": {
-		"positive": "(latex nun habit:1.1), thighs, rope, bondage",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a chattel habit": {
-		"positive": "(white latex nun habit:1.1), gold belt, bare breasts",
-		"negative": "",
-	},
-	"a string bikini": {  // Cupless part doesn't work well
-		"positive": "string microbikini, cupless bikini",
-		"negative": "jeans, nude, pussy",
-	},
-	"a scalemail bikini": {  // Doesn't work well
-		"positive": "chainmail bikini, navel",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"striped panties": {
-		"positive": "blue striped panties, underwear only",
-		"negative": "jeans, nude, pussy",
-	},
-	"a cheerleader outfit": {
-		"positive": "(cheerleader outfit:1.1), skirt, thighs, crop top, navel, midriff",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"clubslut netting": {
-		"colors": ["light blue", "pink", "lime green"],
-		"positive": "rave clothing, lewd, transparent clothing, $color fishnet bodysuit, $color fishnet, choker",
-		"negative": "cloth, jeans, pants, corset",
-	},
-	"cutoffs and a t-shirt": {
-		"positive": "white t-shirt, jean shorts",
-		"negative": "nude, pussy, nipples",
-	},
-	"slutty business attire": {
-		"positive": "suit jacket, cleavage, black skirt, thighs",
-		"negative": "jeans, nude, pussy, nipples"
-	},
-	"nice business attire": {
-		"positive": "suit jacket, collared shirt, black skirt",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a ball gown": {
-		"positive": "ballgown, long dress, luxurious dress, thighhighs",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a slave gown": {
-		"positive": "ballgown, long dress, luxurious dress, thighhighs, cleavage, see-through, translucent clothing, straps, bdsm",
-		"negative": "jeans, nude",
-	},
-	"a halter top dress": {
-		"positive": "(halterneck:1.1), long dress, luxurious dress, bare back,",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"an evening dress": {
-		"positive": "evening gown, long dress, luxurious dress, thighs",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a mini dress": {
-		"positive": "short dress, tight dress, strapless, cleavage, thighs",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a comfortable bodysuit": {
-		"positive": "latex bodysuit, long sleeves",
-		"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy, nipples",
-	},
-	"a leotard": {
-		"positive": "leotard, thighs",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a monokini": {  // Doesn't work well
-		"positive": "monokini",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"an apron": {
-		"positive": "apron, thighs, nude",
-		"negative": "clothes, shirt, pants, shorts, pussy, nipples",
-	},
-	"overalls": {
-		"positive": "overalls, naked overalls",
-		"negative": "shirt, pants, shorts, pussy, nipples, topless",
-	},
-	"a cybersuit": {  // Doesn't work well
-		"positive": "cybersuit, latex bodysuit, long sleeves, cybernetic, science fiction",
-		"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy, nipples",
-	},
-	"a tight Imperial bodysuit": {  // Doesn't work well
-		"positive": "imperial bodysuit, latex bodysuit, long sleeves, cybernetic, science fiction",
-		"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy, nipples",
-	},
-	"battlearmor": {  // Doesn't work well
-		"positive": "(armor, science fiction, soldier:1.1)",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"Imperial Plate": {  // Doesn't work well
-		"positive": "(armor, science fiction, soldier:1.1)",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a bunny outfit": {
-		"positive": "playboy bunny, backless leotard, pantyhose",
-		"negative": "jeans, nude, pussy, nipples, rabbit ears",
-	},
-	"a slutty maid outfit": {
-		"positive": "maid, minidress, apron, white shirt, cleavage, thighs",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a nice maid outfit": {
-		"positive": "maid, dress, apron, white shirt",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a slutty nurse outfit": {
-		"positive": "nurse, white jacket, cleavage, white skirt, thighs",
-		"negative": "jeans, shirt, pussy, nipples",
-	},
-	"a nice nurse outfit": {
-		"positive": "nurse, white medical scrubs, pants",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a dirndl": {
-		"positive": "(dirndl:1.1)",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a long qipao": {
-		"positive": "(qipao:1.1), long dress, chinese clothes",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"lederhosen": {
-		"positive": "(lederhosen:1.1)",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a biyelgee costume": {  // Doesn't work well
-		"positive": "mongolian traditional clothes",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a hanbok": {
-		"positive": "(hanbok:1.1)",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"burkini": {
-		"positive": "burqa, muslim clothes, burkini, pants",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a hijab and blouse": {
-		"positive": "(hijab:1.1), blouse, short sleeves, long skirt",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a hijab and abaya": {
-		"positive": "hijab, abaya",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a niqab and abaya": {  // Doesn't work well
-		"positive": "niqab, covered face, abaya",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a burqa": {  // Doesn't work well
-		"positive": "burqa, muslim clothes",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a police uniform": {
-		"positive": "police uniform, policewoman, police hat, jacket, pants, belt",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a gothic lolita dress": {
-		"positive": "gothic lolita, dress, thighhighs",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a one-piece swimsuit": {
-		"positive": "one-piece swimsuit, thighs",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a nice pony outfit": {
-		"positive": "<lora:ponygirl:0.7> ponygirl, bdsm, bodysuit, horse mask",
-		"negative": "nude"
-	},
-	"a slutty pony outfit": {
-		"positive": "<lora:ponygirl:0.7> ponygirl, bdsm, horse mask",
-		"negative": "pussy",
-	},
-	"a button-up shirt and panties": {  // Often not bottomless
-		"positive": "collared shirt, oversized clothes, panties, (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nude, pussy, nipples",
-	},
-	"a button-up shirt": {  // Often not bottomless
-		"positive": "collared shirt, oversized clothes, nude, (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples",
-	},
-	"a sweater": {  // Often not bottomless
-		"positive": "sweater, oversized clothes, nude, (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples",
-	},
-	"a t-shirt": {  // Often not bottomless
-		"positive": "t-shirt, (nude:1.1), (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples",
-	},
-	"a tank-top": {  // Often not bottomless
-		"positive": "tank top, bare shoulders, (nude:1.1), (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples",
-	},
-	"a tube top": {  // Often not bottomless
-		"positive": "tube top, bare shoulders, (nude:1.1), (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nude, nipples",
-	},
-	"an oversized t-shirt": {  // Often not bottomless
-		"positive": "t-shirt, oversized clothes, (nude:1.1), (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples",
-	},
-	"a bra": {  // Often not bottomless
-		"positive": "bra, (nude:1.1), (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples",
-	},
-	"a sports bra": {  // Often not bottomless
-		"positive": "sports bra, (nude:1.1), (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples",
-	},
-	"a striped bra": {  // Often not bottomless
-		"positive": "striped bra, (nude:1.1), (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples",
-	},
-	"pasties": {  // Doesn't work well
-		"positive": "pasties, nude, (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples",
-	},
-	"a tube top and thong": {
-		"positive": "tube top, bare shoulders, (nude:1.1), (bottomless:1.1), g-string, thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples, pussy",
-	},
-	"a sweater and panties": {  // Often not bottomless
-		"positive": "sweater, oversized clothes, panties, (nude:1.1), (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples, pussy",
-	},
-	"a tank-top and panties": {  // Often not bottomless
-		"positive": "tank top, bare shoulders, panties, (nude:1.1), (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples, pussy",
-	},
-	"a t-shirt and thong": {  // Often not bottomless
-		"positive": "t-shirt, (nude:1.1), (bottomless:1.1), g-string, thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples, pussy",
-	},
-	"an oversized t-shirt and boyshorts": {  // Doesn't work well
-		"positive": "t-shirt, oversized clothes, boyshort panties, (nude:1.1), (bottomless:1.1), thighs",
-		"negative": "jeans, pants, skirt, nipples, pussy",
-	},
-	"sport shorts and a t-shirt": {
-		"positive": "t-shirt, sport shorts",
-		"negative": "jeans, pants, skirt, nipples, pussy",
-	},
-	"sport shorts and a sports bra": {
-		"positive": "sports bra, sport shorts",
-		"negative": "jeans, pants, skirt, nipples, pussy",
-	},
-	"a t-shirt and panties": {  // Often not bottomless
-		"positive": "t-shirt, (nude:1.1), (bottomless:1.1), panties, thighs",
-		"negative": "jeans, pants, skirt, shorts, nipples, pussy",
-	},
-	"striped underwear": {  // Often not bottomless
-		"positive": "striped panties, striped bra",
-		"negative": "jeans, pants, skirt, shorts, nipples, pussy",
-	},
-	"a thong": {
-		"positive": "thong, topless",
-		"negative": "jeans, pants, skirt, shorts, pussy",
-	},
-	"a skimpy loincloth": {  // Doesn't work well
-		"positive": "loincloth, topless",
-		"negative": "jeans, pants, skirt, shorts, pussy",
-	},
-	"boyshorts": {
-		"positive": "boyshort panties, topless",
-		"negative": "jeans, pants, skirt, pussy",
-	},
-	"panties": {
-		"positive": "panties, topless",
-		"negative": "jeans, pants, skirt, pussy",
-	},
-	"panties and pasties": {  // Doesn't work well
-		"positive": "panties, pasties, topless",
-		"negative": "jeans, pants, skirt, pussy, nipples",
-	},
-	"cutoffs": {
-		"positive": "jean shorts, topless",
-		"negative": "pussy",
-	},
-	"sport shorts": {
-		"positive": "sport shorts, topless",
-		"negative": "jeans, pants, skirt, pussy",
-	},
-	"a sweater and cutoffs": {
-		"positive": "sweater, jean shorts",
-		"negative": "pussy, nipples",
-	},
-	"leather pants and a tube top": {
-		"positive": "leather pants, tube top, bare shoulders",
-		"negative": "jeans, pants, skirt, shorts, pussy, nipples",
-	},
-	"a t-shirt and jeans": {
-		"positive": "t-shirt, jeans",
-		"negative": "pussy, nipples",
-	},
-	"leather pants and pasties": {  // Doesn't work well
-		"positive": "leather pants, pasties, topless",
-		"negative": "jeans, pants, skirt, shorts, pussy, nipples",
-	},
-	"leather pants": {
-		"positive": "leather pants, topless",
-		"negative": "jeans, pants, skirt, shorts, pussy",
-	},
-	"jeans": {
-		"positive": "jeans, topless",
-		"negative": "pussy",
-	},
-	"a military uniform": {
-		"positive": "military uniform, shirt, necktie, skirt",
-		"negative": "jeans, shorts, pussy, nipples",
-	},
-	"battledress": {
-		"positive": "military fatigues, camouflage pants, tank top",
-		"negative": "shorts, pussy, nipples",
-	},
-	"a mounty outfit": {  // Doesn't work well
-		"positive": "mounty, red military jacket",
-		"negative": "jeans, shorts, pussy, nipples",
-	},
-	"harem gauze": {
-		"positive": "harem outfit, loose dress, see-through, transparent clothes",
-		"negative": "jeans, shorts",
-	},
-	"slutty jewelry": {
-		"positive": "nude, jewelry, gem, gold chains, armlet",
-		"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties"
-	},
-	"a Santa dress": {
-		"positive": "santa costume, santa dress, thighs",
-		"negative": "jeans, nude, pussy, nipples"
-	},
-	"a bimbo outfit": {
-		"positive": "(pink:1.1) tube top, bra, cleavage, pink microskirt, thighs, panties, navel, midriff",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a slutty outfit": {
-		"positive": "(pink:1.1) crop top, pink lowleg microskirt, hip bones, groin, tight clothes, midriff, navel, (thighs:1.1)",
-		"negative": "jeans, nude, nipples",
-	},
-	"a courtesan dress": {  // Corset was messing stuff up, so I removed it
-		"positive": "(luxurious flowing dress:1.1), bare shoulders, long sleeves, detached sleeves",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a schoolgirl outfit": {
-		"positive": "school uniform, white shirt, plaid skirt",
-		"negative": "jeans, nude, pussy, nipples",
-	}
-};
-
-const clothesPromptsAgeControl = {
-	"no clothing": {
-		"positive": "strapless tube top, visible shoulders",
-		"negative": "",
-	},
-	"chains": {
-		"positive": "metal chains collar, chainmail tube top, visible shoulders, chain belt, chainmail skirt",
-		"negative": "jeans, pants, skirt",
-	},
-	"body oil": {
-		"positive": "(shiny skin, glistening skin, body oil:1.1), strapless swimsuit, visible shoulders",
-		"negative": "jeans",
-	},
-	"a slutty qipao": {
-		"positive": "qipao, chinese clothing",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"spats and a tank top": {
-		"positive": "bike shorts, tank top",
-		"negative": "bike, jeans, nude, pussy, nipples",
-	},
-	"uncomfortable straps": {
-		"positive": "leather straps top, visible shoulders, leather belt, leather straps skirt",
-		"negative": "jeans, pants, shorts",
-	},
-	"shibari ropes": {
-		"positive": "macrame tube top, ropes, rope belt, macrame skirt",
-		"negative": "jeans, pants, shorts",
-	},
-	"attractive lingerie": {
-		"positive": "strapless swimsuit, visible shoulders",
-		"negative": "jeans, pants",
-	},
-	"attractive lingerie for a pregnant woman": {
-		"positive": "strapless swimsuit, visible shoulders",
-		"negative": "jeans, pants",
-	},
-	"kitty lingerie": { // Broken for photorealistic models, probably works for anime models
-		"positive": "strapless hello kitty swimsuit, visible shoulders",
-		"negative": "cat ears, jeans",
-	},
-	"a maternity dress": {
-		"positive": "wide dress, loose dress",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a succubus outfit": {
-		"positive": "demon costume, red leather top, red leather miniskirt, black demon horns",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-	"a penitent nuns habit": {
-		"positive": "(latex nun habit:1.1), ropes",
-		"negative": "jeans",
-	},
-	"a chattel habit": {
-		"positive": "(white latex nun habit:1.1), gold belt, sleveless, cleavage, visible shoulders",
-		"negative": "",
-	},
-	"a string bikini": {
-		"positive": "strapless swimsuit",
-		"negative": "jeans,",
-	},
-	"a scalemail bikini": {
-		"positive": "chainmail swimsuit",
-		"negative": "jeans",
-	},
-	"striped panties": {
-		"positive": "strapless blue striped swimsuit",
-		"negative": "jeans",
-	},
-	"clubslut netting": {
-		"colors": ["light blue", "pink", "lime green"],
-		"positive": "rave clothing, fishnet clothing, $color bodysuit, choker",
-		"negative": "jeans, pants, corset",
-	},
-	"a slave gown": {
-		"positive": "ballgown, long dress, luxurious dress, cleavage, slave straps",
-		"negative": "jeans",
-	},
-	"a halter top dress": {
-		"positive": "(halterneck:1.1), long dress, luxurious dress, backless dress",
-		"negative": "jeans",
-	},
-	"a leotard": {
-		"positive": "leotard",
-		"negative": "jeans",
-	},
-	"a monokini": {
-		"positive": "swimsuit",
-		"negative": "jeans",
-	},
-	"an apron": {
-		"positive": "apron swimsuit",
-		"negative": "t-shirt, shirt, pants, shorts",
-	},
-	"overalls": {
-		"positive": "overalls, visible shoulders, sleeveless",
-		"negative": "t-shirt, shirt, pants, shorts, topless",
-	},
-	"a bunny outfit": {
-		"positive": "magazine bunny costume, backless leotard",
-		"negative": "jeans, nude, rabbit ears",
-	},
-	"a gothic lolita dress": {
-		"positive": "gothic dress, short dress, thighhighs",
-		"negative": "jeans",
-	},
-	"a button-up shirt and panties": {
-		"positive": "collared shirt, oversized clothes, swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"a button-up shirt": {
-		"positive": "collared shirt, oversized clothes, swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"a sweater": {
-		"positive": "only sweater, oversized clothes, swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"a t-shirt": {
-		"positive": "only t-shirt, swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"a tank-top": {
-		"positive": "only tank top, visible shoulders",
-		"negative": "jeans",
-	},
-	"a tube top": {
-		"positive": "only tube top, visible shoulders",
-		"negative": "jeans",
-	},
-	"an oversized t-shirt": {
-		"positive": "only t-shirt, oversized clothes, swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"a bra": {
-		"positive": "white swimsuit top",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"a sports bra": {
-		"positive": "sports swimsuit top",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"a striped bra": {
-		"positive": "striped swimsuit top",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"pasties": {
-		"positive": "strapless tube top, visible shoulders",
-		"negative": "",
-	},
-	"a tube top and thong": {
-		"positive": "tube top, visible shoulders",
-		"negative": "jeans",
-	},
-	"a sweater and panties": {
-		"positive": "sweater, oversized clothes, swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"a tank-top and panties": {
-		"positive": "tank top, visible shoulders",
-		"negative": "jeans",
-	},
-	"a t-shirt and thong": {
-		"positive": "t-shirt, swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"an oversized t-shirt and boyshorts": {
-		"positive": "t-shirt, oversized clothes, swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"sport shorts and a sports bra": {
-		"positive": "sports swimsuit top",
-		"negative": "jeans, pants, skirt",
-	},
-	"a t-shirt and panties": {
-		"positive": "t-shirt, swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"striped underwear": {
-		"positive": "striped swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"a thong": {
-		"positive": "tube top, visible shoulders",
-		"negative": "jeans",
-	},
-	"a skimpy loincloth": {
-		"positive": "leather straples swimsuit",
-		"negative": "jeans, pants, skirt, shorts",
-	},
-	"boyshorts": {
-		"positive": "swimsuit top",
-		"negative": "jeans",
-	},
-	"panties": {
-		"positive": "swimsuit top",
-		"negative": "jeans",
-	},
-	"panties and pasties": {
-		"positive": "swimsuit top",
-		"negative": "jeans",
-	},
-	"cutoffs": {
-		"positive": "jean shorts, strapless tube top, visible shoulders",
-		"negative": "",
-	},
-	"sport shorts": {
-		"positive": "sports swimsuit top, sport shorts",
-		"negative": "jeans, pants, skirt",
-	},
-	"leather pants and a tube top": {
-		"positive": "leather pants, tube top, visible shoulders",
-		"negative": "jeans, skirt, shorts",
-	},
-	"leather pants and pasties": {
-		"positive": "leather pants, swimsuit top",
-		"negative": "jeans, skirt, shorts",
-	},
-	"leather pants": {
-		"positive": "leather pants, swimsuit top",
-		"negative": "jeans, skirt, shorts",
-	},
-	"jeans": {
-		"positive": "jeans, swimsuit top",
-		"negative": "",
-	},
-	"harem gauze": {
-		"positive": "harem outfit, loose dress",
-		"negative": "jeans, shorts",
-	},
-	"slutty jewelry": {
-		"positive": "jewelry, gem, gold chains, armlet, visible shoulders",
-		"negative": "jeans, pants, shorts"
-	},
-	"a bimbo outfit": {
-		"positive": "(pink tube top:1.1), cleavage",
-		"negative": "",
-	},
-	"a slutty outfit": {
-		"positive": "(pink crop top:1.1), cleavage",
-		"negative": "",
-	},
-	"a courtesan dress": {  // Corset was messing stuff up, so I removed it
-		"positive": "(luxurious flowing dress:1.1), exposed shoulders, long sleeves, detached sleeves",
-		"negative": "jeans, nude, pussy, nipples",
-	},
-};
 App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.PromptPart {
+	clothesPrompts = {
+		"no clothing": {
+			"positive": "nude",
+			"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties",
+		},
+		"a Fuckdoll suit": {  // NG good gen requires LoRA, but below will work without LoRA as well
+			"positive": "black latex bodysuit, long sleeves" + App.Art.GenAI.PromptHelpers.lora("xxmaskedxx_lora_v01", .8, " xxmaskedxx", ", "),
+			"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy, nipples",
+		},
+		"conservative clothing": {
+			"positive": "slacks, pants, silk blouse",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"chains": {
+			"positive": "(metal chains:1.1), nude, navel",
+			"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties",
+		},
+		"Western clothing": {
+			"positive": "flannel shirt, chaps, cowboy hat",
+			"negative": "nude, pussy, nipples",
+		},
+		"body oil": {  // Doesn't work well
+			"positive": "body oil, nude, navel",
+			"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties",
+		},
+		"a toga": {  // Doesn't work well
+			"positive": "white toga",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a huipil": {  // Doesn't work well
+			"positive": "huipil, mexican clothing",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a slutty qipao": {
+			"positive": "qipao, chinese clothing, thighs",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a kimono": {
+			"positive": "kimono",
+			"negative": "jeans, nude, nipples",
+		},
+		"spats and a tank top": {  // Spats don't work well
+			"positive": "bike shorts, tank top",
+			"negative": "bike, jeans, nude, pussy, nipples",
+		},
+		"uncomfortable straps": {
+			"positive": "(leather straps, bondage:1.1), nude, navel",
+			"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties",
+		},
+		"shibari ropes": {
+			"positive": "shibari rope, bondage, nude, navel",
+			"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties",
+		},
+		"restrictive latex": {  // Doesn't work well
+			"positive": "latex bodysuit, long sleeves",
+			"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy",
+		},
+		"a latex catsuit": {  // Doesn't work well
+			"positive": "latex bodysuit, long sleeves",
+			"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy",
+		},
+		"attractive lingerie": {  // Cupless part doesn't work well
+			"positive": "lingerie, cupless bra, thong",
+			"negative": "clothes, jeans, pants",
+		},
+		"attractive lingerie for a pregnant woman": {  // Cupless part doesn't work well
+			"positive": "lingerie, cupless bra, thong",
+			"negative": "clothes, jeans, pants",
+		},
+		"kitty lingerie": {  // Broken for photorealistic models, probably works for anime models
+			"positive": "kitty lingerie, cat lingerie, kawaii lingerie",
+			"negative": "cat ears, jeans, nude, pussy, nipples",
+		},
+		"a maternity dress": {
+			"positive": "maternity dress, loose dress",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"stretch pants and a crop-top": {
+			"positive": "crop top, midriff, navel, leggings",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a succubus outfit": {
+			"positive": "succubus costume, red leather corset, red leather miniskirt, black demon horns",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a fallen nuns habit": {
+			"positive": "(latex nun habit:1.1), thighs",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a penitent nuns habit": {
+			"positive": "(latex nun habit:1.1), thighs, rope, bondage",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a chattel habit": {
+			"positive": "(white latex nun habit:1.1), gold belt, bare breasts",
+			"negative": "",
+		},
+		"a string bikini": {  // Cupless part doesn't work well
+			"positive": "string microbikini, cupless bikini",
+			"negative": "jeans, nude, pussy",
+		},
+		"a scalemail bikini": {  // Doesn't work well
+			"positive": "chainmail bikini, navel",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"striped panties": {
+			"positive": "blue striped panties, underwear only",
+			"negative": "jeans, nude, pussy",
+		},
+		"a cheerleader outfit": {
+			"positive": "(cheerleader outfit:1.1), skirt, thighs, crop top, navel, midriff",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"clubslut netting": {
+			"colors": ["light blue", "pink", "lime green"],
+			"positive": "rave clothing, lewd, transparent clothing, $color fishnet bodysuit, $color fishnet, choker",
+			"negative": "cloth, jeans, pants, corset",
+		},
+		"cutoffs and a t-shirt": {
+			"positive": "white t-shirt, jean shorts",
+			"negative": "nude, pussy, nipples",
+		},
+		"slutty business attire": {
+			"positive": "suit jacket, cleavage, black skirt, thighs",
+			"negative": "jeans, nude, pussy, nipples"
+		},
+		"nice business attire": {
+			"positive": "suit jacket, collared shirt, black skirt",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a ball gown": {
+			"positive": "ballgown, long dress, luxurious dress, thighhighs",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a slave gown": {
+			"positive": "ballgown, long dress, luxurious dress, thighhighs, cleavage, see-through, translucent clothing, straps, bdsm",
+			"negative": "jeans, nude",
+		},
+		"a halter top dress": {
+			"positive": "(halterneck:1.1), long dress, luxurious dress, bare back,",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"an evening dress": {
+			"positive": "evening gown, long dress, luxurious dress, thighs",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a mini dress": {
+			"positive": "short dress, tight dress, strapless, cleavage, thighs",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a comfortable bodysuit": {
+			"positive": "latex bodysuit, long sleeves",
+			"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy, nipples",
+		},
+		"a leotard": {
+			"positive": "leotard, thighs",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a monokini": {  // Doesn't work well
+			"positive": "monokini",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"an apron": {
+			"positive": "apron, thighs, nude",
+			"negative": "clothes, shirt, pants, shorts, pussy, nipples",
+		},
+		"overalls": {
+			"positive": "overalls, naked overalls",
+			"negative": "shirt, pants, shorts, pussy, nipples, topless",
+		},
+		"a cybersuit": {  // Doesn't work well
+			"positive": "cybersuit, latex bodysuit, long sleeves, cybernetic, science fiction",
+			"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy, nipples",
+		},
+		"a tight Imperial bodysuit": {  // Doesn't work well
+			"positive": "imperial bodysuit, latex bodysuit, long sleeves, cybernetic, science fiction",
+			"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy, nipples",
+		},
+		"battlearmor": {  // Doesn't work well
+			"positive": "(armor, science fiction, soldier:1.1)",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"Imperial Plate": {  // Doesn't work well
+			"positive": "(armor, science fiction, soldier:1.1)",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a bunny outfit": {
+			"positive": "playboy bunny, backless leotard, pantyhose",
+			"negative": "jeans, nude, pussy, nipples, rabbit ears",
+		},
+		"a slutty maid outfit": {
+			"positive": "maid, minidress, apron, white shirt, cleavage, thighs",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a nice maid outfit": {
+			"positive": "maid, dress, apron, white shirt",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a slutty nurse outfit": {
+			"positive": "nurse, white jacket, cleavage, white skirt, thighs",
+			"negative": "jeans, shirt, pussy, nipples",
+		},
+		"a nice nurse outfit": {
+			"positive": "nurse, white medical scrubs, pants",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a dirndl": {
+			"positive": "(dirndl:1.1)",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a long qipao": {
+			"positive": "(qipao:1.1), long dress, chinese clothes",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"lederhosen": {
+			"positive": "(lederhosen:1.1)",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a biyelgee costume": {  // Doesn't work well
+			"positive": "mongolian traditional clothes",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a hanbok": {
+			"positive": "(hanbok:1.1)",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"burkini": {
+			"positive": "burqa, muslim clothes, burkini, pants",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a hijab and blouse": {
+			"positive": "(hijab:1.1), blouse, short sleeves, long skirt",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a hijab and abaya": {
+			"positive": "hijab, abaya",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a niqab and abaya": {  // Doesn't work well
+			"positive": "niqab, covered face, abaya",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a burqa": {  // Doesn't work well
+			"positive": "burqa, muslim clothes",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a police uniform": {
+			"positive": "police uniform, policewoman, police hat, jacket, pants, belt",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a gothic lolita dress": {
+			"positive": "gothic lolita, dress, thighhighs",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a one-piece swimsuit": {
+			"positive": "one-piece swimsuit, thighs",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a nice pony outfit": {
+			"positive":  App.Art.GenAI.PromptHelpers.lora("ponygirl", .7, " ponygirl, ",) + "bdsm, bodysuit, horse mask",
+			"negative": "nude"
+		},
+		"a slutty pony outfit": {
+			"positive": App.Art.GenAI.PromptHelpers.lora("ponygirl", .7, " ponygirl, ",) + "bdsm, horse mask",
+			"negative": "pussy",
+		},
+		"a button-up shirt and panties": {  // Often not bottomless
+			"positive": "collared shirt, oversized clothes, panties, (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nude, pussy, nipples",
+		},
+		"a button-up shirt": {  // Often not bottomless
+			"positive": "collared shirt, oversized clothes, nude, (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples",
+		},
+		"a sweater": {  // Often not bottomless
+			"positive": "sweater, oversized clothes, nude, (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples",
+		},
+		"a t-shirt": {  // Often not bottomless
+			"positive": "t-shirt, (nude:1.1), (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples",
+		},
+		"a tank-top": {  // Often not bottomless
+			"positive": "tank top, bare shoulders, (nude:1.1), (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples",
+		},
+		"a tube top": {  // Often not bottomless
+			"positive": "tube top, bare shoulders, (nude:1.1), (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nude, nipples",
+		},
+		"an oversized t-shirt": {  // Often not bottomless
+			"positive": "t-shirt, oversized clothes, (nude:1.1), (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples",
+		},
+		"a bra": {  // Often not bottomless
+			"positive": "bra, (nude:1.1), (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples",
+		},
+		"a sports bra": {  // Often not bottomless
+			"positive": "sports bra, (nude:1.1), (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples",
+		},
+		"a striped bra": {  // Often not bottomless
+			"positive": "striped bra, (nude:1.1), (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples",
+		},
+		"pasties": {  // Doesn't work well
+			"positive": "pasties, nude, (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples",
+		},
+		"a tube top and thong": {
+			"positive": "tube top, bare shoulders, (nude:1.1), (bottomless:1.1), g-string, thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples, pussy",
+		},
+		"a sweater and panties": {  // Often not bottomless
+			"positive": "sweater, oversized clothes, panties, (nude:1.1), (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples, pussy",
+		},
+		"a tank-top and panties": {  // Often not bottomless
+			"positive": "tank top, bare shoulders, panties, (nude:1.1), (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples, pussy",
+		},
+		"a t-shirt and thong": {  // Often not bottomless
+			"positive": "t-shirt, (nude:1.1), (bottomless:1.1), g-string, thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples, pussy",
+		},
+		"an oversized t-shirt and boyshorts": {  // Doesn't work well
+			"positive": "t-shirt, oversized clothes, boyshort panties, (nude:1.1), (bottomless:1.1), thighs",
+			"negative": "jeans, pants, skirt, nipples, pussy",
+		},
+		"sport shorts and a t-shirt": {
+			"positive": "t-shirt, sport shorts",
+			"negative": "jeans, pants, skirt, nipples, pussy",
+		},
+		"sport shorts and a sports bra": {
+			"positive": "sports bra, sport shorts",
+			"negative": "jeans, pants, skirt, nipples, pussy",
+		},
+		"a t-shirt and panties": {  // Often not bottomless
+			"positive": "t-shirt, (nude:1.1), (bottomless:1.1), panties, thighs",
+			"negative": "jeans, pants, skirt, shorts, nipples, pussy",
+		},
+		"striped underwear": {  // Often not bottomless
+			"positive": "striped panties, striped bra",
+			"negative": "jeans, pants, skirt, shorts, nipples, pussy",
+		},
+		"a thong": {
+			"positive": "thong, topless",
+			"negative": "jeans, pants, skirt, shorts, pussy",
+		},
+		"a skimpy loincloth": {  // Doesn't work well
+			"positive": "loincloth, topless",
+			"negative": "jeans, pants, skirt, shorts, pussy",
+		},
+		"boyshorts": {
+			"positive": "boyshort panties, topless",
+			"negative": "jeans, pants, skirt, pussy",
+		},
+		"panties": {
+			"positive": "panties, topless",
+			"negative": "jeans, pants, skirt, pussy",
+		},
+		"panties and pasties": {  // Doesn't work well
+			"positive": "panties, pasties, topless",
+			"negative": "jeans, pants, skirt, pussy, nipples",
+		},
+		"cutoffs": {
+			"positive": "jean shorts, topless",
+			"negative": "pussy",
+		},
+		"sport shorts": {
+			"positive": "sport shorts, topless",
+			"negative": "jeans, pants, skirt, pussy",
+		},
+		"a sweater and cutoffs": {
+			"positive": "sweater, jean shorts",
+			"negative": "pussy, nipples",
+		},
+		"leather pants and a tube top": {
+			"positive": "leather pants, tube top, bare shoulders",
+			"negative": "jeans, pants, skirt, shorts, pussy, nipples",
+		},
+		"a t-shirt and jeans": {
+			"positive": "t-shirt, jeans",
+			"negative": "pussy, nipples",
+		},
+		"leather pants and pasties": {  // Doesn't work well
+			"positive": "leather pants, pasties, topless",
+			"negative": "jeans, pants, skirt, shorts, pussy, nipples",
+		},
+		"leather pants": {
+			"positive": "leather pants, topless",
+			"negative": "jeans, pants, skirt, shorts, pussy",
+		},
+		"jeans": {
+			"positive": "jeans, topless",
+			"negative": "pussy",
+		},
+		"a military uniform": {
+			"positive": "military uniform, shirt, necktie, skirt",
+			"negative": "jeans, shorts, pussy, nipples",
+		},
+		"battledress": {
+			"positive": "military fatigues, camouflage pants, tank top",
+			"negative": "shorts, pussy, nipples",
+		},
+		"a mounty outfit": {  // Doesn't work well
+			"positive": "mounty, red military jacket",
+			"negative": "jeans, shorts, pussy, nipples",
+		},
+		"harem gauze": {
+			"positive": "harem outfit, loose dress, see-through, transparent clothes",
+			"negative": "jeans, shorts",
+		},
+		"slutty jewelry": {
+			"positive": "nude, jewelry, gem, gold chains, armlet",
+			"negative": "clothes, jeans, underwear, pants, shorts, skirt, panties"
+		},
+		"a Santa dress": {
+			"positive": "santa costume, santa dress, thighs",
+			"negative": "jeans, nude, pussy, nipples"
+		},
+		"a bimbo outfit": {
+			"positive": "(pink:1.1) tube top, bra, cleavage, pink microskirt, thighs, panties, navel, midriff",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a slutty outfit": {
+			"positive": "(pink:1.1) crop top, pink lowleg microskirt, hip bones, groin, tight clothes, midriff, navel, (thighs:1.1)",
+			"negative": "jeans, nude, nipples",
+		},
+		"a courtesan dress": {  // Corset was messing stuff up, so I removed it
+			"positive": "(luxurious flowing dress:1.1), bare shoulders, long sleeves, detached sleeves",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a schoolgirl outfit": {
+			"positive": "school uniform, white shirt, plaid skirt",
+			"negative": "jeans, nude, pussy, nipples",
+		}
+	};
+
+	clothesPromptsAgeControl = {
+		"no clothing": {
+			"positive": "strapless tube top, visible shoulders",
+			"negative": "",
+		},
+		"chains": {
+			"positive": "metal chains collar, chainmail tube top, visible shoulders, chain belt, chainmail skirt",
+			"negative": "jeans, pants, skirt",
+		},
+		"body oil": {
+			"positive": "(shiny skin, glistening skin, body oil:1.1), strapless swimsuit, visible shoulders",
+			"negative": "jeans",
+		},
+		"a slutty qipao": {
+			"positive": "qipao, chinese clothing",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"spats and a tank top": {
+			"positive": "bike shorts, tank top",
+			"negative": "bike, jeans, nude, pussy, nipples",
+		},
+		"uncomfortable straps": {
+			"positive": "leather straps top, visible shoulders, leather belt, leather straps skirt",
+			"negative": "jeans, pants, shorts",
+		},
+		"shibari ropes": {
+			"positive": "macrame tube top, ropes, rope belt, macrame skirt",
+			"negative": "jeans, pants, shorts",
+		},
+		"attractive lingerie": {
+			"positive": "strapless swimsuit, visible shoulders",
+			"negative": "jeans, pants",
+		},
+		"attractive lingerie for a pregnant woman": {
+			"positive": "strapless swimsuit, visible shoulders",
+			"negative": "jeans, pants",
+		},
+		"kitty lingerie": { // Broken for photorealistic models, probably works for anime models
+			"positive": "strapless hello kitty swimsuit, visible shoulders",
+			"negative": "cat ears, jeans",
+		},
+		"a maternity dress": {
+			"positive": "wide dress, loose dress",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a succubus outfit": {
+			"positive": "demon costume, red leather top, red leather miniskirt, black demon horns",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+		"a penitent nuns habit": {
+			"positive": "(latex nun habit:1.1), ropes",
+			"negative": "jeans",
+		},
+		"a chattel habit": {
+			"positive": "(white latex nun habit:1.1), gold belt, sleveless, cleavage, visible shoulders",
+			"negative": "",
+		},
+		"a string bikini": {
+			"positive": "strapless swimsuit",
+			"negative": "jeans,",
+		},
+		"a scalemail bikini": {
+			"positive": "chainmail swimsuit",
+			"negative": "jeans",
+		},
+		"striped panties": {
+			"positive": "strapless blue striped swimsuit",
+			"negative": "jeans",
+		},
+		"clubslut netting": {
+			"colors": ["light blue", "pink", "lime green"],
+			"positive": "rave clothing, fishnet clothing, $color bodysuit, choker",
+			"negative": "jeans, pants, corset",
+		},
+		"a slave gown": {
+			"positive": "ballgown, long dress, luxurious dress, cleavage, slave straps",
+			"negative": "jeans",
+		},
+		"a halter top dress": {
+			"positive": "(halterneck:1.1), long dress, luxurious dress, backless dress",
+			"negative": "jeans",
+		},
+		"a leotard": {
+			"positive": "leotard",
+			"negative": "jeans",
+		},
+		"a monokini": {
+			"positive": "swimsuit",
+			"negative": "jeans",
+		},
+		"an apron": {
+			"positive": "apron swimsuit",
+			"negative": "t-shirt, shirt, pants, shorts",
+		},
+		"overalls": {
+			"positive": "overalls, visible shoulders, sleeveless",
+			"negative": "t-shirt, shirt, pants, shorts, topless",
+		},
+		"a bunny outfit": {
+			"positive": "magazine bunny costume, backless leotard",
+			"negative": "jeans, nude, rabbit ears",
+		},
+		"a gothic lolita dress": {
+			"positive": "gothic dress, short dress, thighhighs",
+			"negative": "jeans",
+		},
+		"a button-up shirt and panties": {
+			"positive": "collared shirt, oversized clothes, swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"a button-up shirt": {
+			"positive": "collared shirt, oversized clothes, swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"a sweater": {
+			"positive": "only sweater, oversized clothes, swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"a t-shirt": {
+			"positive": "only t-shirt, swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"a tank-top": {
+			"positive": "only tank top, visible shoulders",
+			"negative": "jeans",
+		},
+		"a tube top": {
+			"positive": "only tube top, visible shoulders",
+			"negative": "jeans",
+		},
+		"an oversized t-shirt": {
+			"positive": "only t-shirt, oversized clothes, swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"a bra": {
+			"positive": "white swimsuit top",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"a sports bra": {
+			"positive": "sports swimsuit top",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"a striped bra": {
+			"positive": "striped swimsuit top",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"pasties": {
+			"positive": "strapless tube top, visible shoulders",
+			"negative": "",
+		},
+		"a tube top and thong": {
+			"positive": "tube top, visible shoulders",
+			"negative": "jeans",
+		},
+		"a sweater and panties": {
+			"positive": "sweater, oversized clothes, swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"a tank-top and panties": {
+			"positive": "tank top, visible shoulders",
+			"negative": "jeans",
+		},
+		"a t-shirt and thong": {
+			"positive": "t-shirt, swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"an oversized t-shirt and boyshorts": {
+			"positive": "t-shirt, oversized clothes, swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"sport shorts and a sports bra": {
+			"positive": "sports swimsuit top",
+			"negative": "jeans, pants, skirt",
+		},
+		"a t-shirt and panties": {
+			"positive": "t-shirt, swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"striped underwear": {
+			"positive": "striped swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"a thong": {
+			"positive": "tube top, visible shoulders",
+			"negative": "jeans",
+		},
+		"a skimpy loincloth": {
+			"positive": "leather straples swimsuit",
+			"negative": "jeans, pants, skirt, shorts",
+		},
+		"boyshorts": {
+			"positive": "swimsuit top",
+			"negative": "jeans",
+		},
+		"panties": {
+			"positive": "swimsuit top",
+			"negative": "jeans",
+		},
+		"panties and pasties": {
+			"positive": "swimsuit top",
+			"negative": "jeans",
+		},
+		"cutoffs": {
+			"positive": "jean shorts, strapless tube top, visible shoulders",
+			"negative": "",
+		},
+		"sport shorts": {
+			"positive": "sports swimsuit top, sport shorts",
+			"negative": "jeans, pants, skirt",
+		},
+		"leather pants and a tube top": {
+			"positive": "leather pants, tube top, visible shoulders",
+			"negative": "jeans, skirt, shorts",
+		},
+		"leather pants and pasties": {
+			"positive": "leather pants, swimsuit top",
+			"negative": "jeans, skirt, shorts",
+		},
+		"leather pants": {
+			"positive": "leather pants, swimsuit top",
+			"negative": "jeans, skirt, shorts",
+		},
+		"jeans": {
+			"positive": "jeans, swimsuit top",
+			"negative": "",
+		},
+		"harem gauze": {
+			"positive": "harem outfit, loose dress",
+			"negative": "jeans, shorts",
+		},
+		"slutty jewelry": {
+			"positive": "jewelry, gem, gold chains, armlet, visible shoulders",
+			"negative": "jeans, pants, shorts"
+		},
+		"a bimbo outfit": {
+			"positive": "(pink tube top:1.1), cleavage",
+			"negative": "",
+		},
+		"a slutty outfit": {
+			"positive": "(pink crop top:1.1), cleavage",
+			"negative": "",
+		},
+		"a courtesan dress": {  // Corset was messing stuff up, so I removed it
+			"positive": "(luxurious flowing dress:1.1), exposed shoulders, long sleeves, detached sleeves",
+			"negative": "jeans, nude, pussy, nipples",
+		},
+	};
 	/**
 	 * @returns {string}
 	 */
 	getClothes() {
 		let clothes = this.slave.clothes;
-		if (!clothesPrompts.hasOwnProperty(clothes) && !V.customClothesPrompts.hasOwnProperty(clothes)) {
+		if (!this.clothesPrompts.hasOwnProperty(clothes) && !V.customClothesPrompts.hasOwnProperty(clothes)) {
 			clothes = "no clothing";
 		}
 		if (this.slave.race === "catgirl") {
@@ -714,28 +713,52 @@ App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.
 		}
 		if (this.slave.dick === 0 && this.slave.vagina === -1) { // Null slave
 			if (App.Art.GenAI.sdClient.hasLora("nopussy_v1")) {
-				return prompt.replace(/( *)pussy(,)*/g, " <lora:nopussy_v1:1>,"); // Removes pussy or penis for null slaves
+				return prompt.replace(
+					/( *)pussy(,)*/g,
+					this.helper.lora("nopussy_v1", 1, ",", " ")
+				); // Removes pussy or penis for null slaves
 			} else {
 				return prompt.replace(/( *)pussy(,)*/g, ""); // probably renders as female anyway; use the LoRA if you want good results
 			}
 		} else if (this.isFeminine || this.slave.boobs > 800) { // female-looking based on hormones, aligned with genderPromptPart, or if very large breasts and a dick
 		// } else if (perceivedGender(this.slave) > -1) { // new perceivedGender gender function: tried, needs further tuning
-			if (this.slave.dick > 4 && App.Art.GenAI.sdClient.hasLora("flaccidfutanarimix-locon-dim64-alpha64-highLR-000003")) {
-				return prompt.replace(/( *)pussy(,)*/g, " <lora:flaccidfutanarimix-locon-dim64-alpha64-highLR-000003:0.8> penis,"); // Massive, unrealistic penis for futa - Converts to female appearance
-			} else if (this.slave.dick >= 2 && App.Art.GenAI.sdClient.hasLora("futanari-000009")) {
-				return prompt.replace(/( *)pussy(,)*/g, " <lora:futanari-000009:0.5> penis,"); // Normal penis for futa - Converts to female appearance
-			} else if (this.slave.dick < 2 && this.slave.dick > 0 && App.Art.GenAI.sdClient.hasLora("micropp_32dim_nai_v2")) {
-				return prompt.replace(/( *)pussy(,)*/g, " <lora:micropp_32dim_nai_v2:0.8> penis,"); // Micro penis for futa - Converts to female appearance
+			if (this.slave.dick > 4 && App.Art.GenAI.sdClient.hasLora("flaccidfutanarimix-locon-dim64-alpha64-highLR")) {
+				return prompt.replace(
+					/( *)pussy(,)*/g,
+					this.helper.lora("flaccidfutanarimix-locon-dim64-alpha64-highLR", .8, " penis,", " ")
+				); // Massive, unrealistic penis for futa - Converts to female appearance
+			} else if (this.slave.dick >= 2 && App.Art.GenAI.sdClient.hasLora("futanari")) {
+				return prompt.replace(
+					/( *)pussy(,)*/g,
+					this.helper.lora("futanari", .5, " penis,", " ")
+				); // Normal penis for futa - Converts to female appearance
+			} else if (this.slave.dick < 2 && this.slave.dick > 0 && App.Art.GenAI.sdClient.hasLora("micropp_128dim_nai_v2")) {
+				return prompt.replace(
+					/( *)pussy(,)*/g,
+					this.helper.lora("micropp_128dim_nai_v2", .8, " penis,", " ")
+				); // Micro penis for futa - Converts to female appearance
 			} // else fall through to female default - don't even try to render futas without a LoRA
 		} else if (this.slave.dick > 0) { // Looks male, has penis
-			if (this.slave.dick < 2 && App.Art.GenAI.sdClient.hasLora("micropp_32dim_nai_v2")) {
-				return prompt.replace(/( *)pussy(,)*/g, " <lora:micropp_32dim_nai_v2:0.8> small penis,"); // Micropenis
+			if (this.slave.dick < 2 && App.Art.GenAI.sdClient.hasLora("micropp_128dim_nai_v2")) {
+				return prompt.replace(
+					/( *)pussy(,)*/g,
+					this.helper.lora("micropp_128dim_nai_v2", .8, " small penis,", " ")
+				); // Micropenis
 			} else if (this.slave.dick < 4 && App.Art.GenAI.sdClient.hasLora("OnlyCocksV1LORA")) {
-				return prompt.replace(/( *)pussy(,)*/g, " <lora:OnlyCocksV1LORA:0.8> penis,"); // Average Male Penis. Note this LoRA is always erect...
-			} else if (App.Art.GenAI.sdClient.hasLora("flaccidfutanarimix-locon-dim64-alpha64-highLR-000003")) {
-				return prompt.replace(/( *)pussy(,)*/g, " <lora:flaccidfutanarimix-locon-dim64-alpha64-highLR-000003:0.8> large penis,"); // Massive schlong. Always flaccid...
+				return prompt.replace(
+					/( *)pussy(,)*/g,
+					this.helper.lora("OnlyCocksV1LORA", .8, " penis,", " ")
+				); // Average Male Penis. Note this LoRA is always erect...
+			} else if (App.Art.GenAI.sdClient.hasLora("flaccidfutanarimix-locon-dim64-alpha64-highLR")) {
+				return prompt.replace(
+					/( *)pussy(,)*/g,
+					this.helper.lora("flaccidfutanarimix-locon-dim64-alpha64-highLR", .8, " large penis,", " ")
+				); // Massive schlong. Always flaccid...
 			} else {
-				return prompt.replace(/( *)pussy(,)*/g, " penis,"); // no LoRA applied; won't work well in most models, but try anyway?
+				return prompt.replace(
+					/( *)pussy(,)*/g,
+					" penis,"
+				); // no LoRA applied; won't work well in most models, but try anyway?
 			}
 		}
 		return prompt; // female default
@@ -781,9 +804,9 @@ App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.
 			basePrompt = V.customClothesPrompts[this.getClothes()];
 		} else {
 			if (this.censored){
-				basePrompt = clothesPromptsAgeControl[this.getClothes()] ?? clothesPrompts[this.getClothes()];
+				basePrompt = this.clothesPromptsAgeControl[this.getClothes()] ?? this.clothesPrompts[this.getClothes()];
 			} else {
-				basePrompt = clothesPrompts[this.getClothes()];
+				basePrompt = this.clothesPrompts[this.getClothes()];
 			}
 		}
 
@@ -798,7 +821,7 @@ App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.
 		if (V.customClothesPrompts.hasOwnProperty(this.getClothes()) && V.customClothesPrompts[this.getClothes()].negative !== '') {
 			return this.addNegativeControl(V.customClothesPrompts[this.getClothes()].negative + (this.censored) ? ", (nude:1.3), (nipples:1.1), areola" : "");
 		} else {
-			return this.censored ? this.addNegativeControl(clothesPromptsAgeControl[this.getClothes()]?.negative ?? clothesPrompts[this.getClothes()].negative) : clothesPrompts[this.getClothes()].negative;
+			return this.censored ? this.addNegativeControl(this.clothesPromptsAgeControl[this.getClothes()]?.negative ?? this.clothesPrompts[this.getClothes()].negative) : this.clothesPrompts[this.getClothes()].negative;
 		}
 	}
 };
diff --git a/src/art/genAI/prompts/crotchPromptPart.js b/src/art/genAI/prompts/crotchPromptPart.js
index 5c06038a24295cc5452908fdd72446d2920db3ad..7e22c7d78b185a1011375b665ac8210b25a9f73e 100644
--- a/src/art/genAI/prompts/crotchPromptPart.js
+++ b/src/art/genAI/prompts/crotchPromptPart.js
@@ -6,7 +6,7 @@ App.Art.GenAI.CrotchPromptPart = class CrotchPromptPart extends App.Art.GenAI.Pr
 		const exposesCrotch = this.helper.exposesCrotch(this.slave.clothes);
 		if (!exposesCrotch || this.censored) {
 			if (!this.censored && this.slave.dick) {
-				return "bulge"
+				return "bulge";
 			}
 			return undefined;
 		}
@@ -14,22 +14,19 @@ App.Art.GenAI.CrotchPromptPart = class CrotchPromptPart extends App.Art.GenAI.Pr
 
 		if (this.slave.chastityPenis  || this.slave.chastityVagina) {
 			let type = this.slave.chastityPenis ? "cage" : "belt";
-			if (App.Art.GenAI.sdClient.hasLora("chastitybelt")) {
-				return `chastity ${type}, <lora:chastitybelt:0.7>`;
-			} else {
-				return `chastity ${type}`;
-			}
+			return `chastity ${type}` + this.helper.lora("chastitybelt", .7, "", ", ");
 		}
 
 		const {vagina, clit, labia, dick, foreskin, scrotum, balls} = this.slave;
 
 		if (dick && vagina >= 0) {
-			if (this.slave.dick > 4 && App.Art.GenAI.sdClient.hasLora("flaccidfutanarimix-locon-dim64-alpha64-highLR-000003")) {
-				promptParts.push("<lora:flaccidfutanarimix-locon-dim64-alpha64-highLR-000003:0.8>"); // Massive, unrealistic penis for futa - Converts to female appearance
-			} else if (this.slave.dick >= 2 && App.Art.GenAI.sdClient.hasLora("futanari-000009")) {
-				promptParts.push("<lora:futanari-000009:0.5>"); // Normal penis for futa - Converts to female appearance
-			} else if (this.slave.dick < 2 && this.slave.dick > 0 && App.Art.GenAI.sdClient.hasLora("micropp_32dim_nai_v2")) {
-				promptParts.push("<lora:micropp_32dim_nai_v2:0.8>"); // Micro penis for futa - Converts to female appearance
+			// cSpell: ignore flaccidfutanarimix, micropp
+			if (this.slave.dick > 4 && App.Art.GenAI.sdClient.hasLora("flaccidfutanarimix-locon-dim64-alpha64-highLR")) {
+				promptParts.push(this.helper.lora("flaccidfutanarimix-locon-dim64-alpha64-highLR", .8)); // Massive, unrealistic penis for futa - Converts to female appearance
+			} else if (this.slave.dick >= 2 && App.Art.GenAI.sdClient.hasLora("futanari")) {
+				promptParts.push(this.helper.lora("futanari")); // Normal penis for futa - Converts to female appearance
+			} else if (this.slave.dick < 2 && this.slave.dick > 0 &&App.Art.GenAI.sdClient.hasLora("micropp_128dim_nai_v2")) {
+				promptParts.push(this.helper.lora("micropp_128dim_nai_v2", .8)); // Micro penis for futa - Converts to female appearance
 			} else {
 				// Applying this hard to override dick and pussy prompts.
 				promptParts.push("futanari, herm");
@@ -37,12 +34,12 @@ App.Art.GenAI.CrotchPromptPart = class CrotchPromptPart extends App.Art.GenAI.Pr
 		}
 
 		if (dick) {
-			if (this.slave.dick < 2 && App.Art.GenAI.sdClient.hasLora("micropp_32dim_nai_v2")) {
-				promptParts.push("<lora:micropp_32dim_nai_v2:0.8>"); // Micropenis
+			if (this.slave.dick < 2 && App.Art.GenAI.sdClient.hasLora("micropp_128dim_nai_v2")) {
+				promptParts.push(this.helper.lora("micropp_128dim_nai_v2", .8)); // Micropenis
 			} else if (this.slave.dick < 4 && App.Art.GenAI.sdClient.hasLora("OnlyCocksV1LORA")) {
-				promptParts.push("<lora:OnlyCocksV1LORA:0.8>"); // Average Male Penis. Note this LoRA is always erect...
-			} else if (App.Art.GenAI.sdClient.hasLora("flaccidfutanarimix-locon-dim64-alpha64-highLR-000003")) {
-				promptParts.push("<lora:flaccidfutanarimix-locon-dim64-alpha64-highLR-000003:0.8>"); // Massive schlong. Always flaccid...
+				promptParts.push(this.helper.lora("OnlyCocksV1LORA", .8)); // Average Male Penis. Note this LoRA is always erect...
+			} else if (App.Art.GenAI.sdClient.hasLora("flaccidfutanarimix-locon-dim64-alpha64-highLR")) {
+				promptParts.push(this.helper.lora("flaccidfutanarimix-locon-dim64-alpha64-highLR", .8)); // Massive schlong. Always flaccid...
 			}
 			let dickDescriptors = [];
 			switch (dick) {
@@ -140,9 +137,8 @@ App.Art.GenAI.CrotchPromptPart = class CrotchPromptPart extends App.Art.GenAI.Pr
 		}
 
 		if (this.slave.dick === 0 && this.slave.vagina === -1) { // Null slave
-			if (App.Art.GenAI.sdClient.hasLora("nopussy_v1")) {
-				promptParts.push("<lora:nopussy_v1:1>"); // Removes pussy or penis for null slaves
-			}
+			// cSpell: ignore nopussy
+			promptParts.push(this.helper.lora("nopussy_v1", 1)); // Removes pussy or penis for null slaves
 		}
 
 		return promptParts.join(", ");
diff --git a/src/art/genAI/prompts/expressionPromptPart.js b/src/art/genAI/prompts/expressionPromptPart.js
index c754e7aebe8358a53e97023d210c0b8a0f80ee4a..46eb476b39f6db42fde59cfffd7a9ba72e1d1f7c 100644
--- a/src/art/genAI/prompts/expressionPromptPart.js
+++ b/src/art/genAI/prompts/expressionPromptPart.js
@@ -16,7 +16,7 @@ App.Art.GenAI.ExpressionPromptPart = class ExpressionPromptPart extends App.Art.
 				return undefined;
 			}
 		} else if (App.Art.GenAI.sdClient.hasLora("Empty Eyes - Drooling v5 - 32dim") && this.slave.fetish === Fetish.MINDBROKEN) {
-			return `<lora:Empty Eyes - Drooling v5 - 32dim:1> empty eyes, drooling`;
+			return this.helper.lora("Empty Eyes - Drooling v5 - 32dim", 1, " empty eyes, drooling");
 		} else {
 			let devotionPart;
 			if (this.slave.devotion < -50) {
@@ -62,7 +62,7 @@ App.Art.GenAI.ExpressionPromptPart = class ExpressionPromptPart extends App.Art.
 				expression = `${trustPart}`;
 			}
 
-			if (this.helper.xlBaseModel()) {
+			if (this.helper.isXLModel()) {
 				return `${expression}`;
 			}
 			return `(${expression})`;
diff --git a/src/art/genAI/prompts/eyePromptPart.js b/src/art/genAI/prompts/eyePromptPart.js
index f7b5992c101efd5cf19bccb09209b1058bfd149b..ae7243bf90ab803614d848c48e0f9ffdcdbfd4d9 100644
--- a/src/art/genAI/prompts/eyePromptPart.js
+++ b/src/art/genAI/prompts/eyePromptPart.js
@@ -8,11 +8,9 @@ App.Art.GenAI.EyePromptPart = class EyePromptPart extends App.Art.GenAI.PromptPa
 		if (slave?.fuckdoll > 0) {
 			return undefined; // eyes are not visible behind fuckdoll mask
 		} else if (hasBothEyes(this.slave)) {
-			if (App.Art.GenAI.sdClient.hasLora("Eye-LoRa_6433")) {
-				positive.push(`<lora:Loraeyes_V1:0.5>`);
-			}
+			positive.push(this.helper.lora("Loraeyes_V1"));
 			if (!canSee(this.slave) && App.Art.GenAI.sdClient.hasLora("eye-allsclera")) {
-				positive.push(`<lora:eye-allsclera:1>`);
+				positive.push(this.helper.lora("eye-allsclera", 1));
 			} else if (this.slave.eye.left.iris === this.slave.eye.right.iris) {
 				if (this.slave.eye.left.sclera !== "white") {
 					positive.push(`${this.slave.eye.left.iris} eyes, ${this.slave.eye.left.sclera} sclera`);
diff --git a/src/art/genAI/prompts/eyebrowPromptPart.js b/src/art/genAI/prompts/eyebrowPromptPart.js
index f1abbb2d1382ee635ddff17cecbd316a3a05d2c7..ac0e4a874d6c3c7e0aa76d2226d552dbc7803140 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.xlBaseModel()) {
+		if (this.helper.isXLModel()) {
 			return;
 		}
 		const slave = asSlave(this.slave);
diff --git a/src/art/genAI/prompts/genderPromptPart.js b/src/art/genAI/prompts/genderPromptPart.js
index c351df8f7f8c5ad686074d70339d6349115cc8fe..0ea2712f9ce23e84d4784384e1cdc1216d7fc988 100644
--- a/src/art/genAI/prompts/genderPromptPart.js
+++ b/src/art/genAI/prompts/genderPromptPart.js
@@ -6,7 +6,7 @@ App.Art.GenAI.GenderPromptPart = class GenderPromptPart extends App.Art.GenAI.Pr
 		let prompt = undefined;
 		if (this.isFeminine) {
 			if (this.slave.race === "catgirl") {
-				prompt = "catgirl, catperson <lora:CatgirlLoraV7:0.8>";
+				prompt = "catgirl, catperson" + this.helper.lora("CatgirlLoraV7", .8, "", " ");
 			} else if (this.slave.visualAge >= 20) {
 				prompt = "woman";
 			} else {
@@ -14,7 +14,7 @@ App.Art.GenAI.GenderPromptPart = class GenderPromptPart extends App.Art.GenAI.Pr
 			}
 		} else if (this.isMasculine) {
 			if (this.slave.race === "catgirl") {
-				prompt = "catboy, catperson <lora:CatgirlLoraV7:0.8>";
+				prompt = "catboy, catperson" + this.helper.lora("CatgirlLoraV7", .8, "", " ");
 			} else if (this.slave.visualAge >= 20) {
 				prompt = "man";
 			} else {
@@ -22,7 +22,7 @@ App.Art.GenAI.GenderPromptPart = class GenderPromptPart extends App.Art.GenAI.Pr
 			}
 		} else {
 			if (this.slave.race === "catgirl") {
-				prompt = "catperson <lora:CatgirlLoraV7:0.8>";
+				prompt = "catperson" + this.helper.lora("CatgirlLoraV7", .8, "", " ");
 			} else {
 				prompt = undefined;
 			}
diff --git a/src/art/genAI/prompts/hairPromptPart.js b/src/art/genAI/prompts/hairPromptPart.js
index 28596cbf3023fcb263cf2135286a77db39c63dca..eeff48209debe00e4f03ae46ba59ac6df2c708f1 100644
--- a/src/art/genAI/prompts/hairPromptPart.js
+++ b/src/art/genAI/prompts/hairPromptPart.js
@@ -37,7 +37,7 @@ App.Art.GenAI.HairPromptPart = class HairPromptPart extends App.Art.GenAI.Prompt
 		if (stylePostfix) {
 			return `${hairLength} ${this.slave.hColor} hair ${styleStr}`;
 		}
-		if (this.helper.xlBaseModel()) {
+		if (this.helper.isXLModel()) {
 			return `${hairLength} ${this.slave.hStyle} hair, ${this.slave.hColor} hair`;
 		}
 		return `${this.slave.hStyle} hair, ${hairLength} ${this.slave.hColor} hair`;
diff --git a/src/art/genAI/prompts/hugeFaketitsPromptPart.js b/src/art/genAI/prompts/hugeFaketitsPromptPart.js
index 9c0b29f24e8414132b58f232b2df1e808da8408e..b470e09250270936dfaf656b6eea28374214c5d1 100644
--- a/src/art/genAI/prompts/hugeFaketitsPromptPart.js
+++ b/src/art/genAI/prompts/hugeFaketitsPromptPart.js
@@ -5,28 +5,13 @@ App.Art.GenAI.HugeFakeTitsPromptPart = class HugeFakeTitsPromptPart extends App.
 	positive() {
 		if (this.censored){
 			return undefined;
-		}
-		else if (App.Art.GenAI.sdClient.hasLora("hugefaketits1") || App.Art.GenAI.sdClient.hasLora("hugefaketits1-000006")) {
+		} else if (
+			App.Art.GenAI.sdClient.hasLora("hugefaketits1") &&
+			this.slave.boobsImplant !== 0
+		) {
 			const ImplantPercentage = this.slave.boobsImplant / (this.slave.boobs - this.slave.boobsImplant);
-
-			if (ImplantPercentage > 1) {
-				return `<lora:hugefaketits1:0.5>`;
-			} else if (ImplantPercentage > 0.9) {
-				return `<lora:hugefaketits1:0.45>`;
-			} else if (ImplantPercentage > 0.8) {
-				return `<lora:hugefaketits1:0.4>`;
-			} else if (ImplantPercentage > 0.7) {
-				return `<lora:hugefaketits1:0.35>`;
-			} else if (ImplantPercentage > 0.6) {
-				return `<lora:hugefaketits1:0.3>`;
-			} else if (ImplantPercentage > 0.5) {
-				return `<lora:hugefaketits1:0.25>`;
-			} else if (ImplantPercentage > 0.4) {
-				return `<lora:hugefaketits1:0.2>`;
-			} else if (ImplantPercentage > 0.3) {
-				return `<lora:hugefaketits1:0.15>`;
-			} else if (ImplantPercentage > 0.2) {
-				return `<lora:hugefaketits1:0.1>`;
+			if (ImplantPercentage > 0.2) {
+				return this.helper.lora("hugefaketits1", Math.clamp(ImplantPercentage / 2, 0.1, 0.5));
 			}
 		}
 		return undefined;
diff --git a/src/art/genAI/prompts/musclesPromptPart.js b/src/art/genAI/prompts/musclesPromptPart.js
index a1f380bc763a0269a276a8e5493c477556ab944e..ad7196578d0e5ce431715006ea48cfa642ad1eab 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.xlBaseModel()) {
+		if (this.helper.isXLModel()) {
 			if (this.slave.muscles > 95) {
 				return 'huge muscles';
 			} else if (this.slave.muscles > 50) {
diff --git a/src/art/genAI/prompts/posturePromptPart.js b/src/art/genAI/prompts/posturePromptPart.js
index f9e4a0fabe8308e18e73b1fd2dee2cd06c82c462..84ee50f5b95031658fc7e9907c5a334a9b84ea3e 100644
--- a/src/art/genAI/prompts/posturePromptPart.js
+++ b/src/art/genAI/prompts/posturePromptPart.js
@@ -15,7 +15,7 @@ App.Art.GenAI.PosturePromptPart = class PosturePromptPart extends App.Art.GenAI.
 			parts.push(`sitting in chair`); // posture change prevents genning arms/legs, looks more natural
 		} else if (slave?.fuckdoll !== 0) {
 			if (App.Art.GenAI.sdClient.hasLora("Standing Straight v1 - locon 32dim") && !V.aiOpenPose) { // always prefer OpenPose over lora; less side effects
-				parts.push(`<lora:Standing Straight v1 - locon 32dim:1>`);
+				parts.push(this.helper.lora("Standing Straight v1 - locon 32dim", 1));
 			}
 			parts.push(`standing straight`);
 		} else if (isQuadrupedal(this.slave)) {
diff --git a/src/art/genAI/prompts/skinPromptPart.js b/src/art/genAI/prompts/skinPromptPart.js
index d90e96e376099cde02a2aac56d65ebc9a3b602ab..cb58a4e4466781aab039ab76fbc2001fcd8ec920 100644
--- a/src/art/genAI/prompts/skinPromptPart.js
+++ b/src/art/genAI/prompts/skinPromptPart.js
@@ -35,14 +35,14 @@ App.Art.GenAI.SkinPromptPart = class SkinPromptPart extends App.Art.GenAI.Prompt
 			case "olive":
 			case "bronze":
 			case "dark beige":
-				if (this.helper.xlBaseModel()) {
+				if (this.helper.isXLModel()) {
 					return "olive skin";
 				}
 				return "tan skin";
 			case "dark olive":
 			case "light brown":
 			case "brown":
-				if (this.helper.xlBaseModel()) {
+				if (this.helper.isXLModel()) {
 					return "brown skin";
 				}
 				return "tan skin";
@@ -50,15 +50,9 @@ App.Art.GenAI.SkinPromptPart = class SkinPromptPart extends App.Art.GenAI.Prompt
 			case "dark brown":
 			case "black":
 			case "ebony":
-				if (App.Art.GenAI.sdClient.hasLora("melanin3")) {
-					return `<lora:melanin3:0.8>, melanin, dark skin`;
-				}
-				return "dark skin";
+				return this.helper.lora("melanin", .8, ", melanin, ") + "dark skin";
 			case "pure black":
-				if (App.Art.GenAI.sdClient.hasLora("melanin3")) {
-					return `<lora:melanin3:1.0>, melanin, black skin`;
-				}
-				return "black skin";
+				return this.helper.lora("melanin", 1, ", melanin, ") + "black skin";
 			default:
 				return `${this.slave.skin} skin`;
 		}
@@ -68,7 +62,7 @@ App.Art.GenAI.SkinPromptPart = class SkinPromptPart extends App.Art.GenAI.Prompt
 	 * @override
 	 */
 	negative() {
-		if (this.helper.xlBaseModel()) {
+		if (this.helper.isXLModel()) {
 			if (this.positive() === "tan skin") {
 				return "tan lines";
 			}
diff --git a/src/art/genAI/prompts/weightPromptPart.js b/src/art/genAI/prompts/weightPromptPart.js
index 0b812280d8c17bc2c272fcb8687bb2ff2132e511..dab761adcc1763e6a8f88333167b453ad4c7e74e 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.xlBaseModel()) {
+		if (this.helper.isXLModel()) {
 			if (this.slave.weight < -95) {
 				return 'skinny, emaciated';
 			} else if (this.slave.weight < -30) {
@@ -43,7 +43,7 @@ App.Art.GenAI.WeightPromptPart = class WeightPromptPart extends App.Art.GenAI.Pr
 	 * @override
 	 */
 	negative() {
-		if (this.helper.xlBaseModel()) {
+		if (this.helper.isXLModel()) {
 			if (this.slave.weight < -30) {
 				return 'fat';
 			}
diff --git a/src/art/genAI/stableDiffusion.js b/src/art/genAI/stableDiffusion.js
index e0aa85cd92bbce058f61c04ae1aaa916dd6c38d2..6e82b33d92fbfc24cd504dd7b448f615e97f759b 100644
--- a/src/art/genAI/stableDiffusion.js
+++ b/src/art/genAI/stableDiffusion.js
@@ -1,5 +1,5 @@
 /* eslint-disable camelcase */
-// cSpell:ignore swinir, sdapi, yolov, VBOR
+// cSpell:ignore swinir, sdapi, yolov, VBOR, rgthree
 
 App.Art.GenAI.StableDiffusionSettings = class {
 	/**
@@ -69,7 +69,8 @@ App.Art.GenAI.StableDiffusionSettings = class {
 		this.height = height;
 		this.negative_prompt = negative_prompt;
 		this.restore_faces = restore_faces;
-		this.override_strings = override_settings;
+		this.override_strings = override_settings; // I think this is wrong, but I will leave it - franklygeorge
+		this.override_settings = override_settings; // This is right - franklygeorge
 		this.override_settings_restore_afterwards = true;
 		this.alwayson_scripts = alwayson_scripts;
 	}
@@ -177,14 +178,13 @@ App.Art.GenAI.StableDiffusionClientQueue = class {
 				let message = JSON.parse(msg);
 
 				if (message.type === 'execution_success') {
-					// eslint-disable-next-line no-inner-declarations
-					function blobToBase64(blob) {
+					const blobToBase64 = (blob) => {
 						return new Promise((resolve, _) => {
 							const reader = new FileReader();
 							reader.onloadend = () => resolve(reader.result);
 							reader.readAsDataURL(blob.slice(8, -1, 'image/png'));
 						});
-					}
+					};
 
 					blobToBase64(img_blob).then(res => {
 						obj.images = [res];
@@ -249,8 +249,10 @@ App.Art.GenAI.StableDiffusionClientQueue = class {
 		try {
 			this.workingOnID = top.slaveID;
 			this.workingOnBody = top.body;
-			console.log(`Fetching image for slave ${top.slaveID}, ${this.queue.length} requests remaining in the queue; ${this.backlogQueue.length} in backlog.`);
-			// console.log("Generation Settings: ", JSON.parse(top.body));
+			console.debug(`Fetching image for slave ${top.slaveID}, ${this.queue.length} requests remaining in the queue; ${this.backlogQueue.length} in backlog.`);
+			if (V.debugMode) {
+				console.debug(`SD generation settings`, JSON.parse(top.body));
+			}
 			const options = {
 				method: "POST",
 				headers: {
@@ -504,18 +506,23 @@ App.Art.GenAI.StableDiffusionClient = class {
 				steps: steps,
 				width: V.aiWidth,
 				restore_faces: V.aiRestoreFaces,
-				alwayson_scripts: alwaysOnScripts
+				alwayson_scripts: alwaysOnScripts,
+				override_settings: {
+					"always_discard_next_to_last_sigma": true,
+					"sd_model_checkpoint": V.aiCheckpoint,
+				},
 			});
 			return settings;
 		} else {
-			const prompt_wrkflw = await new App.Art.GenAI.ComfyUIWorkflow(slave, steps);
+			const prompt_workflow = await new App.Art.GenAI.ComfyUIWorkflow(slave, steps);
 
-			const settings = new App.Art.GenAI.ComfyUISettings(prompt_wrkflw);
+			const settings = new App.Art.GenAI.ComfyUISettings(prompt_workflow);
 			return settings;
 		}
 	}
 
-	/** Note the long timeout; if SD is actively rendering it'll sometimes stop responding to API queries.
+	/**
+	 * Note the long timeout; if SD is actively rendering or loading a model it'll stop responding to API queries.
 	 * Do not block on API calls.
 	 * @param {string} relativeUrl
 	 * @param {string} [method="GET"]
@@ -534,7 +541,7 @@ App.Art.GenAI.StableDiffusionClient = class {
 		// V.aiUserInterface: 0 = A1111; 1 = ComfyUI
 		const relativeUrl = V.aiUserInterface === 0 ? "/app_id" : "";
 		return fetchWithTimeout(
-			`${V.aiApiUrl}${relativeUrl}`, 1000, {method: "GET"}
+			`${V.aiApiUrl}${relativeUrl}`, 5000, {method: "GET"}
 		).then((res) => {
 			return !!res && res.status === 200;
 		}).catch(err => {
@@ -547,16 +554,133 @@ App.Art.GenAI.StableDiffusionClient = class {
 	 * @returns {Promise<string[]>}
 	 */
 	async getCheckpointList() {
-		return this.fetchAPIQuery(`/models/checkpoints`)
-			.then((value) => {
-				return value.json();
-			})
-			.then((list) => {
-				return list;
+		if (V.aiUserInterface === 0) {
+			await this.fetchAPIQuery("/sdapi/v1/refresh-checkpoints", "POST");
+			return this.fetchAPIQuery(`/sdapi/v1/sd-models`)
+				.then((value) => {
+					return value.json();
+				})
+				.then((list) => {
+					return list.map(entry => entry.title);
+				})
+				.catch(err => {
+					console.log(`Failed to get checkpoint list from Stable Diffusion - ${err}`);
+					return [];
+				});
+		} else if (V.aiUserInterface === 1) {
+			return this.fetchAPIQuery(`/models/checkpoints`)
+				.then((value) => {
+					return value.json();
+				})
+				.then((list) => {
+					return list;
+				})
+				.catch(err => {
+					console.log(`Failed to get checkpoint list from Stable Diffusion - ${err}`);
+					return [];
+				});
+		}
+	}
+
+	/**
+	 * This attempts to get the metadata for the given checkpoint.
+	 * Most checkpoints don't have metadata.
+	 * Returns undefined if there is no metadata or an error occurs, otherwise returns an object.
+	 * @param {string} modelPath
+	 * @returns {Promise<object|undefined>}
+	 */
+	async getCheckpointMetadata(modelPath) {
+		if (V.aiUserInterface === 0) {
+			modelPath = modelPath.replaceAll("\\", "/");
+			if (modelPath.includes("/")) {
+				modelPath = modelPath.split("/").pop();
+			}
+			const pathParts = modelPath.split(".");
+			pathParts.pop();
+			modelPath = pathParts.join(".");
+			const metaURL = `/sd_extra_networks/metadata?page=checkpoints&item=${modelPath}`;
+			return this.fetchAPIQuery(metaURL)
+				.then((rep) => {
+					if (!rep.ok) { return; }
+					return rep.json();
+				})
+				.then((value) => {
+					if ("metadata" in value) {
+						if (typeof value.metadata === "string") {
+							return JSON.parse(value.metadata);
+						}
+					}
+					return;
+				})
+				.catch(err => {
+					console.log(`Failed to get checkpoint metadata from Stable Diffusion - ${err}`);
+					return;
+				});
+		} else if (V.aiUserInterface === 1) {
+			// cSpell: disable
+			const metaURL = `/view_metadata/checkpoints?filename=${encodeURIComponent(modelPath.replaceAll("\\", "/"))}`;
+			// cSpell: enable
+			return this.fetchAPIQuery(metaURL)
+				.then((value) => {
+					if (!value.ok) { return; }
+					return value.json();
+				})
+				.catch(err => {
+					console.log(`Failed to get checkpoint metadata from Stable Diffusion - ${err}`);
+					return;
+				});
+		}
+	}
+
+	/**
+	 * This attempts to narrow down the valid base models for the given checkpoint.
+	 * This is a best guess and should only be used for informational purposes.
+	 * Sadly some checkpoints don't have metadata and pony checkpoints are not different from SDXL in a meaningful way so we guess using the filename.
+	 * [0, 1, 2] will be returned if there is no metadata in the checkpoint.
+	 * pony and sdxl models will return [1, 2] (metadata) or [1]/[2] (filename guessing).
+	 * SD 1.X models will return [0].
+	 * @param {string} modelPath
+	 * @returns {Promise<FC.GameVariables["aiBaseModel"][]>}
+	 */
+	async getCheckpointPotentialBaseModels(modelPath) {
+		return this.getCheckpointMetadata(modelPath)
+			.then((metadata) => {
+				/** @type {FC.GameVariables["aiBaseModel"][]} */
+				const models = [0, 1, 2];
+				let hasUsefulMetadata = false;
+
+				const filenameGuess = () => {
+					// we are going to attempt to detect sdxl/pony models based off their filenames
+					if (modelPath.toLowerCase().includes("pony")) {
+						// assume it is a pony model
+						models.deleteAll(0, 1);
+					} else if (modelPath.toLowerCase().includes("sdxl")) {
+						// assume it is a SDXL model
+						models.deleteAll(0, 2);
+					}
+					// assume nothing
+				};
+
+				if (metadata) {
+					console.debug("checkpoint metadata:", metadata);
+					if ("modelspec.architecture" in metadata) {
+						hasUsefulMetadata = true;
+						if (metadata["modelspec.architecture"] === "stable-diffusion-xl-v1-base") {
+							models.deleteAll(0);
+							filenameGuess();
+						} else {
+							console.error(new Error(`metadata["modelspec.architecture"] === "${metadata["modelspec.architecture"]}" was not handled in getCheckpointPotentialBaseModels`));
+						}
+					}
+				}
+				if (!hasUsefulMetadata) {
+					filenameGuess();
+				}
+				return models;
 			})
 			.catch(err => {
-				console.log(`Failed to get checkpoint list from Stable Diffusion - ${err}`);
-				return [];
+				console.log(`Failed to get checkpoint base model from Stable Diffusion - ${err}`);
+				return [0, 1, 2];
 			});
 	}
 
@@ -777,7 +901,7 @@ App.Art.GenAI.StableDiffusionClient = class {
 
 	/**
 	 * @param {boolean} [useCached=true] if false then we will ask SD WebUI for a fresh list, otherwise we will use a cached version if available
-	 * @returns {Promise<string[]>} the names of all LoRAs currently available
+	 * @returns {Promise<string[]>} the names of all LoRAs currently available, we use internal names by default, falling back to filenames if there is no internal name
 	 */
 	async getLoraList(useCached = true) {
 		if (this.#inflightLoraRequest) {
@@ -786,35 +910,90 @@ App.Art.GenAI.StableDiffusionClient = class {
 			const loraRequest = async () => {
 				if (useCached === false || this.#loraCachedList === undefined) {
 					if (V.aiUserInterface === 0) {
+						// cSpell: disable
 						await this.fetchAPIQuery(`/sdapi/v1/refresh-loras`, "POST"); // Ask SD to update it's list of LoRAs to reflect what is in storage
+						// cSpell: enable
 					}
 				}
 				if (this.#loraCachedList !== undefined) {
 					return (!V.aiLoraPack) ? [] : this.#loraCachedList;
 				}
+				// cSpell: disable
 				let relative_url = V.aiUserInterface === 0 ? `/sdapi/v1/loras` : `/models/loras`;
+				// cSpell: enable
+				/** @type {string[]} */
+				let comfyLoraPaths = [];
 				/** @type {string[]} */
 				let list = await this.fetchAPIQuery(`${relative_url}`)
-					// cSpell:enable
 					.then((value) => { return value.json(); })
 					.then((list) => {
 						let entries = [];
 						if (V.aiUserInterface === 0) {
 							list.forEach((item) => {
-								if ("name" in item && !entries.includes(item.name)) {
+								if ("alias" in item && !entries.includes(item.alias) && item.alias !== "None") {
+									// the internal name or the filename without extension if no internal name exists
+									entries.push(item.alias);
+								} else if ("name" in item && !entries.includes(item.name)) {
+									// the filename without extension
 									entries.push(item.name);
 								}
 							});
 						} else {
 							list.forEach((item) => {
-								if (!entries.includes(item)) {
-									entries.push(item.match(/([^\\]+)\./)[1]);
-								}
+								// add the paths to a list for more processing later
+								// we do this because we cannot await inside an existing await
+								comfyLoraPaths.push(item);
 							});
 						}
 						return entries;
 					});
+				const addComfyPath = (path) => {
+					const item = path.match(/([^\\]+)\./)[1];
+					if (!list.includes(item)) {
+						// push filename
+						list.push(item);
+					}
+				};
+				for (const path of comfyLoraPaths) {
+					// get internal names of LoRAs in ComfyUI
+					// cSpell: disable
+					const metaURL = `/view_metadata/loras?filename=${encodeURIComponent(path.replaceAll("\\", "/"))}`;
+					// cSpell: enable
+					try {
+						await this.fetchAPIQuery(metaURL)
+							.then((value) => { return value.json(); })
+							.then((metadata) => {
+								if (!metadata || typeof metadata !== "object") {
+									console.debug(`getLoraList(): Failed to get metadata object for "${metaURL}"`);
+									addComfyPath(path);
+									return;
+								}
+								if (!("ss_output_name" in metadata)) {
+									console.debug(`getLoraList(): Failed to get 'ss_output_name' for "${metaURL}"`);
+									addComfyPath(path);
+									return;
+								}
+								if (
+									typeof metadata.ss_output_name === "string" &&
+									metadata.ss_output_name.trim() !== "" &&
+									metadata.ss_output_name.trim() !== "None" &&
+									!list.includes(metadata.ss_output_name.trim())
+								) {
+									// push internal name
+									list.push(metadata.ss_output_name.trim());
+								} else {
+									addComfyPath(path);
+								}
+							});
+					} catch {
+						console.debug(`getLoraList(): Failed to get metadata for "${metaURL}"`);
+						addComfyPath(path);
+					}
+				}
 				this.#loraCachedList = list;
+				if (V.debugMode) {
+					console.debug("Available LoRAs", list);
+				}
 				return (!V.aiLoraPack) ? [] : list;
 			};
 			this.#inflightLoraRequest = loraRequest();
@@ -825,10 +1004,10 @@ App.Art.GenAI.StableDiffusionClient = class {
 	}
 
 	/**
-	 * @param {string} loraName The name of the lora to check for
+	 * @param {InternalLoraName} loraKey The name of the lora to check for
 	 * @returns {boolean} returns true if the lora name is a valid lora
 	 */
-	hasLora(loraName) {
+	hasLora(loraKey) {
 		if (this.#loraCachedList === undefined) {
 			// this shouldn't happen, but if it does
 			// call getLoraList, but don't wait for it to return
@@ -840,18 +1019,20 @@ App.Art.GenAI.StableDiffusionClient = class {
 		if (!V.aiLoraPack) {
 			return false; // LoRAs are disabled
 		}
-		if (V.aiDisabledLoRAs.includes(loraName)) {
+		if (V.aiDisabledLoRAs.includes(loraKey)) {
 			return false; // this LoRA is disabled
 		}
-		if (App.Art.GenAI.UI.Options.recommendedLoRAs.has(loraName)) {
-			const lora = App.Art.GenAI.UI.Options.recommendedLoRAs.get(loraName);
+		/** @type {RecommendedLora} */
+		const lora = App.Art.GenAI.UI.Options.recommendedLoRAs.get(loraKey);
+		if (lora) {
 			if (!lora.baseModel.includes(V.aiBaseModel)) {
-				return false;
+				return false; // This lora isn't valid for the selected base model
 			}
 		} else {
-			console.error(`Lora:${loraName} not found in recommended LoRAs`);
+			console.error(`Lora '${loraKey}' not found in App.Art.GenAI.UI.Options.recommendedLoRAs`);
+			// This lora isn't in App.Art.GenAI.UI.Options.recommendedLoRAs, but we let things continue anyways
 		}
-		return (this.#loraCachedList.includes(loraName));
+		return (this.#loraCachedList.includes(loraKey));
 	}
 
 	/**
diff --git a/src/art/genAI/helpers.js b/src/art/genAI/stableDiffusionHelpers.js
similarity index 71%
rename from src/art/genAI/helpers.js
rename to src/art/genAI/stableDiffusionHelpers.js
index fad02eeda6da0eba3dd69cec3c4a787e1f1e64e9..f2b7b254cf4621c45781a7bb17b74f2fb8557cfa 100644
--- a/src/art/genAI/helpers.js
+++ b/src/art/genAI/stableDiffusionHelpers.js
@@ -101,7 +101,9 @@ App.Art.GenAI.PromptHelpers = (() => {
 
 	const validateAgainst = (description, targets) => targets.has(sanitizeDescriptor(description));
 
-	const xlBaseModel = () => V.aiBaseModel === (1||2); // this could be more general when other models are implemented
+	const XLModel = () => V.aiBaseModel === (1||2); // this could be more general when other models are implemented
+
+	const ponyModel = () => V.aiBaseModel === 2;
 
 	const isFeminine = (slave) => {
 		if (V.aiGenderHint === 1) { // Hormone balance
@@ -131,6 +133,28 @@ App.Art.GenAI.PromptHelpers = (() => {
 		}
 	};
 
+	/**
+	 * Returns a string that allows the selected AI User Interface to use the LoRA model.
+	 * Returns an empty string if App.Art.GenAI.sdClient.hasLora(loraKey) returns false.
+	 * @see App.Art.GenAI.sdClient.hasLora
+	 * @param {string} loraKey The LoRA key as defined in App.Art.GenAI.UI.Options.recommendedLoRAs.
+	 * @see App.Art.GenAI.UI.Options.recommendedLoRAs
+	 * @param {number} [strength=0.5] What strength the LoRA should have [default=0.5].
+	 * @param {string} [postString=""] If provided this is added to the end of the string [default=""].
+	 * @param {string} [preString=""] If provided this is added to the beginning of the string [default=""].
+	 * @returns {string} The constructed string.
+	 */
+	const loraBuilder = (loraKey, strength=0.5, postString="", preString="") => {
+		if (!App.Art.GenAI.sdClient.hasLora(loraKey)) { return ""; }
+		strength = strength ?? 0.5;
+		if (typeof strength !== "number" || Number.isNaN(strength)) {
+			throw new Error(`loraBuilder: strength must be a non NaN number! got '${strength}'`);
+		}
+		postString = postString ?? "";
+		preString = preString ?? "";
+		return `${preString}<lora:${App.Art.GenAI.UI.Options.recommendedLoRAs.get(loraKey).name}:${strength}>${postString}`;
+	};
+
 	return {
 		sanitizeDescriptor,
 		exposesMidriff: (description) => validateAgainst(
@@ -145,8 +169,10 @@ App.Art.GenAI.PromptHelpers = (() => {
 		exposesCrotch: (description) => validateAgainst(
 			description, CROTCH_EXPOSING_OUTFITS
 		),
-		xlBaseModel,
+		isXLModel: XLModel,
+		isPonyModel: ponyModel,
 		isFeminine,
 		isMasculine,
+		lora: loraBuilder,
 	};
 })();
diff --git a/src/art/genAI/uiOptions.js b/src/art/genAI/uiOptions.js
index 62133d7a82409f628c0ba659fde3b77afeaa6d7f..d64446376ead1cf0504b239310e82c1335e68f20 100644
--- a/src/art/genAI/uiOptions.js
+++ b/src/art/genAI/uiOptions.js
@@ -2,130 +2,214 @@
 
 App.Art.GenAI.UI.Options = {};
 
+/**
+ * @typedef {string} InternalLoraName the internal name of the lora
+ */
+
+/**
+ * @typedef {object} RecommendedLora
+ * @property {InternalLoraName} name
+ * @property {string[]} urls the urls links to the model. Ideally on Hugging Face or CivitAI and ideally a page link and a direct download link
+ * @property {string} usage the reason this lora is in use
+ * @property {FC.GameVariables["aiBaseModel"][]} baseModel an array with all the base models that this lora supports
+ */
 
+/** @type {Map<InternalLoraName, RecommendedLora>} */
 App.Art.GenAI.UI.Options.recommendedLoRAs = new Map([
 	["amputee-000003", {
 		name: "amputee-000003",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/amputee-000003.safetensors"],
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/amputee-000003.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/amputee-000003.safetensors",
+		],
 		usage: "Amputation. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
-	["hololive_roboco-san-10", {
-		name: "hololive_roboco-san-10",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/hololive_roboco-san-10.safetensors"],
+	["hololive_roboco-san", {
+		name: "hololive_roboco-san",
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/hololive_roboco-san-10.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/hololive_roboco-san-10.safetensors",
+		],
 		usage: "Android arms and legs. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	["BEReaction", {
 		name: "BEReaction",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/BEReaction.safetensors"],
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/BEReaction.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/BEReaction.safetensors",
+		],
 		usage: "Really large breasts. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	["eye-allsclera", {
 		name: "eye-allsclera",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/eye-allsclera.safetensors"],
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/eye-allsclera.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/eye-allsclera.safetensors",
+		],
 		usage: "Blind eyes. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	["Empty Eyes - Drooling v5 - 32dim", {
-		name: "Empty Eyes - Drooling v5 - 32dim",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/Empty%20Eyes%20-%20Drooling%20v5%20-%2032dim.safetensors"],
+		get name() {
+			if (V.aiUserInterface === 1) {
+				return "Empty Eyes - Drooling v5 - 32dim";
+			}
+			return "empty eyes - drooling v5";
+		},
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/Empty%20Eyes%20-%20Drooling%20v5%20-%2032dim.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/Empty%20Eyes%20-%20Drooling%20v5%20-%2032dim.safetensors",
+		],
 		usage: "Mindbroken slaves. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	// cSpell:ignore-word flaccidfutanarimix
-	["flaccidfutanarimix-locon-dim64-alpha64-highLR-000003", {
-		name: "flaccidfutanarimix-locon-dim64-alpha64-highLR-000003",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/flaccidfutanarimix-locon-dim64-alpha64-highLR-000003.safetensors"],
+	["flaccidfutanarimix-locon-dim64-alpha64-highLR", {
+		name: "flaccidfutanarimix-locon-dim64-alpha64-highLR",
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/flaccidfutanarimix-locon-dim64-alpha64-highLR-000003.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/blob/resolve/flaccidfutanarimix-locon-dim64-alpha64-highLR-000003.safetensors",
+		],
 		usage: "Really big futanari (dickgirl) dicks. Required for futas with large dicks to render at all. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
-	["futanari-000009", {
-		name: "futanari-000009",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/futanari-000009.safetensors"],
+	["futanari", {
+		name: "futanari",
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/futanari-000009.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/futanari-000009.safetensors",
+		],
 		usage: "Normal futanari (dickgirl) dicks. Required for futas with normal dicks to render at all. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	// cSpell:ignore-word micropp
-	["micropp_32dim_nai_v2", {
-		name: "micropp_32dim_nai_v2",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/micropp_32dim_nai_v2.safetensors"],
+	["micropp_128dim_nai_v2", {
+		name: "micropp_128dim_nai_v2",
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/micropp_32dim_nai_v2.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/micropp_32dim_nai_v2.safetensors",
+		],
 		usage: "Small futanari (dickgirl) dicks. Required for futas with small dicks to render at all. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	// cSpell:ignore-word nopussy
 	["nopussy_v1", {
 		name: "nopussy_v1",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/nopussy_v1.safetensors"],
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/nopussy_v1.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/nopussy_v1.safetensors",
+		],
 		usage: "Null gender slaves. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	// cSpell:ignore-word xxmaskedxx
 	["xxmaskedxx_lora_v01", {
 		name: "xxmaskedxx_lora_v01",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/xxmaskedxx_lora_v01.safetensors"],
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/xxmaskedxx_lora_v01.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/xxmaskedxx_lora_v01.safetensors",
+		],
 		usage: "Fuckdoll mask. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	["Standing Straight v1 - locon 32dim", {
-		name: "Standing Straight v1 - locon 32dim",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/Standing%20Straight%20%20v1%20-%20locon%2032dim.safetensors"],
+		get name() {
+			if (V.aiUserInterface === 1) {
+				return "Standing Straight v1 - locon 32dim";
+			}
+			return "Standing straight  - arms at sides - legs together - v1 - locon 32dim";
+		},
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/Standing%20Straight%20%20v1%20-%20locon%2032dim.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/Standing%20Straight%20%20v1%20-%20locon%2032dim.safetensors",
+		],
 		usage: "Make fuckdolls stand up straight. This will not be used if you are using OpenPose. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	["OnlyCocksV1LORA", {
 		name: "OnlyCocksV1LORA",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/OnlyCocksV1LORA.safetensors"],
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/OnlyCocksV1LORA.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/OnlyCocksV1LORA.safetensors",
+		],
 		usage: "Improved male penis. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	["CatgirlLoraV7", {
 		name: "CatgirlLoraV7",
-		urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/CatgirlLoraV7.safetensors"],
+		urls: [
+			"https://huggingface.co/NGBot/ampuLora/blob/main/CatgirlLoraV7.safetensors",
+			"https://huggingface.co/NGBot/ampuLora/resolve/main/CatgirlLoraV7.safetensors",
+		],
 		usage: "Catpeople. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}],
-	["hugefaketits1-000006", {
-		name: "hugefaketits1-000006",
-		urls: ["https://civitai.com/api/download/models/131998?type=Model&format=SafeTensor"],
+	["hugefaketits1", {
+		name: "hugefaketits1",
+		urls: [
+			"https://civitai.com/api/download/models/131998?type=Model&format=SafeTensor",
+		],
 		usage: "Large boob implants. Part of NGBot's FC LoRA pack.",
-		baseModel:[0]
+		baseModel: [0]
 	}], // TODO: test the updated version of this: https://civitai.com/models/97221?modelVersionId=103897
 	["LowRA_v2", {
 		name: "LowRA_v2",
-		urls: ["https://huggingface.co/XpucT/Loras/blob/main/LowRA_v2.safetensors"],
+		urls: [
+			"https://huggingface.co/XpucT/Loras/blob/main/LowRA_v2.safetensors",
+			"https://huggingface.co/XpucT/Loras/resolve/main/LowRA_v2.safetensors",
+		],
 		usage: "Makes realistic models have more dynamic contrast. Only used if 'AI style prompting' is set to 'Photorealistic'",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	["RobotDog0903", {
 		name: "RobotDog0903",
-		urls: ["https://civitai.com/models/139298/conceptprosthetic-quadruped-girl?modelVersionId=154248"],
+		urls: [
+			"https://civitai.com/models/139298/conceptprosthetic-quadruped-girl?modelVersionId=154248",
+			"https://civitai.com/api/download/models/154248?type=Model&format=SafeTensor",
+		],
 		usage: "Quadruped androids",
-		baseModel:[0]
+		baseModel: [0]
 	}],
 	["ponygirl", {
 		name: "ponygirl",
-		urls: ["https://civitai.com/models/90831?modelVersionId=96789"],
+		urls: [
+			"https://civitai.com/models/90831?modelVersionId=96789",
+			"https://civitai.com/api/download/models/96789?type=Model&format=SafeTensor",
+		],
 		usage: "Pony Girl outfits, if available",
-		baseModel:[0]
+		baseModel: [0]
 	}],
-	["Eye-LoRa_6433", {
-		name: "Eye-LoRa_6433",
-		urls: ["https://civitai.com/api/download/models/6433?type=Model&format=SafeTensor&size=full&fp=fp16"],
+	["Loraeyes_V1", {
+		name: "Loraeyes_V1",
+		urls: [
+			"https://civitai.com/api/download/models/6433?type=Model&format=SafeTensor&size=full&fp=fp16",
+		],
 		usage: "Fixes some eyes Problems",
-		baseModel:[0]
+		baseModel: [0]
 	}],
-	["melanin3", {
-		name: "melanin3",
-		urls: ["https://civitai.com/models/133279?modelVersionId=263127"],
+	["melanin", {
+		name: "melanin",
+		urls: [
+			"https://civitai.com/models/133279?modelVersionId=263127",
+			"https://civitai.com/api/download/models/263127?type=Model&format=SafeTensor",
+		],
 		usage: "Apply a darker skintone for dark skins",
-		baseModel:[0]
+		baseModel: [0]
 	}]
 ]);
 
 App.Art.GenAI.UI.Options.loraList = () => {
-	let recommendedLoRAs = App.Art.GenAI.UI.Options.recommendedLoRAs;
+	/** @type {RecommendedLora[]} */
+	let recommendedLoRAs = [];
+	App.Art.GenAI.UI.Options.recommendedLoRAs.forEach((lora) => {
+		// filter so that recommendedLoRAs only has the LoRAs that are compatable with the current aiBaseModel
+		if (lora.baseModel.includes(V.aiBaseModel)) {
+			recommendedLoRAs.push(lora);
+		}
+	});
 
 	/**
 	 * @param {string} link
@@ -139,12 +223,21 @@ App.Art.GenAI.UI.Options.loraList = () => {
 				return "CIVITAI (account needed)";
 			}
 		} else if (link.includes("//huggingface")) {
-			return "Hugging Face";
+			if (link.includes("/resolve/")) {
+				return "Hugging Face (direct download)";
+			} else {
+				return "Hugging Face";
+			}
 		} else {
 			return link;
 		}
 	};
 
+	/**
+	 * @param {RecommendedLora} lora
+	 * @param {boolean} [installed=false]
+	 * @returns {HTMLDivElement}
+	 */
 	const loraDiv = (lora, installed = false) => {
 		const lDiv = App.UI.DOM.makeElement("div");
 		const links = [];
@@ -204,7 +297,7 @@ App.Art.GenAI.UI.Options.loraList = () => {
 		return lDiv;
 	};
 
-	let accordionDiv = App.UI.DOM.makeElement("div");
+	let accordionDiv = document.createElement("div");
 	let title = "LoRAs";
 	App.Art.GenAI.sdClient.getLoraList(false)
 		.then((availableLoRAs) => {
@@ -218,7 +311,7 @@ App.Art.GenAI.UI.Options.loraList = () => {
 					recommended.push(lora);
 				}
 			});
-			title = `${recommendedInstalled.length} out of ${recommendedLoRAs.size} recommended LoRAs installed`;
+			title = `${recommendedInstalled.length} out of ${recommendedLoRAs.length} recommended LoRAs installed`;
 
 			contentDiv.append(App.UI.DOM.generateLinksStrip([
 				App.UI.loraInstallationGuide(),
@@ -251,7 +344,7 @@ App.Art.GenAI.UI.Options.loraList = () => {
 			if (availableLoRAs.length !== 0) {
 				const note = [
 					"You can use these in custom prompts by adding '<lora:[lora name]:[weight]>' to the prompt.",
-					"For example if the LoRA is 'futanari-000009' then you might add '<lora:futanari-000009:0.5>' to the prompt.",
+					"For example if the LoRA is 'futanari' then you might add '<lora:futanari:0.5>' to the prompt.",
 					"Some LoRAs require extra tags to work. These tags are often listed on the LoRA's download page."
 				];
 				contentDiv.append(
@@ -266,8 +359,15 @@ App.Art.GenAI.UI.Options.loraList = () => {
 			}
 
 			accordionDiv.append(App.UI.DOM.accordion(title, contentDiv, (V.useAccordion > 0)));
+		}).finally(() => {
+			let parentDiv = document.getElementById("lora-list");
+			if (!parentDiv || parentDiv === null) {
+				throw new Error(`App.Art.GenAI.UI.Options.loraList() expects a div to exist with the id 'lora-list'`);
+			} else {
+				parentDiv.innerHTML = "";
+				parentDiv.append(accordionDiv);
+			}
 		});
-	return accordionDiv;
 };
 
 /**
@@ -278,34 +378,33 @@ App.Art.GenAI.UI.Options.promptingOptions = (options) => {
 	options.addOption("AI User Interface", "aiUserInterface").addValueList([
 		["A1111", 0],
 		["ComfyUI", 1]
-	]).addCallback(res => {
-		if (res === 1) {
-			V.aiOpenPose = false; // remove when finished open pose integration
-			V.aiSamplingMethod = "dpmpp_2m_sde";
-		}
-	});
+	]);
+
+	if (V.aiUserInterface === 0) {
+		V.aiSamplingMethod = (V.aiSamplingMethod === "dpmpp_2m_sde") ? "DPM++ 2M SDE" : V.aiSamplingMethod;
+		V.aiSchedulingMethod = (V.aiSchedulingMethod === "karras") ? "Karras" : V.aiSchedulingMethod;
+	} else if (V.aiUserInterface === 1) {
+		V.aiOpenPose = false; // remove when finished open pose integration
+		V.aiSamplingMethod = (V.aiSamplingMethod === "DPM++ 2M SDE") ? "dpmpp_2m_sde" : V.aiSamplingMethod;
+		V.aiSchedulingMethod = (V.aiSchedulingMethod === "Karras") ? "karras" : V.aiSchedulingMethod;
+	}
 
 	if (V.aiUserInterface === 1) {
 		options.addOption("AI Prebuilt Workflow", "aiPrebuiltWorkflow").addValueList([
 			["Simple ComfyUI", 0],
 			["BreezeIndigo's Pony Diffusion", 2],
 			["Custom Workflow", 1],
-
 		]);
 	}
 
 	if (V.aiPrebuiltWorkflow === 1 && V.aiUserInterface === 1) {
-		options.addOption("Workflow", "aiCustomWorkflow").showTextBox({unit: '', forceString: true}).addComment('Load a custom workflow. <span class="warning">Must be placed in resources/workflows/. Only usuable locally hosted.</span>');
-	}
-
-	if (V.aiPrebuiltWorkflow === 2) {
-		V.aiBaseModel = 2;
-	} else {
-		options.addOption("AI Base Model", "aiBaseModel").addValueList([
-			["SD 1.X", 0],
-			["SDXL", 1],
-			["Pony", 2],
-		]);
+		const commentDiv = document.createElement("div");
+		commentDiv.append(App.UI.DOM.makeElement("span", "Load a custom workflow. See "));
+		commentDiv.append(App.UI.comfyUICustomWorkflowGuide());
+		commentDiv.append(App.UI.DOM.makeElement("span", " for instructions."));
+		options.addOption("Workflow", "aiCustomWorkflow")
+			.showTextBox({unit: '', forceString: true})
+			.addComment(commentDiv);
 	}
 
 	options.addOption("AI style prompting", "aiStyle")
@@ -330,26 +429,7 @@ App.Art.GenAI.UI.Options.promptingOptions = (options) => {
 	options.addOption("Visual age filter", 'aiAgeFilter')
 		.addValue("Enabled", true).on().addValue("Disabled", false).off()
 		.addComment(`Creating images of characters that <U>appear to be</U> minors may be questionable in some countries, especially if they are generated by AI. Realistic images are even riskier due to their easy confusion with real ones. This option attempts to generate SFW images for them. <span class="red">You may want to check you local laws before disabling this option.</span>`);
-	const loraSpan = App.UI.DOM.makeElement('span', ``);
-	if (V.aiUserInterface === 1) {
-		loraSpan.textContent = `Fetching lora-loader, please wait...`;
-		App.Art.GenAI.sdClient.hasRGThreeComfy().then(result => {
-			if (!result) {
-				loraSpan.textContent = 'Couldn\'t find lora-loader script';
-				loraSpan.classList.add('warning');
-			} else {
-				loraSpan.textContent = '';
-			}
-		});
-	}
 
-	if (V.aiUserInterface === 0 || V.aiPrebuiltWorkflow !== 2) {
-		options.addOption("LoRA models are", "aiLoraPack")
-			.addValue("Enabled", true).on().addValue("Disabled", false).off().addComment(loraSpan);
-	}
-	if (V.aiLoraPack) {
-		options.addCustom(App.Art.GenAI.UI.Options.loraList());
-	}
 	options.addOption("Nationality factor in prompt", "aiNationality")
 		.addValue("Strong", 2).addValue("Weak", 1).on().addValue("Disabled", 0).off()
 		.addComment("Helps differentiate between ethnicities that share a Free Cities race, like Japanese and Korean or Spanish and Greek. May cause flags/national colors to appear unexpectedly, and can have a negative impact on slaves that belong to a minority race for their nationality.");
@@ -363,8 +443,9 @@ App.Art.GenAI.UI.Options.promptingOptions = (options) => {
  */
 App.Art.GenAI.UI.Options.artOptions = (options) => {
 	options.addComment("This is experimental. Please follow the setup instructions below.");
-	options.addCustom(App.UI.stableDiffusionInstallationGuide("Stable Diffusion Installation Guide"));
-	options.addCustom(App.UI.comfyUIInstallationGuide("ComfyUI Installation Guide"));
+	options.addCustom(App.UI.A1111InstallationGuide("A1111 WebUI Installation Guide (easier to use)"));
+	options.addCustom(App.UI.comfyUIInstallationGuide("ComfyUI Installation Guide (uses less system resources; faster)"));
+	options.addCustom(App.UI.comfyUICustomWorkflowGuide());
 	if (V.aiApiUrl.endsWith('/')) { // common error is including a trailing slash, which will fuck us up, so strip it automatically
 		V.aiApiUrl = V.aiApiUrl.slice(0, -1);
 	}
@@ -396,8 +477,27 @@ App.Art.GenAI.UI.Options.artOptions = (options) => {
 		.addValue("Enabled", true).on().addValue("Disabled", false).off()
 		.addComment("Apply image generation prompt changes from Rules Assistant for event images, including slave marketplace images. Useful for customizing prompts of non-owned slaves.");
 
+	const loraSpan = App.UI.DOM.makeElement('span', ``);
+	if (V.aiUserInterface === 1) {
+		loraSpan.textContent = `Fetching lora-loader, please wait...`;
+		App.Art.GenAI.sdClient.hasRGThreeComfy().then(result => {
+			if (!result) {
+				loraSpan.textContent = 'Couldn\'t find lora-loader script';
+				loraSpan.classList.add('warning');
+			} else {
+				loraSpan.textContent = '';
+			}
+		});
+	}
 
-	options.addCustom(App.UI.DOM.makeElement("h3", "Advanced Config"));
+	if (V.aiUserInterface === 0 || V.aiPrebuiltWorkflow !== 2) {
+		options.addOption("LoRA models are", "aiLoraPack")
+			.addValue("Enabled", true).on().addValue("Disabled", false).off().addComment(loraSpan);
+	}
+
+	const loraListDiv = document.createElement("div");
+	loraListDiv.id = "lora-list";
+	options.addCustom(loraListDiv);
 	const advancedConfigDiv = document.createElement("div");
 	options.addCustom(advancedConfigDiv);
 	setTimeout(() => {
@@ -406,6 +506,12 @@ App.Art.GenAI.UI.Options.artOptions = (options) => {
 				const span = App.UI.DOM.appendNewElement("span", advancedConfigDiv, "", ["warning"]);
 				span.innerHTML = `Failed to contact SD at <a class="link-external" target="_blank" href="${V.aiApiUrl}">${V.aiApiUrl}</a>! Make sure 'AI User Interface' is set correctly and that the provided url is accessable from this machine.`;
 			} else {
+				if (V.aiLoraPack) {
+					loraListDiv.textContent = "Loading LoRA list... This may take some time depending on how many LoRAs you have and your storage speed...";
+					setTimeout(App.Art.GenAI.UI.Options.loraList, 1);
+				} else {
+					loraListDiv.append(document.createElement("hr"));
+				}
 				App.Art.GenAI.UI.Options.advancedConfig(advancedConfigDiv);
 			}
 		});
@@ -476,16 +582,27 @@ App.Art.GenAI.UI.Options.artOptions = (options) => {
  * @param {HTMLDivElement} parent
  */
 App.Art.GenAI.UI.Options.advancedConfig = async function(parent) {
+	parent.textContent = `Getting info from ${V.aiUserInterface === 0 ? "A1111" : "ComfyUI"}...`;
 	let options = new App.UI.OptionsGroup();
-	if (V.aiUserInterface === 1) {
-		if (V.aiPrebuiltWorkflow === 1) {
-			return;
-		}
+	if (!(V.aiUserInterface === 1 && V.aiPrebuiltWorkflow === 1)) {
 		await App.Art.GenAI.sdClient.getCheckpointList().then(list => {
 			if (list.length === 0) {
 				options.addCustom(App.UI.DOM.makeElement('span', `Could not fetch valid checkpoints. Make sure you have at least one checkpoint installed.`, ["warning"]));
 			} else {
 				const checkpointSpan = App.UI.DOM.makeElement('span', ``);
+				if (!list.includes(V.aiCheckpoint)) {
+					if (V.aiUserInterface === 0 && V.aiCheckpoint.match(/\s\[.*\]$/) === null) {
+						// this is likely an A1111 checkpoint that didn't have a known hash and now it does
+						// so see if any checkpoints exist that start with the existing checkpoint path
+						for (const entry of list) {
+							if (entry.startsWith(V.aiCheckpoint)) {
+								// and fix the checkpoint key
+								V.aiCheckpoint = entry;
+								break;
+							}
+						}
+					}
+				}
 				if (!list.includes(V.aiCheckpoint)) {
 					checkpointSpan.classList.add('warning');
 					checkpointSpan.textContent = `ERROR: Valid options on your Stable Diffusion installation: ${toSentence(list)}.`;
@@ -498,18 +615,44 @@ App.Art.GenAI.UI.Options.advancedConfig = async function(parent) {
 					.addComment(App.UI.DOM.combineNodes(`The checkpoint model to use. `, checkpointSpan))
 					.pulldown();
 			}
+		}).catch((err) => {
+			options.addCustom(App.UI.DOM.makeElement('span', `Could not fetch valid checkpoints. This may be due to A1111 taking too long to load a checkpoint.`, ["warning"]));
+			options.addCustom(App.UI.DOM.link("Refresh", () => { App.UI.reload(); }, undefined, undefined, "Reload Options and try again"));
 		});
 
-		if (V.aiPrebuiltWorkflow === 2) {
-			options.addOption("Pony Diffusion Turbo Model", "aiPonyTurbo").addValue("Enabled", true).on().addValue("Disabled", false).off()
-				.addComment("Use preset for the faster (but noticeably lower fidelity) Pony Turbo model");
-			options.addOption("Custom LoRA list", "aiPonyLoraStack").showTextBox({large: true, forceString: true})
-				.addComment(`Enter a valid JSON with the name of each LoRA and its weight, e.g. { "Style LoRA Example 1.safetensors": 1.0, "Style 	LoRA Example 2.safetensors": 0.5 }`);
-			options.addOption("High Res Pass", "aiUpscale").addValue("Enabled", true).on().addValue("Disabled", false).off()
-				.addComment("Applies latent upscaling and then an img2img high res pass -- similar to A111's hiRes fix option");
-			options.addOption("Face Detailer", "aiFaceDetailer").addValue("Enabled", true).on().addValue("Disabled", false).off()
-				.addComment("Face detailer for Pony workflow");
-		}
+		await App.Art.GenAI.sdClient.getCheckpointPotentialBaseModels(V.aiCheckpoint)
+			.then((baseModels) => {
+				if (V.aiPrebuiltWorkflow === 2) {
+					V.aiBaseModel = 2;
+				} else {
+					const baseModelMap = {
+						0: "SD 1.X",
+						1: "SDXL",
+						2: "Pony",
+					};
+					/** @type {string[]} */
+					const baseModelStrings = baseModels.map(val => baseModelMap[val]);
+
+					const option = options.addOption("AI Base Model", "aiBaseModel").addValueList([
+						["SD 1.X", 0],
+						["SDXL", 1],
+						["Pony", 2],
+					]);
+					if (!baseModels.includes(V.aiBaseModel)) {
+						option.addComment(App.UI.DOM.makeElement("span", `This may be incorrect! Best guess${baseModels.length === 1 ? "" : "es"}: ${baseModelStrings.toString()}`, ["orange"]));
+					}
+				}
+			});
+	}
+	if (V.aiUserInterface === 1 && V.aiPrebuiltWorkflow === 2) {
+		options.addOption("Pony Diffusion Turbo Model", "aiPonyTurbo").addValue("Enabled", true).on().addValue("Disabled", false).off()
+			.addComment("Use preset for the faster (but noticeably lower fidelity) Pony Turbo model");
+		options.addOption("Custom LoRA list", "aiPonyLoraStack").showTextBox({large: true, forceString: true})
+			.addComment(`Enter a valid JSON with the name of each LoRA and its weight, e.g. { "Style LoRA Example 1.safetensors": 1.0, "Style 	LoRA Example 2.safetensors": 0.5 }`);
+		options.addOption("High Res Pass", "aiUpscale").addValue("Enabled", true).on().addValue("Disabled", false).off()
+			.addComment("Applies latent upscaling and then an img2img high res pass -- similar to A111's hiRes fix option");
+		options.addOption("Face Detailer", "aiFaceDetailer").addValue("Enabled", true).on().addValue("Disabled", false).off()
+			.addComment("Face detailer for Pony workflow");
 	}
 	if (V.aiPrebuiltWorkflow === 0 || V.aiUserInterface === 0) {
 		await App.Art.GenAI.sdClient.getSamplerList().then(list => {
@@ -712,5 +855,6 @@ App.Art.GenAI.UI.Options.advancedConfig = async function(parent) {
 			}
 		}
 	}
+	parent.textContent = "";
 	parent.append(options.render());
 };
diff --git a/src/gui/options/stableDiffusionInstallationGuide.js b/src/gui/options/stableDiffusionInstallationGuide.js
index 3a8c4fa074bd1cc3bbf2f4d96840e6b936b5512e..2c255c3239a768425676f8ceedab89a4fd501f11 100644
--- a/src/gui/options/stableDiffusionInstallationGuide.js
+++ b/src/gui/options/stableDiffusionInstallationGuide.js
@@ -1,6 +1,6 @@
 // cSpell:ignore Optimise, webui, medvram, rgthree
 
-const html = `
+const A1111InstallHTML = `
 <h1>What is Stable Diffusion and Automatic1111's Stable Diffusion WebUI?</h1>
 Stable Diffusion is an AI model for generating images given a text prompt. Automatic1111's Stable Diffusion WebUI is a web interface for running Stable Diffusion. It is the easiest way to run Stable Diffusion on your computer, and provides an API that we can use to integrate Stable Diffusion into other applications.
 
@@ -72,6 +72,7 @@ Once it's running, open your browser and go to <code>localhost:7860</code>. The
 </ol>
 `;
 
+// cSpell: ignore loras
 const loraHTML = `
 <h1>Why are LoRAs helpful?</h1>
 Slaves in the Free Cities can sometimes have unusual features that Stable Diffusion base models are not trained to handle. LoRAs are small models specifically designed to handle these situations.
@@ -81,7 +82,7 @@ Each of the LoRAs serves a specific purpose; you may install any or all of them
 
 Note that many of these LoRAs tend to work better on less realistic base models. If you have many slaves with exotic features, it may be worth switching to an anime-style or pseudorealistic model, rather than a realistic one.
 
-Download and copy any LoRAs that you want to use into your '<code>stable-diffusion-webui/models/Lora/</code>' folder (see the Stable Diffusion Installation instructions for details).
+Download and copy any LoRAs that you want to use into your '<code>stable-diffusion-webui/models/Lora/</code>' (A1111) or '<code>ComfyUI/models/loras/</code>' (ComfyUI) folder.
 `;
 
 const comfyHTML = `
@@ -119,14 +120,17 @@ Restart ComfyUI
 	<li>A blank grey square; It's probably a prompt related issue, check the ComfyUI terminal, the issue should be mentioned there.</li>
 </ol>
 
+`;
+
+const comfyCustomWorkflowHTML = `
 <h1>Instructions to run custom workflows in FC with a locally hosted copy</h1>
 
 <h2>1. Host FC locally:</h2>
 <ol>
-  <li>1. Run the <code>compile.bat</code> (Windows) or <code>compile.sh</code> (Linux/Mac) script inside the FC directory.</li>
-  <li>2. Follow the instructions for installing the required dependencies and wait for the compiler to compile FC.</li>
-  <li>3. Run the <code>serve.bat</code> (Windows) or <code>serve.sh</code> (Linux/Mac) script to start the local server.</li>
-  <li>4. Open your web browser and go to the local server url (usually <a class="link-external" target="_blank" href="http://localhost:6969/bin/FC_pregmod.html"><code>http://localhost:6969/bin/FC_pregmod.html</code></a>).</li>
+  <li>Run the <code>compile.bat</code> (Windows) or <code>compile.sh</code> (Linux/Mac) script inside the FC directory.</li>
+  <li>Follow the instructions for installing the required dependencies and wait for the compiler to compile FC.</li>
+  <li>Run the <code>serve.bat</code> (Windows) or <code>serve.sh</code> (Linux/Mac) script to start the local server.</li>
+  <li>Open your web browser and go to the local server url (usually <a class="link-external" target="_blank" href="http://localhost:6969/bin/FC_pregmod.html"><code>http://localhost:6969/bin/FC_pregmod.html</code></a>).</li>
   <li>Repeat steps 1 & 2 each time you update FC</li>
   <li>Repeat steps 3 & 4 each time you want to play FC</li>
 </ol>
@@ -134,7 +138,7 @@ Restart ComfyUI
 <h2>2. Prepare your Workflow:</h2>
 <ol>
   <li>Identify the node that holds your seed image and add the word "Seed" (case-insensitive) to its title.</li> 
-  <li>Find the nodes containing your positive and negative prompts, and add "Positive Prompts" and "Negative Prompts" (respectively, case-insensitive) to their titles. If both prompts are in the same node, convert one of the prompt widgets to an input and use an extension like WAS Node Suite.</li>
+  <li>Find the nodes containing your positive and negative prompts, and add "Positive Prompt" and "Negative Prompt" (respectively, case-insensitive) to their titles. If both prompts are in the same node, convert one of the prompt widgets to an input and use an extension like WAS Node Suite.</li>
   <li>Ensure that the final generated image is connected to a SaveImageWebsocket node.</li>
 </ol>
 
@@ -144,7 +148,7 @@ Restart ComfyUI
   <li>Click the "Save in API Format" button.</li>
   <li>Create a folder named "workflows" inside the "bin/resources" directory.</li>
   <li>Save your workflow file in the "workflows" folder.</li> 
-  <li>In FC, switch the AI Prebuilt Workflow option to Custom Workflow and enter the name of your saved workflow file into the workflow option field.</li>
+  <li>In FC, switch the AI Prebuilt Workflow option to Custom Workflow and enter the filename of your saved workflow file into the workflow option field.</li>
 </ol>
 
 <p>By following these steps, you should be able to run custom workflows in your locally hosted instance of FC.</p>
@@ -152,14 +156,14 @@ Restart ComfyUI
 `;
 
 /**
- * Generates a link which shows a Stable Diffusion installation guide.
+ * Generates a link which shows the A1111 installation guide.
  * @param {string} [text] link text to use
  * @returns {HTMLElement} link
  */
-App.UI.stableDiffusionInstallationGuide = function(text) {
-	return App.UI.DOM.link(text ? text : "Stable Diffusion Installation Guide", () => {
-		Dialog.create("Stable Diffusion Installation Guide");
-		const content = document.createElement("div").innerHTML = html;
+App.UI.A1111InstallationGuide = function(text) {
+	return App.UI.DOM.link(text ? text : "A1111 WebUI Installation Guide", () => {
+		Dialog.create("A1111 WebUI Installation Guide");
+		const content = document.createElement("div").innerHTML = A1111InstallHTML;
 		Dialog.append(content);
 		Dialog.open();
 	});
@@ -192,3 +196,17 @@ App.UI.comfyUIInstallationGuide = function(text) {
 		Dialog.open();
 	});
 };
+
+/**
+ * Generates a link with the ComfyUI custom workflow guide.
+ * @param {string} [text] link text to use
+ * @returns {HTMLElement} link
+ */
+App.UI.comfyUICustomWorkflowGuide = function(text) {
+	return App.UI.DOM.link(text ? text : "ComfyUI Custom Workflow Guide", () => {
+		Dialog.create("ComfyUI Custom Workflow Guide");
+		const content = document.createElement("div").innerHTML = comfyCustomWorkflowHTML;
+		Dialog.append(content);
+		Dialog.open();
+	});
+};