Add outfit gallery and AI-powered creation for characters and outfits #2

Merged
aodhan merged 1 commits from clothing-gallery into master 2026-02-19 18:36:03 +00:00
87 changed files with 2325 additions and 384 deletions

View File

@@ -5,6 +5,8 @@ A local web-based GUI for managing character profiles (JSON) and generating cons
## Features ## Features
- **Character Gallery**: Automatically scans your `characters/` folder and builds a searchable, sortable database. - **Character Gallery**: Automatically scans your `characters/` folder and builds a searchable, sortable database.
- **Outfit Gallery**: Manage reusable outfit presets that can be applied to any character.
- **AI-Powered Creation**: Create new characters and outfits using AI to generate profiles from descriptions, or manually create blank templates.
- **Granular Prompt Control**: Every field in your character JSON (Identity, Wardrobe, Styles) has a checkbox. You decide exactly what is sent to the AI. - **Granular Prompt Control**: Every field in your character JSON (Identity, Wardrobe, Styles) has a checkbox. You decide exactly what is sent to the AI.
- **ComfyUI Integration**: - **ComfyUI Integration**:
- **SDXL Optimized**: Designed for high-quality SDXL workflows. - **SDXL Optimized**: Designed for high-quality SDXL workflows.
@@ -45,6 +47,11 @@ A local web-based GUI for managing character profiles (JSON) and generating cons
## Usage ## Usage
### Creating Characters & Outfits
- **AI Generation**: Toggle "Use AI to generate profile from description" on, then describe your character or outfit. The AI will generate a complete profile with appropriate tags.
- **Manual Creation**: Toggle AI generation off to create a blank template you can edit yourself.
- **Auto-naming**: Leave the filename field empty to auto-generate one from the name. If a file already exists, a number will be appended automatically.
### Gallery Management ### Gallery Management
- **Rescan**: Use the "Rescan Character Files" button if you've added new JSON files or manually edited them. - **Rescan**: Use the "Rescan Character Files" button if you've added new JSON files or manually edited them.
- **Save Defaults**: On a character page, select your favorite prompt combination and click "Save as Default Selection" to remember it for future quick generations. - **Save Defaults**: On a character page, select your favorite prompt combination and click "Save as Default Selection" to remember it for future quick generations.
@@ -57,9 +64,85 @@ A local web-based GUI for managing character profiles (JSON) and generating cons
./launch.sh --clean ./launch.sh --clean
``` ```
## JSON Structure
### Character Profile
```json
{
"character_id": "example_character",
"character_name": "Example Character",
"identity": {
"base_specs": "1girl, slender build, fair skin",
"hair": "long blue hair",
"eyes": "blue eyes",
"hands": "",
"arms": "",
"torso": "",
"pelvis": "",
"legs": "",
"feet": "",
"extra": ""
},
"defaults": {
"expression": "smile",
"pose": "standing",
"scene": "simple background"
},
"wardrobe": {
"default": {
"full_body": "",
"headwear": "",
"top": "white blouse",
"bottom": "blue skirt",
"legwear": "black thighhighs",
"footwear": "black shoes",
"hands": "",
"accessories": "ribbon"
}
},
"styles": {
"aesthetic": "anime style",
"primary_color": "blue",
"secondary_color": "white",
"tertiary_color": ""
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
},
"tags": ["tag1", "tag2"]
}
```
### Outfit Profile
```json
{
"outfit_id": "school_uniform_01",
"outfit_name": "School Uniform",
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "white blouse, sailor collar",
"bottom": "pleated skirt",
"legwear": "knee socks",
"footwear": "loafers",
"hands": "",
"accessories": "ribbon tie"
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": ["school uniform", "uniform"]
}
```
## File Structure ## File Structure
- `/characters`: Your character JSON files. - `/data/characters`: Your character JSON files.
- `/data/clothing`: Outfit preset JSON files.
- `/static/uploads`: Generated images (organized by character subfolders). - `/static/uploads`: Generated images (organized by character subfolders).
- `/templates`: HTML UI using Bootstrap 5. - `/templates`: HTML UI using Bootstrap 5.
- `app.py`: Flask backend and prompt-building logic. - `app.py`: Flask backend and prompt-building logic.

873
app.py

File diff suppressed because it is too large Load Diff

View File

@@ -1,45 +0,0 @@
{
"character_id": "camilla_(fire_emblem)",
"character_name": "Camilla Nohr",
"identity": {
"base_specs": "1girl, curvaceous build, fair skin",
"hair": "long wavy lavender hair, hair covering one eye",
"eyes": "purple eyes",
"hands": "purple nails",
"arms": "",
"torso": "large breasts",
"pelvis": "",
"legs": "",
"feet": "",
"extra": "black headband with horns"
},
"defaults": {
"expression": "",
"pose": "",
"scene": ""
},
"wardrobe": {
"default": {
"headwear": "",
"top": "black armor, cleavage",
"legwear": "black leggings, armored plates",
"footwear": "black armored boots",
"hands": "",
"accessories": "purple cape, large axe"
}
},
"styles": {
"aesthetic": "dark fantasy, gothic, fire emblem style",
"primary_color": "black",
"secondary_color": "gold",
"tertiary_color": "purple"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
},
"tags": [
"Fire Emblem"
]
}

View File

@@ -169,5 +169,15 @@
"clip": ["4", 1] "clip": ["4", 1]
}, },
"class_type": "LoraLoader" "class_type": "LoraLoader"
},
"17": {
"inputs": {
"lora_name": "",
"strength_model": 0.8,
"strength_clip": 0.8,
"model": ["16", 0],
"clip": ["16", 1]
},
"class_type": "LoraLoader"
} }
} }

View File

@@ -0,0 +1,16 @@
{
"action_id": "belly_dancing",
"action_name": "Belly Dancing",
"action": {
"full_body": "belly dancing",
"head": "",
"eyes": "",
"arms": "hands above head",
"hands": "hands together",
"torso": "",
"pelvis": "shaking hips",
"legs": "",
"feet": "",
"additional": ""
}
}

View File

@@ -13,36 +13,40 @@
"extra": "pink hair ribbon" "extra": "pink hair ribbon"
}, },
"defaults": { "defaults": {
"expression": "", "expression": "gentle smile, looking at viewer",
"pose": "", "pose": "handing flower to viewer",
"scene": "" "scene": "city street, night"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "long pink dress",
"headwear": "", "headwear": "",
"top": "pink dress, red bolero jacket", "top": "red bolero jacket",
"legwear": "long pink dress", "bottom": "",
"legwear": "",
"footwear": "brown boots", "footwear": "brown boots",
"hands": "", "hands": "",
"accessories": "gold bracelets, flower basket" "accessories": "gold bracelets, flower basket"
}, },
"red_dress": { "red_dress": {
"full_body": "long dress, frilled dress, red dress",
"headwear": "red hair ribbons", "headwear": "red hair ribbons",
"top": "long dress, frilled dress, red dress", "top": "",
"legwear": "long dress, frilled dress, red dress", "bottom": "",
"legwear": "",
"footwear": "white high heels", "footwear": "white high heels",
"hands": "red nails", "hands": "red nails",
"accessories": "gold bracelets" "accessories": "gold bracelets"
} }
}, },
"styles": { "styles": {
"aesthetic": "floral, gentle, final fantasy style", "aesthetic": "floral, final fantasy vii style",
"primary_color": "pink", "primary_color": "pink",
"secondary_color": "red", "secondary_color": "red",
"tertiary_color": "brown" "tertiary_color": "brown"
}, },
"lora": { "lora": {
"lora_name": "Illustrious/Looks/Aerith.safetensors", "lora_name": "",
"lora_weight": 1.0, "lora_weight": 1.0,
"lora_triggers": "" "lora_triggers": ""
}, },

View File

@@ -14,22 +14,24 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "neutral",
"pose": "", "pose": "tucking hair behind ear",
"scene": "" "scene": "wasteland, mountains, "
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "black long sleeved shirt, striped sleeves", "full_body": "",
"top": "blue denim vest,", "headwear": "",
"legwear": "blue denim skirt, black stockings", "top": "blue denim vest,black long sleeved shirt, striped sleeves",
"bottom": "blue denim skirt",
"legwear": "black stockings",
"footwear": "brown boots", "footwear": "brown boots",
"hands": "", "hands": "",
"accessories": "gold hoop earrings" "accessories": "gold hoop earrings"
} }
}, },
"styles": { "styles": {
"aesthetic": "wasteland, mountains, anime, dragon ball style", "aesthetic": "anime, dragon ball style",
"primary_color": "blue", "primary_color": "blue",
"secondary_color": "black", "secondary_color": "black",
"tertiary_color": "white" "tertiary_color": "white"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "black Eden Academy uniform, gold trim",
"headwear": "", "headwear": "",
"top": "black Eden Academy uniform, gold trim", "top": "",
"legwear": "uniform skirt", "bottom": "",
"legwear": "",
"footwear": "black shoes, white socks", "footwear": "black shoes, white socks",
"hands": "", "hands": "",
"accessories": "black and gold hair cones" "accessories": "black and gold hair cones"

View File

@@ -14,22 +14,24 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "thinking",
"pose": "", "pose": "reading",
"scene": "" "scene": "library, sun beam"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white shirt", "full_body": "tracen school uniform",
"top": "tracen school uniform", "headwear": "",
"legwear": "pleated skirt", "top": "",
"bottom": "",
"legwear": "",
"footwear": "heeled shoes", "footwear": "heeled shoes",
"hands": "", "hands": "",
"accessories": "" "accessories": ""
} }
}, },
"styles": { "styles": {
"aesthetic": "library,intellectual,", "aesthetic": "anime,umasumame",
"primary_color": "maroon", "primary_color": "maroon",
"secondary_color": "white", "secondary_color": "white",
"tertiary_color": "grey" "tertiary_color": "grey"

View File

@@ -20,8 +20,10 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "black playboy bunny",
"headwear": "", "headwear": "",
"top": "black playboy bunny", "top": "",
"bottom": "",
"legwear": "pantyhose", "legwear": "pantyhose",
"footwear": "red high heels", "footwear": "red high heels",
"hands": "detatched cuffs", "hands": "detatched cuffs",

View File

@@ -20,10 +20,12 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "belt between breasts", "full_body": "black armor, gold trim",
"top": "black armor, gold trim, cleavage", "headwear": "",
"legwear": "purple sash, pelvic curtain, black panties", "top": "belt between breasts, cleavage",
"footwear": "black armored thigh boots", "bottom": "purple sash, pelvic curtain, black panties",
"legwear": "black armored thigh boots",
"footwear": "gold heels",
"hands": "purple velvet gloves", "hands": "purple velvet gloves",
"accessories": "purple cape, large axe" "accessories": "purple cape, large axe"
} }

