topguy commited on
Commit
54d620e
·
1 Parent(s): de132df

feat: integrate HF Router and enhance UI workflow

Browse files

- Switched Hugging Face integration to the OpenAI-compatible Router.
- Refactored `modules/integrations.py` to use `openai` SDK.
- Updated `modules/config.py` with `HF_BASE_URL` and `Qwen/Qwen2.5-72B-Instruct` model.
- Implemented dynamic backend availability checking for Ollama and ComfyUI at startup.
- Fixed refined prompt persistence issue when loading characters.
- Fixed aspect ratio handling for Hugging Face image generation.
- Renamed "Settings & Generation" section to "AI Backend Configuration" and relocated "Refine Prompt" button for better workflow.
- Updated `requirements.txt` with `openai` and `python-dotenv`.

features.yaml CHANGED
@@ -42,6 +42,13 @@ appearance:
42
  Blue: "deep sapphire blue"
43
  Green: "forest green"
44
  Purple: "regal purple"
 
 
 
 
 
 
 
45
  hair_style:
46
  Short: "neatly trimmed short"
47
  Long: "flowing long"
@@ -51,6 +58,9 @@ appearance:
51
  Ponytail: "tied back in a ponytail"
52
  Bun: "styled in a tight bun"
53
  Curly: "thickly curly"
 
 
 
54
  eye_color:
55
  Brown: "warm brown"
56
  Blue: "piercing blue"
@@ -59,6 +69,9 @@ appearance:
59
  Amber: "glowing amber"
60
  Red: "intense crimson red"
61
  Violet: "mysterious violet"
 
 
 
62
  build:
63
  Athletic: "an athletic and toned"
64
  Muscular: "a powerful, muscular"
@@ -66,6 +79,7 @@ appearance:
66
  Stocky: "a solid and stocky"
67
  Average: "a well-proportioned"
68
  Scrawny: "a thin and scrawny"
 
69
  skin_tone:
70
  Pale: "porcelain pale"
71
  Fair: "smooth fair"
@@ -73,6 +87,9 @@ appearance:
73
  Olive: "warm olive"
74
  Dark: "rich dark"
75
  Deep: "deep ebony"
 
 
 
76
  distinguishing_feature:
77
  Scars: "a collection of jagged battle scars across the face"
78
  Tattoos: "intricate glowing tattoos covering the neck and arms"
@@ -80,6 +97,9 @@ appearance:
80
  Cybernetic Eye: "a glowing cybernetic eye that whirrs softly"
81
  Glowing Runes: "mystical runes etched into the skin that pulse with light"
82
  Jeweled Bindi: "a shimmering jeweled bindi placed between the brows"
 
 
 
83
  None: "no particularly distinguishing facial features"
84
 
85
  expression_pose:
@@ -98,6 +118,13 @@ expression_pose:
98
  Ready Stance: "crouched in a dynamic ready stance"
99
  Meditating: "seated in a peaceful meditating position"
100
  Action: "frozen in the middle of a powerful action"
 
 
 
 
 
 
 
101
 
102
  equipment:
103
  armor:
@@ -156,6 +183,14 @@ environment:
156
  Mountains: "rugged mountain peak against a dramatic sky"
157
  City Street: "bustling medieval city street with cobblestones"
158
  Abstract Magic: "swirling vortex of abstract magical energy"
 
 
 
 
 
 
 
 
159
  lighting:
160
  Natural Sunlight: "bathed in warm, natural sunlight"
161
  Dim Torchlight: "illuminated by the flickering glow of dim torchlight"
 
42
  Blue: "deep sapphire blue"
43
  Green: "forest green"
44
  Purple: "regal purple"
45
+ Salt and Pepper: "salt and pepper"
46
+ Dirty Blonde: "dusty dirty blonde"
47
+ Auburn: "rich auburn"
48
+ Copper: "burnished copper"
49
+ Platinum: "pale platinum blonde"
50
+ Pink: "soft pastel pink"
51
+ Teal: "vibrant teal"
52
  hair_style:
53
  Short: "neatly trimmed short"
54
  Long: "flowing long"
 
58
  Ponytail: "tied back in a ponytail"
59
  Bun: "styled in a tight bun"
60
  Curly: "thickly curly"
61
+ Mohawk: "a sharp, jagged mohawk"
62
+ Undercut: "a stylish undercut"
63
+ Dreadlocks: "thick, well-maintained dreadlocks"
64
  eye_color:
65
  Brown: "warm brown"
66
  Blue: "piercing blue"
 
69
  Amber: "glowing amber"
70
  Red: "intense crimson red"
71
  Violet: "mysterious violet"
72
+ Heterochromia: "mismatched, with one blue and one brown eye"
73
+ Cloudy: "milky white and cloudy, suggesting blindness"
74
+ Black: "solid, ink-black voids"
75
  build:
76
  Athletic: "an athletic and toned"
77
  Muscular: "a powerful, muscular"
 
79
  Stocky: "a solid and stocky"
80
  Average: "a well-proportioned"
81
  Scrawny: "a thin and scrawny"
82
+ Towering: "a towering and massive"
83
  skin_tone:
84
  Pale: "porcelain pale"
85
  Fair: "smooth fair"
 
87
  Olive: "warm olive"
88
  Dark: "rich dark"
89
  Deep: "deep ebony"
90
+ Alabaster: "ghostly alabaster"
91
+ Vitiligo: "mottled with striking vitiligo patterns"
92
+ Ashen: "pale, ashen grey"
93
  distinguishing_feature:
94
  Scars: "a collection of jagged battle scars across the face"
95
  Tattoos: "intricate glowing tattoos covering the neck and arms"
 
97
  Cybernetic Eye: "a glowing cybernetic eye that whirrs softly"
98
  Glowing Runes: "mystical runes etched into the skin that pulse with light"
99
  Jeweled Bindi: "a shimmering jeweled bindi placed between the brows"
100
+ Mechanical Jaw: "a heavy, reinforced mechanical jaw"
101
+ Third Eye: "a mystical third eye centered on the forehead"
102
+ Burn Marks: "faint, web-like burn marks tracing down the neck"
103
  None: "no particularly distinguishing facial features"
104
 
105
  expression_pose:
 
118
  Ready Stance: "crouched in a dynamic ready stance"
119
  Meditating: "seated in a peaceful meditating position"
120
  Action: "frozen in the middle of a powerful action"
121
+ Kneeling: "humbly kneeling on one knee"
122
+ Floating: "magically floating inches above the ground"
123
+ Weapon Maintenance: "focused on meticulously sharpening their weapon"
124
+ Casting: "with hands glowing, in the middle of casting a powerful spell"
125
+ Defending: "braced firmly behind a raised shield"
126
+ Sitting: "seated regally upon an ornate throne"
127
+ Crouching: "crouched low, blending into the shadows"
128
 
129
  equipment:
130
  armor:
 
183
  Mountains: "rugged mountain peak against a dramatic sky"
184
  City Street: "bustling medieval city street with cobblestones"
185
  Abstract Magic: "swirling vortex of abstract magical energy"
186
+ Desert: "vast desert with shifting orange sands"
187
+ Arctic: "frozen arctic tundra under a pale sun"
188
+ Steampunk Lab: "cluttered steampunk laboratory with hissing brass pipes"
189
+ Volcanic Cave: "dark volcanic cave with rivers of molten lava"
190
+ Enchanted Grove: "ethereal grove filled with giant glowing mushrooms"
191
+ Ruined Temple: "ancient ruined temple overgrown with thick ivy"
192
+ Cyberpunk Street: "rain-slicked cyberpunk street with neon holographic signs"
193
+ Undersea: "majestic undersea city surrounded by coral and bubbles"
194
  lighting:
195
  Natural Sunlight: "bathed in warm, natural sunlight"
196
  Dim Torchlight: "illuminated by the flickering glow of dim torchlight"
hf_example.py DELETED
@@ -1,23 +0,0 @@
1
- import os
2
- from openai import OpenAI
3
- from dotenv import load_dotenv
4
-
5
- # Load environment variables
6
- load_dotenv()
7
-
8
- client = OpenAI(
9
- base_url="https://router.huggingface.co/v1",
10
- api_key=os.environ["HF_TOKEN"],
11
- )
12
-
13
- completion = client.completions.create(
14
- model="mistralai/Mixtral-8x22B-Instruct-v0.1:fireworks-ai",
15
- messages=[
16
- {
17
- "role": "user",
18
- "content": "Calculate the weight of the earth."
19
- }
20
- ],
21
- )
22
-
23
- print(completion.choices[0].message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
hf_hub_example.py DELETED
@@ -1,13 +0,0 @@
1
- import os
2
- from huggingface_hub import InferenceClient
3
-
4
- client = InferenceClient(
5
- provider="replicate",
6
- api_key=os.environ["HF_TOKEN"],
7
- )
8
-
9
- # output is a PIL.Image object
10
- image = client.text_to_image(
11
- "Astronaut riding a horse",
12
- model="ByteDance/SDXL-Lightning",
13
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modules/config.py CHANGED
@@ -15,8 +15,9 @@ OLLAMA_HOST = os.getenv("OLLAMA_HOST", "127.0.0.1")
15
  OLLAMA_PORT = os.getenv("OLLAMA_PORT", "11434")
16
  OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3")
17
  HF_TOKEN = os.getenv("HF_TOKEN")
18
- HF_TEXT_MODEL = "mistralai/Mistral-7B-Instruct-v0.2"
19
- HF_IMAGE_MODEL = "black-forest-labs/FLUX.1-schnell"
 
20
 
21
  # Gemini Settings
22
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
 
15
  OLLAMA_PORT = os.getenv("OLLAMA_PORT", "11434")
16
  OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3")
17
  HF_TOKEN = os.getenv("HF_TOKEN")
18
+ 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")
modules/integrations.py CHANGED
@@ -2,6 +2,7 @@ import os
2
  import requests
3
  import json
4
  import gradio as gr
 
5
  import uuid
6
  import time
7
  import io
@@ -16,7 +17,7 @@ from google import genai
16
  from google.genai import types
17
  from .config import (
18
  GEMINI_API_KEY, OLLAMA_HOST, OLLAMA_PORT, COMFY_URL,
19
- COMFY_WORKFLOW_FILE, PROMPTS_FILE, HF_TOKEN,
20
  HF_TEXT_MODEL, HF_IMAGE_MODEL, GEMINI_TEXT_MODEL,
21
  GEMINI_IMAGE_MODEL
22
  )
@@ -31,6 +32,17 @@ if GEMINI_API_KEY:
31
  except Exception as e:
32
  print(f"Error initializing Gemini: {e}")
33
 
 
 
 
 
 
 
 
 
 
 
 
34
  def load_system_prompt(key="refinement"):
35
  """Loads a system prompt from prompts.yaml."""
36
  try:
@@ -53,6 +65,14 @@ def get_ollama_models():
53
  except Exception:
54
  return []
55
 
 
 
 
 
 
 
 
 
56
  def refine_with_gemini(prompt, mode="refinement"):
57
  if not gemini_active:
58
  return "Gemini API key not found in .env file."
@@ -104,22 +124,32 @@ def refine_with_ollama(prompt, model, mode="refinement"):
104
  return f"Error refining prompt with Ollama: {e}"
105
 
106
  def refine_with_hf(prompt, token=None, mode="refinement"):
107
- """Refines the prompt using Hugging Face Inference API."""
108
- active_token = token if token else HF_TOKEN
109
- if not active_token:
 
 
 
 
 
 
 
 
 
 
 
110
  return "Error: Hugging Face token not found. Please log in or provide a token."
111
 
112
  system_prompt = load_system_prompt(mode)
113
  model_id = HF_TEXT_MODEL
114
 
115
  try:
116
- client = InferenceClient(api_key=active_token)
117
  messages = [
118
  {"role": "system", "content": system_prompt},
119
  {"role": "user", "content": f"Original Prompt: {prompt}"}
120
  ]
121
 
122
- response = client.chat.completions.create(
123
  model=model_id,
124
  messages=messages,
125
  max_tokens=500,
@@ -127,7 +157,7 @@ def refine_with_hf(prompt, token=None, mode="refinement"):
127
  )
128
  return response.choices[0].message.content.strip()
129
  except Exception as e:
130
- return f"Hugging Face Error: {e}"
131
 
132
  def refine_master(prompt, backend, ollama_model, manual_token=None, character_name=None):
133
  """Routes prompt refinement to the selected backend."""
@@ -269,15 +299,24 @@ def generate_image_with_hf(prompt, aspect_ratio, token=None, character_name="Unn
269
  return None, None, "Error: Hugging Face token not found. Please log in or provide a token."
270
 
271
  model_id = HF_IMAGE_MODEL
272
- final_prompt = f"{prompt}, aspect ratio {aspect_ratio}"
 
 
 
 
 
 
 
 
 
273
 
274
  try:
275
  client = InferenceClient(api_key=active_token)
276
- img = client.text_to_image(final_prompt, model=model_id)
277
 
278
  # Embed metadata
279
  metadata = PngInfo()
280
- metadata.add_text("Comment", final_prompt)
281
  metadata.add_text("CharacterName", character_name)
282
 
283
  safe_name = "".join([c if c.isalnum() else "_" for c in character_name]).strip("_")
 
2
  import requests
3
  import json
4
  import gradio as gr
5
+ from openai import OpenAI
6
  import uuid
7
  import time
8
  import io
 
17
  from google.genai import types
18
  from .config import (
19
  GEMINI_API_KEY, OLLAMA_HOST, OLLAMA_PORT, COMFY_URL,
20
+ COMFY_WORKFLOW_FILE, PROMPTS_FILE, HF_TOKEN, HF_BASE_URL,
21
  HF_TEXT_MODEL, HF_IMAGE_MODEL, GEMINI_TEXT_MODEL,
22
  GEMINI_IMAGE_MODEL
23
  )
 
32
  except Exception as e:
33
  print(f"Error initializing Gemini: {e}")
34
 
35
+ # Setup Hugging Face Router
36
+ hf_client = None
37
+ if HF_TOKEN:
38
+ try:
39
+ hf_client = OpenAI(
40
+ base_url=HF_BASE_URL,
41
+ api_key=HF_TOKEN,
42
+ )
43
+ except Exception as e:
44
+ print(f"Error initializing HF Client: {e}")
45
+
46
  def load_system_prompt(key="refinement"):
47
  """Loads a system prompt from prompts.yaml."""
48
  try:
 
65
  except Exception:
66
  return []
67
 
68
+ def check_comfy_availability():
69
+ """Checks if ComfyUI is running by pinging the URL."""
70
+ try:
71
+ response = requests.get(f"{COMFY_URL}/system_stats", timeout=2)
72
+ return response.status_code == 200
73
+ except Exception:
74
+ return False
75
+
76
  def refine_with_gemini(prompt, mode="refinement"):
77
  if not gemini_active:
78
  return "Gemini API key not found in .env file."
 
124
  return f"Error refining prompt with Ollama: {e}"
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
+
130
+ # If a manual token is provided, create a temporary client
131
+ if token:
132
+ try:
133
+ active_client = OpenAI(
134
+ base_url=HF_BASE_URL,
135
+ api_key=token,
136
+ )
137
+ except Exception as e:
138
+ return f"Error initializing manual HF Client: {e}"
139
+
140
+ if not active_client:
141
  return "Error: Hugging Face token not found. Please log in or provide a token."
142
 
143
  system_prompt = load_system_prompt(mode)
144
  model_id = HF_TEXT_MODEL
145
 
146
  try:
 
147
  messages = [
148
  {"role": "system", "content": system_prompt},
149
  {"role": "user", "content": f"Original Prompt: {prompt}"}
150
  ]
151
 
152
+ response = active_client.chat.completions.create(
153
  model=model_id,
154
  messages=messages,
155
  max_tokens=500,
 
157
  )
158
  return response.choices[0].message.content.strip()
159
  except Exception as e:
160
+ return f"Hugging Face Router Error: {e}"
161
 
162
  def refine_master(prompt, backend, ollama_model, manual_token=None, character_name=None):
163
  """Routes prompt refinement to the selected backend."""
 
299
  return None, None, "Error: Hugging Face token not found. Please log in or provide a token."
300
 
301
  model_id = HF_IMAGE_MODEL
302
+
303
+ # Resolution mapping
304
+ res_map = {
305
+ "1:1": (1024, 1024),
306
+ "16:9": (1344, 768),
307
+ "9:16": (768, 1344),
308
+ "4:3": (1152, 864),
309
+ "3:4": (864, 1152)
310
+ }
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=model_id, width=width, height=height)
316
 
317
  # Embed metadata
318
  metadata = PngInfo()
319
+ metadata.add_text("Comment", prompt)
320
  metadata.add_text("CharacterName", character_name)
321
 
322
  safe_name = "".join([c if c.isalnum() else "_" for c in character_name]).strip("_")
modules/ui_layout.py CHANGED
@@ -5,7 +5,7 @@ from .core_logic import (
5
  save_character, load_character, get_example_list, load_example_character
6
  )
7
  from .integrations import (
8
- get_ollama_models, refine_master, generate_image_master
9
  )
10
  from .name_generator import generate_fantasy_name
11
 
@@ -100,13 +100,18 @@ def build_ui():
100
  load_btn = gr.UploadButton("📂 Load Character", file_types=[".json"], variant="secondary", scale=1)
101
 
102
  with gr.Group():
103
- gr.Markdown("### ⚙️ Settings & Generation")
104
  with gr.Row():
105
  ollama_models = get_ollama_models()
106
  ollama_active = len(ollama_models) > 0
 
107
 
 
 
 
 
108
  refinement_backend = gr.Radio(
109
- choices=["Gemini (Cloud)", "Hugging Face (Cloud)", "Ollama (Local)"] if ollama_active else ["Gemini (Cloud)", "Hugging Face (Cloud)"],
110
  value="Gemini (Cloud)",
111
  label="Prompt Refinement Backend",
112
  scale=2
@@ -121,14 +126,17 @@ def build_ui():
121
  )
122
 
123
  with gr.Row():
 
 
 
 
124
  backend_selector = gr.Radio(
125
- choices=["Gemini (Cloud)", "Hugging Face (Cloud)", "ComfyUI (Local)"],
126
  value="Gemini (Cloud)",
127
  label="Image Generation Backend",
128
  scale=2
129
  )
130
  with gr.Column(scale=1):
131
- refine_btn = gr.Button("🧠 Refine Prompt", variant="primary")
132
  gen_img_btn = gr.Button("🖼️ Generate Image", variant="primary")
133
 
134
  with gr.Row():
@@ -145,6 +153,7 @@ def build_ui():
145
  with gr.Column(scale=1):
146
  gr.Markdown("### 📝 Prompts & Output")
147
  prompt_output = gr.Textbox(label="Generated Technical Prompt", lines=4, interactive=False, buttons=["copy"])
 
148
  regenerate_btn = gr.Button("✨ Randomize Features", variant="secondary")
149
  refined_output = gr.Textbox(label="Refined Artistic Prompt", lines=6, interactive=True, buttons=["copy", "paste", "clear"])
150
 
@@ -217,6 +226,9 @@ def build_ui():
217
  fn=generate_prompt,
218
  inputs=all_input_components,
219
  outputs=prompt_output
 
 
 
220
  )
221
 
222
  load_example_btn.click(
@@ -227,6 +239,9 @@ def build_ui():
227
  fn=generate_prompt,
228
  inputs=all_input_components,
229
  outputs=prompt_output
 
 
 
230
  )
231
 
232
  demo.load(fn=generate_prompt, inputs=all_input_components, outputs=prompt_output)
 
5
  save_character, load_character, get_example_list, load_example_character
6
  )
