topguy commited on
Commit
0493f14
·
1 Parent(s): 5b0247a

feat: Integrate Gemini AI for prompt refinement and image generation

Browse files

- Migrated to google-genai SDK.
- Implemented prompt refinement with gemini-3-pro-preview.
- Integrated image generation using imagen-4.0-generate-001.
- Fixed image display by converting Gemini response to PIL Image.
- Added PNG conversion and friendly filenames (rpg_portrait.png).
- Expanded exotic weapon, armor, and accessory variants in features.yaml.
- Added support for dual accessory selection.
- Implemented "Save Character" and "Load Character" functionality (JSON).
- Added detailed traceback logging for debugging.
- Fixed Gradio theme and CopyButton compatibility issues.

Files changed (3) hide show
  1. app.py +270 -50
  2. features.yaml +18 -1
  3. requirements.txt +2 -0
app.py CHANGED
@@ -2,6 +2,28 @@ import gradio as gr
2
  import yaml
3
  import random
4
  import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  # Load features from YAML
7
  def load_features():
@@ -10,59 +32,99 @@ def load_features():
10
 
11
  features_data = load_features()
12
 
13
- # Define the sequence of features to ensure consistency between logic and UI
14
- # This MUST match the order components are created in build_ui()
15
  FEATURE_SEQUENCE = [
16
- # Identity
17
- ('identity', 'race'), ('identity', 'class'), ('identity', 'gender'), ('identity', 'age'),
18
- # Expression & Pose
19
- ('expression_pose', 'expression'), ('expression_pose', 'pose'),
20
- # Appearance
21
- ('appearance', 'hair_color'), ('appearance', 'hair_style'), ('appearance', 'eye_color'),
22
- ('appearance', 'build'), ('appearance', 'skin_tone'), ('appearance', 'distinguishing_feature'),
23
- # Equipment
24
- ('equipment', 'armor'), ('equipment', 'weapon'), ('equipment', 'accessory'), ('equipment', 'material'),
25
- # Environment
26
- ('environment', 'background'), ('environment', 'lighting'), ('environment', 'atmosphere'),
27
- # Style & Effects
28
- ('vfx_style', 'vfx'), ('vfx_style', 'style'), ('vfx_style', 'mood'), ('vfx_style', 'camera'),
29
- # Technical
30
- ('technical', 'aspect_ratio')
 
 
 
 
 
 
 
 
 
 
31
  ]
32
 
 
 
 
33
  def get_detail(category, subcategory, key):
34
  """Retrieves the detailed description for a given key in a category/subcategory."""
35
  return features_data.get(category, {}).get(subcategory, {}).get(key, key)
36
 
37
  def generate_prompt(*args):
38
  """
39
- Assembles the prompt based on dropdown selections, mapping keys to detailed descriptions.
40
  """
41
  num_features = len(FEATURE_SEQUENCE)
42
  feature_keys = args[:num_features]
 
43
 
44
  template = features_data.get("templates", {}).get("default", "")
45
 
46
- # Build context dictionary dynamically based on FEATURE_SEQUENCE
47
  context = {}
48
- for i, (cat, subcat) in enumerate(FEATURE_SEQUENCE):
49
  key = feature_keys[i]
50
- template_var = subcat
51
- context[template_var] = get_detail(cat, subcat, key)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
- return template.format(**context)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  def handle_regeneration(*args):
56
- """
57
- Checks each randomization checkbox. If checked, picks a random item for the dropdown.
58
- Returns the new list of dropdown values.
59
- """
60
  num_features = len(FEATURE_SEQUENCE)
61
  current_values = list(args[:num_features])
62
- checkboxes = args[num_features:]
63
 
64
  new_values = []
65
- for i, (is_random, (cat, subcat)) in enumerate(zip(checkboxes, FEATURE_SEQUENCE)):
66
  if is_random:
67
  choices = list(features_data[cat][subcat].keys())
68
  new_values.append(random.choice(choices))