View File

@@ -20,8 +20,10 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "green high-leg leotard",
"headwear": "", "headwear": "",
"top": "green high-leg leotard", "top": "",
"bottom": "",
"legwear": "bare legs", "legwear": "bare legs",
"footwear": "black combat boots, green socks", "footwear": "black combat boots, green socks",
"hands": "red gauntlets", "hands": "red gauntlets",

View File

@@ -14,22 +14,24 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "confident",
"pose": "", "pose": "fighting stance",
"scene": "" "scene": "market, daytime"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "blue qipao, gold embroidery, white accents",
"headwear": "", "headwear": "",
"top": "blue qipao, gold embroidery, white accents, puffy shoulders", "top": " puffy shoulders",
"legwear": "brown tights", "bottom": "brown tights",
"legwear": "",
"footwear": "white lace-up boots", "footwear": "white lace-up boots",
"hands": "", "hands": "",
"accessories": "white hair ribbons, spiked bracelets" "accessories": "white hair ribbons, spiked bracelets"
} }
}, },
"styles": { "styles": {
"aesthetic": "chinese style, market,", "aesthetic": "chinese style, ",
"primary_color": "blue", "primary_color": "blue",
"secondary_color": "white", "secondary_color": "white",
"tertiary_color": "gold" "tertiary_color": "gold"

View File

@@ -14,22 +14,24 @@
"extra": "scar over eye" "extra": "scar over eye"
}, },
"defaults": { "defaults": {
"expression": "", "expression": "serious",
"pose": "", "pose": "fighting stance, holding sword",
"scene": "" "scene": ""
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white blouse", "full_body": "",
"top": "", "headwear": "",
"legwear": "brown leather trousers", "top": "white blouse",
"bottom": "brown leather trousers",
"legwear": "",
"footwear": "brown leather boots", "footwear": "brown leather boots",
"hands": "brown leather gloves", "hands": "brown leather gloves",
"accessories": "silver sword on back, witcher medallion" "accessories": "silver sword on back, witcher medallion"
} }
}, },
"styles": { "styles": {
"aesthetic": "gritty, fantasy, witcher style", "aesthetic": "fantasy, witcher style",
"primary_color": "white", "primary_color": "white",
"secondary_color": "brown", "secondary_color": "brown",
"tertiary_color": "silver" "tertiary_color": "silver"

View File

@@ -5,7 +5,7 @@
"base_specs": "1girl, milf, gyaru, tall", "base_specs": "1girl, milf, gyaru, tall",
"hair": "blonde hair, long hair", "hair": "blonde hair, long hair",
"eyes": "sharp eyes, black eyes, white pupil,", "eyes": "sharp eyes, black eyes, white pupil,",
"hands": "painted nails", "hands": "red nails",
"arms": "", "arms": "",
"torso": "very large breasts", "torso": "very large breasts",
"pelvis": "wide hips", "pelvis": "wide hips",
@@ -14,18 +14,20 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "naughty face",
"pose": "", "pose": "spread legs, leopard print panties",
"scene": "" "scene": "sitting on couch, from below"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "cleavage", "full_body": "",
"top": "light brown sweater, ", "headwear": "",
"legwear": "black skirt", "top": "light brown sweater, cleavage",
"bottom": "leopard print skirt",
"legwear": "",
"footwear": "red high heels", "footwear": "red high heels",
"hands": "", "hands": "",
"accessories": "necklace, rings" "accessories": "necklace, rings,"
} }
}, },
"styles": { "styles": {

View File

@@ -14,22 +14,24 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "bored",
"pose": "", "pose": "looking at phone",
"scene": "" "scene": "sitting on bench"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white shirt", "full_body": "tracen school uniform",
"top": "tracen school uniform", "headwear": "",
"legwear": "pleated skirt", "top": "",
"bottom": "",
"legwear": "",
"footwear": "heeled shoes", "footwear": "heeled shoes",
"hands": "", "hands": "",
"accessories": "choker, earrings" "accessories": "choker, earrings"
} }
}, },
"styles": { "styles": {
"aesthetic": "shopping,modeling,school yard", "aesthetic": "modeling,school yard",
"primary_color": "gold", "primary_color": "gold",
"secondary_color": "white", "secondary_color": "white",
"tertiary_color": "black" "tertiary_color": "black"

View File

@@ -14,15 +14,17 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "smile",
"pose": "", "pose": "running toward viewer",
"scene": "" "scene": "horse race track, sunshine"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white shirt", "full_body": "tracen school uniform",
"top": "tracen school uniform", "headwear": "",
"legwear": "pleated skirt", "top": "",
"bottom": "",
"legwear": "",
"footwear": "heeled shoes", "footwear": "heeled shoes",
"hands": "", "hands": "",
"accessories": "ear covers, hat" "accessories": "ear covers, hat"

View File

@@ -20,8 +20,10 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "",
"headwear": "", "headwear": "",
"top": "grey sleeveless shirt, turquoise tie", "top": "grey sleeveless shirt, turquoise tie",
"bottom": "",
"legwear": "grey miniskirt, turquoise trim", "legwear": "grey miniskirt, turquoise trim",
"footwear": "black thigh-high boots, turquoise trim", "footwear": "black thigh-high boots, turquoise trim",
"hands": "black arm warmers, turquoise trim", "hands": "black arm warmers, turquoise trim",

View File

@@ -14,22 +14,24 @@
"extra": "heavy eyeliner, winged eyeliner" "extra": "heavy eyeliner, winged eyeliner"
}, },
"defaults": { "defaults": {
"expression": "", "expression": "gentle smile",
"pose": "", "pose": "hand in water",
"scene": "" "scene": "sitting beside fountain, sunshine"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "teal crop top, tube top, off-shoulder, cleavage", "full_body": "",
"top": "", "headwear": "",
"legwear": "teal harem pants, baggy pants, sheer fabric", "top": "teal crop top, tube top, off-shoulder, cleavage",
"bottom": "teal harem pants",
"legwear": " baggy pants, sheer fabric",
"footwear": "gold shoes, curling toes, pointed shoes", "footwear": "gold shoes, curling toes, pointed shoes",
"hands": "", "hands": "",
"accessories": "gold hoop earrings, large gold necklace, blue headband, jewel on headband" "accessories": "gold hoop earrings, large gold necklace, blue headband, jewel on headband"
} }
}, },
"styles": { "styles": {
"aesthetic": "desert palace, large fountain, arabian, disney, cartoon, vibrant, ferns", "aesthetic": "desert arabian, disney, cartoon, ",
"primary_color": "teal", "primary_color": "teal",
"secondary_color": "gold", "secondary_color": "gold",
"tertiary_color": "black" "tertiary_color": "black"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "red sequin evening gown",
"headwear": "", "headwear": "",
"top": "red sequin dress, strapless, high slit, backless", "top": "strapless, backless",
"legwear": "side slit,", "bottom": "high slit",
"legwear": "side slit",
"footwear": "red high heels", "footwear": "red high heels",
"hands": "purple opera gloves", "hands": "purple opera gloves",
"accessories": "gold earrings, glitter" "accessories": "gold earrings, glitter"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "black crop top", "full_body": "",
"top": "white Team Rocket uniform jacket, bare stomach, red R logo", "headwear": "",
"legwear": "white miniskirt", "top": "black crop top,white Team Rocket uniform jacket, red R logo",
"bottom": "midriff, white miniskirt",
"legwear": "",
"footwear": "black thigh-high boots", "footwear": "black thigh-high boots",
"hands": "black elbow gloves", "hands": "black elbow gloves",
"accessories": "green earrings" "accessories": "green earrings"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "",
"headwear": "", "headwear": "",
"top": "pink and black bikini, asymmetrical_bikini ", "top": "pink and black bikini, asymmetrical_bikini ",
"legwear": "pink shorts, single pink stocking", "bottom": "pink shorts",
"legwear": "single pink stocking",
"footwear": "combat boots", "footwear": "combat boots",
"hands": "black fingerless gloves, fishnet elbow gloves,", "hands": "black fingerless gloves, fishnet elbow gloves,",
"accessories": "ammo belts, choker, bullet necklace," "accessories": "ammo belts, choker, bullet necklace,"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white shirt, sailor collar", "full_body": "",
"top": "", "headwear": "",
"legwear": "black shorts, yellow belt", "top": "white shirt, sailor collar",
"bottom": "black shorts, yellow belt",
"legwear": "knee-high socks",
"footwear": "white shoes", "footwear": "white shoes",
"hands": "", "hands": "",
"accessories": "headset, hair bow" "accessories": "headset, hair bow"

View File

@@ -20,10 +20,12 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white shirt", "full_body": "luna nova school uniform",
"top": "dark blue witch robes", "headwear": "",
"legwear": "dark blue skirt", "top": "white shirt,dark blue witch robes",
"footwear": "brown boots, white socks", "bottom": "dark blue skirt",
"legwear": "white socks",
"footwear": "brown boots",
"hands": "", "hands": "",
"accessories": "pointed witch hat, brown belt, magic wand" "accessories": "pointed witch hat, brown belt, magic wand"
} }

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "silver crop top", "full_body": "",
"top": "white and silver jacket", "headwear": "",
"legwear": "black leather shorts", "top": "silver crop top, white and silver jacket",
"bottom": "black leather shorts",
"legwear": "",
"footwear": "black thigh-high boots", "footwear": "black thigh-high boots",
"hands": "", "hands": "",
"accessories": "crystal heart, silver jewelry" "accessories": "crystal heart, silver jewelry"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "black crop top", "full_body": "",
"top": "blue and silver motorcycle jacket", "headwear": "",
"legwear": "black leather pants", "top": "black crop top, blue and silver motorcycle jacket",
"bottom": "black leather pants",
"legwear": "",
"footwear": "blue sneakers", "footwear": "blue sneakers",
"hands": "black fingerless gloves", "hands": "black fingerless gloves",
"accessories": "kama and kunai" "accessories": "kama and kunai"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "black leather bra", "full_body": "",
"top": "iridescent blue jacket, fur collar", "headwear": "",
"legwear": "black leather skirt", "top": "black leather bra, iridescent blue jacket, fur collar",
"bottom": "black leather skirt",
"legwear": "",
"footwear": "black high-heeled boots", "footwear": "black high-heeled boots",
"hands": "", "hands": "",
"accessories": "diamond earrings" "accessories": "diamond earrings"

