tori29umai commited on
Commit
7d2e3d8
·
verified ·
1 Parent(s): ba72a11

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +222 -123
app.py CHANGED
@@ -15,7 +15,6 @@ import math
15
  dtype = torch.bfloat16
16
  device = "cuda" if torch.cuda.is_available() else "cpu"
17
 
18
- # Scheduler configuration for Lightning
19
  scheduler_config = {
20
  "base_image_seq_len": 256,
21
  "base_shift": math.log(3),
@@ -32,11 +31,8 @@ scheduler_config = {
32
  "use_exponential_sigmas": False,
33
  "use_karras_sigmas": False,
34
  }
35
-
36
- # Initialize scheduler
37
  scheduler = FlowMatchEulerDiscreteScheduler.from_config(scheduler_config)
38
 
39
- # Load model
40
  pipe = QwenImageEditPlusPipeline.from_pretrained(
41
  "Qwen/Qwen-Image-Edit-2509",
42
  scheduler=scheduler,
@@ -62,68 +58,127 @@ optimize_pipeline_(pipe, image=[Image.new("RGB", (1024, 1024)), Image.new("RGB",
62
  # --- Constants ---
63
  MAX_SEED = np.iinfo(np.int32).max
64
 
65
- # プルダウンの定義(value は実際に送る中国語のみ)
66
- CAMERA_CHOICES = [
67
- {
68
- "label": "镜头方向左回转45度,カメラを左に45度回転,Rotate camera 45° left,パン左45度(Pan Left 45°)",
69
- "value": "镜头方向左回转45度",
70
- },
71
- {
72
- "label": "镜头向右回转45度,カメラを右に45度回転,Rotate camera 45° right,パン右45度(Pan Right 45°)",
73
- "value": "镜头向右回转45度",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  },
75
- {
76
- "label": "镜头方向左回转90度,カメラを左に90度回転,Rotate camera 90° left,パン左45度(Pan Left 90°)",
77
- "value": "镜头方向左回转90度",
 
78
  },
79
- {
80
- "label": "镜头向右回转90度,カメラを右に90度回転,Rotate camera 90° right,パン右45度(Pan Right 90°)",
81
- "value": "镜头向右回转90度",
 
 
82
  },
83
- {
84
- "label": "将镜头转为俯视,カメラを上から見下ろす視点に切り替える,Switch to top-down view,上から全体を見下ろす(トップビュー / Top-down View)",
85
- "value": "将镜头转为俯视",
 
86
  },
87
- {
88
- "label": "将镜头转为仰视,カメラを下から見上げる視点に切り替える,Switch to low-angle view,被写体を下から強調して映す(ローアングル / Low Angle)",
89
- "value": "将镜头转为仰视",
 
90
  },
91
- {
92
- "label": "镜头转相机平面视,カメラを平面視に切り替える,Switch to orthographic view,真横からの視点(オルソビュー / Orthographic)",
93
- "value": "镜头转相机平面视",
 
94
  },
95
- {
96
- "label": "将镜头转为特写镜头,カメラをクローズアップに切り替える,Switch to close-up lens,被写体に寄る(望遠・ズームイン / Close-up / Zoom In)",
97
- "value": "将镜头转为特写镜头",
 
98
  },
99
- {
100
- "label": "将镜头转为中近景镜头,カメラをややクローズアップに切り替える,Switch to medium close-up lens,被写体の上半身や顔周辺を中心に映す(中近景 / Medium Close-up)",
101
- "value": "将镜头转为中近景镜头",
 
 
 
 
 
 
 
102
  },
103
- {
104
- "label": "镜头转为广角镜头,カメラをズームアウトに切り替える,Switch to wide-angle lens,広い範囲を捉える(広角 / Wide Angle)",
105
- "value": "镜头转为广角镜头",
 
106
  },
107
- {
108
- "label": "拉远镜头以拍摄被摄体全景,被写体の全容を映すようにカメラを引く,Pull back the camera to capture the whole subject,被写体全体を画面に収めるためにカメラを後退させる(ドリーアウト / Zoom Out / Full view)",
109
- "value": "拉远镜头以拍摄被摄体全景",
 
 
 
 
110
  },
111
- {
112
- "label": "将镜头移动到被摄体正面,カメラを被写体の正面に移動する,Move the camera to the front of the subject,被写体の正面にカメラを配置し、正面ショットを撮影する(フロントビュー / Front shot)",
113
- "value": "将镜头移动到被摄体正面",
 
114
  },
115
- {
116
- "label": "将镜头移动到被摄体背后,カメラを被写体の背面に移動する,Move camera behind the subject,被写体の背後へ回り込み、後ろから撮影する(背面ショット / Behind-the-subject shot)",
117
- "value": "将镜头移动到被摄体背后",
 
118
  },
119
- ]
 
120
 
121
- # 自由入力オプション
122
- CUSTOM_OPTION_VALUE = "__custom__"
123
- CUSTOM_OPTION_LABEL = "自由入力 / Custom"
 
 
 
 
 
 
 
 
 
 
124
 
125
  def _append_prompt(base: str, extra: str) -> str:
126
- """末尾にユーザー指定のプロンプトを追記(空なら変更なし)"""
127
  extra = (extra or "").strip()
128
  return (base if not extra else f"{base} {extra}").strip()
129
 
@@ -143,19 +198,21 @@ def generate_single_view(input_images, prompt, seed, num_inference_steps, true_g
143
  @spaces.GPU()
144
  def generate_from_dropdown(
145
  image,
146
- dropdown_value,
147
- custom,
148
  extra_prompt="",
149
- seed=42,
150
- randomize_seed=False,
151
- true_guidance_scale=1.0,
152
- num_inference_steps=4,
 
153
  progress=gr.Progress(track_tqdm=True),
154
  ):
155
  if randomize_seed:
156
  seed = random.randint(0, MAX_SEED)
 
157
  if image is None:
158
- return None, seed, "エラー: 入力画像をアップロードしてください", ""
159
 
160
  if isinstance(image, Image.Image):
161
  input_image = image.convert("RGB")
@@ -164,24 +221,23 @@ def generate_from_dropdown(
164
 
165
  pil_images = [input_image]
166
 
167
- if dropdown_value == CUSTOM_OPTION_VALUE:
168
- base = (custom or "").strip()
169
- if not base:
170
- return None, seed, "エラー: 自由入力のプロンプトを入力してください", ""
171
  else:
172
- base = dropdown_value or CAMERA_CHOICES[0]["value"]
173
 
174
- final_prompt = _append_prompt(base, extra_prompt)
175
 
176
- progress(0.6, desc="画像生成中...")
177
  out = generate_single_view(pil_images, final_prompt, seed, num_inference_steps, true_guidance_scale)
178
- progress(1.0, desc="完了")
179
 
180
- return out, seed, "1枚生成しました(PNG)", final_prompt
181
 
182
  # --- UI ---
183
  css = """
184
- /* レイアウトとカード風スタイル */
185
  #app-wrap {margin: 0 auto; max-width: 1200px;}
186
  .notice {
187
  background: #fff8e1;
@@ -200,10 +256,7 @@ css = """
200
  padding: 14px;
201
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
202
  }
203
- .small {
204
- font-size: 12px;
205
- color: #6b7280;
206
- }
207
  .preview {
208
  background: #f9fafb;
209
  border: 1px dashed #cbd5e1;
@@ -215,95 +268,141 @@ css = """
215
  """
216
 
217
  with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
218
- gr.Markdown("## 🎥 カメラワーク選択")
 
 
 
 
 
 
 
 
 
219
  with gr.Column(elem_id="app-wrap"):
220
- gr.HTML(
221
- "<div class='notice'>"
222
- "注意:他者が作成した画像のアップロードはご遠慮ください。権利侵害の可能性があります。"
223
- "</div>"
224
- )
225
 
226
  with gr.Row():
227
  with gr.Column(scale=1):
228
- input_image = gr.Image(label="入力画像", type="pil", height=420)
229
 
230
  with gr.Column(scale=1, elem_classes=["card"]):
231
  dropdown = gr.Dropdown(
232
- label="カメラワーク(説明付き)",
233
- choices=[(item["label"], item["value"]) for item in CAMERA_CHOICES] + [(CUSTOM_OPTION_LABEL, CUSTOM_OPTION_VALUE)],
234
- value=CAMERA_CHOICES[0]["value"],
235
- allow_custom_value=False, # ★ ユーザーは「自由入力」オプションを選んでテキストボックスに入力
236
  interactive=True,
237
  )
238
 
239
- custom = gr.Textbox(
240
- label="自由入力/中国語or英語がおすすめ",
241
- placeholder="例: 将镜头转为斜俯视 并 拉远镜头",
242
  visible=False,
243
  lines=2
244
  )
245
 
246
  extra_prompt = gr.Textbox(
247
- label="追加プロンプト(任意・末尾に付加/中国語or英語がおすすめ)",
248
- placeholder="例:被摄体是一名女孩子",
249
  lines=2
250
  )
251
 
252
- with gr.Row():
253
- seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
254
- randomize_seed = gr.Checkbox(label="ランダムシード", value=True)
255
-
256
- with gr.Row():
257
- true_guidance_scale = gr.Slider(label="True guidance scale", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
258
- num_inference_steps = gr.Slider(label="生成ステップ数", minimum=1, maximum=40, step=1, value=4)
259
 
260
- run_button = gr.Button("この設定で生成", variant="primary")
261
 
262
- # プレビュー(選択中の中国語、最終送信文)
263
- selected = gr.Textbox(label="選択中のカメラプロンプト", value=CAMERA_CHOICES[0]["value"], interactive=False)
264
- final_prompt_preview = gr.Textbox(label="最終的に送信されるプロンプト(+追記)", value="", interactive=False)
265
 
266
- # 選択/入力のたびにプレビュー更新 & 自由入力欄の表示切り替え
267
- def _sync(v, extra, custom_text):
268
- is_custom = (v == CUSTOM_OPTION_VALUE)
269
- base = (custom_text.strip() if is_custom else (v or CAMERA_CHOICES[0]["value"]))
270
- # 空の自由入力はプレビューでは空表示(エラーは実行時に出す)
271
  final = _append_prompt(base, extra) if base else ""
272
- return (
273
- base, # selected
274
- final, # final_prompt_preview
275
- gr.update(visible=is_custom), # custom visibility
276
- )
277
 
278
  dropdown.change(
279
  fn=_sync,
280
- inputs=[dropdown, extra_prompt, custom],
281
- outputs=[selected, final_prompt_preview, custom]
282
  )
283
  extra_prompt.change(
284
  fn=_sync,
285
- inputs=[dropdown, extra_prompt, custom],
286
- outputs=[selected, final_prompt_preview, custom]
287
  )
288
- custom.change(
289
  fn=_sync,
290
- inputs=[dropdown, extra_prompt, custom],
291
- outputs=[selected, final_prompt_preview, custom]
292
  )
293
 
294
  with gr.Row():
295
  with gr.Column(scale=1, elem_classes=["card"]):
296
- result_image = gr.Image(label="出力画像", type="pil", format="png", height=520, show_download_button=True)
297
- status_text = gr.Textbox(label="ステータス", interactive=False)
298
-
299
- gr.Markdown("**送信プロンプト(確認用)**")
300
  final_prompt_small = gr.Textbox(show_label=False, interactive=False, elem_classes=["preview", "small"])
301
 
302
- # 実行ボタンの配線(custom を追加で渡す)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  run_button.click(
304
  fn=generate_from_dropdown,
305
- inputs=[input_image, dropdown, custom, extra_prompt, seed, randomize_seed, true_guidance_scale, num_inference_steps],
306
- outputs=[result_image, seed, status_text, final_prompt_small],
307
  )
308
 
309
  if __name__ == "__main__":
 
15
  dtype = torch.bfloat16
16
  device = "cuda" if torch.cuda.is_available() else "cpu"
17
 
 
18
  scheduler_config = {
19
  "base_image_seq_len": 256,
20
  "base_shift": math.log(3),
 
31
  "use_exponential_sigmas": False,
32
  "use_karras_sigmas": False,
33
  }
 
 
34
  scheduler = FlowMatchEulerDiscreteScheduler.from_config(scheduler_config)
35
 
 
36
  pipe = QwenImageEditPlusPipeline.from_pretrained(
37
  "Qwen/Qwen-Image-Edit-2509",
38
  scheduler=scheduler,
 
58
  # --- Constants ---
59
  MAX_SEED = np.iinfo(np.int32).max
60
 
61
+ # 内部デフォルト(アコーディオンの初期値にも使用)
62
+ DEFAULT_SEED = 0
63
+ DEFAULT_RANDOMIZE = True
64
+ DEFAULT_TRUE_GUIDANCE_SCALE = 1.0
65
+ DEFAULT_NUM_INFERENCE_STEPS = 4
66
+
67
+ # カメラオプション(送信値は常に 'cn')
68
+ CAMERA_OPTIONS = [
69
+ {"cn": "镜头方向左回转45度", "ja": "カメラを左に45度回転", "en": "Rotate camera 45° left"},
70
+ {"cn": "镜头向右回转45度", "ja": "カメラを右に45度回転", "en": "Rotate camera 45° right"},
71
+ {"cn": "镜头方向左回转90度", "ja": "カメラを左に90度回転", "en": "Rotate camera 90° left"},
72
+ {"cn": "镜头向右回转90度", "ja": "カメラを右に90度回転", "en": "Rotate camera 90° right"},
73
+ {"cn": "将镜头转为俯视", "ja": "カメラを上から見下ろす視点に切り替える", "en": "Switch to top-down view"},
74
+ {"cn": "将镜头转为仰视", "ja": "カメラを下から見上げる視点に切り替える", "en": "Switch to low-angle view"},
75
+ {"cn": "镜头转相机平面视", "ja": "カメラを平面視に切り替える", "en": "Switch to orthographic view"},
76
+ {"cn": "将镜头转为特写镜头", "ja": "カメラをクローズアップに切り替える", "en": "Switch to close-up lens"},
77
+ {"cn": "将镜头转为中近景镜头", "ja": "カメラをややクローズアップに切り替える", "en": "Switch to medium close-up lens"},
78
+ {"cn": "镜头转为广角镜头", "ja": "カメラをズームアウトに切り替える", "en": "Switch to wide-angle lens"},
79
+ {"cn": "拉远镜头以拍摄被摄体全景", "ja": "被写体の全容を映すようにカメラを引く", "en": "Pull back the camera to capture the whole subject"},
80
+ {"cn": "将镜头移动到被摄体正面", "ja": "カメラを被写体の正面に移動する", "en": "Move the camera to the front of the subject"},
81
+ {"cn": "将镜头移动到被摄体背后", "ja": "カメラを被写体の背面に移動する", "en": "Move camera behind the subject"},
82
+ ]
83
+
84
+ # 自由入力オプション(言語別表示)
85
+ CUSTOM_OPTION_VALUE = "__custom__"
86
+ CUSTOM_LABELS = {
87
+ "en": "Custom (enter Chinese prompt)",
88
+ "ja": "自由入力(中国語で入力)",
89
+ "zh": "自定义(用中文输入)",
90
+ }
91
+
92
+ # i18n 辞書
93
+ I18N = {
94
+ "title": {
95
+ "en": "Camera Work ",
96
+ "ja": "カメラワーク",
97
+ "zh": "镜头控制",
98
  },
99
+ "notice": {
100
+ "en": "Note: Please avoid uploading images created by others. There may be rights infringements.",
101
+ "ja": "注意:他者が作成した画像のアップロードはご遠慮ください。権利侵害の可能性があります。",
102
+ "zh": "注意:请勿上传他人创作的图片,可能涉及权利侵害。",
103
  },
104
+ "input_image": {"en": "Input image", "ja": "入力画像", "zh": "输入图像"},
105
+ "dropdown_label": {
106
+ "en": "Camera work (label shows CN + selected language)",
107
+ "ja": "カメラワーク(表示は 中国語+選択言語)",
108
+ "zh": "镜头操作(显示为 中文+所选语言)",
109
  },
110
+ "custom_cn_label": {
111
+ "en": "Custom Chinese prompt",
112
+ "ja": "自由入力の中国語プロンプト",
113
+ "zh": "自定义中文提示词",
114
  },
115
+ "custom_cn_ph": {
116
+ "en": "e.g., 将镜头转为斜俯视 拉远镜头",
117
+ "ja": "例: 将镜头转为斜俯视 并 拉远镜头",
118
+ "zh": "例如:将镜头转为斜俯视 并 拉远镜头",
119
  },
120
+ "extra_label": {
121
+ "en": "Extra prompt (optional, appended at end)",
122
+ "ja": "追加プロンプト(任意・末尾に付加)",
123
+ "zh": "附加提示词(可选,追加在末尾)",
124
  },
125
+ "extra_ph": {
126
+ "en": "e.g., high detail, soft lighting, anime style, 4k",
127
+ "ja": "例: high detail, soft lighting, anime style, 4k",
128
+ "zh": "例如:high detail, soft lighting, anime style, 4k",
129
  },
130
+ "accordion": {"en": "Show advanced settings", "ja": "詳細設定を開く", "zh": "展开高级设置"},
131
+ "seed": {"en": "Seed", "ja": "Seed", "zh": "Seed"},
132
+ "rand": {"en": "Randomize seed", "ja": "ランダムシード", "zh": "随机种子"},
133
+ "tgs": {"en": "True guidance scale", "ja": "True guidance scale", "zh": "True guidance scale"},
134
+ "steps": {"en": "Steps", "ja": "生成ステップ数", "zh": "生成步数"},
135
+ "run": {"en": "Generate", "ja": "生成", "zh": "生成"},
136
+ "sel_cn": {
137
+ "en": "Selected camera prompt (to be sent, Chinese)",
138
+ "ja": "選択中のカメラプロンプト(送信対象・中国語)",
139
+ "zh": "所选镜头提示(发送内容,中文)",
140
  },
141
+ "final_prev": {
142
+ "en": "Final prompt to be sent (Chinese + extra)",
143
+ "ja": "最終的に送信されるプロンプト(中国語+追記)",
144
+ "zh": "最终发送的提示(中文+附加)",
145
  },
146
+ "output": {"en": "Output image", "ja": "出力画像", "zh": "输出图像"},
147
+ "status": {"en": "Status", "ja": "ステータス", "zh": "状态"},
148
+ "sent_hdr": {"en": "**Prompt sent (for reference)**", "ja": "**送信プロンプト(確認用)**", "zh": "**已发送提示(供参考)**"},
149
+ "status_ok": {
150
+ "en": "✅ Generated 1 image with Chinese prompt (PNG).",
151
+ "ja": "✅ 中国語プロンプトで1枚生成しました(PNG)。",
152
+ "zh": "✅ 使用中文提示生成了 1 张图片(PNG)。",
153
  },
154
+ "err_no_img": {
155
+ "en": "Error: Please upload an input image.",
156
+ "ja": "エラー: 入力画像をアップロードしてください",
157
+ "zh": "错误:请先上传输入图像。",
158
  },
159
+ "err_no_custom": {
160
+ "en": "Error: Please enter a custom Chinese prompt.",
161
+ "ja": "エラー: 自由入力の中国語プロンプトを入力してください",
162
+ "zh": "错误:请输入自定义中文提示词。",
163
  },
164
+ "lang_label": {"en": "UI Language", "ja": "UI言語", "zh": "界面语言"},
165
+ }
166
 
167
+ def t(key, lang):
168
+ return I18N[key][lang]
169
+
170
+ def build_dropdown_choices(lang):
171
+ # ラベルは「中国語 + (選択言語の説明)」で表示、送信値は常に中国語
172
+ if lang not in ("en", "ja", "zh"):
173
+ lang = "en"
174
+ ch = []
175
+ for item in CAMERA_OPTIONS:
176
+ label = f"{item['cn']}, {item[lang]}"
177
+ ch.append((label, item["cn"]))
178
+ ch.append((CUSTOM_LABELS[lang], CUSTOM_OPTION_VALUE))
179
+ return ch
180
 
181
  def _append_prompt(base: str, extra: str) -> str:
 
182
  extra = (extra or "").strip()
183
  return (base if not extra else f"{base} {extra}").strip()
184
 
 
198
  @spaces.GPU()
199
  def generate_from_dropdown(
200
  image,
201
+ dropdown_value_cn,
202
+ custom_cn,
203
  extra_prompt="",
204
+ seed=DEFAULT_SEED,
205
+ randomize_seed=DEFAULT_RANDOMIZE,
206
+ true_guidance_scale=DEFAULT_TRUE_GUIDANCE_SCALE,
207
+ num_inference_steps=DEFAULT_NUM_INFERENCE_STEPS,
208
+ lang="en",
209
  progress=gr.Progress(track_tqdm=True),
210
  ):
211
  if randomize_seed:
212
  seed = random.randint(0, MAX_SEED)
213
+
214
  if image is None:
215
+ return None, t("err_no_img", lang), ""
216
 
217
  if isinstance(image, Image.Image):
218
  input_image = image.convert("RGB")
 
221
 
222
  pil_images = [input_image]
223
 
224
+ if dropdown_value_cn == CUSTOM_OPTION_VALUE:
225
+ base_cn = (custom_cn or "").strip()
226
+ if not base_cn:
227
+ return None, t("err_no_custom", lang), ""
228
  else:
229
+ base_cn = dropdown_value_cn or CAMERA_OPTIONS[0]["cn"]
230
 
231
+ final_prompt = _append_prompt(base_cn, extra_prompt)
232
 
233
+ progress(0.6, desc="Generating..." if lang=="en" else ("生成中..." if lang=="ja" else "生成中..."))
234
  out = generate_single_view(pil_images, final_prompt, seed, num_inference_steps, true_guidance_scale)
235
+ progress(1.0, desc="Done" if lang=="en" else ("完了" if lang=="ja" else "完成"))
236
 
237
+ return out, t("status_ok", lang), final_prompt
238
 
239
  # --- UI ---
240
  css = """
 
241
  #app-wrap {margin: 0 auto; max-width: 1200px;}
242
  .notice {
243
  background: #fff8e1;
 
256
  padding: 14px;
257
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
258
  }
259
+ .small { font-size: 12px; color: #6b7280; }
 
 
 
260
  .preview {
261
  background: #f9fafb;
262
  border: 1px dashed #cbd5e1;
 
268
  """
269
 
270
  with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
271
+ # 言語選択(デフォルト英語)
272
+ lang_selector = gr.Radio(
273
+ label=I18N["lang_label"]["en"],
274
+ choices=[("English", "en"), ("日本語", "ja"), ("中文", "zh")],
275
+ value="en",
276
+ interactive=True,
277
+ )
278
+
279
+ title_md = gr.Markdown(I18N["title"]["en"])
280
+
281
  with gr.Column(elem_id="app-wrap"):
282
+ notice_html = gr.HTML(f"<div class='notice'>{I18N['notice']['en']}</div>")
 
 
 
 
283
 
284
  with gr.Row():
285
  with gr.Column(scale=1):
286
+ input_image = gr.Image(label=I18N["input_image"]["en"], type="pil", height=420)
287
 
288
  with gr.Column(scale=1, elem_classes=["card"]):
289
  dropdown = gr.Dropdown(
290
+ label=I18N["dropdown_label"]["en"],
291
+ choices=build_dropdown_choices("en"),
292
+ value=CAMERA_OPTIONS[0]["cn"],
293
+ allow_custom_value=False,
294
  interactive=True,
295
  )
296
 
297
+ custom_cn = gr.Textbox(
298
+ label=I18N["custom_cn_label"]["en"],
299
+ placeholder=I18N["custom_cn_ph"]["en"],
300
  visible=False,
301
  lines=2
302
  )
303
 
304
  extra_prompt = gr.Textbox(
305
+ label=I18N["extra_label"]["en"],
306
+ placeholder=I18N["extra_ph"]["en"],
307
  lines=2
308
  )
309
 
310
+ # 詳細設定アコーディオン
311
+ with gr.Accordion(I18N["accordion"]["en"], open=False) as adv_acc:
312
+ seed = gr.Slider(label=I18N["seed"]["en"], minimum=0, maximum=MAX_SEED, step=1, value=DEFAULT_SEED)
313
+ randomize_seed = gr.Checkbox(label=I18N["rand"]["en"], value=DEFAULT_RANDOMIZE)
314
+ true_guidance_scale = gr.Slider(label=I18N["tgs"]["en"], minimum=1.0, maximum=10.0, step=0.1, value=DEFAULT_TRUE_GUIDANCE_SCALE)
315
+ num_inference_steps = gr.Slider(label=I18N["steps"]["en"], minimum=1, maximum=40, step=1, value=DEFAULT_NUM_INFERENCE_STEPS)
 
316
 
317
+ run_button = gr.Button(I18N["run"]["en"], variant="primary")
318
 
319
+ selected_cn = gr.Textbox(label=I18N["sel_cn"]["en"], value=CAMERA_OPTIONS[0]["cn"], interactive=False)
320
+ final_prompt_preview = gr.Textbox(label=I18N["final_prev"]["en"], value="", interactive=False)
 
321
 
322
+ # 選択/入力のたびにプレビュー更新 & 自由入力欄の表示切替
323
+ def _sync(v_cn, extra, custom_text):
324
+ is_custom = (v_cn == CUSTOM_OPTION_VALUE)
325
+ base = (custom_text.strip() if is_custom else (v_cn or CAMERA_OPTIONS[0]["cn"]))
 
326
  final = _append_prompt(base, extra) if base else ""
327
+ return base, final, gr.update(visible=is_custom)
 
 
 
 
328
 
329
  dropdown.change(
330
  fn=_sync,
331
+ inputs=[dropdown, extra_prompt, custom_cn],
332
+ outputs=[selected_cn, final_prompt_preview, custom_cn]
333
  )
334
  extra_prompt.change(
335
  fn=_sync,
336
+ inputs=[dropdown, extra_prompt, custom_cn],
337
+ outputs=[selected_cn, final_prompt_preview, custom_cn]
338
  )
339
+ custom_cn.change(
340
  fn=_sync,
341
+ inputs=[dropdown, extra_prompt, custom_cn],
342
+ outputs=[selected_cn, final_prompt_preview, custom_cn]
343
  )
344
 
345
  with gr.Row():
346
  with gr.Column(scale=1, elem_classes=["card"]):
347
+ result_image = gr.Image(label=I18N["output"]["en"], type="pil", format="png", height=520, show_download_button=True)
348
+ status_text = gr.Textbox(label=I18N["status"]["en"], interactive=False)
349
+ sent_hdr_md = gr.Markdown(I18N["sent_hdr"]["en"])
 
350
  final_prompt_small = gr.Textbox(show_label=False, interactive=False, elem_classes=["preview", "small"])
351
 
352
+ # 言語切替の実装
353
+ def _switch_lang(lang, current_dropdown_value):
354
+ # ラベル/見出し/プレースホルダの更新 + ドロップダンのchoicesの再構築
355
+ return (
356
+ gr.update(label=I18N["lang_label"][lang]), # lang_selector label
357
+ I18N["title"][lang], # title_md value
358
+ gr.update(value=f"<div class='notice'>{I18N['notice'][lang]}</div>"), # notice_html
359
+ gr.update(label=I18N["input_image"][lang]), # input_image label
360
+ gr.update(label=I18N["dropdown_label"][lang],
361
+ choices=build_dropdown_choices(lang),
362
+ value=current_dropdown_value if current_dropdown_value else CAMERA_OPTIONS[0]["cn"]), # dropdown
363
+ gr.update(label=I18N["custom_cn_label"][lang], placeholder=I18N["custom_cn_ph"][lang]), # custom_cn
364
+ gr.update(label=I18N["extra_label"][lang], placeholder=I18N["extra_ph"][lang]), # extra_prompt
365
+ gr.update(label=I18N["seed"][lang]), # seed
366
+ gr.update(label=I18N["rand"][lang]), # randomize_seed
367
+ gr.update(label=I18N["tgs"][lang]), # true_guidance_scale
368
+ gr.update(label=I18N["steps"][lang]), # num_inference_steps
369
+ gr.update(value=I18N["run"][lang]), # run_button text
370
+ gr.update(label=I18N["sel_cn"][lang]), # selected_cn
371
+ gr.update(label=I18N["final_prev"][lang]), # final_prompt_preview
372
+ gr.update(label=I18N["output"][lang]), # result_image
373
+ gr.update(label=I18N["status"][lang]), # status_text
374
+ I18N["sent_hdr"][lang], # sent_hdr_md
375
+ )
376
+
377
+ lang_selector.change(
378
+ fn=_switch_lang,
379
+ inputs=[lang_selector, dropdown],
380
+ outputs=[
381
+ lang_selector, # label update
382
+ title_md, # markdown title
383
+ notice_html, # notice
384
+ input_image, # image label
385
+ dropdown, # dropdown (choices/label/value)
386
+ custom_cn, # custom label/ph
387
+ extra_prompt, # extra label/ph
388
+ seed, # seed label
389
+ randomize_seed, # randomize label
390
+ true_guidance_scale, # tgs label
391
+ num_inference_steps, # steps label
392
+ run_button, # button text
393
+ selected_cn, # label
394
+ final_prompt_preview, # label
395
+ result_image, # label
396
+ status_text, # label
397
+ sent_hdr_md, # markdown content
398
+ ],
399
+ )
400
+
401
+ # 実行:アコーディオンの値も渡す + 言語も渡す
402
  run_button.click(
403
  fn=generate_from_dropdown,
404
+ inputs=[input_image, dropdown, custom_cn, extra_prompt, seed, randomize_seed, true_guidance_scale, num_inference_steps, lang_selector],
405
+ outputs=[result_image, status_text, final_prompt_small],
406
  )
407
 
408
  if __name__ == "__main__":