@@ -71,13 +133,119 @@ def handle_regeneration(*args):
71
 
72
  return new_values
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  def build_ui():
75
- with gr.Blocks(title="RPGPortrait Prompt Builder") as demo:
76
- gr.Markdown("# 🎨 RPGPortrait Prompt Builder")
77
- gr.Markdown("Create highly detailed prompts for advanced AI models like Nano Banana Pro.")
78
 
79
  dropdowns = []
80
  checkboxes = []
 
81
 
82
  def create_feature_ui(category, subcategory, label, default_value):
83
  choices = list(features_data[category][subcategory].keys())
@@ -87,7 +255,6 @@ def build_ui():
87
  dropdowns.append(dd)
88
  checkboxes.append(cb)
89
 
90
- # UI Creation order matches FEATURE_SEQUENCE exactly
91
  with gr.Row():
92
  with gr.Column():
93
  gr.Markdown("### 👤 Identity")
@@ -95,6 +262,8 @@ def build_ui():
95
  create_feature_ui('identity', 'class', "Class", "Fighter")
96
  create_feature_ui('identity', 'gender', "Gender", "Male")
97
  create_feature_ui('identity', 'age', "Age", "Young Adult")
 
 
98
 
99
  gr.Markdown("### 🎭 Expression & Pose")
100
  create_feature_ui('expression_pose', 'expression', "Expression", "Determined")
@@ -108,20 +277,27 @@ def build_ui():
108
  create_feature_ui('appearance', 'build', "Build", "Average")
109
  create_feature_ui('appearance', 'skin_tone', "Skin Tone", "Fair")
110
  create_feature_ui('appearance', 'distinguishing_feature', "Distinguishing Feature", "None")
 
 
111
 
112
  with gr.Row():
113
  with gr.Column():
114
  gr.Markdown("### ⚔️ Equipment")
115
  create_feature_ui('equipment', 'armor', "Armor/Clothing", "Travelers Clothes")
116
  create_feature_ui('equipment', 'weapon', "Primary Weapon", "Longsword")
117
- create_feature_ui('equipment', 'accessory', "Accessory", "None")
 
118
  create_feature_ui('equipment', 'material', "Material Detail", "Weathered")
 
 
119
 
120
  with gr.Column():
121
  gr.Markdown("### 🌍 Environment")
122
  create_feature_ui('environment', 'background', "Background", "Forest")
123
  create_feature_ui('environment', 'lighting', "Lighting", "Natural Sunlight")
124
  create_feature_ui('environment', 'atmosphere', "Atmosphere", "Clear")
 
 
125
 
126
  with gr.Row():
127
  with gr.Column():
@@ -130,6 +306,8 @@ def build_ui():
130
  create_feature_ui('vfx_style', 'style', "Art Style", "Digital Illustration")
131
  create_feature_ui('vfx_style', 'mood', "Mood", "Heroic")
132
  create_feature_ui('vfx_style', 'camera', "Camera Angle", "Bust")
 
 
133
 
134
  with gr.Column():
135
  gr.Markdown("### ⚙️ Technical")
@@ -138,37 +316,79 @@ def build_ui():
138
  gr.Markdown("---")
139
 
140
  with gr.Row():
141
- regenerate_btn = gr.Button("✨ Regenerate Random Features", variant="primary")
 
 
 
 
142
 
143
  with gr.Row():
144
- prompt_output = gr.Textbox(
145
- label="Generated Prompt for Nano Banana Pro",
146
- lines=6,
147
- interactive=False
148
- )
 
 
 
149
 
150
- all_inputs = dropdowns + checkboxes
151
-
152
- # Set up event listeners for automatic updates on dropdown change
153
- for dd in dropdowns:
154
- dd.change(fn=generate_prompt, inputs=dropdowns, outputs=prompt_output)
155
 
156
  # Regenerate button logic