View File

@@ -20,8 +20,10 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "silver bodysuit", "full_body": "silver bodysuit",
"headwear": "",
"top": "white and silver jacket", "top": "white and silver jacket",
"bottom": "",
"legwear": "silver leggings", "legwear": "silver leggings",
"footwear": "silver high-heeled boots", "footwear": "silver high-heeled boots",
"hands": "", "hands": "",

View File

@@ -14,15 +14,17 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "shy,",
"pose": "", "pose": "holding notebook",
"scene": "" "scene": "classroom"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white shirt", "full_body": "itan private high school uniform",
"top": "itan private high school uniform, blazer, striped bow tie", "headwear": "",
"legwear": "plaid skirt", "top": "blazer, striped bow tie, white shirt",
"bottom": "plaid skirt",
"legwear": "black pantyhose",
"footwear": "loafers", "footwear": "loafers",
"hands": "", "hands": "",
"accessories": "" "accessories": ""

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "",
"headwear": "", "headwear": "",
"top": "teal tank top,", "top": "teal tank top,",
"legwear": "brown shorts", "bottom": "brown shorts",
"legwear": "thigh holsters",
"footwear": "brown combat boots, red laces", "footwear": "brown combat boots, red laces",
"hands": "black fingerless gloves", "hands": "black fingerless gloves",
"accessories": "dual thigh pistol holsters, brown leatherbackpack, red round sunglasses" "accessories": "dual thigh pistol holsters, brown leatherbackpack, red round sunglasses"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "",
"headwear": "purple dress, corset", "headwear": "purple dress, corset",
"top": "purple shawl", "top": "purple shawl",
"legwear": "slit skirt", "bottom": "slit skirt",
"legwear": "",
"footwear": "black heels", "footwear": "black heels",
"hands": "purple gloves", "hands": "purple gloves",
"accessories": "witch hat, rose, necklace" "accessories": "witch hat, rose, necklace"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "black corset", "full_body": "",
"top": "black fur-trimmed dress, many belts on front", "headwear": "",
"legwear": "long skirt made of belts", "top": "black fur-trimmed dress, many belts on front, black corset",
"bottom": "long skirt made of belts",
"legwear": "",
"footwear": "black boots", "footwear": "black boots",
"hands": "", "hands": "",
"accessories": "moogle doll, silver jewelry" "accessories": "moogle doll, silver jewelry"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "black tube top", "full_body": "",
"top": "", "headwear": "",
"legwear": "white harem pants", "top": "black tube top",
"bottom": "white harem pants",
"legwear": "baggy pants",
"footwear": "black and yellow boots", "footwear": "black and yellow boots",
"hands": "black sleeves", "hands": "black sleeves",
"accessories": "gold bracelets, gold neck ring, hoop earrings,pink donut" "accessories": "gold bracelets, gold neck ring, hoop earrings,pink donut"

View File

@@ -14,22 +14,34 @@
"extra": "piercings" "extra": "piercings"
}, },
"defaults": { "defaults": {
"expression": "", "expression": "happy",
"pose": "", "pose": "v",
"scene": "" "scene": "sewing room"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "black bikini with yellow flower print", "full_body": "",
"headwear": "",
"top": "white school shirt, loosely tied blue tie", "top": "white school shirt, loosely tied blue tie",
"bottom": "",
"legwear": "blue plaid miniskirt", "legwear": "blue plaid miniskirt",
"footwear": "black loafers, black socks", "footwear": "black loafers, black socks",
"hands": "", "hands": "",
"accessories": "choker, colored bracelets" "accessories": "choker, colored bracelets"
},
"bikini": {
"full_body": "",
"headwear": "",
"top": "black bikini with yellow flower print",
"bottom": "",
"legwear": "",
"footwear": "barefoot",
"hands": "",
"accessories": "choker, colored bracelets"
} }
}, },
"styles": { "styles": {
"aesthetic": "gyaru, modern, anime style, sewing machine", "aesthetic": "gyaru, modern, anime style,",
"primary_color": "white", "primary_color": "white",
"secondary_color": "blue", "secondary_color": "blue",
"tertiary_color": "pink" "tertiary_color": "pink"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "",
"headwear": "", "headwear": "",
"top": "crop top, detached sleeves, gold trim", "top": "crop top, detached sleeves, gold trim",
"legwear": "side slit, lace-up skirt", "bottom": "lace-up skirt",
"legwear": "side slit",
"footwear": "thinghighs, lace-up boots, gold boots, gold armlet", "footwear": "thinghighs, lace-up boots, gold boots, gold armlet",
"hands": "", "hands": "",
"accessories": "headset" "accessories": "headset"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "",
"headwear": "red crop top, sleeveless", "headwear": "red crop top, sleeveless",
"top": "", "top": "",
"legwear": "red skirt, mini skirt", "bottom": "red skirt, mini skirt",
"legwear": "",
"footwear": "brown boots", "footwear": "brown boots",
"hands": "", "hands": "",
"accessories": "choker" "accessories": "choker"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white crop top, blue trim", "full_body": "blue trim",
"top": "gym uniform, number '049'", "headwear": "",
"legwear": "midriff, white and blue shorts, black trim", "top": "white crop top, gym uniform, number '049'",
"bottom": "midriff,white and blue shorts, black trim",
"legwear": "",
"footwear": "white and blue sandals, orange trim", "footwear": "white and blue sandals, orange trim",
"hands": "fingerless gloves", "hands": "fingerless gloves",
"accessories": "wristband, small life buoy, pokeball, gold hoop earrings" "accessories": "wristband, small life buoy, pokeball, gold hoop earrings"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "black shirt", "full_body": "",
"top": "blue military coat, fur collar", "headwear": "",
"legwear": "black pants", "top": "blue military coat, fur collar, black shirt",
"bottom": "black pants",
"legwear": "",
"footwear": "black boots", "footwear": "black boots",
"hands": "black gloves", "hands": "black gloves",
"accessories": "sword" "accessories": "sword"

View File

@@ -14,15 +14,17 @@
"extra": "pink lips, blue earrings" "extra": "pink lips, blue earrings"
}, },
"defaults": { "defaults": {
"expression": "", "expression": "smile",
"pose": "", "pose": "hands together",
"scene": "" "scene": "throne room"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white petticoat", "full_body": "pink ball gown",
"top": "pink floor-length ball gown, puffy sleeves, dark pink panniers", "headwear": "gold crown",
"legwear": "long skirt", "top": "white petticoat, puffy sleeves, dark pink panniers",
"bottom": "",
"legwear": "floor length skirt",
"footwear": "red high heels", "footwear": "red high heels",
"hands": "white opera gloves", "hands": "white opera gloves",
"accessories": "gold crown with red and blue jewels, blue brooch" "accessories": "gold crown with red and blue jewels, blue brooch"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "blue tunic", "full_body": "",
"top": "blue champion's tunic, brown leather belts", "headwear": "",
"legwear": "tan trousers", "top": "blue champion's tunic, ",
"bottom": "brown leather belts, tan trousers",
"legwear": "",
"footwear": "brown leather boots", "footwear": "brown leather boots",
"hands": "brown fingerless gloves", "hands": "brown fingerless gloves",
"accessories": "sheikah slate, gold jewelry" "accessories": "sheikah slate, gold jewelry"

View File

@@ -20,12 +20,14 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white shirt", "full_body": "tracen school uniform",
"top": "tracen school uniform", "headwear": "hair flower, small hat",
"legwear": "pleated skirt", "top": "white shirt",
"bottom": "pleated skirt",
"legwear": "",
"footwear": "heeled shoes", "footwear": "heeled shoes",
"hands": "", "hands": "",
"accessories": "blue rose, hair flower, small hat," "accessories": "blue rose"
} }
}, },
"styles": { "styles": {

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "",
"headwear": "", "headwear": "",
"top": "black top, gold trim", "top": "black top, gold trim",
"legwear": "black sarong, pelvic curtain,", "bottom": "black sarong, pelvic curtain",
"legwear": "",
"footwear": "black high heels, gold trim", "footwear": "black high heels, gold trim",
"hands": "", "hands": "",
"accessories": "gold jewelry, earrings," "accessories": "gold jewelry, earrings,"

View File

@@ -20,8 +20,10 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "", "full_body": "turquoise gown, silver trim",
"top": "turquoise off-the-shoulder gown, silver trim", "headwear": "silver crown",
"top": "bare shoulders",
"bottom": "",
"legwear": "long skirt", "legwear": "long skirt",
"footwear": "silver high heels", "footwear": "silver high heels",
"hands": "", "hands": "",

View File

@@ -4,7 +4,7 @@
"identity": { "identity": {
"base_specs": "1girl, anthro, bat girl, white fur", "base_specs": "1girl, anthro, bat girl, white fur",
"hair": "short white hair", "hair": "short white hair",
"eyes": "teal eyes", "eyes": "teal eyes, blue eyeshadow",
"hands": "white gloves", "hands": "white gloves",
"arms": "", "arms": "",
"torso": "large breasts", "torso": "large breasts",
@@ -20,12 +20,14 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "black skin-tight jumpsuit",
"headwear": "", "headwear": "",
"top": "black skin-tight jumpsuit, pink heart-shaped chest plate, bare shoulders, cleavage", "top": "pink heart-shaped chest plate, bare shoulders, cleavage",
"legwear": "jumpsuit", "bottom": "",
"legwear": "",
"footwear": "white boots, pink heart motifs", "footwear": "white boots, pink heart motifs",
"hands": "white gloves, pink cuffs", "hands": "white gloves, pink cuffs",
"accessories": "blue eyeshadow" "accessories": ""
} }
}, },
"styles": { "styles": {

View File

@@ -20,12 +20,14 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "long white dress, plunging neckline, black belt", "full_body": "long white dress",
"top": "black and orange long sleeve jacket with purple trim,", "headwear": "",
"legwear": "side_slit,, red trousers", "top": "black and orange long sleeve jacket with purple trim, plunging neckline",
"bottom": "black belt, red trousers",
"legwear": "side slit",
"footwear": "", "footwear": "",
"hands": "red gloves", "hands": "red gloves, red gem on back of hand",
"accessories": "red gems, wristbands" "accessories": "wristbands"
} }
}, },
"styles": { "styles": {

View File

@@ -20,8 +20,10 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "green bodysuit, catsuit, skin tight", "full_body": "green bodysuit, catsuit, skin tight",
"headwear": "",
"top": "", "top": "",
"bottom": "",
"legwear": "", "legwear": "",
"footwear": "heels ", "footwear": "heels ",
"hands": "green bodysuit", "hands": "green bodysuit",

View File

@@ -20,10 +20,12 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "blue skin-tight bodysuit, zero suit, pink lines",
"headwear": "", "headwear": "",
"top": "blue skin-tight bodysuit, pink symbols", "top": "",
"legwear": "bodysuit", "bottom": "",
"footwear": "blue high-heeled boots", "legwear": "",
"footwear": "metal high-heeled boots, yellow glow",
"hands": "zero suit", "hands": "zero suit",
"accessories": "paralyzer pistol" "accessories": "paralyzer pistol"
} }

