yeq6x commited on
Commit
46b0d09
·
1 Parent(s): b9b8012

Refactor UI in app.py to implement a tabbed layout for the Trainer and Prompt Generator sections, enhancing organization and user experience. The update includes restructured input fields and improved accessibility for image uploads and prompt generation functionalities.

Browse files
Files changed (2) hide show
  1. QIE_prompt_generator.py +144 -0
  2. app.py +175 -138
QIE_prompt_generator.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import mimetypes
3
+ from pathlib import Path
4
+ import gradio as gr
5
+ from typing import Optional, Tuple
6
+
7
+ # ===== ユーティリティ =====
8
+
9
+ def file_to_data_url(path: str) -> Optional[str]:
10
+ """画像ファイルを data URL に変換"""
11
+ if not path:
12
+ return None
13
+ p = Path(path)
14
+ if not p.exists():
15
+ return None
16
+ mime, _ = mimetypes.guess_type(str(p))
17
+ if not mime:
18
+ mime = "image/png"
19
+ with open(p, "rb") as f:
20
+ b64 = base64.b64encode(f.read()).decode("utf-8")
21
+ return f"data:{mime};base64,{b64}"
22
+
23
+
24
+ # ===== Meta Prompt =====
25
+ META_PROMPT = """You are an AI prompt generator for image-to-image transformation tasks in art and illustration pipelines.
26
+ Given two images — A (input) and B (output) — and an optional description or notes, your goal is to write a precise, structured English prompt that fully explains how A transforms into B, including both conceptual and visual rules of the transformation.
27
+
28
+ Instructions:
29
+ 1) Write 3–6 concise sentences describing the transformation.
30
+ 2) Begin with “The [type of A] transforms into …” or “Convert the [A] into …”.
31
+ 3) Clearly describe:
32
+ - What disappears or remains (e.g., hair, clothes, shadows, lines).
33
+ - What structural or stylistic simplifications occur (e.g., box, sphere, guide lines).
34
+ - How lines, colors, or lighting change (e.g., colored → lineart, flat → shaded).
35
+ - Any rules for anatomy, proportion, or rendering (e.g., keep pose, maintain base colors).
36
+ - Background or presentation constraints (e.g., white background, no shading).
37
+ 4) Integrate any user-provided notes naturally into the text.
38
+
39
+ Output Format:
40
+ - English Prompt: A full paragraph (3–6 sentences) describing the transformation from A to B.
41
+ - Name Suggestions: Propose 3 short, descriptive task-name candidates (e.g., Image2Body, Body2Box, Sketch2Line, Flat2Shade, Guide2Color, Light2Render, etc.).
42
+ - Optional Japanese Translation: Provide a brief Japanese version of the English prompt for understanding if requested.
43
+
44
+ Make the wording professional, objective, and consistent with technical art pipeline prompts.
45
+ """
46
+
47
+
48
+ # ===== OpenAI 呼び出し =====
49
+ def call_openai_chat(api_key: str, a_data_url: Optional[str], b_data_url: Optional[str], notes: str, want_japanese: bool):
50
+ """OpenAI GPT-5 API 呼び出し"""
51
+ if not api_key:
52
+ return ("", "", "(エラー:API Key が未入力です)")
53
+
54
+ try:
55
+ from openai import OpenAI
56
+ except Exception:
57
+ return ("", "", "(エラー:openai パッケージが見つかりません。`pip install openai` を実行してください)")
58
+
59
+ client = OpenAI(api_key=api_key)
60
+ model = "gpt-5"
61
+
62
+ user_content = []
63
+ if a_data_url:
64
+ user_content.append({"type": "text", "text": "Image A (input):"})
65
+ user_content.append({"type": "image_url", "image_url": {"url": a_data_url}})
66
+ if b_data_url:
67
+ user_content.append({"type": "text", "text": "Image B (output):"})
68
+ user_content.append({"type": "image_url", "image_url": {"url": b_data_url}})
69
+ user_content.append({
70
+ "type": "text",
71
+ "text": f"Additional notes: {notes or '(none)'}\n{'Include Japanese translation.' if want_japanese else 'No Japanese translation.'}"
72
+ })
73
+
74
+ try:
75
+ resp = client.chat.completions.create(
76
+ model=model,
77
+ messages=[
78
+ {"role": "system", "content": META_PROMPT},
79
+ {"role": "user", "content": user_content},
80
+ ]
81
+ )
82
+ text = resp.choices[0].message.content.strip()
83
+ except Exception as e:
84
+ return ("", "", f"(APIエラー:{e})")
85
+
86
+ # 簡易パース
87
+ english, names, japanese = "", "", ""
88
+ lower = text.lower()
89
+ if "name suggestions:" in lower:
90
+ split = text.split("English Prompt:")[1].split("Name Suggestions:")
91
+ english = split[0].strip()
92
+ rest = split[1]
93
+ if "Japanese Translation:" in rest:
94
+ name_part, jp_part = rest.split("Japanese Translation:", 1)
95
+ names = name_part.strip()
96
+ japanese = jp_part.strip()
97
+ else:
98
+ names = rest.strip()
99
+ else:
100
+ english = text
101
+
102
+ if not want_japanese:
103
+ japanese = ""
104
+
105
+ return english, names, japanese
106
+
107
+
108
+ # ===== Gradio UI =====
109
+
110
+ with gr.Blocks(title="A→B 変換プロンプト自動生成(GPT-5固定)") as demo:
111
+ gr.Markdown("""
112
+ # 🎨 A→B 変換プロンプト自動生成
113
+ 画像A(入力)と画像B(出力)、補足説明を入力すると、
114
+ A→B の変換内容を**英語プロンプト**として自動生成し、
115
+ さらに**タスク名候補(3件)**を提案します。
116
+ モデルは `gpt-5` を使用します。
117
+ """)
118
+
119
+ api_key = gr.Textbox(label="OpenAI API Key", type="password", placeholder="sk-...")
120
+ with gr.Row():
121
+ img_a = gr.Image(type="filepath", label="Image A (Input)", height=300)
122
+ img_b = gr.Image(type="filepath", label="Image B (Output)", height=300)
123
+
124
+ notes = gr.Textbox(label="補足説明(日本語可)", lines=4, placeholder="例)髪・服・背景は消す。目は四角。鎖骨はピンク線。など", value="この画像は例であって、汎用的なプロンプトにする")
125
+ want_japanese = gr.Checkbox(label="日本語訳を含める", value=True)
126
+ run_btn = gr.Button("生成する", variant="primary")
127
+
128
+ english_out = gr.Textbox(label="English Prompt", lines=8)
129
+ names_out = gr.Textbox(label="Name Suggestions", lines=4)
130
+ japanese_out = gr.Textbox(label="日本語訳(任意)", lines=8)
131
+
132
+ def on_click(api_key_in, a_path, b_path, notes_in, ja_flag):
133
+ a_url = file_to_data_url(a_path) if a_path else None
134
+ b_url = file_to_data_url(b_path) if b_path else None
135
+ return call_openai_chat(api_key_in, a_url, b_url, notes_in, ja_flag)
136
+
137
+ run_btn.click(
138
+ fn=on_click,
139
+ inputs=[api_key, img_a, img_b, notes, want_japanese],
140
+ outputs=[english_out, names_out, japanese_out],
141
+ )
142
+
143
+ if __name__ == "__main__":
144
+ demo.launch()
app.py CHANGED
@@ -11,6 +11,7 @@ import time
11
  import json
