topguy commited on
Commit
db5af08
·
1 Parent(s): bd05868

style: reorganize UI into tabs and refine button hierarchy

Browse files

- Implemented gr.Tabs for character attributes
- Aligned randomization dice with dropdowns on the same line
- Reordered output column (prompts above image)
- Grouped Save/Load buttons with example characters
- Moved 'Randomize' button below technical prompt
- Updated design docs with process management lessons

Files changed (3) hide show
  1. app.py +119 -114
  2. design.md +14 -1
  3. features.yaml +1 -0
app.py CHANGED
@@ -426,8 +426,8 @@ def generate_image_master(refined_prompt, technical_prompt, aspect_ratio, backen
426
 
427
  def build_ui():
428
  with gr.Blocks(title="RPGPortrait Prompt Builder Pro") as demo:
429
- gr.Markdown("# 🎨 RPGPortrait Prompt Builder Pro")
430
- gr.Markdown("Create professional AI prompts and generate portraits with Gemini integration.")
431
 
432
  dropdowns = []
433
  checkboxes = []
@@ -435,127 +435,121 @@ def build_ui():
435
 
436
  def create_feature_ui(category, subcategory, label, default_value):
437
  choices = list(features_data[category][subcategory].keys())
438
- with gr.Row():
439
- dd = gr.Dropdown(choices=choices, label=label, value=default_value, scale=4)
440
- cb = gr.Checkbox(label="🎲", value=False, scale=1, min_width=50)
 
441
  dropdowns.append(dd)
442
  checkboxes.append(cb)
443
 
444
  with gr.Row():
445
- with gr.Column():
446
- gr.Markdown("### 👤 Identity")
447
- create_feature_ui('identity', 'race', "Race", "Human")
448
- create_feature_ui('identity', 'class', "Class", "Fighter")
449
- create_feature_ui('identity', 'gender', "Gender", "Male")
450
- create_feature_ui('identity', 'age', "Age", "Young Adult")
451
- extra_id = gr.Textbox(placeholder="Extra Identity details (e.g. lineage, title)", label="Additional Identity Info")
452
- extra_texts.append(extra_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
 
454
- gr.Markdown("### 🎭 Expression & Pose")
455
- create_feature_ui('expression_pose', 'expression', "Expression", "Determined")
456
- create_feature_ui('expression_pose', 'pose', "Pose", "Standing")
457
-
458
- with gr.Column():
459
- gr.Markdown("### 🎨 Appearance")
460
- create_feature_ui('appearance', 'hair_color', "Hair Color", "Brown")
461
- create_feature_ui('appearance', 'hair_style', "Hair Style", "Short")
462
- create_feature_ui('appearance', 'eye_color', "Eye Color", "Brown")
463
- create_feature_ui('appearance', 'build', "Build", "Average")
464
- create_feature_ui('appearance', 'skin_tone', "Skin Tone", "Fair")
465
- create_feature_ui('appearance', 'distinguishing_feature', "Distinguishing Feature", "None")
466
- extra_app = gr.Textbox(placeholder="Extra Appearance details (e.g. tattoos, scars)", label="Additional Appearance Info")
467
- extra_texts.append(extra_app)
468
-
469
- with gr.Row():
470
- with gr.Column():
471
- gr.Markdown("### ⚔️ Equipment")
472
- create_feature_ui('equipment', 'armor', "Armor/Clothing", "Travelers Clothes")
473
- create_feature_ui('equipment', 'weapon', "Primary Weapon", "Longsword")
474
- create_feature_ui('equipment', 'accessory', "Accessory 1", "None")
475
- create_feature_ui('equipment', 'accessory', "Accessory 2", "None")
476
- create_feature_ui('equipment', 'material', "Material Detail", "Weathered")
477
- extra_eq = gr.Textbox(placeholder="Extra Equipment details (e.g. weapon enchantments)", label="Additional Equipment Info")
478
- extra_texts.append(extra_eq)
479
-
480
- with gr.Column():
481
- gr.Markdown("### 🌍 Environment")
482
- create_feature_ui('environment', 'background', "Background", "Forest")
483
- create_feature_ui('environment', 'lighting', "Lighting", "Natural Sunlight")
484
- create_feature_ui('environment', 'atmosphere', "Atmosphere", "Clear")
485
- extra_env = gr.Textbox(placeholder="Extra Environment details (e.g. time of day, weather)", label="Additional Environment Info")
486
- extra_texts.append(extra_env)
 
 
 
 
487
 
488
- with gr.Row():
489
- with gr.Column():
490
- gr.Markdown("### 🪄 Style & Effects")
491
- create_feature_ui('vfx_style', 'vfx', "Special Effects", "None")
492
- create_feature_ui('vfx_style', 'style', "Art Style", "Digital Illustration")
493
- create_feature_ui('vfx_style', 'mood', "Mood", "Heroic")
494
- create_feature_ui('vfx_style', 'camera', "Camera Angle", "Bust")
495
- extra_sty = gr.Textbox(placeholder="Extra Style details (e.g. specific artists, colors)", label="Additional Style Info")
496
- extra_texts.append(extra_sty)
497
-
498
- with gr.Column():
499
- gr.Markdown("### ⚙️ Technical")
500
- create_feature_ui('technical', 'aspect_ratio', "Aspect Ratio", "1:1")
501
-
502
- gr.Markdown("---")
503
-
504
- with gr.Row():
505
- regenerate_btn = gr.Button("✨ Randomize Features", variant="secondary")
506
- save_btn = gr.Button("💾 Save Character", variant="secondary")
507
- load_btn = gr.UploadButton("📂 Load Character", file_types=[".json"], variant="secondary")
508
- refine_btn = gr.Button("🧠 Refine with Gemini", variant="primary")
509
- gen_img_btn = gr.Button("🖼️ Generate Image", variant="primary")
510
-
511
- with gr.Row():
512
- ollama_models = get_ollama_models()
513
- ollama_active = len(ollama_models) > 0
514
-
515
- refinement_backend = gr.Radio(
516
- choices=["Gemini (Cloud)", "Ollama (Local)"] if ollama_active else ["Gemini (Cloud)"],
517
- value="Gemini (Cloud)",
518
- label="Prompt Refinement Backend",
519
- info="Ollama is " + ("available" if ollama_active else "offline or no models found") + "."
520
- )
521
-
522
- ollama_model_dropdown = gr.Dropdown(
523
- choices=ollama_models,
524
- value=ollama_models[0] if ollama_active else None,
525
- label="Ollama Model",
526
- visible=ollama_active,
527
- scale=1
528
- )
529
- backend_selector = gr.Radio(
530
- choices=["Gemini (Cloud)", "ComfyUI (Local)"],
531
- value="Gemini (Cloud)",
532
- label="Image Generation Backend",
533
- info="ComfyUI requires the local server to be running on port 8188."
534
- )
535
-
536
- # Simple toggle for Ollama model visibility
537
- def toggle_ollama_visibility(backend):
538
- return gr.update(visible=(backend == "Ollama (Local)"))
539
-
540
- refinement_backend.change(
541
- fn=toggle_ollama_visibility,
542
- inputs=refinement_backend,
543
- outputs=ollama_model_dropdown
544
- )
545
-
546
- with gr.Row():
547
- example_dropdown = gr.Dropdown(choices=get_example_list(), label="Example Characters", scale=3)
548
- load_example_btn = gr.Button("📂 Load Example", variant="secondary", scale=1)
549
-
550
- with gr.Row():
551
- with gr.Column(scale=2):
552
- prompt_output = gr.Textbox(label="Generated Technical Prompt", lines=5, interactive=False)
553
- refined_output = gr.Textbox(label="Gemini Refined Prompt", lines=5, interactive=True)
554
  with gr.Column(scale=1):
555
- image_output = gr.Image(label="Gemini Generated Portrait")
 
 
 
 
 
 
556
  download_img_btn = gr.DownloadButton("📥 Download Portrait (PNG)", variant="secondary", visible=False)
557
- download_file = gr.File(label="Saved Character JSON", visible=False)
558
  status_msg = gr.Markdown("")
 
559
 
560
  all_input_components = dropdowns + extra_texts
561
 
@@ -598,6 +592,17 @@ def build_ui():
598
  )
599
 
600
  # Save/Load Logic
 
 
 
 
 
 
 
 
 
 
 
601
  save_btn.click(
602
  fn=save_character,
603
  inputs=dropdowns + checkboxes + extra_texts,
 
426
 
427
  def build_ui():
428
  with gr.Blocks(title="RPGPortrait Prompt Builder Pro") as demo:
429
+ gr.Markdown("# 🎨 RPGPortrait Pro")
430
+ gr.Markdown("Create professional AI prompts and generate portraits.")
431
 
432
  dropdowns = []
433
  checkboxes = []
 
435
 
436
  def create_feature_ui(category, subcategory, label, default_value):
437
  choices = list(features_data[category][subcategory].keys())
438
+ gr.Markdown(f"**{label}**")
439
+ with gr.Row(equal_height=True):
440
+ dd = gr.Dropdown(choices=choices, value=default_value, scale=12, show_label=False)
441
+ cb = gr.Checkbox(label="🎲", value=False, scale=1, min_width=50, container=False)
442
  dropdowns.append(dd)
443
  checkboxes.append(cb)
444
 
445
  with gr.Row():
446
+ with gr.Column(scale=2):
447
+ with gr.Tabs():
448
+ with gr.TabItem("👤 Identity & Expression"):
449
+ gr.Markdown("### 👤 Identity")
450
+ create_feature_ui('identity', 'race', "Race", "Human")
451
+ create_feature_ui('identity', 'class', "Class", "Fighter")
452
+ create_feature_ui('identity', 'gender', "Gender", "Male")
453
+ create_feature_ui('identity', 'age', "Age", "Young Adult")
454
+ extra_id = gr.Textbox(placeholder="Extra Identity details (e.g. lineage, title)", label="Additional Identity Info")
455
+ extra_texts.append(extra_id)
456
+
457
+ gr.Markdown("### 🎭 Expression & Pose")
458
+ create_feature_ui('expression_pose', 'expression', "Expression", "Determined")
459
+ create_feature_ui('expression_pose', 'pose', "Pose", "Standing")
460
+
461
+ with gr.TabItem("🎨 Appearance"):
462
+ gr.Markdown("### 🎨 Appearance")
463
+ create_feature_ui('appearance', 'hair_color', "Hair Color", "Brown")
464
+ create_feature_ui('appearance', 'hair_style', "Hair Style", "Short")
465
+ create_feature_ui('appearance', 'eye_color', "Eye Color", "Brown")
466
+ create_feature_ui('appearance', 'build', "Build", "Average")
467
+ create_feature_ui('appearance', 'skin_tone', "Skin Tone", "Fair")
468
+ create_feature_ui('appearance', 'distinguishing_feature', "Distinguishing Feature", "None")
469
+ extra_app = gr.Textbox(placeholder="Extra Appearance details (e.g. tattoos, scars)", label="Additional Appearance Info")
470
+ extra_texts.append(extra_app)
471
+
472
+ with gr.TabItem("⚔️ Equipment"):
473
+ gr.Markdown("### ⚔️ Equipment")
474
+ create_feature_ui('equipment', 'armor', "Armor/Clothing", "Travelers Clothes")
475
+ create_feature_ui('equipment', 'weapon', "Primary Weapon", "Longsword")
476
+ create_feature_ui('equipment', 'accessory', "Accessory 1", "None")
477
+ create_feature_ui('equipment', 'accessory', "Accessory 2", "None")
478
+ create_feature_ui('equipment', 'material', "Material Detail", "Weathered")
479
+ extra_eq = gr.Textbox(placeholder="Extra Equipment details (e.g. weapon enchantments)", label="Additional Equipment Info")
480
+ extra_texts.append(extra_eq)
481
+
482
+ with gr.TabItem("🌍 Environment"):
483
+ gr.Markdown("### 🌍 Environment")
484
+ create_feature_ui('environment', 'background', "Background", "Forest")
485
+ create_feature_ui('environment', 'lighting', "Lighting", "Natural Sunlight")
486
+ create_feature_ui('environment', 'atmosphere', "Atmosphere", "Clear")
487
+ extra_env = gr.Textbox(placeholder="Extra Environment details (e.g. time of day, weather)", label="Additional Environment Info")
488
+ extra_texts.append(extra_env)
489
+
490
+ with gr.TabItem("🪄 Style & Technical"):
491
+ gr.Markdown("### 🪄 Style & Effects")
492
+ create_feature_ui('vfx_style', 'vfx', "Special Effects", "None")
493
+ create_feature_ui('vfx_style', 'style', "Art Style", "Digital Illustration")
494
+ create_feature_ui('vfx_style', 'mood', "Mood", "Heroic")
495
+ create_feature_ui('vfx_style', 'camera', "Camera Angle", "Bust")
496
+ extra_sty = gr.Textbox(placeholder="Extra Style details (e.g. specific artists, colors)", label="Additional Style Info")
497
+ extra_texts.append(extra_sty)
498
+
499
+ gr.Markdown("### ⚙️ Technical")
500
+ create_feature_ui('technical', 'aspect_ratio', "Aspect Ratio", "1:1")
501
+
502
+ gr.Markdown("---")
503
 
504
+ with gr.Row():
505
+ example_dropdown = gr.Dropdown(choices=get_example_list(), label="Example Characters", scale=3)
506
+ load_example_btn = gr.Button("📂 Load Example", variant="secondary", scale=1)
507
+ save_btn = gr.Button("💾 Save Character", variant="secondary", scale=1)
508
+ load_btn = gr.UploadButton("📂 Load Character", file_types=[".json"], variant="secondary", scale=1)
509
+
510
+ with gr.Group():
511
+ gr.Markdown("### ⚙️ Settings & Generation")
512
+ with gr.Row():
513
+ ollama_models = get_ollama_models()
514
+ ollama_active = len(ollama_models) > 0
515
+
516
+ refinement_backend = gr.Radio(
517
+ choices=["Gemini (Cloud)", "Ollama (Local)"] if ollama_active else ["Gemini (Cloud)"],
518
+ value="Gemini (Cloud)",
519
+ label="Prompt Refinement Backend",
520
+ scale=2
521
+ )
522
+
523
+ ollama_model_dropdown = gr.Dropdown(
524
+ choices=ollama_models,
525
+ value=ollama_models[0] if ollama_active else None,
526
+ label="Ollama Model",
527
+ visible=ollama_active and False,
528
+ scale=1
529
+ )
530
+
531
+ with gr.Row():
532
+ backend_selector = gr.Radio(
533
+ choices=["Gemini (Cloud)", "ComfyUI (Local)"],
534
+ value="Gemini (Cloud)",
535
+ label="Image Generation Backend",
536
+ scale=2
537
+ )
538
+ with gr.Column(scale=1):
539
+ refine_btn = gr.Button("🧠 Refine Prompt", variant="primary")
540
+ gen_img_btn = gr.Button("🖼️ Generate Image", variant="primary")
541
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
  with gr.Column(scale=1):
543
+ gr.Markdown("### 📝 Prompts & Output")
544
+ prompt_output = gr.Textbox(label="Generated Technical Prompt", lines=4, interactive=False)
545
+ regenerate_btn = gr.Button("✨ Randomize Features", variant="secondary")
546
+ refined_output = gr.Textbox(label="Refined Artistic Prompt", lines=6, interactive=True)
547
+
548
+ gr.Markdown("---")
549
+ image_output = gr.Image(label="Portrait", show_label=False)
550
  download_img_btn = gr.DownloadButton("📥 Download Portrait (PNG)", variant="secondary", visible=False)
 
551
  status_msg = gr.Markdown("")
552
+ download_file = gr.File(label="Saved Character JSON", visible=False)
553
 
554
  all_input_components = dropdowns + extra_texts
555
 
 
592
  )
593
 
594
  # Save/Load Logic
595
+
596
+ # Simple toggle for Ollama model visibility
597
+ def toggle_ollama_visibility(backend):
598
+ return gr.update(visible=(backend == "Ollama (Local)"))
599
+
600
+ refinement_backend.change(
601
+ fn=toggle_ollama_visibility,
602
+ inputs=refinement_backend,
603
+ outputs=ollama_model_dropdown
604
+ )
605
+
606
  save_btn.click(
607
  fn=save_character,
608
  inputs=dropdowns + checkboxes + extra_texts,
design.md CHANGED
@@ -49,4 +49,17 @@ The app uses a 3-stage prompt pipeline:
49
  - **Image Local**: ComfyUI (Local Server)
50
  - **Configuration**:
51
  - `.env` for API keys and connection hosts/ports (ComfyUI, Ollama).
52
- - `prompts.yaml` for LLM system instructions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  - **Image Local**: ComfyUI (Local Server)
50
  - **Configuration**:
51
  - `.env` for API keys and connection hosts/ports (ComfyUI, Ollama).
52
+ ## Maintenance & Development Lessons
53
+
54
+ ### 1. Server Lifecycle
55
+ - **Mandatory Restarts**: Any modification to `app.py` or configuration files (`features.yaml`, `prompts.yaml`) requires a manual restart of the Gradio server to take effect. The UI does not hot-reload backend logic or YAML data.
56
+ - **Process Management (CRITICAL)**: Always ensure the previously running process is fully terminated before starting a new one. Gradio will automatically increment the port (7860 -> 7861 -> 7862) if the old process is still bound to the port.
57
+ - **Command**: Use `Get-Process -Name python | Stop-Process -Force` in PowerShell to clear all existing instances before a fresh run.
58
+
59
+ ### 2. UI Verification
60
+ - **Data Dependency**: When testing UI components or dropdowns via browser automation, verify that the target values exist in `features.yaml` first. Testing with invalid data (e.g., "Silver" hair color before it was added) leads to verification failures.
61
+ - **UI State Reset**: Always refresh the browser page after a server restart to ensure the frontend state is synchronized with the new backend.
62
+
63
+ ### 3. Documentation & Artifacts
64
+ - **Path Formatting**: For markdown artifacts (like `walkthrough.md`), use absolute paths for media embeds. On Windows, prefix with a leading slash (e.g., `/C:/Users/...`) to comply with internal linting and ensuring reliable rendering.
65
+ - **Visual Proof**: Capture final screenshots of UI layouts to provide immediate visual confirmation of successes, especially after complex layout reorganizations.
features.yaml CHANGED
@@ -37,6 +37,7 @@ appearance:
37
  Blonde: "golden blonde"
38
  Red: "fiery red"
39
  White: "snow white"
 
40
  Grey: "silver grey"
41
  Blue: "deep sapphire blue"
42
  Green: "forest green"
 
37
  Blonde: "golden blonde"
38
  Red: "fiery red"
39
  White: "snow white"
40
+ Silver: "shimmering silver"
41
  Grey: "silver grey"
42
  Blue: "deep sapphire blue"
43
  Green: "forest green"