View File

@@ -14,29 +14,31 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "smile",
"pose": "", "pose": "leaning back",
"scene": "" "scene": "living room, couch, low lighting"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "grey t-shirt, white shirt", "full_body": "",
"headwear": "grey t-shirt,",
"top": "", "top": "",
"legwear": "blue jeans", "bottom": "",
"footwear": "sneakers", "legwear": "pajama pants",
"footwear": "barefoot",
"hands": "", "hands": "",
"accessories": "wristwatch" "accessories": "wristwatch"
} }
}, },
"styles": { "styles": {
"aesthetic": "casual, 2013 fashion, living room", "aesthetic": "casual, 2013 fashion,",
"primary_color": "grey", "primary_color": "grey",
"secondary_color": "blue", "secondary_color": "blue",
"tertiary_color": "white" "tertiary_color": "white"
}, },
"lora": { "lora": {
"lora_name": "Illustrious/Looks/Sarah_Miller_Illustrious.safetensors", "lora_name": "Illustrious/Looks/SarahMillerOGILf_1328931.safetensors",
"lora_weight": 0.8, "lora_weight": 0.6,
"lora_triggers": "" "lora_triggers": ""
}, },
"tags": [ "tags": [

View File

@@ -14,15 +14,17 @@
"extra": "red lipstick, heavy makeup" "extra": "red lipstick, heavy makeup"
}, },
"defaults": { "defaults": {
"expression": "", "expression": "smirk",
"pose": "", "pose": "stepping on viewer, from below",
"scene": "" "scene": "penthouse office, night"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "red dress, formal dress, pencil dress",
"headwear": "", "headwear": "",
"top": "red dress, formal dress, pencil dress, sleeveless, chest cutout", "top": "sleeveless, chest cutout",
"legwear": "long skirt, high slit, side slit,black stockings", "bottom": "high slit",
"legwear": "side slit, black stockings",
"footwear": "high heels, red heels, stiletto heels", "footwear": "high heels, red heels, stiletto heels",
"hands": "", "hands": "",
"accessories": "jewelry, gold earrings, necklace" "accessories": "jewelry, gold earrings, necklace"

View File

@@ -14,14 +14,16 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "smile",
"pose": "", "pose": "belly dancing, hands above head",
"scene": "" "scene": "desert town, oasis"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "gold trim",
"headwear": "", "headwear": "",
"top": "red bikini top, red harem pants, gold trim", "top": "red bikini top",
"bottom": "red harem pants",
"legwear": "", "legwear": "",
"footwear": "gold shoes", "footwear": "gold shoes",
"hands": "", "hands": "",

View File

@@ -20,10 +20,12 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white top", "full_body": "",
"top": "black corset,, clothing cutout, low cut, witch hat", "headwear": "witch hat",
"legwear": "skirt, high slit, side slit", "top": "black corset, white top, clothing cutout, low cut",
"footwear": "boots, ", "bottom": "skirt, high slit, black skirt",
"legwear": "side slit",
"footwear": "boots",
"hands": "", "hands": "",
"accessories": "staff, necklace, bracelets, jewelry, wooden staff" "accessories": "staff, necklace, bracelets, jewelry, wooden staff"
} }

View File

@@ -14,22 +14,24 @@
"extra": "dark circles under eyes" "extra": "dark circles under eyes"
}, },
"defaults": { "defaults": {
"expression": "", "expression": "neutral expression",
"pose": "", "pose": "looking at mushroom",
"scene": "" "scene": "dark forest"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "", "full_body": "luna nova school uniform, dark purple witch robes",
"top": "dark purple witch robes", "headwear": "pointed witch hat",
"top": "oversized sleeves",
"bottom": "",
"legwear": "long skirt with frayed edges", "legwear": "long skirt with frayed edges",
"footwear": "brown boots", "footwear": "brown boots",
"hands": "", "hands": "",
"accessories": "pointed witch hat, potion bottle" "accessories": "potion bottle, mushroom"
} }
}, },
"styles": { "styles": {
"aesthetic": "mushroom, gothic, whimsical, little witch academia style", "aesthetic": "gothic, magic, little witch academia style",
"primary_color": "purple", "primary_color": "purple",
"secondary_color": "mauve", "secondary_color": "mauve",
"tertiary_color": "green" "tertiary_color": "green"

View File

@@ -14,16 +14,18 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "smile",
"pose": "", "pose": "leaning on counter, looking at viewer",
"scene": "" "scene": "tavern, bartending"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "black sports bra", "full_body": "",
"top": "white tank top, black suspenders", "headwear": "",
"legwear": "black miniskirt", "top": "white tank top, black sports bra,black suspenders",
"footwear": "red boots, thigh high black socks", "bottom": "black miniskirt",
"legwear": "thigh high black socks",
"footwear": "red boots",
"hands": "red fingerless gloves", "hands": "red fingerless gloves",
"accessories": "silver earrings" "accessories": "silver earrings"
} }

View File

@@ -14,17 +14,19 @@
"extra": "freckles" "extra": "freckles"
}, },
"defaults": { "defaults": {
"expression": "", "expression": "smile",
"pose": "", "pose": "dashing, blue afterimage",
"scene": "" "scene": "futuristic city"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "orange leggings", "full_body": "",
"headwear": "",
"top": "brown flight jacket, yellow vest", "top": "brown flight jacket, yellow vest",
"legwear": "orange leggings", "bottom": "orange leggings",
"legwear": "",
"footwear": "white and orange sneakers", "footwear": "white and orange sneakers",
"hands": "", "hands": "fingerless gloves",
"accessories": "chronal accelerator, yellow goggles" "accessories": "chronal accelerator, yellow goggles"
} }
}, },

View File

@@ -20,8 +20,10 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "",
"headwear": "", "headwear": "",
"top": "blue top, green sash, green shoulder guards,", "top": "blue top, green sash, green shoulder guards,",
"bottom": "",
"legwear": "blue sarong", "legwear": "blue sarong",
"footwear": "anklet, gold heels", "footwear": "anklet, gold heels",
"hands": "", "hands": "",

View File

@@ -14,15 +14,17 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "light smile",
"pose": "", "pose": "sniping, prone",
"scene": "" "scene": "rooftop, night"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "purple bodysuit, skintight",
"headwear": "", "headwear": "",
"top": "purple bodysuit, plunging neckline", "top": " plunging neckline",
"legwear": "bodysuit", "bottom": "",
"legwear": "",
"footwear": "purple high-heeled boots", "footwear": "purple high-heeled boots",
"hands": "purple gauntlets", "hands": "purple gauntlets",
"accessories": "sniper rifle, visor" "accessories": "sniper rifle, visor"

View File