7
  from .integrations import (
8
+ get_ollama_models, check_comfy_availability, refine_master, generate_image_master
9
  )
10
  from .name_generator import generate_fantasy_name
11
 
 
100
  load_btn = gr.UploadButton("📂 Load Character", file_types=[".json"], variant="secondary", scale=1)
101
 
102
  with gr.Group():
103
+ gr.Markdown("### ⚙️ AI Backend Configuration")
104
  with gr.Row():
105
  ollama_models = get_ollama_models()
106
  ollama_active = len(ollama_models) > 0
107
+ comfy_active = check_comfy_availability()
108
 
109
+ refinement_choices = ["Gemini (Cloud)", "Hugging Face (Cloud)"]
110
+ if ollama_active:
111
+ refinement_choices.append("Ollama (Local)")
112
+
113
  refinement_backend = gr.Radio(
114
+ choices=refinement_choices,
115
  value="Gemini (Cloud)",
116
  label="Prompt Refinement Backend",
117
  scale=2
 
126
  )
127
 
128
  with gr.Row():
129
+ img_choices = ["Gemini (Cloud)", "Hugging Face (Cloud)"]
130
+ if comfy_active:
131
+ img_choices.append("ComfyUI (Local)")
132
+
133
  backend_selector = gr.Radio(
134
+ choices=img_choices,
135
  value="Gemini (Cloud)",
136
  label="Image Generation Backend",
137
  scale=2
138
  )