157
  regenerate_btn.click(
158
  fn=handle_regeneration,
159
- inputs=all_inputs,
160
  outputs=dropdowns
161
  ).then(
 
162
  fn=generate_prompt,
163
- inputs=dropdowns,
164
  outputs=prompt_output
165
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- # Initialize the prompt on load
168
- demo.load(fn=generate_prompt, inputs=dropdowns, outputs=prompt_output)
169
 
170
  return demo
171
 
172
  if __name__ == "__main__":
173
  demo = build_ui()
174
- demo.launch()
 
2
  import yaml
3
  import random
4
  import os
5
+ from dotenv import load_dotenv
6
+ from google import genai
7
+ from google.genai import types
8
+ from PIL import Image
9
+ import io
10
+ import json
11
+ import tempfile
12
+ import traceback
13
+
14
+ # Load environment variables
15
+ load_dotenv()
16
+
17
+ # Setup Gemini
18
+ api_key = os.getenv("GEMINI_API_KEY")
19
+ client = None
20
+ gemini_active = False
21
+ if api_key:
22
+ try:
23
+ client = genai.Client(api_key=api_key)
24
+ gemini_active = True
25
+ except Exception as e:
26
+ print(f"Error initializing Gemini: {e}")
27
 
28
  # Load features from YAML
29
  def load_features():
 
32
 
33
  features_data = load_features()
34
 
35
+ # Define segments for prompt building
36
+ # (Category, Subcategory, Template Key)
37
  FEATURE_SEQUENCE = [
38
+ ('identity', 'race', 'race'),
39
+ ('identity', 'class', 'class'),
40
+ ('identity', 'gender', 'gender'),
41
+ ('identity', 'age', 'age'),
42
+ ('expression_pose', 'expression', 'expression'),
43
+ ('expression_pose', 'pose', 'pose'),
44
+ ('appearance', 'hair_color', 'hair_color'),
45
+ ('appearance', 'hair_style', 'hair_style'),
46
+ ('appearance', 'eye_color', 'eye_color'),
47
+ ('appearance', 'build', 'build'),
48
+ ('appearance', 'skin_tone', 'skin_tone'),
49
+ ('appearance', 'distinguishing_feature', 'distinguishing_feature'),
50
+ ('equipment', 'armor', 'armor'),
51
+ ('equipment', 'weapon', 'weapon'),
52
+ ('equipment', 'accessory', 'accessory'),
53
+ ('equipment', 'accessory', 'accessory2'),
54
+ ('equipment', 'material', 'material'),
55
+ ('environment', 'background', 'background'),
56
+ ('environment', 'lighting', 'lighting'),
57
+ ('environment', 'atmosphere', 'atmosphere'),
58
+ ('vfx_style', 'vfx', 'vfx'),
59
+ ('vfx_style', 'style', 'style'),
60
+ ('vfx_style', 'mood', 'mood'),
61
+ ('vfx_style', 'camera', 'camera'),
62
+ ('technical', 'aspect_ratio', 'aspect_ratio')
63
  ]
64
 
65
+ # Section names for extra info
66
+ SECTIONS = ['Identity', 'Appearance', 'Equipment', 'Environment', 'Style']
67
+
68
  def get_detail(category, subcategory, key):
69
  """Retrieves the detailed description for a given key in a category/subcategory."""
70
  return features_data.get(category, {}).get(subcategory, {}).get(key, key)
71
 
72
  def generate_prompt(*args):
73
  """
74
+ Assembles the prompt based on dropdown selections and extra text info.
75
  """
76
  num_features = len(FEATURE_SEQUENCE)
77
  feature_keys = args[:num_features]
78
+ extra_infos = args[num_features : num_features + len(SECTIONS)]
79
 
80
  template = features_data.get("templates", {}).get("default", "")
81
 
 
82
  context = {}
83
+ for i, (cat, subcat, t_key) in enumerate(FEATURE_SEQUENCE):
84
  key = feature_keys[i]
85
+ context[t_key] = get_detail(cat, subcat, key)
86
+
87
+ # Handle multiple accessories dynamically
88
+ acc_list = []
89
+ # Identify accessory keys in context
90
+ for i, (cat, subcat, t_key) in enumerate(FEATURE_SEQUENCE):
91
+ if subcat == 'accessory' and feature_keys[i] != "None":
92
+ acc_list.append(context[t_key])
93
+
94
+ if acc_list:
95
+ context['accessories'] = ", and has " + " as well as ".join(acc_list)
96
+ else:
97
+ context['accessories'] = ""
98
+
99
+ # Inject extra info into the context or append to segments
100
+ # For simplicity, we'll append the extra info to specific segments if present
101
+ # Mapping section index to template keys
102
+ # Identity (0-3), Appearance (4-11), Equipment (12-15), Environment (16-18), Style (19-22)
103
 
104
+ if extra_infos[0]: # Identity
105
+ context['age'] += f", {extra_infos[0]}"
106
+ if extra_infos[1]: # Appearance
107
+ context['distinguishing_feature'] += f", also {extra_infos[1]}"
108
+ if extra_infos[2]: # Equipment
109
+ context['accessory'] += f", complemented by {extra_infos[2]}"
110
+ if extra_infos[3]: # Environment
111
+ context['atmosphere'] += f", additionally {extra_infos[3]}"
112
+ if extra_infos[4]: # Style
113
+ context['camera'] += f", art style notes: {extra_infos[4]}"
114
+
115
+ try:
116
+ return template.format(**context)
117
+ except Exception as e:
118
+ return f"Error building prompt: {e}"
119
 
120
  def handle_regeneration(*args):
121
+ """Randomizes checkboxes and returns new values for dropdowns."""
 
 
 
122
  num_features = len(FEATURE_SEQUENCE)
123
  current_values = list(args[:num_features])
124
+ checkboxes = args[num_features : num_features*2]
125
 
126
  new_values = []
127
+ for i, (is_random, (cat, subcat, t_key)) in enumerate(zip(checkboxes, FEATURE_SEQUENCE)):
128
  if is_random:
129
  choices = list(features_data[cat][subcat].keys())
130
  new_values.append(random.choice(choices))
 
133
 
134
  return new_values
135
 
136
+ def save_character(*args):
137
+ """Saves all current UI values to a JSON file."""
138
+ num_features = len(FEATURE_SEQUENCE)
139
+ feature_keys = args[:num_features]
140
+ checkboxes = args[num_features : num_features*2]
141
+ extra_infos = args[num_features*2 : num_features*2 + len(SECTIONS)]
142
+
143
+ data = {
144
+ "features": {FEATURE_SEQUENCE[i][2]: feature_keys[i] for i in range(num_features)},
145
+ "randomization": {FEATURE_SEQUENCE[i][2]: checkboxes[i] for i in range(num_features)},
146
+ "extra_info": {SECTIONS[i].lower(): extra_infos[i] for i in range(len(SECTIONS))}
147
+ }
148
+
149
+ # Save to a file with a friendly name in a temp directory
150
+ temp_dir = tempfile.mkdtemp()
151
+ path = os.path.join(temp_dir, "rpg_character_config.json")
152
+ with open(path, 'w') as f:
153
+ json.dump(data, f, indent=4)
154
+ return path
155
+
156
+ def load_character(file):
157
+ """Loads UI values from a JSON file."""
158
+ if file is None:
159
+ return [gr.update()] * (len(FEATURE_SEQUENCE) + len(SECTIONS))
160
+
161
+ try:
162
+ with open(file.name, 'r') as f:
163
+ data = json.load(f)
164
+
165
+ updates = []
166
+ # Update dropdowns
167
+ for _, _, t_key in FEATURE_SEQUENCE:
168
+ updates.append(data.get("features", {}).get(t_key, gr.update()))
169
+
170
+ # Note: We don't necessarily update checkboxes unless requested,
171
+ # but for a full load, let's just return the values for dropdowns and textboxes first.
172
+ # Following the pattern of handle_regeneration, we might need to return specific updates.
173
+
174
+ # Update extra info textboxes
175
+ for section in SECTIONS:
176
+ updates.append(data.get("extra_info", {}).get(section.lower(), ""))
177
+
178
+ return updates
179
+ except Exception as e:
180
+ print(f"Error loading character: {e}")
181
+ return [gr.update()] * (len(FEATURE_SEQUENCE) + len(SECTIONS))
182
+
183
+ def refine_with_gemini(prompt):
184
+ if not gemini_active:
185
+ return "Gemini API key not found in .env file."
186
+
187
+ system_prompt = (
188
+ "You are an expert prompt engineer for AI image generators. "
189
+ "Your task is to take the provided technical prompt and refine it into a more vivid, "
190
+ "artistic, and detailed description while maintaining all the core features. "
191
+ "Enhance the vocabulary, lighting, and textures to be professional quality. "
192
+ "IMPORTANT: RETURN ONLY THE REFINED PROMPT TEXT. DO NOT INCLUDE ANY CONVERSATIONAL FILLER, MARKDOWN CODE BLOCKS, OR EXPLANATIONS."
193
+ )
194
+
195
+ try:
196
+ response = client.models.generate_content(
197
+ model='gemini-3-pro-preview',
198
+ contents=f"{system_prompt}\n\nOriginal Prompt: {prompt}"
199
+ )
200
+ text = response.text.strip()
201
+ # Remove common markdown code block wrappings if present
202
+ if text.startswith("```"):
203
+ lines = text.splitlines()
204
+ if lines[0].startswith("```"):
205
+ lines = lines[1:]
206
+ if lines and lines[-1].startswith("```"):
207
+ lines = lines[:-1]
208
+ text = "\n".join(lines).strip()
209
+ return text
210
+ except Exception as e:
211
+ return f"Error refining prompt with Gemini: {e}"
212
+
213
+ def generate_image_with_gemini(refined_prompt, technical_prompt):
214
+ if not gemini_active:
215
+ return None, "Gemini API key not found in .env file."
216
+
217
+ # Priority: Refined Prompt -> Technical Prompt
218
+ final_prompt = refined_prompt.strip() if refined_prompt and refined_prompt.strip() else technical_prompt.strip()
219
+
220
+ if not final_prompt:
221
+ return None, "No prompt available for generation."
222
+
223
+ try:
224
+ # Using the new SDK's generate_images method
225
+ response = client.models.generate_images(
226
+ model='imagen-4.0-generate-001',
227
+ prompt=final_prompt
228
+ )
229
+ if response.generated_images:
230
+ img = Image.open(io.BytesIO(response.generated_images[0].image.image_bytes))
231
+ # Save as PNG to a temp file for download
232
+ temp_dir = tempfile.mkdtemp()
233
+ img_path = os.path.join(temp_dir, "rpg_portrait.png")
234
+ img.save(img_path, "PNG")
235
+ return img, img_path, f"Image generated using {'refined' if refined_prompt.strip() else 'technical'} prompt!"
236
+ return None, None, "Gemini Image generation did not return any images."
237
+ except Exception as e:
238
+ traceback.print_exc()
239
+ return None, f"Image Generation Error: {e}"
240
+
241
  def build_ui():
242
+ with gr.Blocks(title="RPGPortrait Prompt Builder Pro") as demo:
243
+ gr.Markdown("# 🎨 RPGPortrait Prompt Builder Pro")
244
+ gr.Markdown("Create professional AI prompts and generate portraits with Gemini integration.")
245
 
246
  dropdowns = []
247
  checkboxes = []
248
+ extra_texts = []
249
 
250
  def create_feature_ui(category, subcategory, label, default_value):
251
  choices = list(features_data[category][subcategory].keys())
 
255
  dropdowns.append(dd)
256
  checkboxes.append(cb)
257
 
 
258
  with gr.Row():
259
  with gr.Column():
260
  gr.Markdown("### 👤 Identity")
 
262
  create_feature_ui('identity', 'class', "Class", "Fighter")
263
  create_feature_ui('identity', 'gender', "Gender", "Male")
264
  create_feature_ui('identity', 'age', "Age", "Young Adult")
265
+ extra_id = gr.Textbox(placeholder="Extra Identity details (e.g. lineage, title)", label="Additional Identity Info")
266
+ extra_texts.append(extra_id)
267
 
268
  gr.Markdown("### 🎭 Expression & Pose")
269
  create_feature_ui('expression_pose', 'expression', "Expression", "Determined")
 
277
  create_feature_ui('appearance', 'build', "Build", "Average")
278
  create_feature_ui('appearance', 'skin_tone', "Skin Tone", "Fair")
279
  create_feature_ui('appearance', 'distinguishing_feature', "Distinguishing Feature", "None")
280
+ extra_app = gr.Textbox(placeholder="Extra Appearance details (e.g. tattoos, scars)", label="Additional Appearance Info")
281
+ extra_texts.append(extra_app)
282
 
283
  with gr.Row():
284
  with gr.Column():
285
  gr.Markdown("### ⚔️ Equipment")
286
  create_feature_ui('equipment', 'armor', "Armor/Clothing", "Travelers Clothes")
287
  create_feature_ui('equipment', 'weapon', "Primary Weapon", "Longsword")
288
+ create_feature_ui('equipment', 'accessory', "Accessory 1", "None")
289
+ create_feature_ui('equipment', 'accessory', "Accessory 2", "None")
290
  create_feature_ui('equipment', 'material', "Material Detail", "Weathered")
291
+ extra_eq = gr.Textbox(placeholder="Extra Equipment details (e.g. weapon enchantments)", label="Additional Equipment Info")
292
+ extra_texts.append(extra_eq)
293
 
294
  with gr.Column():
295
  gr.Markdown("### 🌍 Environment")
296
  create_feature_ui('environment', 'background', "Background", "Forest")
297
  create_feature_ui('environment', 'lighting', "Lighting", "Natural Sunlight")
298
  create_feature_ui('environment', 'atmosphere', "Atmosphere", "Clear")
299
+ extra_env = gr.Textbox(placeholder="Extra Environment details (e.g. time of day, weather)", label="Additional Environment Info")
300
+ extra_texts.append(extra_env)
301
 
302
  with gr.Row():
303
  with gr.Column():
 
306
  create_feature_ui('vfx_style', 'style', "Art Style", "Digital Illustration")
307
  create_feature_ui('vfx_style', 'mood', "Mood", "Heroic")
308
  create_feature_ui('vfx_style', 'camera', "Camera Angle", "Bust")
309
+ extra_sty = gr.Textbox(placeholder="Extra Style details (e.g. specific artists, colors)", label="Additional Style Info")
310
+ extra_texts.append(extra_sty)
311
 
312
  with gr.Column():
313
  gr.Markdown("### ⚙️ Technical")
 
316
  gr.Markdown("---")
317
 
318
  with gr.Row():
319
+ regenerate_btn = gr.Button("✨ Randomize Features", variant="secondary")
320
+ save_btn = gr.Button("💾 Save Character", variant="secondary")
321
+ load_btn = gr.UploadButton("📂 Load Character", file_types=[".json"], variant="secondary")
322
+ refine_btn = gr.Button("🧠 Refine with Gemini", variant="primary")
323
+ gen_img_btn = gr.Button("🖼️ Generate Image (Gemini)", variant="primary")
324
 
325
  with gr.Row():
326
+ with gr.Column(scale=2):
327
+ prompt_output = gr.Textbox(label="Generated Technical Prompt", lines=5, interactive=False)
328
+ refined_output = gr.Textbox(label="Gemini Refined Prompt", lines=5, interactive=True)
329
+ with gr.Column(scale=1):
330
+ image_output = gr.Image(label="Gemini Generated Portrait")
331
+ download_img_btn = gr.DownloadButton("📥 Download Portrait (PNG)", variant="secondary", visible=False)
332
+ download_file = gr.File(label="Saved Character JSON", visible=False)
333
+ status_msg = gr.Markdown("")
334
 
335
+ all_input_components = dropdowns + extra_texts
336
+
337
+ # Automatic prompt update on any input change
338
+ for comp in all_input_components:
339
+ comp.change(fn=generate_prompt, inputs=all_input_components, outputs=prompt_output)
340
 
341
  # Regenerate button logic
342
  regenerate_btn.click(
343
  fn=handle_regeneration,
344
+ inputs=dropdowns + checkboxes,
345
  outputs=dropdowns
346
  ).then(
347
+ # Trigger prompt update after dropdowns change
348
  fn=generate_prompt,
349
+ inputs=all_input_components,
350
  outputs=prompt_output
351
  )
352
+
353
+ # Gemini Refinement
354
+ refine_btn.click(
355
+ fn=refine_with_gemini,
356
+ inputs=prompt_output,
357
+ outputs=refined_output
358
+ )
359
+
360
+ # Gemini Image Generation
361
+ gen_img_btn.click(
362
+ fn=generate_image_with_gemini,
363
+ inputs=[refined_output, prompt_output],
364
+ outputs=[image_output, download_img_btn, status_msg]
365
+ ).then(
366
+ fn=lambda x: gr.update(value=x, visible=True) if x else gr.update(visible=False),
367
+ inputs=download_img_btn,
368
+ outputs=download_img_btn
369
+ )
370
+
371
+ # Save/Load Logic
372
+ save_btn.click(
373
+ fn=save_character,
374
+ inputs=dropdowns + checkboxes + extra_texts,
375
+ outputs=download_file
376
+ ).then(
377
+ fn=lambda: gr.update(visible=True),
378
+ outputs=download_file
379
+ )
380
+
381
+ load_btn.upload(
382
+ fn=load_character,
383
+ inputs=load_btn,
384
+ outputs=dropdowns + extra_texts
385
+ )
386
 
387
+ # Initialize
388
+ demo.load(fn=generate_prompt, inputs=all_input_components, outputs=prompt_output)
389
 
390
  return demo
391
 
392
  if __name__ == "__main__":
393
  demo = build_ui()
394
+ demo.launch(theme=gr.themes.Soft())
features.yaml CHANGED
@@ -105,6 +105,9 @@ equipment:
105
  Chainmail: "clad in a suit of shimmering, interlocking chainmail"
106
  Travelers Clothes: "dressed in practical, durable travelers' garments"
107
  Elegant Robes: "wearing flowing, silk-threaded elegant robes with intricate embroidery"
 
 
 
108
  Ragged Tunic: "attired in a simple, worn, and slightly ragged tunic"
109
  weapon:
110
  Longsword: "confidently wielding a gleaming steel longsword in a two-handed grip"
@@ -112,6 +115,14 @@ equipment:
112
  Daggers: "gripping a pair of obsidian daggers, ready for a swift strike"
113
  Bow and Quiver: "carrying a finely crafted longbow with a full quiver of arrows slung over the shoulder"
114
  Greataxe: "resting a massive, notched greataxe upon one shoulder"
 
 
 
 
 
 
 
 
115
  Shield: "defensively holding a heavy heater shield emblazoned with a crest"
116
  None: "with hands held open and empty"
117
  accessory:
@@ -119,6 +130,12 @@ equipment:
119
  Hood: "a deep hood casting shadows over the face"
120
  Amulet: "a glowing amulet hanging from a silver chain"
121
  Belt with Pouches: "a sturdy leather belt laden with various pouches and tools"
 
 
 
 
 
 
122
  Eyepatch: "a leather eyepatch covering one eye"
123
  None: "no visible accessories"
124
  material:
@@ -193,4 +210,4 @@ technical:
193
  "2:3": "--ar 2:3"
194
 
195
  templates:
196
- default: "A {mood} {style} portrait of {race} {class}, {gender}, {age}. {build} build, {hair_style} {hair_color} hair, {eye_color} eyes, with {distinguishing_feature}. The character is {expression} while {pose}, {armor}, {weapon}, and has {accessory}. All equipment is {material}. The setting is a {background}, {lighting}, {atmosphere}. {vfx}. {camera}. Highly detailed, masterpiece, 8k resolution. {aspect_ratio}"
 
105
  Chainmail: "clad in a suit of shimmering, interlocking chainmail"
106
  Travelers Clothes: "dressed in practical, durable travelers' garments"
107
  Elegant Robes: "wearing flowing, silk-threaded elegant robes with intricate embroidery"
108
+ Bone Armor: "wearing intimidating, primal armor crafted from the bones of prehistoric beasts"
109
+ Arcane Plate: "clad in futuristic, bioluminescent plate armor etched with glowing circuitry"
110
+ Leaf-weave Armor: "attired in flexible armor made of magically hardened, vibrant-green leaves"
111
  Ragged Tunic: "attired in a simple, worn, and slightly ragged tunic"
112
  weapon:
113
  Longsword: "confidently wielding a gleaming steel longsword in a two-handed grip"
 
115
  Daggers: "gripping a pair of obsidian daggers, ready for a swift strike"
116
  Bow and Quiver: "carrying a finely crafted longbow with a full quiver of arrows slung over the shoulder"
117
  Greataxe: "resting a massive, notched greataxe upon one shoulder"
118
+ Katana: "holding a slender, razor-sharp katana with a traditional silken hilt-wrap"
119
+ Whip-blade: "wielding a segmented, metallic whip-blade that can transition between sword and whip"
120
+ Twin Scimitars: "deftly handling a pair of curved, engraved scimitars"
121
+ Meteor Hammer: "swinging a heavy, spiked metal sphere attached to a long, sturdy chain"
122
+ Primitive Club: "gripping a heavy, notched primitive club made of dark, weathered wood"
123
+ Rapier: "wielding a slender, elegant rapier with an intricate basket hilt"
124
+ Morning Star: "holding a lethal, spiked morning star, its heavy head gleaming with menace"
125
+ Sword and Shield: "wielding a balanced shortsword in one hand and a sturdy, emblazoned heater shield in the other"
126
  Shield: "defensively holding a heavy heater shield emblazoned with a crest"
127
  None: "with hands held open and empty"
128
  accessory:
 
130
  Hood: "a deep hood casting shadows over the face"
131
  Amulet: "a glowing amulet hanging from a silver chain"
132
  Belt with Pouches: "a sturdy leather belt laden with various pouches and tools"
133
+ Feathered Hat: "wearing a wide-brimmed hat adorned with a long, colorful feather"
134
+ Silk Scarf: "wrapped in a long, vibrant silk scarf that catches the breeze"
135
+ Adventurer's Backpack: "shouldering a heavy leather backpack laden with supplies and rope"
136
+ Oil Lantern: "carrying a flickering oil lantern that casts a warm, localized glow"
137
+ Ornate Mask: "wearing a decorative gold and velvet masquerade mask"
138
+ Jeweled Circlet: "adorned with a delicate jeweled circlet that rests upon the brow"
139
  Eyepatch: "a leather eyepatch covering one eye"
140
  None: "no visible accessories"
141
  material:
 
210
  "2:3": "--ar 2:3"
211
 
212
  templates:
213
+ default: "A {mood} {style} portrait of {race} {class}, {gender}, {age}. {build} build, {hair_style} {hair_color} hair, {eye_color} eyes, with {distinguishing_feature}. The character is {expression} while {pose}, {armor}, {weapon}{accessories}. All equipment is {material}. The setting is a {background}, {lighting}, {atmosphere}. {vfx}. {camera}. Highly detailed, 8k resolution. {aspect_ratio}"
requirements.txt CHANGED
@@ -1,2 +1,4 @@
1
  gradio
2
  PyYAML
 
 
 
1
  gradio
2
  PyYAML
3
+ python-dotenv
4
+ google-genai