feat: refine AI backend options and UI layout
Browse files- Curated Hugging Face model lists (4 text, 4 image models).
- Added manual Inference Provider support for HF backend (e.g., fal-ai).
- Set default HF provider to 'auto' for robust model discovery.
- Relocated 'Generate Image' button to the right column for better UX.
- Improved error handling for prompt refinement to prevent UI प्रदूषण.
- Added MIT License and updated README with new features and usage info.
- LICENSE +21 -0
- README.md +20 -15
- modules/config.py +13 -0
- modules/integrations.py +38 -22
- modules/ui_layout.py +59 -18
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Topguy (and contributors)
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -15,9 +15,13 @@ RPGPortrait is a Gradio-based web application that helps users build highly deta
|
|
| 15 |
|
| 16 |
## Features
|
| 17 |
- **25+ Character Parameters**: Deep customization including Identity, Appearance, Equipment, Environment, VFX, and Technical settings.
|
| 18 |
-
- **🧠 AI Refinement**: Intelligent prompt enhancement using **Gemini (Cloud)**, **Hugging Face
|
| 19 |
- **🛠️ Externalized Prompts**: Tweak the core AI system instructions by editing `prompts.yaml`.
|
| 20 |
- **🖼️ Multi-Backend Image Gen**: Toggle between **Gemini (Cloud)**, **Hugging Face (Cloud)**, and **ComfyUI (Local)**.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
- **🔍 Dynamic Model Discovery**: Automatically pings local Ollama and ComfyUI servers to fetch available models and hide unavailable backends.
|
| 22 |
- **Workflow Injection**: Automated prompt, resolution, and seed injection into custom ComfyUI workflows.
|
| 23 |
- **💾 Save & Load**: Export your character configurations as JSON files and import them back to restore your exact selections.
|
|
@@ -25,6 +29,7 @@ RPGPortrait is a Gradio-based web application that helps users build highly deta
|
|
| 25 |
- **🎒 Dual Accessories**: Select up to two different accessories for your character.
|
| 26 |
- **📥 Pro Downloads**: Standard PNG downloads for portraits with friendly filenames.
|
| 27 |
- **Randomization**: Check individual 🎲 boxes to randomize specific features on regeneration.
|
|
|
|
| 28 |
- **YAML Data Storage**: Easily add or modify races, classes, backgrounds, and templates in `features.yaml`.
|
| 29 |
|
| 30 |
## Installation
|
|
@@ -41,19 +46,17 @@ RPGPortrait is a Gradio-based web application that helps users build highly deta
|
|
| 41 |
```bash
|
| 42 |
pip install -r requirements.txt
|
| 43 |
```
|
| 44 |
-
5. **Set up
|
| 45 |
- Create a `.env` file in the root directory.
|
| 46 |
- Add your keys and connection info:
|
| 47 |
```env
|
| 48 |
-
GEMINI_API_KEY=
|
|
|
|
| 49 |
COMFY_HOST=127.0.0.1
|
| 50 |
COMFY_PORT=8188
|
| 51 |
OLLAMA_HOST=127.0.0.1
|
| 52 |
OLLAMA_PORT=11434
|
| 53 |
-
OLLAMA_MODEL=llama3
|
| 54 |
```
|
| 55 |
-
6. **Configure AI Prompts**:
|
| 56 |
-
- Modify `prompts.yaml` to adjust the refinement logic without changing code.
|
| 57 |
|
| 58 |
## Usage
|
| 59 |
|
|
@@ -62,13 +65,15 @@ RPGPortrait is a Gradio-based web application that helps users build highly deta
|
|
| 62 |
python app.py
|
| 63 |
```
|
| 64 |
2. **Access the UI**: Open your browser and navigate to `http://127.0.0.1:7860`.
|
| 65 |
-
3. **Build your prompt**: Select features
|
| 66 |
-
4. **Refine
|
| 67 |
-
- Choose a **Refinement Backend**
|
| 68 |
-
-
|
| 69 |
-
|
| 70 |
-
-
|
| 71 |
-
|
|
|
|
| 72 |
|
| 73 |
-
##
|
| 74 |
-
|
|
|
|
|
|
| 15 |
|
| 16 |
## Features
|
| 17 |
- **25+ Character Parameters**: Deep customization including Identity, Appearance, Equipment, Environment, VFX, and Technical settings.
|
| 18 |
+
- **🧠 AI Refinement**: Intelligent prompt enhancement using **Gemini (Cloud)**, **Curated Hugging Face Models**, or **Ollama (Local)**.
|
| 19 |
- **🛠️ Externalized Prompts**: Tweak the core AI system instructions by editing `prompts.yaml`.
|
| 20 |
- **🖼️ Multi-Backend Image Gen**: Toggle between **Gemini (Cloud)**, **Hugging Face (Cloud)**, and **ComfyUI (Local)**.
|
| 21 |
+
- **⚡ Hugging Face Pro Features**:
|
| 22 |
+
- Curated list of 4 text and 4 image models.
|
| 23 |
+
- Manual **Inference Provider** support (e.g., `fal-ai`, `black-forest-labs`) to bypass rate limits or use partner backends.
|
| 24 |
+
- Automated "auto" provider selection by default.
|
| 25 |
- **🔍 Dynamic Model Discovery**: Automatically pings local Ollama and ComfyUI servers to fetch available models and hide unavailable backends.
|
| 26 |
- **Workflow Injection**: Automated prompt, resolution, and seed injection into custom ComfyUI workflows.
|
| 27 |
- **💾 Save & Load**: Export your character configurations as JSON files and import them back to restore your exact selections.
|
|
|
|
| 29 |
- **🎒 Dual Accessories**: Select up to two different accessories for your character.
|
| 30 |
- **📥 Pro Downloads**: Standard PNG downloads for portraits with friendly filenames.
|
| 31 |
- **Randomization**: Check individual 🎲 boxes to randomize specific features on regeneration.
|
| 32 |
+
- **🛡️ Robust Error Handling**: AI refinement errors are logged to the console and displayed in the UI status area without polluting your current prompt.
|
| 33 |
- **YAML Data Storage**: Easily add or modify races, classes, backgrounds, and templates in `features.yaml`.
|
| 34 |
|
| 35 |
## Installation
|
|
|
|
| 46 |
```bash
|
| 47 |
pip install -r requirements.txt
|
| 48 |
```
|
| 49 |
+
5. **Set up API Keys**:
|
| 50 |
- Create a `.env` file in the root directory.
|
| 51 |
- Add your keys and connection info:
|
| 52 |
```env
|
| 53 |
+
GEMINI_API_KEY=your_gemini_key
|
| 54 |
+
HF_TOKEN=your_huggingface_token # Required for Cloud backends
|
| 55 |
COMFY_HOST=127.0.0.1
|
| 56 |
COMFY_PORT=8188
|
| 57 |
OLLAMA_HOST=127.0.0.1
|
| 58 |
OLLAMA_PORT=11434
|
|
|
|
| 59 |
```
|
|
|
|
|
|
|
| 60 |
|
| 61 |
## Usage
|
| 62 |
|
|
|
|
| 65 |
python app.py
|
| 66 |
```
|
| 67 |
2. **Access the UI**: Open your browser and navigate to `http://127.0.0.1:7860`.
|
| 68 |
+
3. **Build your prompt**: Select features in the left column; the technical prompt updates in real-time.
|
| 69 |
+
4. **Refine Prompt**:
|
| 70 |
+
- Choose a **Refinement Backend** in the configuration panel.
|
| 71 |
+
- Click **🧠 Refine Prompt** in the right column to polish your description.
|
| 72 |
+
5. **Generate Image**:
|
| 73 |
+
- Select an **Image Generation Backend**.
|
| 74 |
+
- Click **🖼️ Generate Image** (located directly under the portrait output) to create your character.
|
| 75 |
+
6. **Save/Load**: Use the 💾 and 📂 buttons to manage your character library.
|
| 76 |
|
| 77 |
+
## License
|
| 78 |
+
|
| 79 |
+
This project is licensed under the [MIT License](LICENSE).
|
modules/config.py
CHANGED
|
@@ -19,6 +19,19 @@ HF_BASE_URL = "https://router.huggingface.co/v1"
|
|
| 19 |
HF_TEXT_MODEL = "Qwen/Qwen2.5-72B-Instruct"
|
| 20 |
HF_IMAGE_MODEL = "black-forest-labs/FLUX.1-dev"
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
# Gemini Settings
|
| 23 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 24 |
GEMINI_TEXT_MODEL = "gemini-3-pro-preview"
|
|
|
|
| 19 |
HF_TEXT_MODEL = "Qwen/Qwen2.5-72B-Instruct"
|
| 20 |
HF_IMAGE_MODEL = "black-forest-labs/FLUX.1-dev"
|
| 21 |
|
| 22 |
+
HF_TEXT_MODELS = [
|
| 23 |
+
"Qwen/Qwen2.5-72B-Instruct",
|
| 24 |
+
"meta-llama/Llama-3.1-70B-Instruct",
|
| 25 |
+
"mistralai/Mistral-7B-Instruct-v0.3",
|
| 26 |
+
"microsoft/Phi-3-mini-4k-instruct"
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
HF_IMAGE_MODELS = [
|
| 30 |
+
"black-forest-labs/FLUX.1-dev",
|
| 31 |
+
"Tongyi-MAI/Z-Image-Turbo",
|
| 32 |
+
"Qwen/Qwen-Image-2512"
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
# Gemini Settings
|
| 36 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 37 |
GEMINI_TEXT_MODEL = "gemini-3-pro-preview"
|
modules/integrations.py
CHANGED
|
@@ -96,7 +96,9 @@ def refine_with_gemini(prompt, mode="refinement"):
|
|
| 96 |
)
|
| 97 |
return response.text.strip()
|
| 98 |
except Exception as e:
|
| 99 |
-
|
|
|
|
|
|
|
| 100 |
|
| 101 |
def refine_with_ollama(prompt, model, mode="refinement"):
|
| 102 |
"""Refines the prompt using a local Ollama instance."""
|
|
@@ -121,9 +123,10 @@ def refine_with_ollama(prompt, model, mode="refinement"):
|
|
| 121 |
text = "\n".join(lines).strip()
|
| 122 |
return text
|
| 123 |
except Exception as e:
|
| 124 |
-
|
|
|
|
| 125 |
|
| 126 |
-
def refine_with_hf(prompt, token=None, mode="refinement"):
|
| 127 |
"""Refines the prompt using Hugging Face Router (OpenAI compatible)."""
|
| 128 |
active_client = hf_client
|
| 129 |
|
|
@@ -141,7 +144,8 @@ def refine_with_hf(prompt, token=None, mode="refinement"):
|
|
| 141 |
return "Error: Hugging Face token not found. Please log in or provide a token."
|
| 142 |
|
| 143 |
system_prompt = load_system_prompt(mode)
|
| 144 |
-
|
|
|
|
| 145 |
|
| 146 |
try:
|
| 147 |
messages = [
|
|
@@ -149,17 +153,21 @@ def refine_with_hf(prompt, token=None, mode="refinement"):
|
|
| 149 |
{"role": "user", "content": f"Original Prompt: {prompt}"}
|
| 150 |
]
|
| 151 |
|
|
|
|
|
|
|
| 152 |
response = active_client.chat.completions.create(
|
| 153 |
-
model=
|
| 154 |
messages=messages,
|
| 155 |
max_tokens=500,
|
| 156 |
-
temperature=0.7
|
|
|
|
| 157 |
)
|
| 158 |
return response.choices[0].message.content.strip()
|
| 159 |
except Exception as e:
|
| 160 |
-
|
|
|
|
| 161 |
|
| 162 |
-
def refine_master(prompt, backend, ollama_model, manual_token=None, character_name=None):
|
| 163 |
"""Routes prompt refinement to the selected backend."""
|
| 164 |
if not prompt.strip():
|
| 165 |
return ""
|
|
@@ -168,13 +176,18 @@ def refine_master(prompt, backend, ollama_model, manual_token=None, character_na
|
|
| 168 |
hf_token = manual_token.strip() if manual_token and manual_token.strip() else None
|
| 169 |
|
| 170 |
if backend == "Ollama (Local)":
|
| 171 |
-
|
| 172 |
elif backend == "Hugging Face (Cloud)":
|
| 173 |
-
|
| 174 |
else:
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
-
def generate_name_master(prompt, backend, ollama_model, manual_token=None):
|
| 178 |
"""Generates a thematic name based on the current prompt context."""
|
| 179 |
if not prompt.strip():
|
| 180 |
return "Unnamed Hero"
|
|
@@ -182,11 +195,13 @@ def generate_name_master(prompt, backend, ollama_model, manual_token=None):
|
|
| 182 |
hf_token = manual_token.strip() if manual_token and manual_token.strip() else None
|
| 183 |
|
| 184 |
if backend == "Ollama (Local)":
|
| 185 |
-
|
| 186 |
elif backend == "Hugging Face (Cloud)":
|
| 187 |
-
|
| 188 |
else:
|
| 189 |
-
|
|
|
|
|
|
|
| 190 |
|
| 191 |
def generate_image_with_gemini(refined_prompt, technical_prompt, aspect_ratio, character_name="Unnamed Hero"):
|
| 192 |
if not gemini_active:
|
|
@@ -292,13 +307,14 @@ def generate_image_with_comfy(prompt, aspect_ratio, character_name="Unnamed Hero
|
|
| 292 |
traceback.print_exc()
|
| 293 |
return None, None, f"ComfyUI Error: {e}"
|
| 294 |
|
| 295 |
-
def generate_image_with_hf(prompt, aspect_ratio, token=None, character_name="Unnamed Hero"):
|
| 296 |
"""Generates an image using Hugging Face Inference API."""
|
| 297 |
active_token = token if token else HF_TOKEN
|
| 298 |
if not active_token:
|
| 299 |
return None, None, "Error: Hugging Face token not found. Please log in or provide a token."
|
| 300 |
|
| 301 |
-
|
|
|
|
| 302 |
|
| 303 |
# Resolution mapping
|
| 304 |
res_map = {
|
|
@@ -311,8 +327,8 @@ def generate_image_with_hf(prompt, aspect_ratio, token=None, character_name="Unn
|
|
| 311 |
width, height = res_map.get(aspect_ratio, (1024, 1024))
|
| 312 |
|
| 313 |
try:
|
| 314 |
-
client = InferenceClient(api_key=active_token)
|
| 315 |
-
img = client.text_to_image(prompt, model=
|
| 316 |
|
| 317 |
# Embed metadata
|
| 318 |
metadata = PngInfo()
|
|
@@ -325,12 +341,12 @@ def generate_image_with_hf(prompt, aspect_ratio, token=None, character_name="Unn
|
|
| 325 |
temp_dir = tempfile.mkdtemp()
|
| 326 |
img_path = os.path.join(temp_dir, filename)
|
| 327 |
img.save(img_path, "PNG", pnginfo=metadata)
|
| 328 |
-
return img, img_path, f"Image generated via Hugging Face ({
|
| 329 |
except Exception as e:
|
| 330 |
traceback.print_exc()
|
| 331 |
return None, None, f"Hugging Face Image Error: {e}"
|
| 332 |
|
| 333 |
-
def generate_image_master(refined_prompt, technical_prompt, aspect_ratio, backend, manual_token=None, character_name="Unnamed Hero"):
|
| 334 |
"""Routes image generation to the selected backend."""
|
| 335 |
final_prompt = refined_prompt.strip() if refined_prompt.strip() else technical_prompt
|
| 336 |
|
|
@@ -340,6 +356,6 @@ def generate_image_master(refined_prompt, technical_prompt, aspect_ratio, backen
|
|
| 340 |
if backend == "ComfyUI (Local)":
|
| 341 |
return generate_image_with_comfy(final_prompt, aspect_ratio, character_name)
|
| 342 |
elif backend == "Hugging Face (Cloud)":
|
| 343 |
-
return generate_image_with_hf(final_prompt, aspect_ratio, hf_token, character_name)
|
| 344 |
else:
|
| 345 |
return generate_image_with_gemini(refined_prompt, technical_prompt, aspect_ratio, character_name)
|
|
|
|
| 96 |
)
|
| 97 |
return response.text.strip()
|
| 98 |
except Exception as e:
|
| 99 |
+
print(f"Gemini Refinement Error: {e}")
|
| 100 |
+
traceback.print_exc()
|
| 101 |
+
return None
|
| 102 |
|
| 103 |
def refine_with_ollama(prompt, model, mode="refinement"):
|
| 104 |
"""Refines the prompt using a local Ollama instance."""
|
|
|
|
| 123 |
text = "\n".join(lines).strip()
|
| 124 |
return text
|
| 125 |
except Exception as e:
|
| 126 |
+
print(f"Ollama Refinement Error: {e}")
|
| 127 |
+
return None
|
| 128 |
|
| 129 |
+
def refine_with_hf(prompt, model_id=None, provider=None, token=None, mode="refinement"):
|
| 130 |
"""Refines the prompt using Hugging Face Router (OpenAI compatible)."""
|
| 131 |
active_client = hf_client
|
| 132 |
|
|
|
|
| 144 |
return "Error: Hugging Face token not found. Please log in or provide a token."
|
| 145 |
|
| 146 |
system_prompt = load_system_prompt(mode)
|
| 147 |
+
active_model = model_id if model_id else HF_TEXT_MODEL
|
| 148 |
+
active_provider = provider if provider and provider.strip() else "auto"
|
| 149 |
|
| 150 |
try:
|
| 151 |
messages = [
|
|
|
|
| 153 |
{"role": "user", "content": f"Original Prompt: {prompt}"}
|
| 154 |
]
|
| 155 |
|
| 156 |
+
# Note: Provider for Chat Completions is currently handled by the route or specific model naming conventions.
|
| 157 |
+
# But we pass it if the client supports it or for future use.
|
| 158 |
response = active_client.chat.completions.create(
|
| 159 |
+
model=active_model,
|
| 160 |
messages=messages,
|
| 161 |
max_tokens=500,
|
| 162 |
+
temperature=0.7,
|
| 163 |
+
extra_body={"provider": active_provider}
|
| 164 |
)
|
| 165 |
return response.choices[0].message.content.strip()
|
| 166 |
except Exception as e:
|
| 167 |
+
print(f"HF Refinement Error: {e}")
|
| 168 |
+
return None
|
| 169 |
|
| 170 |
+
def refine_master(prompt, backend, ollama_model, hf_text_model, hf_text_provider, manual_token=None, character_name=None):
|
| 171 |
"""Routes prompt refinement to the selected backend."""
|
| 172 |
if not prompt.strip():
|
| 173 |
return ""
|
|
|
|
| 176 |
hf_token = manual_token.strip() if manual_token and manual_token.strip() else None
|
| 177 |
|
| 178 |
if backend == "Ollama (Local)":
|
| 179 |
+
result = refine_with_ollama(prompt, ollama_model, mode="refinement")
|
| 180 |
elif backend == "Hugging Face (Cloud)":
|
| 181 |
+
result = refine_with_hf(prompt, hf_text_model, hf_text_provider, hf_token, mode="refinement")
|
| 182 |
else:
|
| 183 |
+
result = refine_with_gemini(prompt, mode="refinement")
|
| 184 |
+
|
| 185 |
+
if result is None:
|
| 186 |
+
return gr.update(), f"⚠️ Refinement failed. Check console for details. Original prompt preserved."
|
| 187 |
+
|
| 188 |
+
return result, ""
|
| 189 |
|
| 190 |
+
def generate_name_master(prompt, backend, ollama_model, hf_text_model, hf_text_provider, manual_token=None):
|
| 191 |
"""Generates a thematic name based on the current prompt context."""
|
| 192 |
if not prompt.strip():
|
| 193 |
return "Unnamed Hero"
|
|
|
|
| 195 |
hf_token = manual_token.strip() if manual_token and manual_token.strip() else None
|
| 196 |
|
| 197 |
if backend == "Ollama (Local)":
|
| 198 |
+
result = refine_with_ollama(prompt, ollama_model, mode="naming")
|
| 199 |
elif backend == "Hugging Face (Cloud)":
|
| 200 |
+
result = refine_with_hf(prompt, hf_text_model, hf_text_provider, hf_token, mode="naming")
|
| 201 |
else:
|
| 202 |
+
result = refine_with_gemini(prompt, mode="naming")
|
| 203 |
+
|
| 204 |
+
return result if result else "Unnamed Hero"
|
| 205 |
|
| 206 |
def generate_image_with_gemini(refined_prompt, technical_prompt, aspect_ratio, character_name="Unnamed Hero"):
|
| 207 |
if not gemini_active:
|
|
|
|
| 307 |
traceback.print_exc()
|
| 308 |
return None, None, f"ComfyUI Error: {e}"
|
| 309 |
|
| 310 |
+
def generate_image_with_hf(prompt, aspect_ratio, model_id=None, provider=None, token=None, character_name="Unnamed Hero"):
|
| 311 |
"""Generates an image using Hugging Face Inference API."""
|
| 312 |
active_token = token if token else HF_TOKEN
|
| 313 |
if not active_token:
|
| 314 |
return None, None, "Error: Hugging Face token not found. Please log in or provide a token."
|
| 315 |
|
| 316 |
+
active_model = model_id if model_id else HF_IMAGE_MODEL
|
| 317 |
+
active_provider = provider if provider and provider.strip() else "auto"
|
| 318 |
|
| 319 |
# Resolution mapping
|
| 320 |
res_map = {
|
|
|
|
| 327 |
width, height = res_map.get(aspect_ratio, (1024, 1024))
|
| 328 |
|
| 329 |
try:
|
| 330 |
+
client = InferenceClient(api_key=active_token, provider=active_provider)
|
| 331 |
+
img = client.text_to_image(prompt, model=active_model, width=width, height=height)
|
| 332 |
|
| 333 |
# Embed metadata
|
| 334 |
metadata = PngInfo()
|
|
|
|
| 341 |
temp_dir = tempfile.mkdtemp()
|
| 342 |
img_path = os.path.join(temp_dir, filename)
|
| 343 |
img.save(img_path, "PNG", pnginfo=metadata)
|
| 344 |
+
return img, img_path, f"Image generated via Hugging Face ({active_model})!"
|
| 345 |
except Exception as e:
|
| 346 |
traceback.print_exc()
|
| 347 |
return None, None, f"Hugging Face Image Error: {e}"
|
| 348 |
|
| 349 |
+
def generate_image_master(refined_prompt, technical_prompt, aspect_ratio, backend, hf_image_model, hf_image_provider, manual_token=None, character_name="Unnamed Hero"):
|
| 350 |
"""Routes image generation to the selected backend."""
|
| 351 |
final_prompt = refined_prompt.strip() if refined_prompt.strip() else technical_prompt
|
| 352 |
|
|
|
|
| 356 |
if backend == "ComfyUI (Local)":
|
| 357 |
return generate_image_with_comfy(final_prompt, aspect_ratio, character_name)
|
| 358 |
elif backend == "Hugging Face (Cloud)":
|
| 359 |
+
return generate_image_with_hf(final_prompt, aspect_ratio, hf_image_model, hf_image_provider, hf_token, character_name)
|
| 360 |
else:
|
| 361 |
return generate_image_with_gemini(refined_prompt, technical_prompt, aspect_ratio, character_name)
|
modules/ui_layout.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
from .config import FEATURE_SEQUENCE, SECTIONS
|
| 3 |
from .core_logic import (
|
| 4 |
features_data, generate_prompt, handle_regeneration,
|
| 5 |
save_character, load_character, get_example_list, load_example_character
|
|
@@ -137,38 +137,65 @@ def build_ui():
|
|
| 137 |
ollama_active = len(ollama_models) > 0
|
| 138 |
comfy_active = check_comfy_availability()
|
| 139 |
|
| 140 |
-
refinement_choices = ["
|
|
|
|
|
|
|
| 141 |
if ollama_active:
|
| 142 |
refinement_choices.append("Ollama (Local)")
|
| 143 |
|
| 144 |
refinement_backend = gr.Radio(
|
| 145 |
choices=refinement_choices,
|
| 146 |
-
value=
|
| 147 |
label="Prompt Refinement Backend",
|
| 148 |
scale=2
|
| 149 |
)
|
| 150 |
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
|
| 159 |
with gr.Row():
|
| 160 |
-
img_choices = ["
|
|
|
|
|
|
|
| 161 |
if comfy_active:
|
| 162 |
img_choices.append("ComfyUI (Local)")
|
| 163 |
|
| 164 |
backend_selector = gr.Radio(
|
| 165 |
choices=img_choices,
|
| 166 |
-
value=
|
| 167 |
label="Image Generation Backend",
|
| 168 |
scale=2
|
| 169 |
)
|
| 170 |
with gr.Column(scale=1):
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
with gr.Row():
|
| 174 |
with gr.Column(scale=1):
|
|
@@ -190,6 +217,7 @@ def build_ui():
|
|
| 190 |
|
| 191 |
gr.Markdown("---")
|
| 192 |
image_output = gr.Image(label="Portrait", show_label=False)
|
|
|
|
| 193 |
download_img_btn = gr.DownloadButton("📥 Download Portrait (PNG)", variant="secondary", visible=False)
|
| 194 |
status_msg = gr.Markdown("")
|
| 195 |
download_file = gr.File(label="Saved Character JSON", visible=False)
|
|
@@ -220,13 +248,13 @@ def build_ui():
|
|
| 220 |
|
| 221 |
refine_btn.click(
|
| 222 |
fn=refine_master,
|
| 223 |
-
inputs=[prompt_output, refinement_backend, ollama_model_dropdown, hf_token_input, character_name],
|
| 224 |
-
outputs=refined_output
|
| 225 |
)
|
| 226 |
|
| 227 |
gen_img_btn.click(
|
| 228 |
fn=generate_image_master,
|
| 229 |
-
inputs=[refined_output, prompt_output, dropdowns[-1], backend_selector, hf_token_input, character_name],
|
| 230 |
outputs=[image_output, download_img_btn, status_msg]
|
| 231 |
).then(
|
| 232 |
fn=lambda x: gr.update(value=x, visible=True) if x else gr.update(visible=False),
|
|
@@ -235,9 +263,22 @@ def build_ui():
|
|
| 235 |
)
|
| 236 |
|
| 237 |
refinement_backend.change(
|
| 238 |
-
fn=lambda b:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
inputs=refinement_backend,
|
| 240 |
-
outputs=ollama_model_dropdown
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
)
|
| 242 |
|
| 243 |
save_btn.click(
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
from .config import FEATURE_SEQUENCE, SECTIONS, HF_TEXT_MODELS, HF_IMAGE_MODELS, GEMINI_API_KEY
|
| 3 |
from .core_logic import (
|
| 4 |
features_data, generate_prompt, handle_regeneration,
|
| 5 |
save_character, load_character, get_example_list, load_example_character
|
|
|
|
| 137 |
ollama_active = len(ollama_models) > 0
|
| 138 |
comfy_active = check_comfy_availability()
|
| 139 |
|
| 140 |
+
refinement_choices = ["Hugging Face (Cloud)"]
|
| 141 |
+
if GEMINI_API_KEY:
|
| 142 |
+
refinement_choices.insert(0, "Gemini (Cloud)")
|
| 143 |
if ollama_active:
|
| 144 |
refinement_choices.append("Ollama (Local)")
|
| 145 |
|
| 146 |
refinement_backend = gr.Radio(
|
| 147 |
choices=refinement_choices,
|
| 148 |
+
value=refinement_choices[0],
|
| 149 |
label="Prompt Refinement Backend",
|
| 150 |
scale=2
|
| 151 |
)
|
| 152 |
|
| 153 |
+
with gr.Column(scale=1):
|
| 154 |
+
ollama_model_dropdown = gr.Dropdown(
|
| 155 |
+
choices=ollama_models,
|
| 156 |
+
value=ollama_models[0] if ollama_active else None,
|
| 157 |
+
label="Olama Model",
|
| 158 |
+
visible=False
|
| 159 |
+
)
|
| 160 |
+
hf_text_model_dropdown = gr.Dropdown(
|
| 161 |
+
choices=HF_TEXT_MODELS,
|
| 162 |
+
value=HF_TEXT_MODELS[0],
|
| 163 |
+
label="HF Text Model",
|
| 164 |
+
visible=("Hugging Face (Cloud)" in refinement_choices and refinement_choices[0] == "Hugging Face (Cloud)")
|
| 165 |
+
)
|
| 166 |
+
hf_text_provider_input = gr.Textbox(
|
| 167 |
+
value="auto",
|
| 168 |
+
placeholder="Optional: e.g. fal-ai",
|
| 169 |
+
label="HF Text Provider",
|
| 170 |
+
visible=("Hugging Face (Cloud)" in refinement_choices and refinement_choices[0] == "Hugging Face (Cloud)")
|
| 171 |
+
)
|
| 172 |
|
| 173 |
with gr.Row():
|
| 174 |
+
img_choices = ["Hugging Face (Cloud)"]
|
| 175 |
+
if GEMINI_API_KEY:
|
| 176 |
+
img_choices.insert(0, "Gemini (Cloud)")
|
| 177 |
if comfy_active:
|
| 178 |
img_choices.append("ComfyUI (Local)")
|
| 179 |
|
| 180 |
backend_selector = gr.Radio(
|
| 181 |
choices=img_choices,
|
| 182 |
+
value=img_choices[0],
|
| 183 |
label="Image Generation Backend",
|
| 184 |
scale=2
|
| 185 |
)
|
| 186 |
with gr.Column(scale=1):
|
| 187 |
+
hf_image_model_dropdown = gr.Dropdown(
|
| 188 |
+
choices=HF_IMAGE_MODELS,
|
| 189 |
+
value=HF_IMAGE_MODELS[0],
|
| 190 |
+
label="HF Image Model",
|
| 191 |
+
visible=("Hugging Face (Cloud)" in img_choices and img_choices[0] == "Hugging Face (Cloud)")
|
| 192 |
+
)
|
| 193 |
+
hf_image_provider_input = gr.Textbox(
|
| 194 |
+
value="auto",
|
| 195 |
+
placeholder="Optional: e.g. fal-ai",
|
| 196 |
+
label="HF Image Provider",
|
| 197 |
+
visible=("Hugging Face (Cloud)" in img_choices and img_choices[0] == "Hugging Face (Cloud)")
|
| 198 |
+
)
|
| 199 |
|
| 200 |
with gr.Row():
|
| 201 |
with gr.Column(scale=1):
|
|
|
|
| 217 |
|
| 218 |
gr.Markdown("---")
|
| 219 |
image_output = gr.Image(label="Portrait", show_label=False)
|
| 220 |
+
gen_img_btn = gr.Button("🖼️ Generate Image", variant="primary", scale=1)
|
| 221 |
download_img_btn = gr.DownloadButton("📥 Download Portrait (PNG)", variant="secondary", visible=False)
|
| 222 |
status_msg = gr.Markdown("")
|
| 223 |
download_file = gr.File(label="Saved Character JSON", visible=False)
|
|
|
|
| 248 |
|
| 249 |
refine_btn.click(
|
| 250 |
fn=refine_master,
|
| 251 |
+
inputs=[prompt_output, refinement_backend, ollama_model_dropdown, hf_text_model_dropdown, hf_text_provider_input, hf_token_input, character_name],
|
| 252 |
+
outputs=[refined_output, status_msg]
|
| 253 |
)
|
| 254 |
|
| 255 |
gen_img_btn.click(
|
| 256 |
fn=generate_image_master,
|
| 257 |
+
inputs=[refined_output, prompt_output, dropdowns[-1], backend_selector, hf_image_model_dropdown, hf_image_provider_input, hf_token_input, character_name],
|
| 258 |
outputs=[image_output, download_img_btn, status_msg]
|
| 259 |
).then(
|
| 260 |
fn=lambda x: gr.update(value=x, visible=True) if x else gr.update(visible=False),
|
|
|
|
| 263 |
)
|
| 264 |
|
| 265 |
refinement_backend.change(
|
| 266 |
+
fn=lambda b: (
|
| 267 |
+
gr.update(visible=(b == "Ollama (Local)")),
|
| 268 |
+
gr.update(visible=(b == "Hugging Face (Cloud)")),
|
| 269 |
+
gr.update(visible=(b == "Hugging Face (Cloud)"))
|
| 270 |
+
),
|
| 271 |
inputs=refinement_backend,
|
| 272 |
+
outputs=[ollama_model_dropdown, hf_text_model_dropdown, hf_text_provider_input]
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
backend_selector.change(
|
| 276 |
+
fn=lambda b: (
|
| 277 |
+
gr.update(visible=(b == "Hugging Face (Cloud)")),
|
| 278 |
+
gr.update(visible=(b == "Hugging Face (Cloud)"))
|
| 279 |
+
),
|
| 280 |
+
inputs=backend_selector,
|
| 281 |
+
outputs=[hf_image_model_dropdown, hf_image_provider_input]
|
| 282 |
)
|
| 283 |
|
| 284 |
save_btn.click(
|