139
  with gr.Column(scale=1):
 
140
  gen_img_btn = gr.Button("🖼️ Generate Image", variant="primary")
141
 
142
  with gr.Row():
 
153
  with gr.Column(scale=1):
154
  gr.Markdown("### 📝 Prompts & Output")
155
  prompt_output = gr.Textbox(label="Generated Technical Prompt", lines=4, interactive=False, buttons=["copy"])
156
+ refine_btn = gr.Button("🧠 Refine Prompt", variant="primary")
157
  regenerate_btn = gr.Button("✨ Randomize Features", variant="secondary")
158
  refined_output = gr.Textbox(label="Refined Artistic Prompt", lines=6, interactive=True, buttons=["copy", "paste", "clear"])
159
 
 
226
  fn=generate_prompt,
227
  inputs=all_input_components,
228
  outputs=prompt_output
229
+ ).then(
230
+ fn=lambda: "",
231
+ outputs=refined_output
232
  )
233
 
234
  load_example_btn.click(
 
239
  fn=generate_prompt,
240
  inputs=all_input_components,
241
  outputs=prompt_output
242
+ ).then(
243
+ fn=lambda: "",
244
+ outputs=refined_output
245
  )
246
 
247
  demo.load(fn=generate_prompt, inputs=all_input_components, outputs=prompt_output)
requirements.txt CHANGED
@@ -6,3 +6,5 @@ requests
6
  Pillow
7
  huggingface_hub
8
  fictional-names
 
 
 
6
  Pillow
7
  huggingface_hub
8
  fictional-names
9
+ openai
10
+ python-dotenv