12
 
13
  import gradio as gr
 
14
  import spaces
15
 
16
  # Local modules
@@ -706,154 +707,190 @@ def build_ui() -> gr.Blocks:
706
  }
707
  """
708
  with gr.Blocks(title="Qwen-Image-Edit: Trainer", css=css) as demo:
709
- gr.Markdown("""
710
- # Qwen-Image-Edit Trainer
711
- 学習に使う画像をアップロードし、必要ならファイル名の前後にある共通の文字(prefix/suffix)を指定して、
712
- 自動でデータセットを作成し学習を開始します。難しい操作は不要です。
713
- """)
714
-
715
- with gr.Accordion("Settings", elem_classes=["pad-section"]):
716
- with gr.Group():
717
- with gr.Row():
718
- output_name = gr.Textbox(label="OUTPUT NAME", placeholder="my_lora_output", lines=1)
719
- caption = gr.Textbox(label="CAPTION", placeholder="A photo of ...", lines=2)
 
 
720
 
721
- with gr.Row():
722
- lr_input = gr.Textbox(label="Learning rate", value="1e-3")
723
- dim_input = gr.Number(label="Network dim", value=4, precision=0)
724
- seed_input = gr.Number(label="Seed", value=42, precision=0)
725
- max_epochs = gr.Number(label="Max epochs", value=100, precision=0)
726
- save_every = gr.Number(label="Save every N epochs", value=10, precision=0)
727
-
728
- with gr.Accordion("Target Image", elem_classes=["pad-section_0"]):
729
- with gr.Group():
730
- with gr.Row():
731
- images_input = gr.File(label="Upload target images", file_count="multiple", type="filepath", height=220, scale=3)
732
- main_gallery = gr.Gallery(label="Target preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
733
- with gr.Column(scale=1):
734
  with gr.Row():
735
- main_prefix = gr.Textbox(label="Target prefix", placeholder="e.g., IMG_")
736
- main_suffix = gr.Textbox(label="Target suffix", placeholder="e.g., _v2")
737
- with gr.Accordion("prefix/sufixについて", open=False):
738
- gr.Markdown("""
739
- ファイルの同名判定のため、画像のファイル名から共通の先頭/末尾文字を取り除く指定(例: IMG_ _v2)
740
- - まずターゲット画像のファイル名(拡張子なし)から、指定した Target prefix/suffix を取り除いたものを key とします。
741
- - 各コントロールは「付加」規則で、期待名 = control_prefix_i + key + control_suffix_i + ".png" を探して対応付けます。
742
- - アップロード時に画像は自動で .png に変換して保存します(元のファイル名のベースは維持)。
743
- - Control 0 は必須、Control 1〜7 は任意。コントロール画像が1枚だけのときは、すべてのターゲット画像に適用します。
744
- """)
745
-
746
- # control_0 is required and shown outside the accordion
747
- with gr.Accordion("Control 0", elem_classes=["pad-section_1"]):
748
- with gr.Group():
749
- with gr.Row():
750
- ctrl0_files = gr.File(label="Upload control_0 images (required)", file_count="multiple", type="filepath", height=220, scale=3)
751
- ctrl0_gallery = gr.Gallery(label="control_0 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
752
- with gr.Column(scale=1):
753
  with gr.Row():
754
- ctrl0_prefix = gr.Textbox(label="control_0 prefix", placeholder="e.g., C0_")
755
- ctrl0_suffix = gr.Textbox(label="control_0 suffix", placeholder="e.g., _mask")
756
-
757
- # Optional controls start from 1, accordion closed by default
758
- with gr.Accordion("Control 1", open=False, elem_classes=["pad-section_0"]):
759
- with gr.Group():
760
- with gr.Row():
761
- ctrl1_files = gr.File(label="Upload control_1 images", file_count="multiple", type="filepath", height=220, scale=3)
762
- ctrl1_gallery = gr.Gallery(label="control_1 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
763
- with gr.Column(scale=1):
 
 
 
 
 
 
 
 
764
  with gr.Row():
765
- ctrl1_prefix = gr.Textbox(label="control_1 prefix", placeholder="")
766
- ctrl1_suffix = gr.Textbox(label="control_1 suffix", placeholder="")
767
- with gr.Accordion("Control 2", open=False, elem_classes=["pad-section_1"]):
768
- with gr.Group():
769
- with gr.Row():
770
- ctrl2_files = gr.File(label="Upload control_2 images", file_count="multiple", type="filepath", height=220, scale=3)
771
- ctrl2_gallery = gr.Gallery(label="control_2 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
772
- with gr.Column(scale=1):
 
 
773
  with gr.Row():
774
- ctrl2_prefix = gr.Textbox(label="control_2 prefix", placeholder="")
775
- ctrl2_suffix = gr.Textbox(label="control_2 suffix", placeholder="")
776
- with gr.Accordion("Control 3", open=False, elem_classes=["pad-section_0"]):
777
- with gr.Group():
778
- with gr.Row():
779
- ctrl3_files = gr.File(label="Upload control_3 images", file_count="multiple", type="filepath", height=220, scale=3)
780
- ctrl3_gallery = gr.Gallery(label="control_3 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
781
- with gr.Column(scale=1):
782
  with gr.Row():
783
- ctrl3_prefix = gr.Textbox(label="control_3 prefix", placeholder="")
784
- ctrl3_suffix = gr.Textbox(label="control_3 suffix", placeholder="")
785
- with gr.Accordion("Control 4", open=False, elem_classes=["pad-section_1"]):
786
- with gr.Group():
787
- with gr.Row():
788
- ctrl4_files = gr.File(label="Upload control_4 images", file_count="multiple", type="filepath", height=220, scale=3)
789
- ctrl4_gallery = gr.Gallery(label="control_4 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
790
- with gr.Column(scale=1):
791
  with gr.Row():
792
- ctrl4_prefix = gr.Textbox(label="control_4 prefix", placeholder="")
793
- ctrl4_suffix = gr.Textbox(label="control_4 suffix", placeholder="")
794
- with gr.Accordion("Control 5", open=False, elem_classes=["pad-section_0"]):
795
- with gr.Group():
796
- with gr.Row():
797
- ctrl5_files = gr.File(label="Upload control_5 images", file_count="multiple", type="filepath", height=220, scale=3)
798
- ctrl5_gallery = gr.Gallery(label="control_5 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
799
- with gr.Column(scale=1):
800
  with gr.Row():
801
- ctrl5_prefix = gr.Textbox(label="control_5 prefix", placeholder="")
802
- ctrl5_suffix = gr.Textbox(label="control_5 suffix", placeholder="")
803
- with gr.Accordion("Control 6", open=False, elem_classes=["pad-section_1"]):
804
- with gr.Group():
805
- with gr.Row():
806
- ctrl6_files = gr.File(label="Upload control_6 images", file_count="multiple", type="filepath", height=220, scale=3)
807
- ctrl6_gallery = gr.Gallery(label="control_6 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
808
- with gr.Column(scale=1):
809
  with gr.Row():
810
- ctrl6_prefix = gr.Textbox(label="control_6 prefix", placeholder="")
811
- ctrl6_suffix = gr.Textbox(label="control_6 suffix", placeholder="")
812
- with gr.Accordion("Control 7", open=False, elem_classes=["pad-section_0"]):
813
- with gr.Group():
814
- with gr.Row():
815
- ctrl7_files = gr.File(label="Upload control_7 images", file_count="multiple", type="filepath", height=220, scale=3)
816
- ctrl7_gallery = gr.Gallery(label="control_7 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
817
- with gr.Column(scale=1):
818
  with gr.Row():
819
- ctrl7_prefix = gr.Textbox(label="control_7 prefix", placeholder="")
820
- ctrl7_suffix = gr.Textbox(label="control_7 suffix", placeholder="")
821
-
822
- # Models root / OUTPUT_DIR_BASE / DATASET_CONFIG are auto-resolved at runtime; no user input needed.
823
-
824
- run_btn = gr.Button("Start Training", variant="primary")
825
- logs = gr.Textbox(label="Logs", lines=20)
826
- ckpt_files = gr.Files(label="Checkpoints (live)", interactive=False)
827
-
828
- # moved max_epochs/save_every above next to OUTPUT NAME
829
-
830
- # Wire previews
831
- images_input.change(fn=_files_to_gallery, inputs=images_input, outputs=main_gallery)
832
- ctrl0_files.change(fn=_files_to_gallery, inputs=ctrl0_files, outputs=ctrl0_gallery)
833
- ctrl1_files.change(fn=_files_to_gallery, inputs=ctrl1_files, outputs=ctrl1_gallery)
834
- ctrl2_files.change(fn=_files_to_gallery, inputs=ctrl2_files, outputs=ctrl2_gallery)
835
- ctrl3_files.change(fn=_files_to_gallery, inputs=ctrl3_files, outputs=ctrl3_gallery)
836
- ctrl4_files.change(fn=_files_to_gallery, inputs=ctrl4_files, outputs=ctrl4_gallery)
837
- ctrl5_files.change(fn=_files_to_gallery, inputs=ctrl5_files, outputs=ctrl5_gallery)
838
- ctrl6_files.change(fn=_files_to_gallery, inputs=ctrl6_files, outputs=ctrl6_gallery)
839
- ctrl7_files.change(fn=_files_to_gallery, inputs=ctrl7_files, outputs=ctrl7_gallery)
840
-
841
- run_btn.click(
842
- fn=run_training,
843
- inputs=[
844
- output_name, caption, images_input, main_prefix, main_suffix,
845
- ctrl0_files, ctrl0_prefix, ctrl0_suffix,
846
- ctrl1_files, ctrl1_prefix, ctrl1_suffix,
847
- ctrl2_files, ctrl2_prefix, ctrl2_suffix,
848
- ctrl3_files, ctrl3_prefix, ctrl3_suffix,
849
- ctrl4_files, ctrl4_prefix, ctrl4_suffix,
850
- ctrl5_files, ctrl5_prefix, ctrl5_suffix,
851
- ctrl6_files, ctrl6_prefix, ctrl6_suffix,
852
- ctrl7_files, ctrl7_prefix, ctrl7_suffix,
853
- lr_input, dim_input, seed_input, max_epochs, save_every,
854
- ],
855
- outputs=[logs, ckpt_files],
856
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
857
 
858
  return demo
859
 
 
11
  import json
12
 
13
  import gradio as gr
14
+ import importlib
15
  import spaces
16
 
17
  # Local modules
 
707
  }
708
  """