@@ -14,14 +14,16 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "serious",
"pose": "", "pose": "leaping",
"scene": "" "scene": "rooftop, night, red sky, blood splatter"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "black halter dress",
"headwear": "", "headwear": "",
"top": "black backless halter dress, red rose pattern inside", "top": "backless, pattern inside",
"bottom": "",
"legwear": "black thigh-high boots", "legwear": "black thigh-high boots",
"footwear": "black boots", "footwear": "black boots",
"hands": "black fingerless gloves", "hands": "black fingerless gloves",
@@ -29,7 +31,7 @@
} }
}, },
"styles": { "styles": {
"aesthetic": "elegant, assassin, spy x family style", "aesthetic": "elegant, assassin,red rose, spy x family style",
"primary_color": "black", "primary_color": "black",
"secondary_color": "red", "secondary_color": "red",
"tertiary_color": "gold" "tertiary_color": "gold"

View File

@@ -20,9 +20,11 @@
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "black sorceress robes, ",
"headwear": "", "headwear": "",
"top": "black sorceress robes, fur trim", "top": "bare shoulders, fur trim",
"legwear": "long skirt", "bottom": "",
"legwear": "",
"footwear": "black boots", "footwear": "black boots",
"hands": "", "hands": "",
"accessories": "wooden staff" "accessories": "wooden staff"

View File

@@ -14,22 +14,24 @@
"extra": "headband" "extra": "headband"
}, },
"defaults": { "defaults": {
"expression": "", "expression": "cheeky smile",
"pose": "", "pose": "holding glass orb, materia",
"scene": "" "scene": "forest, sunlight"
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"full_body": "",
"headwear": "", "headwear": "",
"top": "green turtleneck sweater vest, midriff", "top": "green turtleneck sweater vest, midriff",
"legwear": "beige shorts", "bottom": "beige shorts",
"footwear": "boots, socks", "legwear": "single kneehigh sock",
"footwear": "boots, ",
"hands": "fingerless glove on one hand, large gauntlet on one arm", "hands": "fingerless glove on one hand, large gauntlet on one arm",
"accessories": "shuriken" "accessories": "shuriken"
} }
}, },
"styles": { "styles": {
"aesthetic": "ninja, adventurer, final fantasy style", "aesthetic": "ninja, japanese, final fantasy style",
"primary_color": "green", "primary_color": "green",
"secondary_color": "beige", "secondary_color": "beige",
"tertiary_color": "black" "tertiary_color": "black"

View File

@@ -14,22 +14,24 @@
"extra": "" "extra": ""
}, },
"defaults": { "defaults": {
"expression": "", "expression": "serene",
"pose": "", "pose": "dancing",
"scene": "" "scene": "standing on water, sunset, pink sky, "
}, },
"wardrobe": { "wardrobe": {
"default": { "default": {
"headwear": "white kimono top, yellow obi", "full_body": "",
"top": "", "headwear": "",
"legwear": "long blue skirt, floral pattern", "top": "white kimono top, yellow obi, detached sleeves",
"footwear": "boots", "bottom": "long blue skirt, floral pattern",
"hands": "detached sleeves", "legwear": "",
"footwear": "black boots",
"hands": "",
"accessories": "summoner staff, necklace" "accessories": "summoner staff, necklace"
} }
}, },
"styles": { "styles": {
"aesthetic": "sunset, pink sky, shrine maiden,fantasy, final fantasy x style", "aesthetic": "shrine maiden,fantasy, final fantasy x style",
"primary_color": "white", "primary_color": "white",
"secondary_color": "blue", "secondary_color": "blue",
"tertiary_color": "yellow" "tertiary_color": "yellow"

View File

@@ -0,0 +1,26 @@
{
"outfit_id": "bikini_01",
"outfit_name": "Bikini",
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "bikini top",
"bottom": "bikini bottom",
"legwear": "",
"footwear": "barefoot",
"hands": "",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"bikini",
"swimsuit",
"navel",
"cleavage",
"summer"
]
}

View File

@@ -0,0 +1,26 @@
{
"outfit_id": "bikini_02",
"outfit_name": "Bikini (Slingshot)",
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "slingshot swimsuit",
"bottom": "",
"legwear": "",
"footwear": "",
"hands": "",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"slingshot swimsuit",
"swimsuit",
"highleg",
"navel",
"revealing clothes"
]
}

View File

@@ -0,0 +1,27 @@
{
"outfit_id": "cat_cosplay",
"outfit_name": "Cat Cosplay",
"wardrobe": {
"full_body": "",
"headwear": "cat ears, nekomimi, hairband",
"top": "bikini top, string bikini",
"bottom": "bikini bottom, panties",
"legwear": "thighhighs",
"footwear": "high heels",
"hands": "paw gloves, animal hands",
"accessories": "collar, bell choker, cat tail"
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"cat girl",
"nekomimi",
"bikini",
"cosplay",
"animal ears",
"tail"
]
}

View File

@@ -0,0 +1,25 @@
{
"outfit_id": "evening_gown",
"outfit_name": "Evening Gown",
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "evening gown",
"bottom": "",
"legwear": "",
"footwear": "high heels",
"hands": "",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"formal",
"elegant",
"long dress",
"flowing"
]
}

View File

@@ -0,0 +1,22 @@
{
"outfit_id": "french_maid_01",
"outfit_name": "French Maid",
"wardrobe": {
"full_body": "",
"headwear": "hairband",
"top": "corset, low cut top",
"bottom": "frilled skirt",
"legwear": "lace stockings",
"footwear": "heels",
"hands": "frilled sleeves",
"accessories": "apron"
},
"lora": {
"lora_name": "Illustrious/Clothing/V2_Latex_Maid_Illustrious.safetensors",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"French Maid"
]
}

View File

@@ -0,0 +1,23 @@
{
"outfit_id": "french_maid_02",
"outfit_name": "French Maid (Latex)",
"wardrobe": {
"full_body": "",
"headwear": "hairband",
"top": "corset, low cut top",
"bottom": "frilled skirt",
"legwear": "lace stockings",
"footwear": "heels",
"hands": "frilled sleeves",
"accessories": "apron"
},
"lora": {
"lora_name": "Illustrious/Clothing/V2_Latex_Maid_Illustrious.safetensors",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"French Maid",
"latex"
]
}

View File

@@ -0,0 +1,26 @@
{
"outfit_id": "latex_outfit",
"outfit_name": "Latex Outfit",
"wardrobe": {
"full_body": "",
"headwear": "latex cat ears",
"top": "latex bodysuit, breast cutout, zipper, sleeveless, highleg",
"bottom": "",
"legwear": "latex thighhighs, garter belt",
"footwear": "thigh boots, high heels, latex boots",
"hands": "elbow gloves, latex gloves",
"accessories": "choker, collar"
},
"lora": {
"lora_name": "Illustrious/Clothing/latex_clothing.safetensors",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"black latex",
"shiny",
"fetish",
"tight clothing",
"erotic"
]
}

View File

@@ -0,0 +1,26 @@
{
"outfit_id": "lingerie",
"outfit_name": "Lingerie",
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "lace bra",
"bottom": "lace panties",
"legwear": "lace stockings",
"footwear": "",
"hands": "",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"lingerie",
"lace",
"lace trim",
"underwear",
"matching set"
]
}

View File

@@ -0,0 +1,26 @@
{
"outfit_id": "lingerie_02",
"outfit_name": "Lingerie (Latex)",
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "latex bra, latex choker",
"bottom": "latex panties",
"legwear": "latex stockings",
"footwear": "",
"hands": "latex elbow gloves",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"lingerie",
"lace",
"lace trim",
"underwear",
"matching set"
]
}

27
data/clothing/mother.json Normal file
View File

@@ -0,0 +1,27 @@
{
"outfit_id": "mother",
"outfit_name": "Mother",
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "knit sweater, turtleneck, sweater, long sleeves",
"bottom": "long skirt, maxi skirt",
"legwear": "pantyhose",
"footwear": "flat shoes, slippers",
"hands": "wedding ring",
"accessories": "apron, necklace"
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"milf",
"mother",
"mature female",
"casual",
"domestic",
"knitwear"
]
}

View File

@@ -0,0 +1,22 @@
{
"outfit_id": "nurse_01",
"outfit_name": "Nurse",
"wardrobe": {
"full_body": "",
"headwear": "nurse cap",
"top": "nurse outfit",
"bottom": "short skirt",
"legwear": "stockings",
"footwear": "high heels",
"hands": "",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"nurse"
]
}

View File

@@ -0,0 +1,23 @@
{
"outfit_id": "nurse_02",
"outfit_name": "Nurse (Latex)",
"wardrobe": {
"full_body": "",
"headwear": "nurse cap",
"top": "latex nurse outfit",
"bottom": "short skirt",
"legwear": "stockings",
"footwear": "high heels",
"hands": "",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"nurse",
"latex"
]
}

View File

@@ -0,0 +1,20 @@
{
"outfit_id": "pasties_01",
"outfit_name": "Pasties",
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "nipple pasties",
"bottom": "crotch pasties",
"legwear": "",
"footwear": "",
"hands": "",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": []
}

View File

@@ -0,0 +1,32 @@
{
"outfit_id": "playboy_bunny",
"outfit_name": "Playboy Bunny",
"wardrobe": {
"full_body": "playboy bunny, leotard, strapless leotard",
"headwear": "bunny ears, animal ears, headband",
"top": "",
"bottom": "",
"legwear": "pantyhose, fishnets, black pantyhose",
"footwear": "high heels",
"hands": "cuffs, wrist cuffs, white cuffs",
"accessories": "collar, bowtie, bunny tail"
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"playboy bunny",
"bunny ears",
"leotard",
"fishnets",
"collar",
"bowtie",
"cuffs",
"bunny tail",
"pantyhose",
"high heels",
"animal ears"
]
}

View File

@@ -0,0 +1,20 @@
{
"outfit_id": "school_uniform_01",
"outfit_name": "School Uniform (Western)",
"wardrobe": {
"full_body": "",
"headwear": "hairband",
"top": "white shirt, tie, blazer",
"bottom": "skirt",
"legwear": "thigh high socks",
"footwear": "black shoes",
"hands": "",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": []
}

View File

@@ -0,0 +1,23 @@
{
"outfit_id": "school_uniform_02",
"outfit_name": "School Uniform (Sailor)",
"wardrobe": {
"full_body": "",
"headwear": "hairband",
"top": "white shirt, sailor scarf",
"bottom": "pleated skirt",
"legwear": "knee high socks",
"footwear": "black shoes",
"hands": "",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"school uniform",
"sailor"
]
}

View File

@@ -0,0 +1,27 @@
{
"outfit_id": "school_uniform_03",
"outfit_name": "School Uniform (Latex Sailor)",
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "latex serafuku, sailor collar, cropped shirt, midriff, short sleeves",
"bottom": "pleated skirt, miniskirt, latex skirt",
"legwear": "latex thighhighs",
"footwear": "high heels",
"hands": "",
"accessories": "neckerchief"
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": [
"latex",
"school uniform",
"serafuku",
"shiny",
"midriff",
"navel"
]
}

