Compare commits
6 Commits
fixes
...
116941673e
| Author | SHA1 | Date | |
|---|---|---|---|
| 116941673e | |||
|
|
467c90594c | ||
| a4a21051a5 | |||
|
|
c0e6cff7b7 | ||
| 369c92e3ea | |||
|
|
5aede18ad5 |
43
DEVELOPMENT_GUIDE.md
Normal file
43
DEVELOPMENT_GUIDE.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Feature Development Guide: Gallery Pages & Character Integration
|
||||
|
||||
This guide outlines the architectural patterns and best practices developed during the implementation of the **Actions** and **Outfits** galleries. Use this as a blueprint for adding similar features (e.g., "Scenes", "Props", "Effects").
|
||||
|
||||
## 1. Data Model & Persistence
|
||||
- **Database Model:** Add a new class in `models.py`. Include `default_fields` (JSON) to support persistent prompt selections.
|
||||
- **JSON Sync:** Implement a `sync_[feature]()` function in `app.py` to keep the SQLite database in sync with the `data/[feature]/*.json` files.
|
||||
- **Slugs:** Use URL-safe slugs generated from the ID for clean routing.
|
||||
|
||||
## 2. Triple LoRA Chaining
|
||||
Our workflow supports chaining three distinct LoRAs from specific directories:
|
||||
1. **Character:** `Illustrious/Looks/` (Node 16)
|
||||
2. **Outfit:** `Illustrious/Clothing/` (Node 17)
|
||||
3. **Action/Feature:** `Illustrious/Poses/` (Node 18)
|
||||
|
||||
**Implementation Detail:**
|
||||
In `_prepare_workflow`, LoRAs must be chained sequentially. If a previous LoRA is missing, the next one must "reach back" to the Checkpoint (Node 4) or the last valid node in the chain to maintain the model/CLIP connection.
|
||||
|
||||
## 3. Adetailer Routing
|
||||
To improve generation quality, route specific JSON sub-fields to targeted Adetailers:
|
||||
- **Face Detailer (Node 14):** Receives `character_name`, `expression`, and action-specific `head`/`eyes` tags.
|
||||
- **Hand Detailer (Node 15):** Receives priority hand tags (Wardrobe Gloves > Wardrobe Hands > Identity Hands) and action-specific `arms`/`hands` tags.
|
||||
|
||||
## 4. character-Integrated Previews
|
||||
The "Killer Feature" is previewing a standalone item (like an Action or Outfit) on a specific character.
|
||||
|
||||
**Logic Flow:**
|
||||
1. **Merge Data:** Copy `character.data`.
|
||||
2. **Override/Merge:** Replace character `defaults` with feature-specific tags (e.g., Action pose overrides Character pose).
|
||||
3. **Context Injection:** Append character-specific styles (e.g., `[primary_color] simple background`) to the main prompt.
|
||||
4. **Auto-Selection:** When a character is selected, ensure their `identity` and `wardrobe` fields are automatically included in the prompt, even if the feature page has its own manual checkboxes.
|
||||
|
||||
## 5. UI/UX Patterns
|
||||
- **Selection Boxes:** Use checkboxes next to field labels to allow users to toggle specific tags.
|
||||
- **Default Selection:** Implement a "Save as Default Selection" button that persists the current checkbox state to the database.
|
||||
- **Session State:** Store the last selected character and field preferences in the Flask `session` to provide a seamless experience when navigating between items.
|
||||
- **AJAX Generation:** Use the WebSocket + Polling hybrid pattern in the frontend to show real-time progress bars without page reloads.
|
||||
|
||||
## 6. Directory Isolation
|
||||
Always isolate LoRAs by purpose to prevent dropdown clutter:
|
||||
- `get_available_loras()` -> Characters
|
||||
- `get_available_clothing_loras()` -> Outfits
|
||||
- `get_available_action_loras()` -> Actions/Poses
|
||||
32
README.md
32
README.md
@@ -5,15 +5,18 @@ A local web-based GUI for managing character profiles (JSON) and generating cons
|
||||
## Features
|
||||
|
||||
- **Character Gallery**: Automatically scans your `characters/` folder and builds a searchable, sortable database.
|
||||
- **Granular Prompt Control**: Every field in your character JSON (Identity, Wardrobe, Styles) has a checkbox. You decide exactly what is sent to the AI.
|
||||
- **Outfit Gallery**: Manage reusable outfit presets that can be applied to any character.
|
||||
- **Actions Gallery**: A library of reusable poses and actions (e.g., "Belly Dancing", "Sword Fighting") that can be previewed on any character model.
|
||||
- **AI-Powered Creation**: Create new characters, outfits, and actions using AI to generate profiles from descriptions, or manually create blank templates.
|
||||
- **character-Integrated Previews**: Standalone items (Outfits/Actions) can be previewed directly on a specific character's model with automatic style injection (e.g., background color matching).
|
||||
- **Granular Prompt Control**: Every field in your JSON models (Identity, Wardrobe, Styles, Action details) has a checkbox. You decide exactly what is sent to the AI.
|
||||
- **ComfyUI Integration**:
|
||||
- **SDXL Optimized**: Designed for high-quality SDXL workflows.
|
||||
- **SDXL Optimized**: Designed for high-quality SDXL/Illustrious workflows.
|
||||
- **Localized ADetailer**: Automated Face and Hand detailing with focused prompts (e.g., only eye color and expression are sent to the face detailer).
|
||||
- **LoRA Support**: Automatically detects and applies LoRAs specified in your character sheets.
|
||||
- **Triple LoRA Chaining**: Chains up to three distinct LoRAs (Character + Outfit + Action) sequentially in the generation workflow.
|
||||
- **Real-time Progress**: Live progress bars and queue status via WebSockets (with a reliable polling fallback).
|
||||
- **Batch Processing**:
|
||||
- **Fill Missing**: Generate covers for every character missing one with a single click.
|
||||
- **Refresh All**: Unassign all current covers and generate a fresh set for the whole collection.
|
||||
- **Advanced Generator**: A dedicated page to mix-and-match characters with different checkpoints (Illustrious/Noob support) and custom prompt additions.
|
||||
- **Smart URLs**: Sanitized, human-readable URLs (slugs) that handle special characters and slashes gracefully.
|
||||
|
||||
@@ -45,13 +48,18 @@ A local web-based GUI for managing character profiles (JSON) and generating cons
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating Content
|
||||
- **AI Generation**: Toggle "Use AI to generate profile from description" on, then describe your character, outfit, or action. 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
|
||||
- **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.
|
||||
- **Rescan**: Use the "Rescan" buttons if you've added new JSON files or manually edited them.
|
||||
- **Save Defaults**: On any detail page, select your favorite prompt combination and click "Save as Default Selection" to remember it for future generations.
|
||||
|
||||
### Generation
|
||||
- **Preview**: Generates an image and shows it to you without replacing your current cover.
|
||||
- **Replace**: Generates an image and sets it as the character's official gallery cover.
|
||||
- **Replace**: Generates an image and sets it as the item's official gallery cover.
|
||||
- **Clean Start**: If you want to wipe the database and all generated images to start fresh:
|
||||
```bash
|
||||
./launch.sh --clean
|
||||
@@ -59,9 +67,11 @@ A local web-based GUI for managing character profiles (JSON) and generating cons
|
||||
|
||||
## File Structure
|
||||
|
||||
- `/characters`: Your character JSON files.
|
||||
- `/static/uploads`: Generated images (organized by character subfolders).
|
||||
- `/templates`: HTML UI using Bootstrap 5.
|
||||
- `/data/characters`: Character JSON files.
|
||||
- `/data/clothing`: Outfit preset JSON files.
|
||||
- `/data/actions`: Action/Pose preset JSON files.
|
||||
- `/static/uploads`: Generated images (organized by subfolders).
|
||||
- `app.py`: Flask backend and prompt-building logic.
|
||||
- `comfy_workflow.json`: The API-format workflow used for generations.
|
||||
- `models.py`: SQLite database schema.
|
||||
- `models.py`: SQLAlchemy database models.
|
||||
- `DEVELOPMENT_GUIDE.md`: Architectual patterns for extending the browser.
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"character_id": "aerith_gainsborough",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long brown hair, braided, pink ribbon",
|
||||
"eyes": "green eyes",
|
||||
"expression": "cheerful expression",
|
||||
"hands": "pink nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "pink dress, red bolero jacket",
|
||||
"lower_body": "long skirt",
|
||||
"footwear": "brown boots",
|
||||
"gloves": "",
|
||||
"accessories": "gold bracelets, flower basket"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "floral, gentle, final fantasy style",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "red",
|
||||
"tertiary_color": "brown"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Final Fantasy VII"
|
||||
]
|
||||
}
|
||||
@@ -1,39 +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",
|
||||
"expression": "seductive smile",
|
||||
"hands": "purple nails",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "black headband with horns"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black armor, cleavage",
|
||||
"lower_body": "black leggings, armored plates",
|
||||
"footwear": "black armored boots",
|
||||
"gloves": "",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"character_id": "delinquent_mother_flim13",
|
||||
"character_name": "Delinquent Mother",
|
||||
"identity": {
|
||||
"base_specs": "1girl, milf, gyaru, tall",
|
||||
"hair": "blonde hair, long hair",
|
||||
"eyes": "sharp eyes",
|
||||
"expression": "smirk, sharp teeth",
|
||||
"hands": "painted nails",
|
||||
"arms": "",
|
||||
"torso": "very large breasts",
|
||||
"pelvis": "wide hips",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "biege sweater, cleavage",
|
||||
"outer_layer": "",
|
||||
"lower_body": "pencil skirt",
|
||||
"footwear": "high heels",
|
||||
"gloves": "",
|
||||
"accessories": "necklace, rings"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "gyaru, milf, pink leopard print",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "black",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/Gyaru_mom_Flim13_IL_V1.safetensors",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Original","flim13"
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"character_id": "jessica_rabbit",
|
||||
"character_name": "Jessica Rabbit",
|
||||
"identity": {
|
||||
"base_specs": "1girl, voluptuous build, tall,",
|
||||
"hair": "long red hair, side part, hair over one eye",
|
||||
"eyes": "green eyes, heavy makeup, purple eyeshadow",
|
||||
"expression": "seductive smile",
|
||||
"hands": "purple elbow gloves",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "narrow waist",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "red lips"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "red sequin dress, strapless, high slit, backless",
|
||||
"lower_body": "side_slit,",
|
||||
"footwear": "red high heels",
|
||||
"gloves": "purple opera gloves",
|
||||
"accessories": "gold earrings, glitter"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "noir, cartoon, glamorous",
|
||||
"primary_color": "red",
|
||||
"secondary_color": "purple",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Who Framed Roger Rabbit"
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"character_id": "majin_android_21",
|
||||
"character_name": "Majin Android 21",
|
||||
"identity": {
|
||||
"base_specs": "1girl, curvaceous build, pink skin",
|
||||
"hair": "long voluminous white hair",
|
||||
"eyes": "red eyes, black sclera",
|
||||
"expression": "evil smile",
|
||||
"hands": "black claws, pink nails",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "pink skin, long tail, pointy ears"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black tube top",
|
||||
"outer_layer": "",
|
||||
"lower_body": "white harem pants",
|
||||
"footwear": "black and yellow boots",
|
||||
"gloves": "black sleeves",
|
||||
"accessories": "gold bracelets, gold neck ring, hoop earrings"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "supernatural, anime, dragon ball style",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Dragon Ball FighterZ"
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"character_id": "marin_kitagawa",
|
||||
"character_name": "Marin Kitagawa",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin, asian",
|
||||
"hair": "long blonde hair, pink tips",
|
||||
"eyes": "pink eyes (contacts)",
|
||||
"expression": "excited smile",
|
||||
"hands": "long pink nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "piercings"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "white school shirt, loosely tied blue tie",
|
||||
"lower_body": "blue plaid miniskirt",
|
||||
"footwear": "black loafers, black socks",
|
||||
"gloves": "",
|
||||
"accessories": "choker, various bracelets"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "gyaru, modern, anime style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "blue",
|
||||
"tertiary_color": "pink"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"My Dress-Up Darling"
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"character_id": "nessa",
|
||||
"character_name": "Nessa",
|
||||
"identity": {
|
||||
"base_specs": "1girl, athletic build, dark skin",
|
||||
"hair": "long hair, light blue highlights",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "confident smile",
|
||||
"hands": "blue nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "blue earrings"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white and blue bikini top",
|
||||
"outer_layer": "gym uniform, number 049",
|
||||
"lower_body": "white and blue shorts",
|
||||
"footwear": "blue and white sandals",
|
||||
"gloves": "",
|
||||
"accessories": "wristband, life buoy, pokeball"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "sporty, aquatic, pokemon style",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "orange"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Pokemon"
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"character_id": "urbosa",
|
||||
"character_name": "Urbosa",
|
||||
"identity": {
|
||||
"base_specs": "1girl, tall, muscular, dark skin, gerudo",
|
||||
"hair": "long red hair, wild hair",
|
||||
"eyes": "green eyes",
|
||||
"expression": "confident",
|
||||
"hands": "gold nails",
|
||||
"arms": "muscular arms",
|
||||
"torso": "abs, mediumS breasts",
|
||||
"pelvis": "wide hips",
|
||||
"legs": "muscular legs",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "dark blue lipstick, gerudo markings"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "blue top, blue champion's skirt, green sash, green shoulder guards,",
|
||||
"lower_body": "blue skirt",
|
||||
"footwear": "gold heels",
|
||||
"gloves": "",
|
||||
"accessories": "gold jewelry, scimitar"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, warrior, gerudo style",
|
||||
"primary_color": "gold",
|
||||
"secondary_color": "blue",
|
||||
"tertiary_color": "red"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"The Legend of Zelda"
|
||||
]
|
||||
}
|
||||
@@ -169,5 +169,25 @@
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "LoraLoader"
|
||||
},
|
||||
"17": {
|
||||
"inputs": {
|
||||
"lora_name": "",
|
||||
"strength_model": 0.8,
|
||||
"strength_clip": 0.8,
|
||||
"model": ["16", 0],
|
||||
"clip": ["16", 1]
|
||||
},
|
||||
"class_type": "LoraLoader"
|
||||
},
|
||||
"18": {
|
||||
"inputs": {
|
||||
"lora_name": "",
|
||||
"strength_model": 1.0,
|
||||
"strength_clip": 1.0,
|
||||
"model": ["17", 0],
|
||||
"clip": ["17", 1]
|
||||
},
|
||||
"class_type": "LoraLoader"
|
||||
}
|
||||
}
|
||||
|
||||
25
data/actions/belly_dancing.json
Normal file
25
data/actions/belly_dancing.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"action_id": "belly_dancing",
|
||||
"action_name": "Belly Dancing",
|
||||
"action": {
|
||||
"full_body": "belly dancing, standing",
|
||||
"head": "",
|
||||
"eyes": "",
|
||||
"arms": "hands above head",
|
||||
"hands": "palms together",
|
||||
"torso": "",
|
||||
"pelvis": "swaying hips",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"additional": ""
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"belly dancing",
|
||||
"dance"
|
||||
]
|
||||
}
|
||||
57
data/characters/aerith_gainsborough.json
Normal file
57
data/characters/aerith_gainsborough.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"character_id": "aerith_gainsborough",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long brown hair, braided, ",
|
||||
"eyes": "green eyes",
|
||||
"hands": "pink nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"extra": "pink hair ribbon"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "gentle smile, looking at viewer",
|
||||
"pose": "handing flower to viewer",
|
||||
"scene": "city street, night"
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "long pink dress",
|
||||
"headwear": "",
|
||||
"top": "red bolero jacket",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "brown boots",
|
||||
"hands": "",
|
||||
"accessories": "gold bracelets, flower basket"
|
||||
},
|
||||
"red_dress": {
|
||||
"full_body": "long dress, frilled dress, red dress",
|
||||
"headwear": "red hair ribbons",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "white high heels",
|
||||
"hands": "red nails",
|
||||
"accessories": "gold bracelets"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "floral, final fantasy vii style",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "red",
|
||||
"tertiary_color": "brown"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Final Fantasy VII"
|
||||
],
|
||||
"character_name": "Aerith Gainsborough"
|
||||
}
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "shoulder-length blonde hair, tucked behind one ear",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "cool, indifferent expression",
|
||||
"hands": "blue nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "gold hoop earrings"
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "neutral",
|
||||
"pose": "tucking hair behind ear",
|
||||
"scene": "wasteland, mountains, "
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black short-sleeved shirt",
|
||||
"outer_layer": "blue denim vest, 'RR' text on back",
|
||||
"lower_body": "blue denim skirt, black leggings",
|
||||
"footwear": "brown boots",
|
||||
"gloves": "",
|
||||
"accessories": ""
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "blue denim vest,black long sleeved shirt, striped sleeves",
|
||||
"bottom": "blue denim skirt",
|
||||
"legwear": "black stockings",
|
||||
"footwear": "brown boots",
|
||||
"hands": "",
|
||||
"accessories": "gold hoop earrings"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "90s casual, anime, dragon ball style",
|
||||
"aesthetic": "anime, dragon ball style",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "black",
|
||||
"tertiary_color": "white"
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, small build, loli, fair skin",
|
||||
"hair": "short pink hair, two small horns (hair ornaments)",
|
||||
"eyes": "green eyes",
|
||||
"expression": "smirk",
|
||||
"hands": "pink nails",
|
||||
"arms": "",
|
||||
"torso": "flat chest",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black Eden Academy uniform, gold trim",
|
||||
"lower_body": "uniform skirt",
|
||||
"footwear": "black shoes, white socks",
|
||||
"gloves": "",
|
||||
"accessories": "black and gold hair cones"
|
||||
"default": {
|
||||
"full_body": "black Eden Academy uniform, gold trim",
|
||||
"headwear": "",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "black shoes, white socks",
|
||||
"hands": "",
|
||||
"accessories": "black and gold hair cones"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "cute, academic, spy x family style",
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, horse ears, horse tail, tall",
|
||||
"hair": "long grey hair, wild hair",
|
||||
"eyes": "purple eyes, red framed glasses",
|
||||
"expression": "thinking",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "thinking",
|
||||
"pose": "reading",
|
||||
"scene": "library, sun beam"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white shirt",
|
||||
"outer_layer": "tracen school uniform",
|
||||
"lower_body": "pleated skirt",
|
||||
"footwear": "heeled shoes",
|
||||
"gloves": "",
|
||||
"accessories": ""
|
||||
"default": {
|
||||
"full_body": "tracen school uniform",
|
||||
"headwear": "",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "heeled shoes",
|
||||
"hands": "",
|
||||
"accessories": ""
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "intellectual, cool",
|
||||
"aesthetic": "anime,umasumame",
|
||||
"primary_color": "maroon",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "grey"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "turquoise hair, ponytail",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "energetic smile",
|
||||
"hands": "turquoise nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black playboy bunny",
|
||||
"lower_body": "pantyhose",
|
||||
"footwear": "red high heels",
|
||||
"gloves": "detatched cuffs",
|
||||
"accessories": "red hair ribbon, dragon radar"
|
||||
"default": {
|
||||
"full_body": "black playboy bunny",
|
||||
"headwear": "",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "pantyhose",
|
||||
"footwear": "red high heels",
|
||||
"hands": "detatched cuffs",
|
||||
"accessories": "red hair ribbon"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "retro-futuristic, anime, dragon ball style",
|
||||
"aesthetic": "wasteland, anime, dragon ball style",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "turquoise",
|
||||
"tertiary_color": "purple"
|
||||
47
data/characters/camilla_(fire_emblem).json
Normal file
47
data/characters/camilla_(fire_emblem).json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"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 tiara"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "black armor, gold trim",
|
||||
"headwear": "",
|
||||
"top": "belt between breasts, cleavage",
|
||||
"bottom": "purple sash, pelvic curtain, black panties",
|
||||
"legwear": "black armored thigh boots",
|
||||
"footwear": "gold heels",
|
||||
"hands": "purple velvet gloves",
|
||||
"accessories": "purple cape, large axe"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "battlefield,night,gothic, fire emblem style",
|
||||
"primary_color": "black",
|
||||
"secondary_color": "gold",
|
||||
"tertiary_color": "purple"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/fecamilla-illu-nvwls-v2.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Fire Emblem"
|
||||
]
|
||||
}
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, muscular build, fair skin",
|
||||
"hair": "long blonde hair, twin braids",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "serious look",
|
||||
"hands": "green nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "scar on left cheek, green camouflage paint on legs"
|
||||
"extra": "scar on left cheek, green camouflage paint on legs"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "green high-leg leotard",
|
||||
"lower_body": "bare legs",
|
||||
"footwear": "black combat boots, green socks",
|
||||
"gloves": "red gauntlets",
|
||||
"accessories": "red beret"
|
||||
"default": {
|
||||
"full_body": "green high-leg leotard",
|
||||
"headwear": "",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "bare legs",
|
||||
"footwear": "black combat boots, green socks",
|
||||
"hands": "red gauntlets",
|
||||
"accessories": "red beret"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "military, athletic, street fighter style",
|
||||
"aesthetic": "aurora,above valley,stone bridge, street fighter style",
|
||||
"primary_color": "green",
|
||||
"secondary_color": "red",
|
||||
"tertiary_color": "black"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, muscular build, fair skin, asian",
|
||||
"hair": "black hair, hair buns",
|
||||
"eyes": "brown eyes",
|
||||
"expression": "determined smile",
|
||||
"hands": "blue nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "thick thighs",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "confident",
|
||||
"pose": "fighting stance",
|
||||
"scene": "market, daytime"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "blue qipao, gold embroidery, white accents",
|
||||
"lower_body": "brown tights",
|
||||
"footwear": "white combat boots",
|
||||
"gloves": "",
|
||||
"accessories": "white hair ribbons, spiked bracelets"
|
||||
"default": {
|
||||
"full_body": "blue qipao, gold embroidery, white accents",
|
||||
"headwear": "",
|
||||
"top": " puffy shoulders",
|
||||
"bottom": "brown tights",
|
||||
"legwear": "",
|
||||
"footwear": "white lace-up boots",
|
||||
"hands": "",
|
||||
"accessories": "white hair ribbons, spiked bracelets"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "chinese style",
|
||||
"aesthetic": "chinese style, ",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "gold"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, athletic build",
|
||||
"hair": "ashen grey hair, messy bun",
|
||||
"eyes": "emerald green eyes, mascara",
|
||||
"expression": "determined look",
|
||||
"hands": "green nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "scar over eye"
|
||||
"extra": "scar over eye"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "serious",
|
||||
"pose": "fighting stance, holding sword",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white blouse",
|
||||
"outer_layer": "",
|
||||
"lower_body": "brown leather trousers",
|
||||
"footwear": "brown leather boots",
|
||||
"gloves": "brown leather gloves",
|
||||
"accessories": "silver sword on back, witcher medallion"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "white blouse",
|
||||
"bottom": "brown leather trousers",
|
||||
"legwear": "",
|
||||
"footwear": "brown leather boots",
|
||||
"hands": "brown leather gloves",
|
||||
"accessories": "silver sword on back, witcher medallion"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "gritty, fantasy, witcher style",
|
||||
"aesthetic": "fantasy, witcher style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "brown",
|
||||
"tertiary_color": "silver"
|
||||
48
data/characters/delinquent_mother_flim13.json
Normal file
48
data/characters/delinquent_mother_flim13.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"character_id": "delinquent_mother_flim13",
|
||||
"character_name": "Delinquent Mother",
|
||||
"identity": {
|
||||
"base_specs": "1girl, milf, gyaru, tall",
|
||||
"hair": "blonde hair, long hair",
|
||||
"eyes": "sharp eyes, black eyes, white pupil,",
|
||||
"hands": "red nails",
|
||||
"arms": "",
|
||||
"torso": "very large breasts",
|
||||
"pelvis": "wide hips",
|
||||
"legs": "",
|
||||
"feet": "painted nails",
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "naughty face",
|
||||
"pose": "spread legs, leopard print panties",
|
||||
"scene": "sitting on couch, from below"
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "light brown sweater, cleavage",
|
||||
"bottom": "leopard print skirt",
|
||||
"legwear": "",
|
||||
"footwear": "red high heels",
|
||||
"hands": "",
|
||||
"accessories": "necklace, rings,"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "living room",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "black",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/Gyaru_mom_Flim13_IL_V1.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Original",
|
||||
"flim13"
|
||||
]
|
||||
}
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, horse ears, horse tail, tall",
|
||||
"hair": "blonde hair, wavy hair",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "confident expression",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "bored",
|
||||
"pose": "looking at phone",
|
||||
"scene": "sitting on bench"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white shirt",
|
||||
"outer_layer": "tracen school uniform",
|
||||
"lower_body": "pleated skirt",
|
||||
"footwear": "heeled shoes",
|
||||
"gloves": "",
|
||||
"accessories": "choker, earrings"
|
||||
"default": {
|
||||
"full_body": "tracen school uniform",
|
||||
"headwear": "",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "heeled shoes",
|
||||
"hands": "",
|
||||
"accessories": "choker, earrings"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fashionable, model",
|
||||
"aesthetic": "modeling,school yard",
|
||||
"primary_color": "gold",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "black"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, horse ears, horse tail, tall",
|
||||
"hair": "grey hair, short hair",
|
||||
"eyes": "red eyes",
|
||||
"expression": "crazy expression, grin",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "smile",
|
||||
"pose": "running toward viewer",
|
||||
"scene": "horse race track, sunshine"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white shirt",
|
||||
"outer_layer": "tracen school uniform",
|
||||
"lower_body": "pleated skirt",
|
||||
"footwear": "heeled shoes",
|
||||
"gloves": "",
|
||||
"accessories": "ear covers, hat"
|
||||
"default": {
|
||||
"full_body": "tracen school uniform",
|
||||
"headwear": "",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "heeled shoes",
|
||||
"hands": "",
|
||||
"accessories": "ear covers, hat"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "energetic, sporty",
|
||||
"aesthetic": "horse race track,energetic, sporty",
|
||||
"primary_color": "red",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "gold"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long turquoise hair, twin tails, floor-length",
|
||||
"eyes": "turquoise eyes",
|
||||
"expression": "cheerful smile",
|
||||
"hands": "turquoise nails",
|
||||
"arms": "01 tattoo on left shoulder",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "grey sleeveless shirt, turquoise tie",
|
||||
"lower_body": "grey miniskirt, turquoise trim",
|
||||
"footwear": "black thigh-high boots, turquoise trim",
|
||||
"gloves": "black arm warmers, turquoise trim",
|
||||
"accessories": "hair ornament, headset"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "grey sleeveless shirt, turquoise tie",
|
||||
"bottom": "",
|
||||
"legwear": "grey miniskirt, turquoise trim",
|
||||
"footwear": "black thigh-high boots, turquoise trim",
|
||||
"hands": "black arm warmers, turquoise trim",
|
||||
"accessories": "hair ornament, headset"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "vocaloid, futuristic, anime style",
|
||||
"aesthetic": "concert, stage, vocaloid, futuristic, anime style",
|
||||
"primary_color": "teal",
|
||||
"secondary_color": "grey",
|
||||
"tertiary_color": "black"
|
||||
49
data/characters/jasmine_disney.json
Normal file
49
data/characters/jasmine_disney.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"character_id": "jasmine_disney",
|
||||
"character_name": "Jasmine",
|
||||
"identity": {
|
||||
"base_specs": "1girl, dark skin, ",
|
||||
"hair": "black hair, long hair, voluminous hair, banded hair, sectioned hair",
|
||||
"eyes": "brown eyes, ",
|
||||
"hands": "teal nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "narrow waist",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"extra": "heavy eyeliner, winged eyeliner"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "gentle smile",
|
||||
"pose": "hand in water",
|
||||
"scene": "sitting beside fountain, sunshine"
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"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",
|
||||
"hands": "",
|
||||
"accessories": "gold hoop earrings, large gold necklace, blue headband, jewel on headband"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "desert arabian, disney, cartoon, ",
|
||||
"primary_color": "teal",
|
||||
"secondary_color": "gold",
|
||||
"tertiary_color": "black"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/Jasmine-IL_V2.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Aladdin",
|
||||
"princess",
|
||||
"disney"
|
||||
]
|
||||
}
|
||||
47
data/characters/jessica_rabbit.json
Normal file
47
data/characters/jessica_rabbit.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"character_id": "jessica_rabbit",
|
||||
"character_name": "Jessica Rabbit",
|
||||
"identity": {
|
||||
"base_specs": "1girl, tall,",
|
||||
"hair": "long red hair, side part, hair over one eye",
|
||||
"eyes": "green eyes, heavy makeup, purple eyeshadow",
|
||||
"hands": "purple elbow gloves",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "very narrow waist",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"extra": "red lips"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "red sequin evening gown",
|
||||
"headwear": "",
|
||||
"top": "strapless, backless",
|
||||
"bottom": "high slit",
|
||||
"legwear": "side slit",
|
||||
"footwear": "red high heels",
|
||||
"hands": "purple opera gloves",
|
||||
"accessories": "gold earrings, glitter"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "jazz club,noir,",
|
||||
"primary_color": "red",
|
||||
"secondary_color": "purple",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/JessicaRabbitXL_character-12-IL.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Who Framed Roger Rabbit"
|
||||
]
|
||||
}
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long magenta hair, curved back",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "arrogant smirk",
|
||||
"hands": "white nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "green earrings"
|
||||
"extra": "green earrings"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black crop top",
|
||||
"outer_layer": "white Team Rocket uniform jacket, bare stomach, red R logo",
|
||||
"lower_body": "white miniskirt",
|
||||
"footwear": "black thigh-high boots",
|
||||
"gloves": "black elbow gloves",
|
||||
"accessories": "green earrings"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "black crop top,white Team Rocket uniform jacket, red R logo",
|
||||
"bottom": "midriff, white miniskirt",
|
||||
"legwear": "",
|
||||
"footwear": "black thigh-high boots",
|
||||
"hands": "black elbow gloves",
|
||||
"accessories": "green earrings"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "villainous, anime, pokemon style",
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, slender build, pale skin,",
|
||||
"hair": "long aqua hair, twin braids, very long hair, bangs",
|
||||
"eyes": "pink eyes, ",
|
||||
"expression": "crazy eyes, crazy smile",
|
||||
"hands": "black and pink nails",
|
||||
"arms": "",
|
||||
"torso": "flat chest,",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "cloud tattoo,"
|
||||
"extra": "cloud tattoo,"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "pink and black bikini, asymmetrical_bikini ",
|
||||
"lower_body": "pink shorts, single pink stocking",
|
||||
"footwear": "combat boots",
|
||||
"gloves": "black fingerless gloves, fishnet elbow gloves,",
|
||||
"accessories": "ammo belts, choker, bullet necklace,"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "pink and black bikini, asymmetrical_bikini ",
|
||||
"bottom": "pink shorts",
|
||||
"legwear": "single pink stocking",
|
||||
"footwear": "combat boots",
|
||||
"hands": "black fingerless gloves, fishnet elbow gloves,",
|
||||
"accessories": "ammo belts, choker, bullet necklace,"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "punk, chaotic,",
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, petite",
|
||||
"hair": "blonde hair, short hair, hair bow",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "smile, energetic",
|
||||
"hands": "",
|
||||
"arms": "detached sleeves",
|
||||
"torso": "flat chest",
|
||||
"pelvis": "",
|
||||
"legs": "leg warmers",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white shirt, sailor collar",
|
||||
"outer_layer": "",
|
||||
"lower_body": "black shorts, yellow belt",
|
||||
"footwear": "white shoes",
|
||||
"gloves": "",
|
||||
"accessories": "headset, hair bow"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "white shirt, sailor collar",
|
||||
"bottom": "black shorts, yellow belt",
|
||||
"legwear": "knee-high socks",
|
||||
"footwear": "white shoes",
|
||||
"hands": "",
|
||||
"accessories": "headset, hair bow"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "vocaloid, cyber",
|
||||
"aesthetic": "concert, stage, vocaloid, cyber",
|
||||
"primary_color": "yellow",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "black"
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long brown hair, half-ponytail, bangs",
|
||||
"eyes": "red eyes",
|
||||
"expression": "determined smile",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white shirt",
|
||||
"outer_layer": "dark blue witch robes",
|
||||
"lower_body": "dark blue skirt",
|
||||
"footwear": "brown boots, white socks",
|
||||
"gloves": "",
|
||||
"accessories": "pointed witch hat, brown belt, magic wand"
|
||||
"default": {
|
||||
"full_body": "luna nova school uniform",
|
||||
"headwear": "",
|
||||
"top": "white shirt,dark blue witch robes",
|
||||
"bottom": "dark blue skirt",
|
||||
"legwear": "white socks",
|
||||
"footwear": "brown boots",
|
||||
"hands": "",
|
||||
"accessories": "pointed witch hat, brown belt, magic wand"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, magical girl, little witch academia style",
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, slender build, fair skin, fox ears",
|
||||
"hair": "long blonde hair, flowing",
|
||||
"eyes": "yellow eyes",
|
||||
"expression": "charming smile",
|
||||
"hands": "silver nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "whisker markings on cheeks, crystal tails"
|
||||
"extra": "whisker markings on cheeks, crystal tails"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "silver crop top",
|
||||
"outer_layer": "white and silver jacket",
|
||||
"lower_body": "black leather shorts",
|
||||
"footwear": "black thigh-high boots",
|
||||
"gloves": "",
|
||||
"accessories": "crystal heart, silver jewelry"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "silver crop top, white and silver jacket",
|
||||
"bottom": "black leather shorts",
|
||||
"legwear": "",
|
||||
"footwear": "black thigh-high boots",
|
||||
"hands": "",
|
||||
"accessories": "crystal heart, silver jewelry"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "pop star, mystical, k/da style",
|
||||
@@ -34,6 +42,9 @@
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"League of Legends", "K/DA", "KDA", "K-Pop"
|
||||
"League of Legends",
|
||||
"K/DA",
|
||||
"KDA",
|
||||
"K-Pop"
|
||||
]
|
||||
}
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, athletic build, fair skin",
|
||||
"hair": "long dark blue hair, blonde streaks, high ponytail",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "cool, rebellious look",
|
||||
"hands": "blue nails",
|
||||
"arms": "tattoos on arms",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black crop top",
|
||||
"outer_layer": "blue and silver motorcycle jacket",
|
||||
"lower_body": "black leather pants",
|
||||
"footwear": "blue sneakers",
|
||||
"gloves": "black fingerless gloves",
|
||||
"accessories": "kama and kunai"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "black crop top, blue and silver motorcycle jacket",
|
||||
"bottom": "black leather pants",
|
||||
"legwear": "",
|
||||
"footwear": "blue sneakers",
|
||||
"hands": "black fingerless gloves",
|
||||
"accessories": "kama and kunai"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "pop star, street, k/da style",
|
||||
@@ -34,6 +42,9 @@
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"League of Legends", "K/DA", "KDA", "K-Pop"
|
||||
"League of Legends",
|
||||
"K/DA",
|
||||
"KDA",
|
||||
"K-Pop"
|
||||
]
|
||||
}
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, curvaceous build, fair skin",
|
||||
"hair": "light blue hair,",
|
||||
"eyes": "yellow glowing eyes, slit pupils",
|
||||
"expression": "seductive, confident look",
|
||||
"hands": "metal claws",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "two long lashers (shadow tendrils)"
|
||||
"extra": "two long lashers (shadow tendrils)"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black leather bra",
|
||||
"outer_layer": "iridescent blue jacket, fur collar",
|
||||
"lower_body": "black leather skirt",
|
||||
"footwear": "black high-heeled boots",
|
||||
"gloves": "",
|
||||
"accessories": "diamond earrings"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "black leather bra, iridescent blue jacket, fur collar",
|
||||
"bottom": "black leather skirt",
|
||||
"legwear": "",
|
||||
"footwear": "black high-heeled boots",
|
||||
"hands": "",
|
||||
"accessories": "diamond earrings"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "pop star, glamorous, k/da style",
|
||||
@@ -34,6 +42,9 @@
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"League of Legends", "K/DA", "KDA", "K-Pop"
|
||||
"League of Legends",
|
||||
"K/DA",
|
||||
"KDA",
|
||||
"K-Pop"
|
||||
]
|
||||
}
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, athletic build, fair skin",
|
||||
"hair": "long hair, purple hair, hair ornament, ponytail, green highlights",
|
||||
"eyes": "purple eyes",
|
||||
"expression": "focused expression",
|
||||
"hands": "silver nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "silver bodysuit",
|
||||
"outer_layer": "white and silver jacket",
|
||||
"lower_body": "silver leggings",
|
||||
"footwear": "silver high-heeled boots",
|
||||
"gloves": "",
|
||||
"accessories": "crystal shoulder pods"
|
||||
"default": {
|
||||
"full_body": "silver bodysuit",
|
||||
"headwear": "",
|
||||
"top": "white and silver jacket",
|
||||
"bottom": "",
|
||||
"legwear": "silver leggings",
|
||||
"footwear": "silver high-heeled boots",
|
||||
"hands": "",
|
||||
"accessories": "crystal shoulder pods"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "pop star, futuristic, k/da style",
|
||||
@@ -34,6 +42,9 @@
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"League of Legends", "K/DA", "KDA", "K-Pop"
|
||||
"League of Legends",
|
||||
"K/DA",
|
||||
"KDA",
|
||||
"K-Pop"
|
||||
]
|
||||
}
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, slender build, pale skin, asian",
|
||||
"hair": "long dark purple hair, hime cut,",
|
||||
"eyes": "dark purple eyes,",
|
||||
"expression": "neutral expression, stoic, cat ears",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "black pantyhose",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "shy,",
|
||||
"pose": "holding notebook",
|
||||
"scene": "classroom"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white shirt",
|
||||
"outer_layer": "itan private high school uniform, blazer, striped bow tie",
|
||||
"lower_body": "plaid skirt",
|
||||
"footwear": "loafers",
|
||||
"gloves": "",
|
||||
"accessories": ""
|
||||
"default": {
|
||||
"full_body": "itan private high school uniform",
|
||||
"headwear": "",
|
||||
"top": "blazer, striped bow tie, white shirt",
|
||||
"bottom": "plaid skirt",
|
||||
"legwear": "black pantyhose",
|
||||
"footwear": "loafers",
|
||||
"hands": "",
|
||||
"accessories": ""
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "anime, manga, clean lines",
|
||||
"aesthetic": "blackboard,anime, manga, clean lines",
|
||||
"primary_color": "purple",
|
||||
"secondary_color": "magenta",
|
||||
"tertiary_color": "white"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, athletic build,",
|
||||
"hair": "long brown hair, single braid",
|
||||
"eyes": "brown eyes",
|
||||
"expression": "light smile, raised eyebrow",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "teal tank top,",
|
||||
"lower_body": "brown shorts",
|
||||
"footwear": "brown combat boots, red laces",
|
||||
"gloves": "black fingerless gloves",
|
||||
"accessories": "dual thigh pistol holsters, brown leatherbackpack, red circular sunglasses"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "teal tank top,",
|
||||
"bottom": "brown shorts",
|
||||
"legwear": "thigh holsters",
|
||||
"footwear": "brown combat boots, red laces",
|
||||
"hands": "black fingerless gloves",
|
||||
"accessories": "dual thigh pistol holsters, brown leatherbackpack, red round sunglasses"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "adventure, retro, 90s style",
|
||||
"aesthetic": "adventurer, ruins, retro, 90s style",
|
||||
"primary_color": "teal",
|
||||
"secondary_color": "brown",
|
||||
"tertiary_color": "black"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, tall, mature female",
|
||||
"hair": "brown hair, wavy hair, side ponytail",
|
||||
"eyes": "green eyes",
|
||||
"expression": "seductive smile",
|
||||
"hands": "",
|
||||
"arms": "detached sleeves",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "wide hips",
|
||||
"legs": "black pantyhose",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "beauty mark"
|
||||
"extra": "beauty mark"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "purple dress, corset",
|
||||
"outer_layer": "purple shawl",
|
||||
"lower_body": "slit skirt",
|
||||
"footwear": "black heels",
|
||||
"gloves": "purple gloves",
|
||||
"accessories": "witch hat, rose, necklace"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "purple dress, corset",
|
||||
"top": "purple shawl",
|
||||
"bottom": "slit skirt",
|
||||
"legwear": "",
|
||||
"footwear": "black heels",
|
||||
"hands": "purple gloves",
|
||||
"accessories": "witch hat, rose, necklace"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "genshin impact, witch, librarian",
|
||||
"aesthetic": "library, genshin impact, witch",
|
||||
"primary_color": "purple",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "gold"
|
||||
@@ -5,32 +5,40 @@
|
||||
"base_specs": "1girl, curvaceous build, fair skin",
|
||||
"hair": "long black hair, complex braids, hairpins",
|
||||
"eyes": "red eyes",
|
||||
"expression": "thinking, raised eyebrow",
|
||||
"hands": "black nails",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "dark purple lipstick"
|
||||
"extra": "dark purple lipstick"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black corset",
|
||||
"outer_layer": "black fur-trimmed dress, many belts on front",
|
||||
"lower_body": "long skirt made of belts",
|
||||
"footwear": "black boots",
|
||||
"gloves": "",
|
||||
"accessories": "moogle doll, silver jewelry"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "black fur-trimmed dress, many belts on front, black corset",
|
||||
"bottom": "long skirt made of belts",
|
||||
"legwear": "",
|
||||
"footwear": "black boots",
|
||||
"hands": "",
|
||||
"accessories": "moogle doll, silver jewelry"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "gothic, ornate, final fantasy x style",
|
||||
"aesthetic": "exotic flowers, gothic, ornate, final fantasy x style",
|
||||
"primary_color": "black",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "purple"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/Lulu DG illuLoRA_1337272.safetensors",
|
||||
"lora_weight": 1.0,
|
||||
"lora_weight": 0.9,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
47
data/characters/majin_android_21.json
Normal file
47
data/characters/majin_android_21.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"character_id": "majin_android_21",
|
||||
"character_name": "Majin Android 21",
|
||||
"identity": {
|
||||
"base_specs": "1girl, curvaceous build, pink skin",
|
||||
"hair": "long voluminous white hair",
|
||||
"eyes": "red eyes, black sclera",
|
||||
"hands": "black claws, pink nails",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"extra": "pink skin, long tail, pointy ears"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "black tube top",
|
||||
"bottom": "white harem pants",
|
||||
"legwear": "baggy pants",
|
||||
"footwear": "black and yellow boots",
|
||||
"hands": "black sleeves",
|
||||
"accessories": "gold bracelets, gold neck ring, hoop earrings,pink donut"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "wasteland,pink ,anime, dragon ball style",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/Android_21v2.1.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Dragon Ball FighterZ"
|
||||
]
|
||||
}
|
||||
57
data/characters/marin_kitagawa.json
Normal file
57
data/characters/marin_kitagawa.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"character_id": "marin_kitagawa",
|
||||
"character_name": "Marin Kitagawa",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin, asian",
|
||||
"hair": "long blonde hair, pink tips",
|
||||
"eyes": "pink eyes (contacts)",
|
||||
"hands": "long pink nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"extra": "piercings"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "happy",
|
||||
"pose": "v",
|
||||
"scene": "sewing room"
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "white school shirt, loosely tied blue tie",
|
||||
"bottom": "",
|
||||
"legwear": "blue plaid miniskirt",
|
||||
"footwear": "black loafers, black socks",
|
||||
"hands": "",
|
||||
"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": {
|
||||
"aesthetic": "gyaru, modern, anime style,",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "blue",
|
||||
"tertiary_color": "pink"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"My Dress-Up Darling"
|
||||
]
|
||||
}
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, tall, mature female",
|
||||
"hair": "pink hair, long hair",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "light smile",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "crop top, detached sleeves, gold trim",
|
||||
"lower_body": "side slit, lace-up skirt",
|
||||
"footwear": "thinghighs, lace-up boots, gold boots, gold armlet",
|
||||
"gloves": "",
|
||||
"accessories": "headset"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "crop top, detached sleeves, gold trim",
|
||||
"bottom": "lace-up skirt",
|
||||
"legwear": "side slit",
|
||||
"footwear": "thinghighs, lace-up boots, gold boots, gold armlet",
|
||||
"hands": "",
|
||||
"accessories": "headset"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "vocaloid, elegant",
|
||||
"aesthetic": "concert, stage,vocaloid, elegant",
|
||||
"primary_color": "black",
|
||||
"secondary_color": "gold",
|
||||
"tertiary_color": "pink"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, mature female",
|
||||
"hair": "brown hair, short hair",
|
||||
"eyes": "brown eyes",
|
||||
"expression": "smile, confident",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "red crop top, sleeveless",
|
||||
"outer_layer": "",
|
||||
"lower_body": "red skirt, mini skirt",
|
||||
"footwear": "brown boots",
|
||||
"gloves": "",
|
||||
"accessories": "choker"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "red crop top, sleeveless",
|
||||
"top": "",
|
||||
"bottom": "red skirt, mini skirt",
|
||||
"legwear": "",
|
||||
"footwear": "brown boots",
|
||||
"hands": "",
|
||||
"accessories": "choker"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "vocaloid, casual",
|
||||
"aesthetic": "concert, stage, vocaloid, casual",
|
||||
"primary_color": "red",
|
||||
"secondary_color": "brown",
|
||||
"tertiary_color": "black"
|
||||
47
data/characters/nessa.json
Normal file
47
data/characters/nessa.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"character_id": "nessa",
|
||||
"character_name": "Nessa",
|
||||
"identity": {
|
||||
"base_specs": "1girl, athletic build, dark skin",
|
||||
"hair": "long hair, light blue highlights",
|
||||
"eyes": "blue eyes",
|
||||
"hands": "blue nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "blue trim",
|
||||
"headwear": "",
|
||||
"top": "white crop top, gym uniform, number '049'",
|
||||
"bottom": "midriff,white and blue shorts, black trim",
|
||||
"legwear": "",
|
||||
"footwear": "white and blue sandals, orange trim",
|
||||
"hands": "fingerless gloves",
|
||||
"accessories": "wristband, small life buoy, pokeball, gold hoop earrings"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "arena,water,aquatic, pokemon style",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "orange"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/NessaBeaIXL_v2.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Pokemon"
|
||||
]
|
||||
}
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, tall, mature female",
|
||||
"hair": "blonde hair, long hair, hair over one eye",
|
||||
"eyes": "blue eyes, sharp eyes",
|
||||
"expression": "serious",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "thick lips"
|
||||
"extra": "thick lips"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black shirt",
|
||||
"outer_layer": "blue military coat, fur collar",
|
||||
"lower_body": "black pants",
|
||||
"footwear": "black boots",
|
||||
"gloves": "black gloves",
|
||||
"accessories": "sword"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "blue military coat, fur collar, black shirt",
|
||||
"bottom": "black pants",
|
||||
"legwear": "",
|
||||
"footwear": "black boots",
|
||||
"hands": "black gloves",
|
||||
"accessories": "sword"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "military, amestris uniform",
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long blonde hair, voluminous, crown",
|
||||
"eyes": "blue eyes, long eyelashes",
|
||||
"expression": "gentle smile",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "pink lips, blue earrings"
|
||||
"extra": "pink lips, blue earrings"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "smile",
|
||||
"pose": "hands together",
|
||||
"scene": "throne room"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white petticoat",
|
||||
"outer_layer": "pink floor-length ball gown, puffy sleeves, dark pink panniers",
|
||||
"lower_body": "long skirt",
|
||||
"footwear": "red high heels",
|
||||
"gloves": "white opera gloves",
|
||||
"accessories": "gold crown with red and blue jewels, blue brooch"
|
||||
"default": {
|
||||
"full_body": "pink ball gown",
|
||||
"headwear": "gold crown",
|
||||
"top": "white petticoat, puffy sleeves, dark pink panniers",
|
||||
"bottom": "",
|
||||
"legwear": "floor length skirt",
|
||||
"footwear": "red high heels",
|
||||
"hands": "white opera gloves",
|
||||
"accessories": "gold crown with red and blue jewels, blue brooch"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "royal, whimsical, nintendo style",
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, slender build, fair skin, pointed ears",
|
||||
"hair": "long blonde hair, braided, gold hair clips",
|
||||
"eyes": "green eyes",
|
||||
"expression": "curious",
|
||||
"hands": "gold nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "tri-force symbol, elf ears"
|
||||
"extra": "tri-force symbol, elf ears"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "blue tunic",
|
||||
"outer_layer": "blue champion's tunic, brown leather belts",
|
||||
"lower_body": "tan trousers",
|
||||
"footwear": "brown leather boots",
|
||||
"gloves": "brown fingerless gloves",
|
||||
"accessories": "sheikah slate, gold jewelry"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "blue champion's tunic, ",
|
||||
"bottom": "brown leather belts, tan trousers",
|
||||
"legwear": "",
|
||||
"footwear": "brown leather boots",
|
||||
"hands": "brown fingerless gloves",
|
||||
"accessories": "sheikah slate, gold jewelry"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, adventurous, zelda style",
|
||||
@@ -29,8 +37,8 @@
|
||||
"tertiary_color": "brown"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_name": "Illustrious/Looks/Zelda.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, petite, horse ears, horse tail",
|
||||
"hair": "long dark brown hair, bangs, hair over one eye",
|
||||
"eyes": "purple eyes",
|
||||
"expression": "shy expression",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white shirt",
|
||||
"outer_layer": "tracen school uniform",
|
||||
"lower_body": "pleated skirt",
|
||||
"footwear": "heeled shoes",
|
||||
"gloves": "",
|
||||
"accessories": "blue rose, hair flower, small hat, dagger"
|
||||
"default": {
|
||||
"full_body": "tracen school uniform",
|
||||
"headwear": "hair flower, small hat",
|
||||
"top": "white shirt",
|
||||
"bottom": "pleated skirt",
|
||||
"legwear": "",
|
||||
"footwear": "heeled shoes",
|
||||
"hands": "",
|
||||
"accessories": "blue rose"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "gothic lolita, elegant",
|
||||
"aesthetic": "outdoors,umbrella,rain,gothic lolita, elegant",
|
||||
"primary_color": "purple",
|
||||
"secondary_color": "blue",
|
||||
"tertiary_color": "black"
|
||||
@@ -5,31 +5,39 @@
|
||||
"base_specs": "1girl, young, dark skin, gerudo",
|
||||
"hair": "short red hair, braided ponytail, gold hair ornament",
|
||||
"eyes": "green eyes",
|
||||
"expression": "serious",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "darkblue lipstick,"
|
||||
"extra": "dark blue lipstick,"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black top, blue sash",
|
||||
"lower_body": "black skirt, pelvic curtain,",
|
||||
"footwear": "gold high heels",
|
||||
"gloves": "",
|
||||
"accessories": "gold jewelry, earrings"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "black top, gold trim",
|
||||
"bottom": "black sarong, pelvic curtain",
|
||||
"legwear": "",
|
||||
"footwear": "black high heels, gold trim",
|
||||
"hands": "",
|
||||
"accessories": "gold jewelry, earrings,"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, desert, gerudo style",
|
||||
"aesthetic": "lightning,fantasy, desert, gerudo, zelda style",
|
||||
"primary_color": "gold",
|
||||
"secondary_color": "black",
|
||||
"tertiary_color": "red"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_name": "Illustrious/Looks/RijuTotK_IXL_v3.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, tall, slender build, fair skin",
|
||||
"hair": "long platinum blonde hair, side-swept bangs covering one eye",
|
||||
"eyes": "light blue eyes",
|
||||
"expression": "serene expression",
|
||||
"hands": "turquoise nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "star-shaped earrings"
|
||||
"extra": "star-shaped earrings"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "turquoise off-the-shoulder gown, silver trim",
|
||||
"lower_body": "long skirt",
|
||||
"footwear": "silver high heels",
|
||||
"gloves": "",
|
||||
"accessories": "silver crown with blue jewels, star wand, luma"
|
||||
"default": {
|
||||
"full_body": "turquoise gown, silver trim",
|
||||
"headwear": "silver crown",
|
||||
"top": "bare shoulders",
|
||||
"bottom": "",
|
||||
"legwear": "long skirt",
|
||||
"footwear": "silver high heels",
|
||||
"hands": "",
|
||||
"accessories": "silver crown with blue jewels, star wand, luma"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "celestial, elegant, nintendo style",
|
||||
"aesthetic": "celestial, elegant, mario style, stars, night,",
|
||||
"primary_color": "turquoise",
|
||||
"secondary_color": "silver",
|
||||
"tertiary_color": "yellow"
|
||||
@@ -4,26 +4,34 @@
|
||||
"identity": {
|
||||
"base_specs": "1girl, anthro, bat girl, white fur",
|
||||
"hair": "short white hair",
|
||||
"eyes": "teal eyes",
|
||||
"expression": "sly smirk",
|
||||
"eyes": "teal eyes, blue eyeshadow",
|
||||
"hands": "white gloves",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "bat wings, eyeshadow"
|
||||
"extra": "bat wings, eyeshadow"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black skin-tight jumpsuit, pink heart-shaped chest plate, bare shoulders, cleavage",
|
||||
"lower_body": "jumpsuit",
|
||||
"footwear": "white boots, pink heart motifs",
|
||||
"gloves": "white gloves, pink cuffs",
|
||||
"accessories": "blue eyeshadow"
|
||||
"default": {
|
||||
"full_body": "black skin-tight jumpsuit",
|
||||
"headwear": "",
|
||||
"top": "pink heart-shaped chest plate, bare shoulders, cleavage",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "white boots, pink heart motifs",
|
||||
"hands": "white gloves, pink cuffs",
|
||||
"accessories": ""
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "jewels, museum,sleek, spy, sonic style",
|
||||
"aesthetic": "gems,jewels, sleek, spy, sonic style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "pink",
|
||||
"tertiary_color": "black"
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, slim build,",
|
||||
"hair": "long teal hair, spiky, voluminous",
|
||||
"eyes": "golden eyes, cat-like pupils",
|
||||
"expression": "confident smirk",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "red gem on forehead,"
|
||||
"extra": "red gem on forehead,"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "long white dress, plunging neckline, black belt",
|
||||
"outer_layer": "black and orange long sleeve jacket with purple trim,",
|
||||
"lower_body": "side_slit,, red trousers",
|
||||
"footwear": "",
|
||||
"gloves": "red gloves",
|
||||
"accessories": "red gems, wristbands"
|
||||
"default": {
|
||||
"full_body": "long white dress",
|
||||
"headwear": "",
|
||||
"top": "black and orange long sleeve jacket with purple trim, plunging neckline",
|
||||
"bottom": "black belt, red trousers",
|
||||
"legwear": "side slit",
|
||||
"footwear": "",
|
||||
"hands": "red gloves, red gem on back of hand",
|
||||
"accessories": "wristbands"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "90s anime, sci-fi",
|
||||
@@ -34,6 +42,7 @@
|
||||
"lora_triggers": "ryouko hakubi, space pirate"
|
||||
},
|
||||
"tags": [
|
||||
"Tenchi Muyou!", "Tenchi Muyo!"
|
||||
"Tenchi Muyou!",
|
||||
"Tenchi Muyo!"
|
||||
]
|
||||
}
|
||||
51
data/characters/sam_totally_spies.json
Normal file
51
data/characters/sam_totally_spies.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"character_id": "sam_totally_spies",
|
||||
"character_name": "Sam",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slim body, fair skin",
|
||||
"hair": "long hair, orange hair, wavy hair, loose hair",
|
||||
"eyes": "green eyes",
|
||||
"hands": "green nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "narrow hips",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "green bodysuit, catsuit, skin tight",
|
||||
"headwear": "",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "heels ",
|
||||
"hands": "green bodysuit",
|
||||
"accessories": "silver belt, heart buckle"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "western cartoon, 2000s style, cel shaded, girly, hearts, pastel",
|
||||
"primary_color": "green",
|
||||
"secondary_color": "orange",
|
||||
"tertiary_color": "silver"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": "sam (totally spies!), green bodysuit, orange hair"
|
||||
},
|
||||
"tags": [
|
||||
"sam (totally spies!)",
|
||||
"totally spies!",
|
||||
"solo",
|
||||
"western cartoon",
|
||||
"spy"
|
||||
]
|
||||
}
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, athletic build, fair skin",
|
||||
"hair": "long blonde hair, ponytail",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "serious expression",
|
||||
"hands": "blue nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "beauty mark on chin"
|
||||
"extra": "beauty mark on chin"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "blue skin-tight bodysuit, pink symbols",
|
||||
"lower_body": "bodysuit",
|
||||
"footwear": "blue high-heeled boots",
|
||||
"gloves": "zero suit",
|
||||
"accessories": "paralyzer pistol"
|
||||
"default": {
|
||||
"full_body": "blue skin-tight bodysuit, zero suit, pink lines",
|
||||
"headwear": "",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "metal high-heeled boots, yellow glow",
|
||||
"hands": "zero suit",
|
||||
"accessories": "paralyzer pistol"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "sci-fi, sleek, metroid style",
|
||||
@@ -5,32 +5,40 @@
|
||||
"base_specs": "1girl, loli, small build",
|
||||
"hair": "blonde hair, short hair",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "smile",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "flat chest",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "smile",
|
||||
"pose": "leaning back",
|
||||
"scene": "living room, couch, low lighting"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "grey t-shirt, white shirt",
|
||||
"outer_layer": "",
|
||||
"lower_body": "blue jeans",
|
||||
"footwear": "sneakers",
|
||||
"gloves": "",
|
||||
"accessories": "wristwatch"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "grey t-shirt,",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "pajama pants",
|
||||
"footwear": "barefoot",
|
||||
"hands": "",
|
||||
"accessories": "wristwatch"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "casual, 2013 fashion",
|
||||
"aesthetic": "casual, 2013 fashion,",
|
||||
"primary_color": "grey",
|
||||
"secondary_color": "blue",
|
||||
"tertiary_color": "white"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_name": "Illustrious/Looks/SarahMillerOGILf_1328931.safetensors",
|
||||
"lora_weight": 0.6,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
53
data/characters/scarlet_ff7.json
Normal file
53
data/characters/scarlet_ff7.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"character_id": "scarlet_ff7",
|
||||
"character_name": "Scarlet",
|
||||
"identity": {
|
||||
"base_specs": "1girl, mature female, voluptuous, ",
|
||||
"hair": "blonde hair, wavy hair, short hair, swept back",
|
||||
"eyes": "blue eyes, narrow eyes, eyeshadow",
|
||||
"hands": "manicured nails, red nails",
|
||||
"arms": "",
|
||||
"torso": "large breasts, cleavage",
|
||||
"pelvis": "curvy, wide hips",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"extra": "red lipstick, heavy makeup"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "smirk",
|
||||
"pose": "stepping on viewer, from below",
|
||||
"scene": "penthouse office, night"
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "red dress, formal dress, pencil dress",
|
||||
"headwear": "",
|
||||
"top": "sleeveless, chest cutout",
|
||||
"bottom": "high slit",
|
||||
"legwear": "side slit, black stockings",
|
||||
"footwear": "high heels, red heels, stiletto heels",
|
||||
"hands": "",
|
||||
"accessories": "jewelry, gold earrings, necklace"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "corporate, sci-fi, low lighting",
|
||||
"primary_color": "red",
|
||||
"secondary_color": "gold",
|
||||
"tertiary_color": "black"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/ffscarlet-illu-nvwls-v2.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"final fantasy vii",
|
||||
"shinra",
|
||||
"antagonist",
|
||||
"milf",
|
||||
"red dress",
|
||||
"blonde hair",
|
||||
"smirk"
|
||||
]
|
||||
}
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, dark skin, pointy ears",
|
||||
"hair": "purple hair, very long hair, ponytail",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "smile, energetic",
|
||||
"hands": "",
|
||||
"arms": "gold bracelets",
|
||||
"torso": "small breasts, perky breasts",
|
||||
"pelvis": "wide hips",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "smile",
|
||||
"pose": "belly dancing, hands above head",
|
||||
"scene": "desert town, oasis"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "red bikini top, red harem pants, gold trim",
|
||||
"lower_body": "",
|
||||
"footwear": "gold shoes",
|
||||
"gloves": "",
|
||||
"accessories": "gold tiara, hoop earrings"
|
||||
"default": {
|
||||
"full_body": "gold trim",
|
||||
"headwear": "",
|
||||
"top": "red bikini top",
|
||||
"bottom": "red harem pants",
|
||||
"legwear": "",
|
||||
"footwear": "gold shoes",
|
||||
"hands": "",
|
||||
"accessories": "gold tiara, hoop earrings"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "genie, dancer, arabian",
|
||||
48
data/characters/sorceress_dragons_crown.json
Normal file
48
data/characters/sorceress_dragons_crown.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"character_id": "sorceress_dragons_crown",
|
||||
"character_name": "Sorceress",
|
||||
"identity": {
|
||||
"base_specs": "1girl, mature female,",
|
||||
"hair": "long hair, red hair, wavy hair",
|
||||
"eyes": "green eyes, ",
|
||||
"hands": "painted nails",
|
||||
"arms": "bare shoulders, sleeveless",
|
||||
"torso": "huge breasts, cleavage, ",
|
||||
"pelvis": "wide hips, ",
|
||||
"legs": "thick thighs, ",
|
||||
"feet": "",
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "witch hat",
|
||||
"top": "black corset, white top, clothing cutout, low cut",
|
||||
"bottom": "skirt, high slit, black skirt",
|
||||
"legwear": "side slit",
|
||||
"footwear": "boots",
|
||||
"hands": "",
|
||||
"accessories": "staff, necklace, bracelets, jewelry, wooden staff"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, vanillaware, oil painting (style), exaggerated proportions",
|
||||
"primary_color": "black",
|
||||
"secondary_color": "purple",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/Sorceress iIlluLoRA DG.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"dragon's crown",
|
||||
"witch"
|
||||
]
|
||||
}
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, lanky build, pale skin",
|
||||
"hair": "light purple hair, hair covering one eye",
|
||||
"eyes": "red eyes",
|
||||
"expression": "deadpan expression",
|
||||
"hands": "black nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "narrow waist",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "dark circles under eyes"
|
||||
"extra": "dark circles under eyes"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "neutral expression",
|
||||
"pose": "looking at mushroom",
|
||||
"scene": "dark forest"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "dark purple witch robes",
|
||||
"lower_body": "long skirt with frayed edges",
|
||||
"footwear": "brown boots",
|
||||
"gloves": "",
|
||||
"accessories": "pointed witch hat, potion bottle"
|
||||
"default": {
|
||||
"full_body": "luna nova school uniform, dark purple witch robes",
|
||||
"headwear": "pointed witch hat",
|
||||
"top": "oversized sleeves",
|
||||
"bottom": "",
|
||||
"legwear": "long skirt with frayed edges",
|
||||
"footwear": "brown boots",
|
||||
"hands": "",
|
||||
"accessories": "potion bottle, mushroom"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "gothic, whimsical, little witch academia style",
|
||||
"aesthetic": "gothic, magic, little witch academia style",
|
||||
"primary_color": "purple",
|
||||
"secondary_color": "mauve",
|
||||
"tertiary_color": "green"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, athletic build, fair skin",
|
||||
"hair": "long black hair, tied end",
|
||||
"eyes": "red eyes",
|
||||
"expression": "kind smile",
|
||||
"hands": "dark red nails",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "smile",
|
||||
"pose": "leaning on counter, looking at viewer",
|
||||
"scene": "tavern, bartending"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black sports bra",
|
||||
"outer_layer": "white tank top, black suspenders",
|
||||
"lower_body": "black miniskirt",
|
||||
"footwear": "red boots, black socks",
|
||||
"gloves": "red fingerless gloves",
|
||||
"accessories": "silver earrings"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "white tank top, black sports bra,black suspenders",
|
||||
"bottom": "black miniskirt",
|
||||
"legwear": "thigh high black socks",
|
||||
"footwear": "red boots",
|
||||
"hands": "red fingerless gloves",
|
||||
"accessories": "silver earrings"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "urban, martial arts, final fantasy style",
|
||||
"aesthetic": "martial arts, final fantasy style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "black",
|
||||
"tertiary_color": "red"
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "short spiky brown hair",
|
||||
"eyes": "brown eyes",
|
||||
"expression": "energetic smile",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "freckles"
|
||||
"extra": "freckles"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "smile",
|
||||
"pose": "dashing, blue afterimage",
|
||||
"scene": "futuristic city"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "orange leggings",
|
||||
"outer_layer": "brown flight jacket, yellow vest",
|
||||
"lower_body": "orange leggings",
|
||||
"footwear": "white and orange sneakers",
|
||||
"gloves": "",
|
||||
"accessories": "chronal accelerator, yellow goggles"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "brown flight jacket, yellow vest",
|
||||
"bottom": "orange leggings",
|
||||
"legwear": "",
|
||||
"footwear": "white and orange sneakers",
|
||||
"hands": "fingerless gloves",
|
||||
"accessories": "chronal accelerator, yellow goggles"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "sci-fi, pilot, overwatch style",
|
||||
47
data/characters/urbosa.json
Normal file
47
data/characters/urbosa.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"character_id": "urbosa",
|
||||
"character_name": "Urbosa",
|
||||
"identity": {
|
||||
"base_specs": "1girl, tall, muscular, dark skin, gerudo",
|
||||
"hair": "long red hair, wild hair",
|
||||
"eyes": "green eyes",
|
||||
"hands": "gold nails",
|
||||
"arms": "muscular arms",
|
||||
"torso": "abs, medium breasts",
|
||||
"pelvis": "wide hips",
|
||||
"legs": "muscular legs",
|
||||
"feet": "",
|
||||
"extra": "dark blue lipstick, gerudo markings"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "blue top, green sash, green shoulder guards,",
|
||||
"bottom": "",
|
||||
"legwear": "blue sarong",
|
||||
"footwear": "anklet, gold heels",
|
||||
"hands": "",
|
||||
"accessories": "gold jewelry, scimitar"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "oasis,desert ruins,fantasy, warrior, gerudo style",
|
||||
"primary_color": "gold",
|
||||
"secondary_color": "blue",
|
||||
"tertiary_color": "red"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/Urbosa_-_The_Legend_of_Zelda_Illustrious.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"The Legend of Zelda"
|
||||
]
|
||||
}
|
||||
@@ -5,22 +5,30 @@
|
||||
"base_specs": "1girl, slender build, blue skin",
|
||||
"hair": "long purple hair, ponytail",
|
||||
"eyes": "yellow eyes",
|
||||
"expression": "cold expression",
|
||||
"hands": "",
|
||||
"arms": "spider tattoo on arm",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "blue skin"
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "light smile",
|
||||
"pose": "sniping, prone",
|
||||
"scene": "rooftop, night"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "purple tactical bodysuit, plunging neckline",
|
||||
"lower_body": "bodysuit",
|
||||
"footwear": "purple high-heeled boots",
|
||||
"gloves": "purple gauntlets",
|
||||
"accessories": "sniper visor, grappling hook"
|
||||
"default": {
|
||||
"full_body": "purple bodysuit, skintight",
|
||||
"headwear": "",
|
||||
"top": " plunging neckline",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "purple high-heeled boots",
|
||||
"hands": "purple gauntlets",
|
||||
"accessories": "sniper rifle, visor"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "sci-fi, assassin, overwatch style",
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long black hair, styled with gold headband",
|
||||
"eyes": "red eyes",
|
||||
"expression": "gentle yet mysterious smile",
|
||||
"hands": "black nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "serious",
|
||||
"pose": "leaping",
|
||||
"scene": "rooftop, night, red sky, blood splatter"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black backless halter dress, red rose pattern inside",
|
||||
"lower_body": "black thigh-high boots",
|
||||
"footwear": "black boots",
|
||||
"gloves": "black fingerless gloves",
|
||||
"accessories": "gold rose-themed headband, gold needle weapons"
|
||||
"default": {
|
||||
"full_body": "black halter dress",
|
||||
"headwear": "",
|
||||
"top": "backless, pattern inside",
|
||||
"bottom": "",
|
||||
"legwear": "black thigh-high boots",
|
||||
"footwear": "black boots",
|
||||
"hands": "black fingerless gloves",
|
||||
"accessories": "gold rose-themed headband, gold needle weapons"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "elegant, assassin, spy x family style",
|
||||
"aesthetic": "elegant, assassin,red rose, spy x family style",
|
||||
"primary_color": "black",
|
||||
"secondary_color": "red",
|
||||
"tertiary_color": "gold"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, miqo'te, slender build, fair skin, cat ears",
|
||||
"hair": "short white hair, bangs",
|
||||
"eyes": "blind, white eyes",
|
||||
"expression": "stoic expression",
|
||||
"hands": "black nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "facial markings, cat tail"
|
||||
"extra": "facial markings, cat tail"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black sorceress robes, fur trim",
|
||||
"lower_body": "long skirt",
|
||||
"footwear": "black boots",
|
||||
"gloves": "",
|
||||
"accessories": "wooden staff"
|
||||
"default": {
|
||||
"full_body": "black sorceress robes, ",
|
||||
"headwear": "",
|
||||
"top": "bare shoulders, fur trim",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "black boots",
|
||||
"hands": "",
|
||||
"accessories": "wooden staff"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "magical, scholarly, final fantasy xiv style",
|
||||
"aesthetic": "library, magical, scholarly, final fantasy xiv style",
|
||||
"primary_color": "black",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "purple"
|
||||
@@ -34,6 +42,7 @@
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Final Fantasy XIV"
|
||||
"Final Fantasy XIV",
|
||||
"mi'qote"
|
||||
]
|
||||
}
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "short black hair, bob cut",
|
||||
"eyes": "brown eyes",
|
||||
"expression": "playful grin",
|
||||
"hands": "",
|
||||
"arms": "black sleeve on one arm",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "headband"
|
||||
"extra": "headband"
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "cheeky smile",
|
||||
"pose": "holding glass orb, materia",
|
||||
"scene": "forest, sunlight"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "green turtleneck sweater vest, midriff",
|
||||
"lower_body": "beige shorts",
|
||||
"footwear": "boots, socks",
|
||||
"gloves": "fingerless glove on one hand, large gauntlet on one arm",
|
||||
"accessories": "shuriken"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "green turtleneck sweater vest, midriff",
|
||||
"bottom": "beige shorts",
|
||||
"legwear": "single kneehigh sock",
|
||||
"footwear": "boots, ",
|
||||
"hands": "fingerless glove on one hand, large gauntlet on one arm",
|
||||
"accessories": "shuriken"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "ninja, adventurer, final fantasy style",
|
||||
"aesthetic": "ninja, japanese, final fantasy style",
|
||||
"primary_color": "green",
|
||||
"secondary_color": "beige",
|
||||
"tertiary_color": "black"
|
||||
@@ -5,25 +5,33 @@
|
||||
"base_specs": "1girl, slender, fair skin",
|
||||
"hair": "short brown hair, bob cut",
|
||||
"eyes": "heterochromia, blue eye, green eye",
|
||||
"expression": "gentle",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "serene",
|
||||
"pose": "dancing",
|
||||
"scene": "standing on water, sunset, pink sky, "
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white kimono top, yellow obi",
|
||||
"outer_layer": "",
|
||||
"lower_body": "long blue skirt, floral pattern",
|
||||
"footwear": "boots",
|
||||
"gloves": "detached sleeves",
|
||||
"accessories": "summoner staff, necklace"
|
||||
"default": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "white kimono top, yellow obi, detached sleeves",
|
||||
"bottom": "long blue skirt, floral pattern",
|
||||
"legwear": "",
|
||||
"footwear": "black boots",
|
||||
"hands": "",
|
||||
"accessories": "summoner staff, necklace"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, final fantasy x style",
|
||||
"aesthetic": "shrine maiden,fantasy, final fantasy x style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "blue",
|
||||
"tertiary_color": "yellow"
|
||||
26
data/clothing/bikini_01.json
Normal file
26
data/clothing/bikini_01.json
Normal 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"
|
||||
]
|
||||
}
|
||||
26
data/clothing/bikini_02.json
Normal file
26
data/clothing/bikini_02.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
data/clothing/cat_cosplay.json
Normal file
27
data/clothing/cat_cosplay.json
Normal 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"
|
||||
]
|
||||
}
|
||||
25
data/clothing/evening_gown.json
Normal file
25
data/clothing/evening_gown.json
Normal 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"
|
||||
]
|
||||
}
|
||||
22
data/clothing/french_maid_01.json
Normal file
22
data/clothing/french_maid_01.json
Normal 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"
|
||||
]
|
||||
}
|
||||
23
data/clothing/french_maid_02.json
Normal file
23
data/clothing/french_maid_02.json
Normal 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"
|
||||
]
|
||||
}
|
||||
26
data/clothing/latex_outfit.json
Normal file
26
data/clothing/latex_outfit.json
Normal 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"
|
||||
]
|
||||
}
|
||||
26
data/clothing/lingerie.json
Normal file
26
data/clothing/lingerie.json
Normal 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"
|
||||
]
|
||||
}
|
||||
26
data/clothing/lingerie_02.json
Normal file
26
data/clothing/lingerie_02.json
Normal 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
27
data/clothing/mother.json
Normal 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"
|
||||
]
|
||||
}
|
||||
22
data/clothing/nurse_01.json
Normal file
22
data/clothing/nurse_01.json
Normal 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"
|
||||
]
|
||||
}
|
||||
23
data/clothing/nurse_02.json
Normal file
23
data/clothing/nurse_02.json
Normal 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"
|
||||
]
|
||||
}
|
||||
20
data/clothing/pasties_01.json
Normal file
20
data/clothing/pasties_01.json
Normal 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": []
|
||||
}
|
||||
32
data/clothing/playboy_bunny.json
Normal file
32
data/clothing/playboy_bunny.json
Normal 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"
|
||||
]
|
||||
}
|
||||
20
data/clothing/school_uniform_01.json
Normal file
20
data/clothing/school_uniform_01.json
Normal 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": []
|
||||
}
|
||||
23
data/clothing/school_uniform_02.json
Normal file
23
data/clothing/school_uniform_02.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
data/clothing/school_uniform_03.json
Normal file
27
data/clothing/school_uniform_03.json
Normal 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"
|
||||
]
|
||||
}
|
||||
20
data/clothing/school_uniform_04.json
Normal file
20
data/clothing/school_uniform_04.json
Normal 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": []
|
||||
}
|
||||
@@ -17,7 +17,7 @@ source "$VENV_DIR/bin/activate"
|
||||
if [ "$1" == "--clean" ]; then
|
||||
echo "Performing clean start..."
|
||||
echo "Removing database..."
|
||||
rm -f database.db
|
||||
rm -f database.db instance/database.db
|
||||
echo "Clearing uploads..."
|
||||
rm -rf static/uploads/*
|
||||
fi
|
||||
|
||||
153
migrate_wardrobe.py
Normal file
153
migrate_wardrobe.py
Normal file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Migration script to convert wardrobe structure from flat to nested format.
|
||||
|
||||
Before:
|
||||
"wardrobe": {
|
||||
"headwear": "...",
|
||||
"top": "...",
|
||||
...
|
||||
}
|
||||
|
||||
After:
|
||||
"wardrobe": {
|
||||
"default": {
|
||||
"headwear": "...",
|
||||
"top": "...",
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
This enables multiple outfits per character.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def migrate_wardrobe(characters_dir: str = "characters", dry_run: bool = False):
|
||||
"""
|
||||
Migrate all character JSON files to the new wardrobe structure.
|
||||
|
||||
Args:
|
||||
characters_dir: Path to the directory containing character JSON files
|
||||
dry_run: If True, only print what would be changed without modifying files
|
||||
"""
|
||||
characters_path = Path(characters_dir)
|
||||
|
||||
if not characters_path.exists():
|
||||
print(f"Error: Directory '{characters_dir}' does not exist")
|
||||
return
|
||||
|
||||
json_files = list(characters_path.glob("*.json"))
|
||||
|
||||
if not json_files:
|
||||
print(f"No JSON files found in '{characters_dir}'")
|
||||
return
|
||||
|
||||
migrated_count = 0
|
||||
skipped_count = 0
|
||||
error_count = 0
|
||||
|
||||
for json_file in json_files:
|
||||
try:
|
||||
with open(json_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Check if character has a wardrobe
|
||||
if 'wardrobe' not in data:
|
||||
print(f" [SKIP] {json_file.name}: No wardrobe field")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
wardrobe = data['wardrobe']
|
||||
|
||||
# Check if already migrated (wardrobe contains 'default' key with nested dict)
|
||||
if 'default' in wardrobe and isinstance(wardrobe['default'], dict):
|
||||
# Verify it's actually the new format (has wardrobe keys inside)
|
||||
expected_keys = {'headwear', 'top', 'legwear', 'footwear', 'hands', 'accessories',
|
||||
'inner_layer', 'outer_layer', 'lower_body', 'gloves'}
|
||||
if any(key in wardrobe['default'] for key in expected_keys):
|
||||
print(f" [SKIP] {json_file.name}: Already migrated")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# Check if wardrobe is a flat structure (not already nested)
|
||||
# A flat wardrobe has string values, a nested one has dict values
|
||||
if not isinstance(wardrobe, dict):
|
||||
print(f" [ERROR] {json_file.name}: Wardrobe is not a dictionary")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
# Check if any value is a dict (indicating partial migration or different structure)
|
||||
has_nested_values = any(isinstance(v, dict) for v in wardrobe.values())
|
||||
if has_nested_values:
|
||||
print(f" [SKIP] {json_file.name}: Wardrobe has nested values, may already be migrated")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# Perform migration
|
||||
new_wardrobe = {
|
||||
"default": wardrobe
|
||||
}
|
||||
data['wardrobe'] = new_wardrobe
|
||||
|
||||
if dry_run:
|
||||
print(f" [DRY-RUN] {json_file.name}: Would migrate wardrobe")
|
||||
print(f" Old: {json.dumps(wardrobe, indent=2)[:100]}...")
|
||||
print(f" New: {json.dumps(new_wardrobe, indent=2)[:100]}...")
|
||||
else:
|
||||
with open(json_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
print(f" [MIGRATED] {json_file.name}")
|
||||
|
||||
migrated_count += 1
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f" [ERROR] {json_file.name}: Invalid JSON - {e}")
|
||||
error_count += 1
|
||||
except Exception as e:
|
||||
print(f" [ERROR] {json_file.name}: {e}")
|
||||
error_count += 1
|
||||
|
||||
print()
|
||||
print("=" * 50)
|
||||
print(f"Migration complete:")
|
||||
print(f" - Migrated: {migrated_count}")
|
||||
print(f" - Skipped: {skipped_count}")
|
||||
print(f" - Errors: {error_count}")
|
||||
if dry_run:
|
||||
print()
|
||||
print("This was a dry run. No files were modified.")
|
||||
print("Run with --execute to apply changes.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Migrate character wardrobe structure to support multiple outfits"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--execute",
|
||||
action="store_true",
|
||||
help="Actually modify files (default is dry-run)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dir",
|
||||
default="characters",
|
||||
help="Directory containing character JSON files (default: characters)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("=" * 50)
|
||||
print("Wardrobe Migration Script")
|
||||
print("=" * 50)
|
||||
print(f"Directory: {args.dir}")
|
||||
print(f"Mode: {'EXECUTE' if args.execute else 'DRY-RUN'}")
|
||||
print("=" * 50)
|
||||
print()
|
||||
|
||||
migrate_wardrobe(characters_dir=args.dir, dry_run=not args.execute)
|
||||
56
models.py
56
models.py
@@ -6,10 +6,64 @@ class Character(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
character_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)
|
||||
active_outfit = db.Column(db.String(100), default='default')
|
||||
|
||||
def get_active_wardrobe(self):
|
||||
"""Get the currently active wardrobe outfit."""
|
||||
wardrobe = self.data.get('wardrobe', {})
|
||||
# Check if wardrobe is nested (new format) or flat (legacy)
|
||||
if 'default' in wardrobe and isinstance(wardrobe.get('default'), dict):
|
||||
# New nested format - return active outfit
|
||||
return wardrobe.get(self.active_outfit or 'default', wardrobe.get('default', {}))
|
||||
else:
|
||||
# Legacy flat format - return as-is
|
||||
return wardrobe
|
||||
|
||||
def get_available_outfits(self):
|
||||
"""Get list of available outfit names."""
|
||||
wardrobe = self.data.get('wardrobe', {})
|
||||
if 'default' in wardrobe and isinstance(wardrobe.get('default'), dict):
|
||||
return list(wardrobe.keys())
|
||||
return ['default']
|
||||
|
||||
def __repr__(self):
|
||||
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'<Character {self.character_id}>'
|
||||
return f'<Outfit {self.outfit_id}>'
|
||||
|
||||
class Action(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
action_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'<Action {self.action_id}>'
|
||||
|
||||
class Settings(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
openrouter_api_key = db.Column(db.String(255), nullable=True)
|
||||
openrouter_model = db.Column(db.String(100), default='google/gemini-2.0-flash-001')
|
||||
|
||||
def __repr__(self):
|
||||
return '<Settings>'
|
||||
|
||||
74
templates/actions/create.html
Normal file
74
templates/actions/create.html
Normal 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 Action</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ url_for('create_action') }}" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Action Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="e.g. Belly Dancing" 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. belly_dancing">
|
||||
<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 action, pose, or movement. The AI will generate the full action 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 action profile with pose details 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 action 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 Action';
|
||||
promptInput.required = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
407
templates/actions/detail.html
Normal file
407
templates/actions/detail.html
Normal file
@@ -0,0 +1,407 @@
|
||||
{% 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>
|
||||
|
||||
{% macro selection_checkbox(section, key, label, value) %}
|
||||
<input class="form-check-input me-1" type="checkbox" name="include_field" value="{{ section }}::{{ key }}"
|
||||
{% if preferences is not none %}
|
||||
{% if section + '::' + key in preferences %}checked{% endif %}
|
||||
{% elif action.default_fields is not none %}
|
||||
{% if section + '::' + key in action.default_fields %}checked{% endif %}
|
||||
{% else %}
|
||||
{% if value %}checked{% endif %}
|
||||
{% endif %}>
|
||||
{% endmacro %}
|
||||
|
||||
<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 action.image_path %}
|
||||
<img src="{{ url_for('static', filename='uploads/' + action.image_path) }}" alt="{{ action.name }}" class="img-fluid">
|
||||
{% else %}
|
||||
<span class="text-muted">No Image Attached</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ url_for('upload_action_image', slug=action.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 (Action 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 action 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_action_defaults', slug=action.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_action_cover_from_preview', slug=action.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_action_cover_from_preview', slug=action.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 action.default_fields is not none %}
|
||||
{% if 'special::tags' in action.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 action.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">{{ action.name }}</h1>
|
||||
<a href="{{ url_for('edit_action', slug=action.slug) }}" class="btn btn-sm btn-link text-decoration-none">Edit Profile</a>
|
||||
<form action="{{ url_for('clone_action', slug=action.slug) }}" method="post" style="display: inline;">
|
||||
<button type="submit" class="btn btn-sm btn-link text-decoration-none">Clone Action</button>
|
||||
</form>
|
||||
</div>
|
||||
<a href="{{ url_for('actions_index') }}" class="btn btn-outline-secondary">Back to Gallery</a>
|
||||
</div>
|
||||
|
||||
<form id="generate-form" action="{{ url_for('generate_action_image', slug=action.slug) }}" method="post">
|
||||
{# Action details section #}
|
||||
{% set action_details = action.data.get('action', {}) %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light">
|
||||
<strong>Action Details</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row mb-0">
|
||||
{% for key, value in action_details.items() %}
|
||||
<dt class="col-sm-4 text-capitalize">
|
||||
{{ selection_checkbox('action', key, key.replace('_', ' '), value) }}
|
||||
{{ key.replace('_', ' ') }}
|
||||
</dt>
|
||||
<dd class="col-sm-8">{{ value if value else '--' }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Defaults (Pose/Expression Aggregates) #}
|
||||
<div class="card mb-4 border-info">
|
||||
<div class="card-header bg-info text-white">
|
||||
<strong>Prompt Aggregates (Character Overrides)</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-text mb-3">These fields will override character defaults when a character is selected.</div>
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-4 text-capitalize">
|
||||
{{ selection_checkbox('defaults', 'pose', 'Pose', True) }}
|
||||
Pose (Combined)
|
||||
</dt>
|
||||
<dd class="col-sm-8 text-muted small">Aggregated from Action Pose fields</dd>
|
||||
|
||||
<dt class="col-sm-4 text-capitalize">
|
||||
{{ selection_checkbox('defaults', 'expression', 'Expression', True) }}
|
||||
Expression (Combined)
|
||||
</dt>
|
||||
<dd class="col-sm-8 text-muted small">Aggregated from Action Expression fields</dd>
|
||||
|
||||
<dt class="col-sm-4 text-capitalize">
|
||||
{{ selection_checkbox('defaults', 'scene', 'Scene', action_details.get('additional')) }}
|
||||
Scene (Additional)
|
||||
</dt>
|
||||
<dd class="col-sm-8 small">{{ action_details.get('additional') if action_details.get('additional') else '--' }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Character Identity/Wardrobe context when character is selected #}
|
||||
<div id="character-context" class="{% if not selected_character or selected_character == '__random__' %}d-none{% endif %}">
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> When a character is selected, their identity and active wardrobe fields will be automatically included based on the character's default selection.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# LoRA section #}
|
||||
{% set lora = action.data.get('lora', {}) %}
|
||||
{% if lora %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<strong>LoRA</strong>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="include_field" value="lora::lora_triggers" id="includeLora"
|
||||
{% if preferences is not none %}
|
||||
{% if 'lora::lora_triggers' in preferences %}checked{% endif %}
|
||||
{% elif action.default_fields is not none %}
|
||||
{% if 'lora::lora_triggers' in action.default_fields %}checked{% endif %}
|
||||
{% else %}
|
||||
checked
|
||||
{% endif %}>
|
||||
<label class="form-check-label small" for="includeLora">Include Triggers</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row mb-0">
|
||||
{% for key, value in lora.items() %}
|
||||
<dt class="col-sm-4 text-capitalize">{{ 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');
|
||||
const charSelect = document.getElementById('character_select');
|
||||
const charContext = document.getElementById('character-context');
|
||||
|
||||
// Toggle character context info
|
||||
charSelect.addEventListener('change', () => {
|
||||
if (charSelect.value && charSelect.value !== '__random__') {
|
||||
charContext.classList.remove('d-none');
|
||||
} else {
|
||||
charContext.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
// Generate a unique client ID
|
||||
const clientId = 'action_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 = `/action/{{ action.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 = `/action/{{ action.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 %}
|
||||
80
templates/actions/edit.html
Normal file
80
templates/actions/edit.html
Normal 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 Action: {{ action.name }}</h1>
|
||||
<a href="{{ url_for('action_detail', slug=action.slug) }}" class="btn btn-outline-secondary">Cancel</a>
|
||||
</div>
|
||||
|
||||
<form action="{{ url_for('edit_action', slug=action.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="action_name" class="form-label">Display Name</label>
|
||||
<input type="text" class="form-control" id="action_name" name="action_name" value="{{ action.name }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="action_id" class="form-label">Action ID</label>
|
||||
<input type="text" class="form-control" id="action_id" name="action_id" value="{{ action.action_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="{{ action.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 action.data.lora and action.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="{{ action.data.lora.lora_weight if action.data.lora else 1.0 }}">
|
||||
</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="{{ action.data.lora.lora_triggers if action.data.lora else '' }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Details Section -->
|
||||
{% set action_details = action.data.get('action', {}) %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light"><strong>Action Details</strong></div>
|
||||
<div class="card-body">
|
||||
{% for key, value in action_details.items() %}
|
||||
<div class="mb-3">
|
||||
<label for="action_{{ key }}" class="form-label text-capitalize">{{ key.replace('_', ' ') }}</label>
|
||||
<input type="text" class="form-control" id="action_{{ key }}" name="action_{{ key }}" value="{{ value }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<a href="{{ url_for('action_detail', slug=action.slug) }}" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
41
templates/actions/index.html
Normal file
41
templates/actions/index.html
Normal file
@@ -0,0 +1,41 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Action Gallery</h2>
|
||||
<div class="d-flex">
|
||||
<a href="{{ url_for('create_action') }}" class="btn btn-success me-2">Create New Action</a>
|
||||
<form action="{{ url_for('rescan_actions') }}" method="post">
|
||||
<button type="submit" class="btn btn-outline-primary">Rescan Action 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 action in actions %}
|
||||
<div class="col" id="card-{{ action.slug }}">
|
||||
<div class="card h-100 character-card" onclick="window.location.href='/action/{{ action.slug }}'">
|
||||
<div class="img-container">
|
||||
{% if action.image_path %}
|
||||
<img id="img-{{ action.slug }}" src="{{ url_for('static', filename='uploads/' + action.image_path) }}" alt="{{ action.name }}">
|
||||
<span id="no-img-{{ action.slug }}" class="text-muted d-none">No Image</span>
|
||||
{% else %}
|
||||
<img id="img-{{ action.slug }}" src="" alt="{{ action.name }}" class="d-none">
|
||||
<span id="no-img-{{ action.slug }}" class="text-muted">No Image</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center">{{ action.name }}</h5>
|
||||
<p class="card-text small text-center text-muted">{{ action.data.tags | join(', ') }}</p>
|
||||
</div>
|
||||
{% if action.data.lora and action.data.lora.lora_name %}
|
||||
{% set lora_name = action.data.lora.lora_name.split('/')[-1].replace('.safetensors', '') %}
|
||||
<div class="card-footer text-center p-1">
|
||||
<small class="text-muted" title="{{ action.data.lora.lora_name }}">{{ lora_name }}</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
74
templates/create.html
Normal file
74
templates/create.html
Normal 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 Character</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ url_for('create_character') }}" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Character Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="e.g. Cyberpunk Ninja" 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. cyberpunk_ninja">
|
||||
<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 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 class="alert alert-info" id="ai-info">
|
||||
<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 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 Character';
|
||||
promptInput.required = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,10 +1,21 @@
|
||||
{% 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;">
|
||||
<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 %}
|
||||
<img src="{{ url_for('static', filename='uploads/' + character.image_path) }}" alt="{{ character.name }}" class="img-fluid">
|
||||
{% else %}
|
||||
@@ -21,7 +32,6 @@
|
||||
</form>
|
||||
<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="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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -36,18 +46,28 @@
|
||||
|
||||
{% if preview_image %}
|
||||
<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="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">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<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="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">
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,13 +99,77 @@
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>{{ character.name }}</h1>
|
||||
<div>
|
||||
<h1 class="mb-0">{{ character.name }}</h1>
|
||||
<a href="{{ url_for('edit_character', slug=character.slug) }}" class="btn btn-sm btn-link text-decoration-none">Edit Profile</a>
|
||||
</div>
|
||||
<a href="/" class="btn btn-outline-secondary">Back to Gallery</a>
|
||||
</div>
|
||||
|
||||
<!-- Outfit Switcher -->
|
||||
{% set outfits = character.get_available_outfits() %}
|
||||
{% if outfits|length > 1 %}
|
||||
<div class="card mb-4 border-primary">
|
||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||
<span><i class="bi bi-shirt"></i> Active Outfit</span>
|
||||
<span class="badge bg-light text-primary">{{ character.active_outfit or 'default' }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ url_for('switch_outfit', slug=character.slug) }}" method="post" class="row g-2">
|
||||
<div class="col-auto flex-grow-1">
|
||||
<select name="outfit" class="form-select" id="outfit-select">
|
||||
{% for outfit in outfits %}
|
||||
<option value="{{ outfit }}" {% if outfit == character.active_outfit %}selected{% endif %}>
|
||||
{{ outfit }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-primary">Switch</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form id="generate-form" action="{{ url_for('generate_image', slug=character.slug) }}" method="post">
|
||||
{% for section, details in character.data.items() %}
|
||||
{% if section not in ['character_id', 'tags', 'name'] and details is mapping %}
|
||||
{% if section == 'wardrobe' %}
|
||||
{# Special handling for wardrobe - show active outfit #}
|
||||
{% set active_wardrobe = character.get_active_wardrobe() %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<strong>
|
||||
Wardrobe
|
||||
{% if outfits|length > 1 %}
|
||||
<span class="badge bg-secondary ms-2">{{ character.active_outfit or 'default' }}</span>
|
||||
{% endif %}
|
||||
</strong>
|
||||
{% if outfits|length > 1 %}
|
||||
<a href="{{ url_for('edit_character', slug=character.slug) }}#outfits" class="btn btn-sm btn-outline-secondary">Manage Outfits</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row mb-0">
|
||||
{% for key, value in active_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 character.default_fields is not none %}
|
||||
{% if 'wardrobe::' + key in character.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>
|
||||
{% elif section not in ['character_id', 'tags', 'name'] and details is mapping %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light text-capitalize"><strong>{{ section.replace('_', ' ') }}</strong></div>
|
||||
<div class="card-body">
|
||||
@@ -201,7 +285,7 @@
|
||||
form.addEventListener('submit', async (e) => {
|
||||
// Only intercept generate actions
|
||||
const submitter = e.submitter;
|
||||
if (!submitter || (submitter.value !== 'preview' && submitter.value !== 'replace')) {
|
||||
if (!submitter || submitter.value !== 'preview') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -256,7 +340,7 @@
|
||||
progressLabel.textContent = 'Saving image...';
|
||||
const url = `/character/{{ character.slug }}/finalize_generation/${promptId}`;
|
||||
const formData = new FormData();
|
||||
formData.append('action', action);
|
||||
formData.append('action', 'preview'); // Always save as preview
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
@@ -266,12 +350,19 @@
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
if (action === 'preview') {
|
||||
previewImg.src = data.image_url;
|
||||
if (previewCard) previewCard.classList.remove('d-none');
|
||||
} else {
|
||||
// Reload for cover update
|
||||
window.location.reload();
|
||||
// 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 = `/character/{{ character.slug }}/replace_cover_from_preview`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert('Save failed: ' + data.error);
|
||||
@@ -284,5 +375,10 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Image modal function
|
||||
function showImage(src) {
|
||||
document.getElementById('modalImage').src = src;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
240
templates/edit.html
Normal file
240
templates/edit.html
Normal file
@@ -0,0 +1,240 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Edit Profile: {{ character.name }}</h1>
|
||||
<a href="{{ url_for('detail', slug=character.slug) }}" class="btn btn-outline-secondary">Cancel</a>
|
||||
</div>
|
||||
|
||||
<form action="{{ url_for('edit_character', slug=character.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="character_name" class="form-label">Display Name</label>
|
||||
<input type="text" class="form-control" id="character_name" name="character_name" value="{{ character.name }}" required>
|
||||
</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="{{ character.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 character.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="{{ character.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="{{ character.data.lora.lora_triggers }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Identity Section -->
|
||||
{% if character.data.identity %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light"><strong>Identity</strong></div>
|
||||
<div class="card-body">
|
||||
{% for key, value in character.data.identity.items() %}
|
||||
<div class="mb-3">
|
||||
<label for="identity_{{ key }}" class="form-label text-capitalize">{{ key.replace('_', ' ') }}</label>
|
||||
<input type="text" class="form-control" id="identity_{{ key }}" name="identity_{{ key }}" value="{{ value }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Defaults Section -->
|
||||
{% if character.data.defaults %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light"><strong>Defaults</strong></div>
|
||||
<div class="card-body">
|
||||
{% for key, value in character.data.defaults.items() %}
|
||||
<div class="mb-3">
|
||||
<label for="defaults_{{ key }}" class="form-label text-capitalize">{{ key.replace('_', ' ') }}</label>
|
||||
<input type="text" class="form-control" id="defaults_{{ key }}" name="defaults_{{ key }}" value="{{ value }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Wardrobe Section - Show all outfits with tabs -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<strong>Wardrobe</strong>
|
||||
<button type="button" class="btn btn-sm btn-success" data-bs-toggle="modal" data-bs-target="#addOutfitModal">
|
||||
<i class="bi bi-plus-lg"></i> Add Outfit
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% set wardrobe_data = character.data.wardrobe %}
|
||||
{% set outfits = character.get_available_outfits() %}
|
||||
{% if wardrobe_data.default is defined and wardrobe_data.default is mapping %}
|
||||
{# New nested format - show tabs for each outfit #}
|
||||
<ul class="nav nav-tabs mb-3" id="wardrobeTabs" role="tablist">
|
||||
{% for outfit_name in outfits %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link {% if loop.first %}active{% endif %}" id="outfit-{{ outfit_name }}-tab" data-bs-toggle="tab" data-bs-target="#outfit-{{ outfit_name }}" type="button" role="tab">
|
||||
{{ outfit_name }}
|
||||
{% if outfit_name == character.active_outfit %}
|
||||
<span class="badge bg-primary ms-1">Active</span>
|
||||
{% endif %}
|
||||
</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="tab-content" id="wardrobeTabContent">
|
||||
{% for outfit_name in outfits %}
|
||||
<div class="tab-pane fade {% if loop.first %}show active{% endif %}" id="outfit-{{ outfit_name }}" role="tabpanel">
|
||||
<div class="d-flex justify-content-end mb-2">
|
||||
{% if outfit_name != 'default' %}
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#renameOutfitModal" data-outfit="{{ outfit_name }}">Rename</button>
|
||||
<form action="{{ url_for('delete_outfit', slug=character.slug) }}" method="post" class="d-inline" onsubmit="return confirm('Delete outfit \'{{ outfit_name }}\'?');">
|
||||
<input type="hidden" name="outfit" value="{{ outfit_name }}">
|
||||
<button type="submit" class="btn btn-outline-danger">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% for key, value in wardrobe_data[outfit_name].items() %}
|
||||
<div class="mb-3">
|
||||
<label for="wardrobe_{{ outfit_name }}_{{ key }}" class="form-label text-capitalize">{{ key.replace('_', ' ') }}</label>
|
||||
<input type="text" class="form-control" id="wardrobe_{{ outfit_name }}_{{ key }}" name="wardrobe_{{ outfit_name }}_{{ key }}" value="{{ value }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
{# Legacy flat format #}
|
||||
{% for key, value in wardrobe_data.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 %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Styles Section -->
|
||||
{% if character.data.styles %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light"><strong>Styles</strong></div>
|
||||
<div class="card-body">
|
||||
{% for key, value in character.data.styles.items() %}
|
||||
<div class="mb-3">
|
||||
<label for="styles_{{ key }}" class="form-label text-capitalize">{{ key.replace('_', ' ') }}</label>
|
||||
<input type="text" class="form-control" id="styles_{{ key }}" name="styles_{{ key }}" value="{{ value }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-5">
|
||||
<button type="submit" class="btn btn-primary btn-lg w-100">Save Changes to JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="sticky-top" style="top: 20px;">
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-dark">Notice</div>
|
||||
<div class="card-body small">
|
||||
<p>Saving changes here will overwrite the original JSON file in the <code>characters/</code> folder.</p>
|
||||
<p>Character ID (<code>{{ character.character_id }}</code>) cannot be changed via the GUI to maintain file and URL consistency.</p>
|
||||
<hr>
|
||||
<p><strong>Outfits:</strong> Add multiple outfits using the "Add Outfit" button in the Wardrobe section. Switch between them on the character detail page.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Add Outfit Modal -->
|
||||
<div class="modal fade" id="addOutfitModal" tabindex="-1" aria-labelledby="addOutfitModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="{{ url_for('add_outfit', slug=character.slug) }}" method="post">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addOutfitModalLabel">Add New Outfit</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="newOutfitName" class="form-label">Outfit Name</label>
|
||||
<input type="text" class="form-control" id="newOutfitName" name="outfit_name" placeholder="e.g., casual, formal, swimwear">
|
||||
<div class="form-text">Name will be converted to lowercase with underscores.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-success">Add Outfit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rename Outfit Modal -->
|
||||
<div class="modal fade" id="renameOutfitModal" tabindex="-1" aria-labelledby="renameOutfitModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="{{ url_for('rename_outfit', slug=character.slug) }}" method="post">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="renameOutfitModalLabel">Rename Outfit</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="old_name" id="renameOldName">
|
||||
<div class="mb-3">
|
||||
<label for="renameNewName" class="form-label">New Name</label>
|
||||
<input type="text" class="form-control" id="renameNewName" name="new_name" placeholder="Enter new outfit name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Rename</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Populate rename modal with current outfit name
|
||||
document.getElementById('renameOutfitModal').addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var outfitName = button.getAttribute('data-outfit');
|
||||
document.getElementById('renameOldName').value = outfitName;
|
||||
document.getElementById('renameNewName').value = outfitName;
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -40,6 +40,12 @@
|
||||
<h5 class="card-title text-center">{{ char.name }}</h5>
|
||||
<p class="card-text small text-center text-muted">{{ char.data.tags | join(', ') }}</p>
|
||||
</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>
|
||||
{% endfor %}
|
||||
|
||||
@@ -18,7 +18,12 @@
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">Character Browser</a>
|
||||
<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="/actions" class="btn btn-outline-light me-2">Actions</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="/settings" class="btn btn-outline-light">Settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
74
templates/outfits/create.html
Normal file
74
templates/outfits/create.html
Normal 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 %}
|
||||
353
templates/outfits/detail.html
Normal file
353
templates/outfits/detail.html
Normal 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 %}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user