709
  with gr.Blocks(title="Qwen-Image-Edit: Trainer", css=css) as demo:
710
+ with gr.Tabs() as tabs:
711
+ with gr.TabItem("Training"):
712
+ gr.Markdown("""
713
+ # Qwen-Image-Edit Trainer
714
+ 学習に使う画像をアップロードし、必要ならファイル名の前後にある共通の文字(prefix/suffix)を指定して、
715
+ 自動でデータセットを作成し学習を開始します。難しい操作は不要です。
716
+ """)
717
+
718
+ with gr.Accordion("Settings", elem_classes=["pad-section"]):
719
+ with gr.Group():
720
+ with gr.Row():
721
+ output_name = gr.Textbox(label="OUTPUT NAME", placeholder="my_lora_output", lines=1)
722
+ caption = gr.Textbox(label="CAPTION", placeholder="A photo of ...", lines=2)
723
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  with gr.Row():
725
+ lr_input = gr.Textbox(label="Learning rate", value="1e-3")
726
+ dim_input = gr.Number(label="Network dim", value=4, precision=0)
727
+ seed_input = gr.Number(label="Seed", value=42, precision=0)
728
+ max_epochs = gr.Number(label="Max epochs", value=100, precision=0)
729
+ save_every = gr.Number(label="Save every N epochs", value=10, precision=0)
730
+
731
+ with gr.Accordion("Target Image", elem_classes=["pad-section_0"]):
732
+ with gr.Group():
 
 
 
 
 
 
 
 
 
 
733
  with gr.Row():