View File

@@ -0,0 +1,20 @@
{
"outfit_id": "school_uniform_04",
"outfit_name": "School Uniform (Erotic)",
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "topless",
"bottom": "latex skirt, micro-skirt",
"legwear": "latex thigh highs",
"footwear": "high heels",
"hands": "latex elbow gloves",
"accessories": ""
},
"lora": {
"lora_name": "",
"lora_weight": 0.8,
"lora_triggers": ""
},
"tags": []
}

View File

@@ -34,6 +34,19 @@ class Character(db.Model):
def __repr__(self): def __repr__(self):
return f'<Character {self.character_id}>' return f'<Character {self.character_id}>'
class Outfit(db.Model):
id = db.Column(db.Integer, primary_key=True)
outfit_id = db.Column(db.String(100), unique=True, nullable=False)
slug = db.Column(db.String(100), unique=True, nullable=False)
filename = db.Column(db.String(255), nullable=True)
name = db.Column(db.String(100), nullable=False)
data = db.Column(db.JSON, nullable=False)
default_fields = db.Column(db.JSON, nullable=True)
image_path = db.Column(db.String(255), nullable=True)
def __repr__(self):
return f'<Outfit {self.outfit_id}>'
class Settings(db.Model): class Settings(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
openrouter_api_key = db.Column(db.String(255), nullable=True) openrouter_api_key = db.Column(db.String(255), nullable=True)

View File

@@ -14,22 +14,32 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="filename" class="form-label">Filename (Slug)</label> <label for="filename" class="form-label">Filename (Slug) <small class="text-muted">- optional, auto-generated if empty</small></label>
<input type="text" class="form-control" id="filename" name="filename" placeholder="e.g. cyberpunk_ninja" required> <input type="text" class="form-control" id="filename" name="filename" placeholder="e.g. cyberpunk_ninja">
<div class="form-text">Used for the JSON file and URL. No spaces or special characters.</div> <div class="form-text">Used for the JSON file and URL. No spaces or special characters. Auto-generated from name if left empty.</div>
</div> </div>
<div class="mb-3"> <div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" id="use_llm" name="use_llm" checked>
<label class="form-check-label" for="use_llm">Use AI to generate profile from description</label>
</div>
<div class="mb-3" id="prompt-group">
<label for="prompt" class="form-label">Description / Concept</label> <label for="prompt" class="form-label">Description / Concept</label>
<textarea class="form-control" id="prompt" name="prompt" rows="5" placeholder="Describe the character's appearance, clothing, style, and personality. The AI will generate the full profile based on this." required></textarea> <textarea class="form-control" id="prompt" name="prompt" rows="5" placeholder="Describe the character's appearance, clothing, style, and personality. The AI will generate the full profile based on this."></textarea>
<div class="form-text">Required when AI generation is enabled.</div>
</div> </div>
<div class="alert alert-info"> <div class="alert alert-info" id="ai-info">
<i class="bi bi-info-circle"></i> Once created, the system will automatically attempt to generate a cover image using the new profile. <i class="bi bi-info-circle"></i> The AI will generate a complete character profile based on your description.
</div>
<div class="alert alert-secondary d-none" id="manual-info">
<i class="bi bi-info-circle"></i> A blank character profile will be created. You can edit it afterwards to add details.
</div> </div>
<div class="d-grid"> <div class="d-grid">
<button type="submit" class="btn btn-success btn-lg">Create & Generate</button> <button type="submit" class="btn btn-success btn-lg" id="submit-btn">Create & Generate</button>
</div> </div>
</form> </form>
</div> </div>
@@ -37,4 +47,28 @@
</div> </div>
</div> </div>
</div> </div>
<script>
document.getElementById('use_llm').addEventListener('change', function() {
const promptGroup = document.getElementById('prompt-group');
const aiInfo = document.getElementById('ai-info');
const manualInfo = document.getElementById('manual-info');
const submitBtn = document.getElementById('submit-btn');
const promptInput = document.getElementById('prompt');
if (this.checked) {
promptGroup.classList.remove('d-none');
aiInfo.classList.remove('d-none');
manualInfo.classList.add('d-none');
submitBtn.textContent = 'Create & Generate';
promptInput.required = true;
} else {
promptGroup.classList.add('d-none');
aiInfo.classList.add('d-none');
manualInfo.classList.remove('d-none');
submitBtn.textContent = 'Create Character';
promptInput.required = false;
}
});
</script>
{% endblock %} {% endblock %}

View File

@@ -1,10 +1,21 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<!-- Image Modal -->
<div class="modal fade" id="imageModal" tabindex="-1" aria-labelledby="imageModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content bg-transparent border-0">
<div class="modal-body p-0 text-center">
<img id="modalImage" src="" alt="Enlarged Image" class="img-fluid" style="max-height: 90vh;">
</div>
</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<div class="card mb-4"> <div class="card mb-4">
<div class="img-container" style="height: auto; min-height: 400px;"> <div class="img-container" style="height: auto; min-height: 400px; cursor: pointer;" data-bs-toggle="modal" data-bs-target="#imageModal" onclick="showImage(this.querySelector('img').src)">
{% if character.image_path %} {% if character.image_path %}
<img src="{{ url_for('static', filename='uploads/' + character.image_path) }}" alt="{{ character.name }}" class="img-fluid"> <img src="{{ url_for('static', filename='uploads/' + character.image_path) }}" alt="{{ character.name }}" class="img-fluid">
{% else %} {% else %}
@@ -21,7 +32,6 @@
</form> </form>
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<button type="submit" name="action" value="preview" class="btn btn-success" form="generate-form">Generate Preview</button> <button type="submit" name="action" value="preview" class="btn btn-success" form="generate-form">Generate Preview</button>
<button type="submit" name="action" value="replace" class="btn btn-outline-danger" form="generate-form">Generate & Replace Cover</button>
<button type="submit" form="generate-form" formaction="{{ url_for('save_defaults', slug=character.slug) }}" class="btn btn-sm btn-outline-secondary mt-2">Save as Default Selection</button> <button type="submit" form="generate-form" formaction="{{ url_for('save_defaults', slug=character.slug) }}" class="btn btn-sm btn-outline-secondary mt-2">Save as Default Selection</button>
</div> </div>
</div> </div>
@@ -36,18 +46,28 @@
{% if preview_image %} {% if preview_image %}
<div class="card mb-4 border-success"> <div class="card mb-4 border-success">
<div class="card-header bg-success text-white">Latest Preview</div> <div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
<span>Latest Preview</span>
<form action="{{ url_for('replace_cover_from_preview', slug=character.slug) }}" method="post" class="m-0">
<button type="submit" class="btn btn-sm btn-outline-light">Replace Cover</button>
</form>
</div>
<div class="card-body p-0"> <div class="card-body p-0">
<div class="img-container" style="height: auto; min-height: 400px;"> <div class="img-container" style="height: auto; min-height: 400px; cursor: pointer;" data-bs-toggle="modal" data-bs-target="#imageModal" onclick="showImage(this.querySelector('img').src)">
<img id="preview-img" src="{{ url_for('static', filename='uploads/' + preview_image) }}" alt="Preview" class="img-fluid"> <img id="preview-img" src="{{ url_for('static', filename='uploads/' + preview_image) }}" alt="Preview" class="img-fluid">
</div> </div>
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="card mb-4 border-secondary d-none" id="preview-card"> <div class="card mb-4 border-secondary d-none" id="preview-card">
<div class="card-header bg-secondary text-white">Latest Preview</div> <div class="card-header bg-secondary text-white d-flex justify-content-between align-items-center">
<span>Latest Preview</span>
<form action="{{ url_for('replace_cover_from_preview', slug=character.slug) }}" method="post" class="m-0" id="replace-cover-form">
<button type="submit" class="btn btn-sm btn-outline-light" id="replace-cover-btn" disabled>Replace Cover</button>
</form>
</div>
<div class="card-body p-0"> <div class="card-body p-0">
<div class="img-container" style="height: auto; min-height: 400px;"> <div class="img-container" style="height: auto; min-height: 400px; cursor: pointer;" data-bs-toggle="modal" data-bs-target="#imageModal" onclick="showImage(this.querySelector('img').src)">
<img id="preview-img" src="" alt="Preview" class="img-fluid"> <img id="preview-img" src="" alt="Preview" class="img-fluid">
</div> </div>
</div> </div>
@@ -265,7 +285,7 @@
form.addEventListener('submit', async (e) => { form.addEventListener('submit', async (e) => {
// Only intercept generate actions // Only intercept generate actions
const submitter = e.submitter; const submitter = e.submitter;
if (!submitter || (submitter.value !== 'preview' && submitter.value !== 'replace')) { if (!submitter || submitter.value !== 'preview') {
return; return;
} }
@@ -320,7 +340,7 @@
progressLabel.textContent = 'Saving image...'; progressLabel.textContent = 'Saving image...';
const url = `/character/{{ character.slug }}/finalize_generation/${promptId}`; const url = `/character/{{ character.slug }}/finalize_generation/${promptId}`;
const formData = new FormData(); const formData = new FormData();
formData.append('action', action); formData.append('action', 'preview'); // Always save as preview
try { try {
const response = await fetch(url, { const response = await fetch(url, {
@@ -330,12 +350,19 @@
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
if (action === 'preview') { // Update preview image
previewImg.src = data.image_url; previewImg.src = data.image_url;
if (previewCard) previewCard.classList.remove('d-none'); if (previewCard) previewCard.classList.remove('d-none');
} else {
// Reload for cover update // Enable the replace cover button if it exists
window.location.reload(); const replaceBtn = document.getElementById('replace-cover-btn');
if (replaceBtn) {
replaceBtn.disabled = false;
// Check if there's a form to update
const form = replaceBtn.closest('form');
if (form) {
form.action = `/character/{{ character.slug }}/replace_cover_from_preview`;
}
} }
} else { } else {
alert('Save failed: ' + data.error); alert('Save failed: ' + data.error);
@@ -348,5 +375,10 @@
} }
} }
}); });
// Image modal function
function showImage(src) {
document.getElementById('modalImage').src = src;
}
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -40,6 +40,12 @@
<h5 class="card-title text-center">{{ char.name }}</h5> <h5 class="card-title text-center">{{ char.name }}</h5>
<p class="card-text small text-center text-muted">{{ char.data.tags | join(', ') }}</p> <p class="card-text small text-center text-muted">{{ char.data.tags | join(', ') }}</p>
</div> </div>
{% if char.data.lora.lora_name %}
{% set lora_name = char.data.lora.lora_name.split('/')[-1].replace('.safetensors', '') %}
<div class="card-footer text-center p-1">
<small class="text-muted" title="{{ char.data.lora.lora_name }}">{{ lora_name }}</small>
</div>
{% endif %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}

View File

@@ -18,6 +18,8 @@
<div class="container"> <div class="container">
<a class="navbar-brand" href="/">Character Browser</a> <a class="navbar-brand" href="/">Character Browser</a>
<div class="d-flex"> <div class="d-flex">
<a href="/" class="btn btn-outline-light me-2">Characters</a>
<a href="/outfits" class="btn btn-outline-light me-2">Outfits</a>
<a href="/create" class="btn btn-outline-success me-2">Create Character</a> <a href="/create" class="btn btn-outline-success me-2">Create Character</a>
<a href="/generator" class="btn btn-outline-light me-2">Generator</a> <a href="/generator" class="btn btn-outline-light me-2">Generator</a>
<a href="/settings" class="btn btn-outline-light">Settings</a> <a href="/settings" class="btn btn-outline-light">Settings</a>

View File

@@ -0,0 +1,74 @@
{% extends "layout.html" %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-success text-white">Create New Outfit</div>
<div class="card-body">
<form action="{{ url_for('create_outfit') }}" method="post">
<div class="mb-3">
<label for="name" class="form-label">Outfit Name</label>
<input type="text" class="form-control" id="name" name="name" placeholder="e.g. French Maid" required>
</div>
<div class="mb-3">
<label for="filename" class="form-label">Filename (Slug) <small class="text-muted">- optional, auto-generated if empty</small></label>
<input type="text" class="form-control" id="filename" name="filename" placeholder="e.g. french_maid_01">
<div class="form-text">Used for the JSON file and URL. No spaces or special characters. Auto-generated from name if left empty.</div>
</div>
<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" id="use_llm" name="use_llm" checked>
<label class="form-check-label" for="use_llm">Use AI to generate profile from description</label>
</div>
<div class="mb-3" id="prompt-group">
<label for="prompt" class="form-label">Description / Concept</label>
<textarea class="form-control" id="prompt" name="prompt" rows="5" placeholder="Describe the outfit's style, components, colors, and any special features. The AI will generate the full outfit profile based on this."></textarea>
<div class="form-text">Required when AI generation is enabled.</div>
</div>
<div class="alert alert-info" id="ai-info">
<i class="bi bi-info-circle"></i> The AI will generate a complete outfit profile with wardrobe items and tags based on your description.
</div>
<div class="alert alert-secondary d-none" id="manual-info">
<i class="bi bi-info-circle"></i> A blank outfit profile will be created. You can edit it afterwards to add details.
</div>
<div class="d-grid">
<button type="submit" class="btn btn-success btn-lg" id="submit-btn">Create & Generate</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script>
document.getElementById('use_llm').addEventListener('change', function() {
const promptGroup = document.getElementById('prompt-group');
const aiInfo = document.getElementById('ai-info');
const manualInfo = document.getElementById('manual-info');
const submitBtn = document.getElementById('submit-btn');
const promptInput = document.getElementById('prompt');
if (this.checked) {
promptGroup.classList.remove('d-none');
aiInfo.classList.remove('d-none');
manualInfo.classList.add('d-none');
submitBtn.textContent = 'Create & Generate';
promptInput.required = true;
} else {
promptGroup.classList.add('d-none');
aiInfo.classList.add('d-none');
manualInfo.classList.remove('d-none');
submitBtn.textContent = 'Create Outfit';
promptInput.required = false;
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,353 @@
{% extends "layout.html" %}
{% block content %}
<!-- Image Modal -->
<div class="modal fade" id="imageModal" tabindex="-1" aria-labelledby="imageModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content bg-transparent border-0">
<div class="modal-body p-0 text-center">
<img id="modalImage" src="" alt="Enlarged Image" class="img-fluid" style="max-height: 90vh;">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="card mb-4">
<div class="img-container" style="height: auto; min-height: 400px; cursor: pointer;" data-bs-toggle="modal" data-bs-target="#imageModal" onclick="showImage(this.querySelector('img').src)">
{% if outfit.image_path %}
<img src="{{ url_for('static', filename='uploads/' + outfit.image_path) }}" alt="{{ outfit.name }}" class="img-fluid">
{% else %}
<span class="text-muted">No Image Attached</span>
{% endif %}
</div>
<div class="card-body">
<form action="{{ url_for('upload_outfit_image', slug=outfit.slug) }}" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="image" class="form-label">Update Image</label>
<input class="form-control" type="file" id="image" name="image" required>
</div>
<button type="submit" class="btn btn-primary w-100 mb-2">Upload</button>
</form>
{# Character Selector #}
<div class="mb-3">
<label for="character_select" class="form-label">Preview with Character</label>
<select class="form-select" id="character_select" name="character_slug" form="generate-form">
<option value="">-- No Character (Outfit Only) --</option>
<option value="__random__" {% if selected_character == '__random__' %}selected{% endif %}>🎲 Random Character</option>
{% for char in characters %}
<option value="{{ char.slug }}" {% if selected_character == char.slug %}selected{% endif %}>{{ char.name }}</option>
{% endfor %}
</select>
<div class="form-text">Select a character to preview this outfit on their model.</div>
</div>
<div class="d-grid gap-2">
<button type="submit" name="action" value="preview" class="btn btn-success" form="generate-form">Generate Preview</button>
<button type="submit" form="generate-form" formaction="{{ url_for('save_outfit_defaults', slug=outfit.slug) }}" class="btn btn-sm btn-outline-secondary mt-2">Save as Default Selection</button>
</div>
</div>
</div>
<div id="progress-container" class="mb-4 d-none">
<label id="progress-label" class="form-label">Generating...</label>
<div class="progress" role="progressbar" aria-label="Generation Progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div id="progress-bar" class="progress-bar progress-bar-striped progress-bar-animated" style="width: 0%">0%</div>
</div>
</div>
{% if preview_image %}
<div class="card mb-4 border-success">
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
<span>Latest Preview</span>
<form action="{{ url_for('replace_outfit_cover_from_preview', slug=outfit.slug) }}" method="post" class="m-0">
<button type="submit" class="btn btn-sm btn-outline-light">Replace Cover</button>
</form>
</div>
<div class="card-body p-0">
<div class="img-container" style="height: auto; min-height: 400px; cursor: pointer;" data-bs-toggle="modal" data-bs-target="#imageModal" onclick="showImage(this.querySelector('img').src)">
<img id="preview-img" src="{{ url_for('static', filename='uploads/' + preview_image) }}" alt="Preview" class="img-fluid">
</div>
</div>
</div>
{% else %}
<div class="card mb-4 border-secondary d-none" id="preview-card">
<div class="card-header bg-secondary text-white d-flex justify-content-between align-items-center">
<span>Latest Preview</span>
<form action="{{ url_for('replace_outfit_cover_from_preview', slug=outfit.slug) }}" method="post" class="m-0" id="replace-cover-form">
<button type="submit" class="btn btn-sm btn-outline-light" id="replace-cover-btn" disabled>Replace Cover</button>
</form>
</div>
<div class="card-body p-0">
<div class="img-container" style="height: auto; min-height: 400px; cursor: pointer;" data-bs-toggle="modal" data-bs-target="#imageModal" onclick="showImage(this.querySelector('img').src)">
<img id="preview-img" src="" alt="Preview" class="img-fluid">
</div>
</div>
</div>
{% endif %}
<div class="card mb-4">
<div class="card-header bg-dark text-white d-flex justify-content-between align-items-center">
<span>Tags</span>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="include_field" value="special::tags" id="includeTags" form="generate-form"
{% if preferences is not none %}
{% if 'special::tags' in preferences %}checked{% endif %}
{% elif outfit.default_fields is not none %}
{% if 'special::tags' in outfit.default_fields %}checked{% endif %}
{% else %}
checked
{% endif %}>
<label class="form-check-label text-white small" for="includeTags">Include</label>
</div>
</div>
<div class="card-body">
{% for tag in outfit.data.tags %}
<span class="badge bg-secondary">{{ tag }}</span>
{% else %}
<span class="text-muted">No tags</span>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-8">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="mb-0">{{ outfit.name }}</h1>
<a href="{{ url_for('edit_outfit', slug=outfit.slug) }}" class="btn btn-sm btn-link text-decoration-none">Edit Profile</a>
<form action="{{ url_for('clone_outfit', slug=outfit.slug) }}" method="post" style="display: inline;">
<button type="submit" class="btn btn-sm btn-link text-decoration-none">Clone Outfit</button>
</form>
</div>
<a href="{{ url_for('outfits_index') }}" class="btn btn-outline-secondary">Back to Gallery</a>
</div>
<form id="generate-form" action="{{ url_for('generate_outfit_image', slug=outfit.slug) }}" method="post">
{# Wardrobe section #}
{% set wardrobe = outfit.data.get('wardrobe', {}) %}
<div class="card mb-4">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<strong>Wardrobe</strong>
</div>
<div class="card-body">
<dl class="row mb-0">
{% for key, value in wardrobe.items() %}
<dt class="col-sm-4 text-capitalize">
<input class="form-check-input me-1" type="checkbox" name="include_field" value="wardrobe::{{ key }}"
{% if preferences is not none %}
{% if 'wardrobe::' + key in preferences %}checked{% endif %}
{% elif outfit.default_fields is not none %}
{% if 'wardrobe::' + key in outfit.default_fields %}checked{% endif %}
{% else %}
{% if value %}checked{% endif %}
{% endif %}>
{{ key.replace('_', ' ') }}
</dt>
<dd class="col-sm-8">{{ value if value else '--' }}</dd>
{% endfor %}
</dl>
</div>
</div>
{# LoRA section #}
{% set lora = outfit.data.get('lora', {}) %}
{% if lora %}
<div class="card mb-4">
<div class="card-header bg-light"><strong>LoRA</strong></div>
<div class="card-body">
<dl class="row mb-0">
{% for key, value in lora.items() %}
<dt class="col-sm-4 text-capitalize">
<input class="form-check-input me-1" type="checkbox" name="include_field" value="lora::{{ key }}"
{% if preferences is not none %}
{% if 'lora::' + key in preferences %}checked{% endif %}
{% elif outfit.default_fields is not none %}
{% if 'lora::' + key in outfit.default_fields %}checked{% endif %}
{% else %}
{% if value %}checked{% endif %}
{% endif %}>
{{ key.replace('_', ' ') }}
</dt>
<dd class="col-sm-8">{{ value if value else '--' }}</dd>
{% endfor %}
</dl>
</div>
</div>
{% endif %}
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('generate-form');
const progressBar = document.getElementById('progress-bar');
const progressContainer = document.getElementById('progress-container');
const progressLabel = document.getElementById('progress-label');
const previewCard = document.getElementById('preview-card');
const previewImg = document.getElementById('preview-img');
// Generate a unique client ID
const clientId = 'outfit_detail_' + Math.random().toString(36).substring(2, 15);
// ComfyUI WebSocket
const socket = new WebSocket(`ws://127.0.0.1:8188/ws?clientId=${clientId}`);
let currentPromptId = null;
let currentAction = null;
socket.addEventListener('message', (event) => {
if (!currentPromptId) return;
const msg = JSON.parse(event.data);
if (msg.type === 'status') {
const queueRemaining = msg.data.status.exec_info.queue_remaining;
if (queueRemaining > 0) {
progressLabel.textContent = `Queue position: ${queueRemaining}`;
}
}
else if (msg.type === 'progress') {
const value = msg.data.value;
const max = msg.data.max;
const percent = Math.round((value / max) * 100);
progressBar.style.width = `${percent}%`;
progressBar.textContent = `${percent}%`;
}
else if (msg.type === 'executing') {
if (msg.data.node === null && msg.data.prompt_id === currentPromptId) {
// Execution finished via WebSocket
console.log('Finished via WebSocket');
if (resolveCompletion) resolveCompletion();
}
}
});
let resolveCompletion = null;
async function waitForCompletion(promptId) {
return new Promise((resolve) => {
const checkResolve = () => {
clearInterval(pollInterval);
resolve();
};
resolveCompletion = checkResolve;
// Fallback polling in case WebSocket is blocked (403)
const pollInterval = setInterval(async () => {
try {
const resp = await fetch(`/check_status/${promptId}`);
const data = await resp.json();
if (data.status === 'finished') {
console.log('Finished via Polling');
checkResolve();
}
} catch (err) { console.error('Polling error:', err); }
}, 2000);
});
}
form.addEventListener('submit', async (e) => {
// Only intercept generate actions
const submitter = e.submitter;
if (!submitter || submitter.value !== 'preview') {
return;
}
e.preventDefault();
currentAction = submitter.value;
const formData = new FormData(form);
formData.append('action', currentAction);
formData.append('client_id', clientId);
// UI Reset
progressContainer.classList.remove('d-none');
progressBar.style.width = '0%';
progressBar.textContent = '0%';
progressLabel.textContent = 'Starting...';
try {
const response = await fetch(form.getAttribute('action'), {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const data = await response.json();
if (data.error) {
alert('Error: ' + data.error);
progressContainer.classList.add('d-none');
return;
}
currentPromptId = data.prompt_id;
progressLabel.textContent = 'Queued...';
// Wait for completion (WebSocket or Polling)
await waitForCompletion(currentPromptId);
// Finalize
finalizeGeneration(currentPromptId, currentAction);
currentPromptId = null;
} catch (err) {
console.error(err);
alert('Request failed');
progressContainer.classList.add('d-none');
}
});
async function finalizeGeneration(promptId, action) {
progressLabel.textContent = 'Saving image...';
const url = `/outfit/{{ outfit.slug }}/finalize_generation/${promptId}`;
const formData = new FormData();
formData.append('action', 'preview'); // Always save as preview
try {
const response = await fetch(url, {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
// Update preview image
previewImg.src = data.image_url;
if (previewCard) previewCard.classList.remove('d-none');
// Enable the replace cover button if it exists
const replaceBtn = document.getElementById('replace-cover-btn');
if (replaceBtn) {
replaceBtn.disabled = false;
// Check if there's a form to update
const form = replaceBtn.closest('form');
if (form) {
form.action = `/outfit/{{ outfit.slug }}/replace_cover_from_preview`;
}
}
} else {
alert('Save failed: ' + data.error);
}
} catch (err) {
console.error(err);
alert('Finalize request failed');
} finally {
progressContainer.classList.add('d-none');
}
}
});
// Image modal function
function showImage(src) {
document.getElementById('modalImage').src = src;
}
</script>
{% endblock %}

View File

@@ -0,0 +1,80 @@
{% extends "layout.html" %}
{% block content %}
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Edit Outfit: {{ outfit.name }}</h1>
<a href="{{ url_for('outfit_detail', slug=outfit.slug) }}" class="btn btn-outline-secondary">Cancel</a>
</div>
<form action="{{ url_for('edit_outfit', slug=outfit.slug) }}" method="post" id="main-form">
<div class="row">
<div class="col-md-8">
<!-- Basic Info -->
<div class="card mb-4">
<div class="card-header bg-dark text-white">Basic Information</div>
<div class="card-body">
<div class="mb-3">
<label for="outfit_name" class="form-label">Display Name</label>
<input type="text" class="form-control" id="outfit_name" name="outfit_name" value="{{ outfit.name }}" required>
</div>
<div class="mb-3">
<label for="outfit_id" class="form-label">Outfit ID</label>
<input type="text" class="form-control" id="outfit_id" name="outfit_id" value="{{ outfit.outfit_id }}">
</div>
<div class="mb-3">
<label for="tags" class="form-label">Tags (comma separated)</label>
<input type="text" class="form-control" id="tags" name="tags" value="{{ outfit.data.tags | join(', ') }}">
</div>
</div>
</div>
<!-- LoRA -->
<div class="card mb-4">
<div class="card-header bg-info text-white">LoRA Settings</div>
<div class="card-body">
<div class="row">
<div class="col-md-8">
<label for="lora_lora_name" class="form-label">LoRA Name</label>
<select class="form-select" id="lora_lora_name" name="lora_lora_name">
<option value="">None</option>
{% for lora in loras %}
<option value="{{ lora }}" {% if outfit.data.lora.lora_name == lora %}selected{% endif %}>{{ lora }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label for="lora_lora_weight" class="form-label">Weight</label>
<input type="number" step="0.01" class="form-control" id="lora_lora_weight" name="lora_lora_weight" value="{{ outfit.data.lora.lora_weight }}">
</div>
</div>
<div class="mt-3">
<label for="lora_lora_triggers" class="form-label">Triggers</label>
<input type="text" class="form-control" id="lora_lora_triggers" name="lora_lora_triggers" value="{{ outfit.data.lora.lora_triggers }}">
</div>
</div>
</div>
<!-- Wardrobe Section -->
{% set wardrobe = outfit.data.get('wardrobe', {}) %}
<div class="card mb-4">
<div class="card-header bg-light"><strong>Wardrobe</strong></div>
<div class="card-body">
{% for key, value in wardrobe.items() %}
<div class="mb-3">
<label for="wardrobe_{{ key }}" class="form-label text-capitalize">{{ key.replace('_', ' ') }}</label>
<input type="text" class="form-control" id="wardrobe_{{ key }}" name="wardrobe_{{ key }}" value="{{ value }}">
</div>
{% endfor %}
</div>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<a href="{{ url_for('outfit_detail', slug=outfit.slug) }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</div>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,41 @@
{% extends "layout.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Outfit Gallery</h2>
<div class="d-flex">
<a href="{{ url_for('create_outfit') }}" class="btn btn-success me-2">Create New Outfit</a>
<form action="{{ url_for('rescan_outfits') }}" method="post">
<button type="submit" class="btn btn-outline-primary">Rescan Outfit Files</button>
</form>
</div>
</div>
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-4">
{% for outfit in outfits %}
<div class="col" id="card-{{ outfit.slug }}">
<div class="card h-100 character-card" onclick="window.location.href='/outfit/{{ outfit.slug }}'">
<div class="img-container">
{% if outfit.image_path %}
<img id="img-{{ outfit.slug }}" src="{{ url_for('static', filename='uploads/' + outfit.image_path) }}" alt="{{ outfit.name }}">
<span id="no-img-{{ outfit.slug }}" class="text-muted d-none">No Image</span>
{% else %}
<img id="img-{{ outfit.slug }}" src="" alt="{{ outfit.name }}" class="d-none">
<span id="no-img-{{ outfit.slug }}" class="text-muted">No Image</span>
{% endif %}
</div>
<div class="card-body">
<h5 class="card-title text-center">{{ outfit.name }}</h5>
<p class="card-text small text-center text-muted">{{ outfit.data.tags | join(', ') }}</p>
</div>
{% if outfit.data.lora.lora_name %}
{% set lora_name = outfit.data.lora.lora_name.split('/')[-1].replace('.safetensors', '') %}
<div class="card-footer text-center p-1">
<small class="text-muted" title="{{ outfit.data.lora.lora_name }}">{{ lora_name }}</small>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endblock %}