734
+ images_input = gr.File(label="Upload target images", file_count="multiple", type="filepath", height=220, scale=3)
735
+ main_gallery = gr.Gallery(label="Target preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
736
+ with gr.Column(scale=1):
737
+ with gr.Row():
738
+ main_prefix = gr.Textbox(label="Target prefix", placeholder="e.g., IMG_")
739
+ main_suffix = gr.Textbox(label="Target suffix", placeholder="e.g., _v2")
740
+ with gr.Accordion("prefix/sufixについて", open=False):
741
+ gr.Markdown("""
742
+ ファイルの同名判定のため、画像のファイル名から共通の先頭/末尾文字を取り除く指定(例: IMG_ _v2)
743
+ - まずターゲット画像のファイル名(拡張子なし)から、指定した Target prefix/suffix を取り除いたものを key とします。
744
+ - 各コントロールは「付加」規則で、期待名 = control_prefix_i + key + control_suffix_i + ".png" を探して対応付けます。
745
+ - アップロード時に画像は自動で .png に変換して保存します(元のファイル名のベースは維持)。
746
+ - Control 0 は必須、Control 1〜7 は任意。コントロール画像が1枚だけのときは、すべてのターゲット画像に適用します。
747
+ """)
748
+
749
+ # control_0 is required and shown outside the accordion
750
+ with gr.Accordion("Control 0", elem_classes=["pad-section_1"]):
751
+ with gr.Group():
752
  with gr.Row():
753
+ ctrl0_files = gr.File(label="Upload control_0 images (required)", file_count="multiple", type="filepath", height=220, scale=3)
754
+ ctrl0_gallery = gr.Gallery(label="control_0 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
755
+ with gr.Column(scale=1):
756
+ with gr.Row():
757
+ ctrl0_prefix = gr.Textbox(label="control_0 prefix", placeholder="e.g., C0_")
758
+ ctrl0_suffix = gr.Textbox(label="control_0 suffix", placeholder="e.g., _mask")
759
+
760
+ # Optional controls start from 1, accordion closed by default
761
+ with gr.Accordion("Control 1", open=False, elem_classes=["pad-section_0"]):
762
+ with gr.Group():
763
  with gr.Row():
764
+ ctrl1_files = gr.File(label="Upload control_1 images", file_count="multiple", type="filepath", height=220, scale=3)
765
+ ctrl1_gallery = gr.Gallery(label="control_1 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
766
+ with gr.Column(scale=1):
767
+ with gr.Row():
768
+ ctrl1_prefix = gr.Textbox(label="control_1 prefix", placeholder="")
769
+ ctrl1_suffix = gr.Textbox(label="control_1 suffix", placeholder="")
770
+ with gr.Accordion("Control 2", open=False, elem_classes=["pad-section_1"]):
771
+ with gr.Group():
772
  with gr.Row():
773
+ ctrl2_files = gr.File(label="Upload control_2 images", file_count="multiple", type="filepath", height=220, scale=3)
774
+ ctrl2_gallery = gr.Gallery(label="control_2 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
775
+ with gr.Column(scale=1):
776
+ with gr.Row():
777
+ ctrl2_prefix = gr.Textbox(label="control_2 prefix", placeholder="")
778
+ ctrl2_suffix = gr.Textbox(label="control_2 suffix", placeholder="")
779
+ with gr.Accordion("Control 3", open=False, elem_classes=["pad-section_0"]):
780
+ with gr.Group():
781
  with gr.Row():
782
+ ctrl3_files = gr.File(label="Upload control_3 images", file_count="multiple", type="filepath", height=220, scale=3)
783
+ ctrl3_gallery = gr.Gallery(label="control_3 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
784
+ with gr.Column(scale=1):
785
+ with gr.Row():
786
+ ctrl3_prefix = gr.Textbox(label="control_3 prefix", placeholder="")
787
+ ctrl3_suffix = gr.Textbox(label="control_3 suffix", placeholder="")
788
+ with gr.Accordion("Control 4", open=False, elem_classes=["pad-section_1"]):
789
+ with gr.Group():
790
  with gr.Row():
791
+ ctrl4_files = gr.File(label="Upload control_4 images", file_count="multiple", type="filepath", height=220, scale=3)
792
+ ctrl4_gallery = gr.Gallery(label="control_4 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
793
+ with gr.Column(scale=1):
794
+ with gr.Row():
795
+ ctrl4_prefix = gr.Textbox(label="control_4 prefix", placeholder="")
796
+ ctrl4_suffix = gr.Textbox(label="control_4 suffix", placeholder="")
797
+ with gr.Accordion("Control 5", open=False, elem_classes=["pad-section_0"]):
798
+ with gr.Group():
799
  with gr.Row():
800
+ ctrl5_files = gr.File(label="Upload control_5 images", file_count="multiple", type="filepath", height=220, scale=3)
801
+ ctrl5_gallery = gr.Gallery(label="control_5 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
802
+ with gr.Column(scale=1):
803
+ with gr.Row():
804
+ ctrl5_prefix = gr.Textbox(label="control_5 prefix", placeholder="")
805
+ ctrl5_suffix = gr.Textbox(label="control_5 suffix", placeholder="")
806
+ with gr.Accordion("Control 6", open=False, elem_classes=["pad-section_1"]):
807
+ with gr.Group():
808
  with gr.Row():
809
+ ctrl6_files = gr.File(label="Upload control_6 images", file_count="multiple", type="filepath", height=220, scale=3)
810
+ ctrl6_gallery = gr.Gallery(label="control_6 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
811
+ with gr.Column(scale=1):
812
+ with gr.Row():
813
+ ctrl6_prefix = gr.Textbox(label="control_6 prefix", placeholder="")
814
+ ctrl6_suffix = gr.Textbox(label="control_6 suffix", placeholder="")
815
+ with gr.Accordion("Control 7", open=False, elem_classes=["pad-section_0"]):
816
+ with gr.Group():
817
+ with gr.Row():
818
+ ctrl7_files = gr.File(label="Upload control_7 images", file_count="multiple", type="filepath", height=220, scale=3)
819
+ ctrl7_gallery = gr.Gallery(label="control_7 preview", columns=4, height=220, object_fit='contain', preview=True, scale=3)
820
+ with gr.Column(scale=1):
821
+ with gr.Row():
822
+ ctrl7_prefix = gr.Textbox(label="control_7 prefix", placeholder="")
823
+ ctrl7_suffix = gr.Textbox(label="control_7 suffix", placeholder="")
824
+
825
+ # Models root / OUTPUT_DIR_BASE / DATASET_CONFIG are auto-resolved at runtime; no user input needed.
826
+
827
+ run_btn = gr.Button("Start Training", variant="primary")
828
+ logs = gr.Textbox(label="Logs", lines=20)
829
+ ckpt_files = gr.Files(label="Checkpoints (live)", interactive=False)
830
+
831
+ # moved max_epochs/save_every above next to OUTPUT NAME
832
+
833
+ # Wire previews
834
+ images_input.change(fn=_files_to_gallery, inputs=images_input, outputs=main_gallery)
835
+ ctrl0_files.change(fn=_files_to_gallery, inputs=ctrl0_files, outputs=ctrl0_gallery)
836
+ ctrl1_files.change(fn=_files_to_gallery, inputs=ctrl1_files, outputs=ctrl1_gallery)
837
+ ctrl2_files.change(fn=_files_to_gallery, inputs=ctrl2_files, outputs=ctrl2_gallery)
838
+ ctrl3_files.change(fn=_files_to_gallery, inputs=ctrl3_files, outputs=ctrl3_gallery)
839
+ ctrl4_files.change(fn=_files_to_gallery, inputs=ctrl4_files, outputs=ctrl4_gallery)
840
+ ctrl5_files.change(fn=_files_to_gallery, inputs=ctrl5_files, outputs=ctrl5_gallery)
841
+ ctrl6_files.change(fn=_files_to_gallery, inputs=ctrl6_files, outputs=ctrl6_gallery)
842
+ ctrl7_files.change(fn=_files_to_gallery, inputs=ctrl7_files, outputs=ctrl7_gallery)
843
+
844
+ run_btn.click(
845
+ fn=run_training,
846
+ inputs=[
847
+ output_name, caption, images_input, main_prefix, main_suffix,
848
+ ctrl0_files, ctrl0_prefix, ctrl0_suffix,
849
+ ctrl1_files, ctrl1_prefix, ctrl1_suffix,
850
+ ctrl2_files, ctrl2_prefix, ctrl2_suffix,
851
+ ctrl3_files, ctrl3_prefix, ctrl3_suffix,
852
+ ctrl4_files, ctrl4_prefix, ctrl4_suffix,
853
+ ctrl5_files, ctrl5_prefix, ctrl5_suffix,
854
+ ctrl6_files, ctrl6_prefix, ctrl6_suffix,
855
+ ctrl7_files, ctrl7_prefix, ctrl7_suffix,
856
+ lr_input, dim_input, seed_input, max_epochs, save_every,
857
+ ],
858
+ outputs=[logs, ckpt_files],
859
+ )
860
+
861
+ with gr.TabItem("Prompt Generator"):
862
+ gr.Markdown("""
863
+ # 🎨 A→B 変換プロンプト自動生成
864
+ 画像A(入力)と画像B(出力)、補足説明を入力すると、
865
+ A→B の変換内容を英語プロンプトとして自動生成し、タスク名候補(3件)も提案します。
866
+ モデルは `gpt-5` を使用します。
867
+ """)
868
+
869
+ api_key_pg = gr.Textbox(label="OpenAI API Key", type="password", placeholder="sk-...")
870
+ with gr.Row():
871
+ img_a_pg = gr.Image(type="filepath", label="Image A (Input)", height=300)
872
+ img_b_pg = gr.Image(type="filepath", label="Image B (Output)", height=300)
873
+
874
+ notes_pg = gr.Textbox(label="補足説明(日本語可)", lines=4, value="この画像は例であって、汎用的なプロンプトにする")
875
+ want_japanese_pg = gr.Checkbox(label="日本語訳を含める", value=True)
876
+ run_btn_pg = gr.Button("生成する", variant="primary")
877
+
878
+ english_out_pg = gr.Textbox(label="English Prompt", lines=8)
879
+ names_out_pg = gr.Textbox(label="Name Suggestions", lines=4)
880
+ japanese_out_pg = gr.Textbox(label="日本語訳(任意)", lines=8)
881
+
882
+ def _on_click_prompt(api_key_in, a_path, b_path, notes_in, ja_flag):
883
+ # Lazy import to avoid constructing extra Blocks at startup
884
+ qpg = importlib.import_module("QIE_prompt_generator")
885
+ a_url = qpg.file_to_data_url(a_path) if a_path else None
886
+ b_url = qpg.file_to_data_url(b_path) if b_path else None
887
+ return qpg.call_openai_chat(api_key_in, a_url, b_url, notes_in, ja_flag)
888
+
889
+ run_btn_pg.click(
890
+ fn=_on_click_prompt,
891
+ inputs=[api_key_pg, img_a_pg, img_b_pg, notes_pg, want_japanese_pg],
892
+ outputs=[english_out_pg, names_out_pg, japanese_out_pg],
893
+ )
894
 
895
  return demo
896